You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2019/11/02 15:56:43 UTC

[juneau] branch master updated: JUNEAU-161 Exeption-Message header can contain CR/LF characters.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 1bfcd29  JUNEAU-161 Exeption-Message header can contain CR/LF characters.
1bfcd29 is described below

commit 1bfcd2924318f21bff7ad4b0ec975b0259014b33
Author: JamesBognar <ja...@apache.org>
AuthorDate: Sat Nov 2 11:56:28 2019 -0400

    JUNEAU-161 Exeption-Message header can contain CR/LF characters.
---
 .../org/apache/juneau/utils/StringUtilsTest.java   |  9 ++++++
 .../main/java/org/apache/juneau/ObjectList.java    |  2 +-
 .../src/main/java/org/apache/juneau/ObjectMap.java |  2 +-
 .../org/apache/juneau/internal/StringUtils.java    | 35 ++++++++++++++++++++++
 juneau-doc/docs/ReleaseNotes/8.1.2.html            |  8 +++--
 .../apache/juneau/rest/BasicRestCallHandler.java   |  4 +--
 .../java/org/apache/juneau/rest/RestResponse.java  | 26 ++++++++++++++--
 .../juneau/rest/reshandlers/DefaultHandler.java    |  6 ++--
 8 files changed, 80 insertions(+), 12 deletions(-)

diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StringUtilsTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
index 1f8bd5f..427a33e 100755
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
@@ -985,4 +985,13 @@ public class StringUtilsTest {
 		assertEquals(10*w, getDuration("10 W"));
 		assertEquals(10*w, getDuration("  10  W  "));
 	}
+
+	//====================================================================================================
+	// getDuration(String)
+	//====================================================================================================
+	@Test
+	public void testStripInvalidHttpHeaderChars() throws Exception {
+		assertEquals("xxx", stripInvalidHttpHeaderChars("xxx"));
+		assertEquals("\t []^x", stripInvalidHttpHeaderChars("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u0020\\[]^x"));
+	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectList.java
index 8d64b17..21c8379 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectList.java
@@ -141,7 +141,7 @@ public class ObjectList extends LinkedList<Object> {
 	 * @return A new {@link ObjectList} object, or <jk>null</jk> if the input is <jk>null</jk>.
 	 * @throws ParseException Invalid JSON string.
 	 */
-	public static ObjectList create(CharSequence s) throws ParseException {
+	public static ObjectList parse(CharSequence s) throws ParseException {
 		return s == null ? null : new ObjectList(s);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
index d56a6cf..f0e7145 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ObjectMap.java
@@ -162,7 +162,7 @@ public class ObjectMap extends LinkedHashMap<String,Object> {
 	 * @return A new {@link ObjectMap} object, or <jk>null</jk> if the input is <jk>null</jk>.
 	 * @throws ParseException Invalid JSON string.
 	 */
-	public static ObjectMap create(CharSequence s) throws ParseException {
+	public static ObjectMap parse(CharSequence s) throws ParseException {
 		return s == null ? null : new ObjectMap(s);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
index 0b5baa8..9735bd7 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -57,6 +57,13 @@ public final class StringUtils {
 		.chars("{}|\\^[]`")  // unwise characters.
 		.build();
 
+	// Valid HTTP header characters (including quoted strings and comments).
+	private static final AsciiSet httpHeaderChars = AsciiSet
+		.create()
+		.chars("\t -")
+		.ranges("!-[","]-}")
+		.build();
+
 	// Maps BASE64 characters to 6-bit nibbles.
 	private static final byte[] base64m2 = new byte[128];
 	static {
@@ -2681,4 +2688,32 @@ public final class StringUtils {
 		return new String(sArray);
 	}
 
+	/**
+	 * Strips invalid characters such as CTRL characters from a string meant to be encoded
+	 * as an HTTP header value.
+	 *
+	 * @param s The string to strip chars from.
+	 * @return The string with invalid characters removed.
+	 */
+	public static String stripInvalidHttpHeaderChars(String s) {
+		if (s == null)
+			return null;
+
+		boolean needsReplace = false;
+		for (int i = 0; i < s.length() && ! needsReplace; i++)
+			needsReplace |= httpHeaderChars.contains(s.charAt(i));
+
+		if (! needsReplace)
+			return s;
+
+		StringBuilder sb = new StringBuilder(s.length());
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+			if (httpHeaderChars.contains(c))
+				sb.append(c);
+		}
+
+		return sb.toString();
+	}
+
 }
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.2.html b/juneau-doc/docs/ReleaseNotes/8.1.2.html
index 8f1f16c..76c23a8 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.2.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.2.html
@@ -24,8 +24,8 @@
 	<li>
 		New convenience methods:
 		<ul>	
-			<li class='jm'>{@link oaj.ObjectMap.create(String)}
-			<li class='jm'>{@link oaj.ObjectList.create(String)}
+			<li class='jm'>{@link oaj.ObjectMap.parse(String)}
+			<li class='jm'>{@link oaj.ObjectList.parse(String)}
 		</ul>
 	<li>
 		{@link oaj.marshall.CharMarshall} and {@link oaj.marshall.StreamMarshall} now have public constructors.
@@ -33,6 +33,10 @@
 
 <h5 class='topic w800'>juneau-rest-server</h5>
 <ul class='spaced-list'>
+	<li>
+		New method {@link oajr.RestResponse.setHeaderSafe(String,String)} to strip invalid characters from header values.
+	<li>
+		Fixed issues related to invalid characters being set on HTTP header values.
 </ul>
 
 <h5 class='topic w800'>juneau-rest-client</h5>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index 4b287e3..d0dacf1 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -337,8 +337,8 @@ public class BasicRestCallHandler implements RestCallHandler {
 		if (t == null)
 			t = e2.getRootCause();
 		if (t != null) {
-			res.setHeader("Exception-Name", t.getClass().getName());
-			res.setHeader("Exception-Message", t.getMessage());
+			res.setHeader("Exception-Name", stripInvalidHttpHeaderChars(t.getClass().getName()));
+			res.setHeader("Exception-Message", stripInvalidHttpHeaderChars(t.getMessage()));
 		}
 
 		try {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index f44d3ce..e9940e7 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -27,6 +27,7 @@ import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.httppart.bean.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.http.exception.*;
 import org.apache.juneau.rest.util.*;
@@ -81,7 +82,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 		this.request = req;
 
 		for (Map.Entry<String,Object> e : context.getDefaultResponseHeaders().entrySet())
-			setHeader(e.getKey(), stringify(e.getValue()));
+			setHeaderSafe(e.getKey(), stringify(e.getValue()));
 
 		try {
 			String passThroughHeaders = req.getHeader("x-response-headers");
@@ -89,7 +90,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 				HttpPartParser p = context.getPartParser();
 				ObjectMap m = p.createPartSession(req.getParserSessionArgs()).parse(HttpPartType.HEADER, null, passThroughHeaders, context.getClassMeta(ObjectMap.class));
 				for (Map.Entry<String,Object> e : m.entrySet())
-					setHeader(e.getKey(), e.getValue().toString());
+					setHeaderSafe(e.getKey(), e.getValue().toString());
 			}
 		} catch (Exception e1) {
 			throw new BadRequest(e1, "Invalid format for header 'x-response-headers'.  Must be in URL-encoded format.");
@@ -536,6 +537,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 
 	@Override /* ServletResponse */
 	public void setHeader(String name, String value) {
+
 		// Jetty doesn't set the content type correctly if set through this method.
 		// Tomcat/WAS does.
 		if (name.equalsIgnoreCase("Content-Type"))
@@ -545,6 +547,24 @@ public final class RestResponse extends HttpServletResponseWrapper {
 	}
 
 	/**
+	 * Same as {@link #setHeader(String, String)} but strips invalid characters from the value if present.
+	 *
+	 * These include CTRL characters, newlines, and non-ISO8859-1 characters.
+	 *
+	 * @param name Header name.
+	 * @param value Header value.
+	 */
+	public void setHeaderSafe(String name, String value) {
+
+		// Jetty doesn't set the content type correctly if set through this method.
+		// Tomcat/WAS does.
+		if (name.equalsIgnoreCase("Content-Type"))
+			super.setContentType(value);
+		else
+			super.setHeader(name, StringUtils.stripInvalidHttpHeaderChars(value));
+	}
+
+	/**
 	 * Same as {@link #setHeader(String, String)} but header is defined as a response part
 	 *
 	 * @param h Header to set.
@@ -552,7 +572,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 	 * @throws SerializeException Header part could not be serialized.
 	 */
 	public void setHeader(HttpPart h) throws SchemaValidationException, SerializeException {
-		setHeader(h.getName(), h.asString());
+		setHeaderSafe(h.getName(), h.asString());
 	}
 
 	/**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
index e0f9a71..d0f6690 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
@@ -67,8 +67,8 @@ public class DefaultHandler implements ResponseHandler {
 
 			boolean isThrowable = rm.getClassMeta().isType(Throwable.class);
 			if (isThrowable) {
-				res.setHeader("Exception-Name", rm.getClassMeta().getName());
-				res.setHeader("Exception-Message", ((Throwable)o).getMessage());
+				res.setHeaderSafe("Exception-Name", rm.getClassMeta().getName());
+				res.setHeaderSafe("Exception-Message", ((Throwable)o).getMessage());
 			}
 
 			ResponseBeanPropertyMeta stm = rm.getStatusMethod();
@@ -157,7 +157,7 @@ public class DefaultHandler implements ResponseHandler {
 				);
 
 				for (Map.Entry<String,String> h : session.getResponseHeaders().entrySet())
-					res.setHeader(h.getKey(), h.getValue());
+					res.setHeaderSafe(h.getKey(), h.getValue());
 
 				if (! session.isWriterSerializer()) {
 					if (req.isPlainText()) {