You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2015/11/29 12:59:23 UTC
[19/25] incubator-freemarker git commit: FreemarkerServlet
ResponseCharacterEncoding init-param now works properly with the legacy
content_type template attribute (usually specified in the #ftl tag)
FreemarkerServlet ResponseCharacterEncoding init-param now works properly with the legacy content_type template attribute (usually specified in the #ftl tag)
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/43fd9737
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/43fd9737
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/43fd9737
Branch: refs/heads/2.3
Commit: 43fd9737a8acdc33c1d5273439e5d6dcb041accc
Parents: f64aa1f
Author: ddekany <dd...@apache.org>
Authored: Sat Nov 21 12:41:22 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sat Nov 21 12:41:22 2015 +0100
----------------------------------------------------------------------
.../ext/servlet/FreemarkerServlet.java | 104 +++++++++++------
.../ext/servlet/FreemarkerServletTest.java | 114 ++++++++++++-------
2 files changed, 142 insertions(+), 76 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/43fd9737/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 695ac8d..ae48af6 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -407,7 +407,7 @@ public class FreemarkerServlet extends HttpServlet {
private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE = "ignore";
private static final String DEPR_INITPARAM_DEBUG = "debug";
- static final String DEFAULT_CONTENT_TYPE = "text/html";
+ private static final ContentType DEFAULT_CONTENT_TYPE = new ContentType("text/html");
public static final String INIT_PARAM_VALUE_NEVER = "never";
public static final String INIT_PARAM_VALUE_ALWAYS = "always";
@@ -511,12 +511,12 @@ public class FreemarkerServlet extends HttpServlet {
private Configuration config;
@SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
private ObjectWrapper wrapper;
- private String contentType;
+ private ContentType contentType;
private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
+ @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
private Charset forcedResponseCharacterEncoding;
- private boolean contentTypeContainsCharset;
private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
private List/*<MetaInfTldSource>*/ metaInfTldSources;
private List/*<String>*/ classpathTlds;
@@ -661,7 +661,7 @@ public class FreemarkerServlet extends HttpServlet {
} else if (name.equals(INIT_PARAM_DEBUG)) {
debug = StringUtil.getYesNo(value);
} else if (name.equals(INIT_PARAM_CONTENT_TYPE)) {
- contentType = value;
+ contentType = new ContentType(value);
} else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE)) {
overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
} else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
@@ -693,32 +693,14 @@ public class FreemarkerServlet extends HttpServlet {
}
} // while initpnames
- contentTypeContainsCharset = contentTypeContainsCharset(contentType);
- if (contentTypeContainsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
- throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType,
+ if (contentType.containsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
+ throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType.httpHeaderValue,
new IllegalStateException("You can't specify the charset in the content type, because the \"" +
INIT_PARAM_RESPONSE_CHARACTER_ENCODING + "\" init-param isn't set to "
+ "\"" + INIT_PARAM_VALUE_LEGACY + "\"."));
- }
+ }
}
- private boolean contentTypeContainsCharset(String contentType) {
- int charsetIdx = contentType.toLowerCase().indexOf("charset=");
- if (charsetIdx != -1) {
- char c = 0;
- charsetIdx--;
- while (charsetIdx >= 0) {
- c = contentType.charAt(charsetIdx);
- if (!Character.isWhitespace(c)) break;
- charsetIdx--;
- }
- if (charsetIdx == -1 || c == ';') {
- return true;
- }
- }
- return false;
- }
-
private List/*<MetaInfTldSource>*/ parseAsMetaInfTldLocations(String value) throws ParseException {
List/*<MetaInfTldSource>*/ metaInfTldSources = null;
@@ -837,16 +819,24 @@ public class FreemarkerServlet extends HttpServlet {
"Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
}
+ boolean tempSpecContentTypeContainsCharset = false;
if (response.getContentType() == null || overrideResponseContentType != OverrideResponseContentType.NEVER) {
- String templateSpecificContentType = getTemplateSpecificContentType(template);
+ ContentType templateSpecificContentType = getTemplateSpecificContentType(template);
if (templateSpecificContentType != null) {
- response.setContentType(templateSpecificContentType);
+ // With ResponseCharacterEncoding.LEGACY we should append the charset, but we don't do that for b. c.
+ response.setContentType(
+ responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET
+ ? templateSpecificContentType.httpHeaderValue
+ : templateSpecificContentType.getMimeType());
+ tempSpecContentTypeContainsCharset = templateSpecificContentType.containsCharset;
} else if (response.getContentType() == null
|| overrideResponseContentType == OverrideResponseContentType.ALWAYS) {
- if (!contentTypeContainsCharset && responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
- response.setContentType(contentType + "; charset=" + getTemplateSpecificOutputEncoding(template));
+ if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY && !contentType.containsCharset) {
+ // In legacy mode we don't call response.setCharacterEncoding, so the charset must be set here:
+ response.setContentType(
+ contentType.httpHeaderValue + "; charset=" + getTemplateSpecificOutputEncoding(template));
} else {
- response.setContentType(contentType);
+ response.setContentType(contentType.httpHeaderValue);
}
}
}
@@ -855,7 +845,9 @@ public class FreemarkerServlet extends HttpServlet {
&& responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
// Using the Servlet 2.4 way of setting character encoding.
if (responseCharacterEncoding != ResponseCharacterEncoding.FORCE_CHARSET) {
- response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+ if (!tempSpecContentTypeContainsCharset) {
+ response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+ }
} else {
response.setCharacterEncoding(forcedResponseCharacterEncoding.name());
}
@@ -920,21 +912,21 @@ public class FreemarkerServlet extends HttpServlet {
return outputEncoding != null ? outputEncoding : template.getEncoding();
}
- private String getTemplateSpecificContentType(final Template template) {
+ private ContentType getTemplateSpecificContentType(final Template template) {
Object contentTypeAttr = template.getCustomAttribute("content_type");
if (contentTypeAttr != null) {
// Converted with toString() for backward compatibility.
- // Don't add charset for backward compatibility.
- return contentTypeAttr.toString();
+ return new ContentType(contentTypeAttr.toString());
}
String outputFormatMimeType = template.getOutputFormat().getMimeType();
if (outputFormatMimeType != null) {
if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
// In legacy mode we won't call serlvetResponse.setCharacterEncoding(...), so:
- outputFormatMimeType += "; charset=" + getTemplateSpecificOutputEncoding(template);
+ return new ContentType(outputFormatMimeType + "; charset=" + getTemplateSpecificOutputEncoding(template), true);
+ } else {
+ return new ContentType(outputFormatMimeType, false);
}
- return outputFormatMimeType;
}
return null;
@@ -1554,6 +1546,46 @@ public class FreemarkerServlet extends HttpServlet {
}
+ private static class ContentType {
+ private final String httpHeaderValue;
+ private final boolean containsCharset;
+
+ public ContentType(String httpHeaderValue) {
+ this(httpHeaderValue, contentTypeContainsCharset(httpHeaderValue));
+ }
+
+ public ContentType(String httpHeaderValue, boolean containsCharset) {
+ this.httpHeaderValue = httpHeaderValue;
+ this.containsCharset = containsCharset;
+ }
+
+ private static boolean contentTypeContainsCharset(String contentType) {
+ int charsetIdx = contentType.toLowerCase().indexOf("charset=");
+ if (charsetIdx != -1) {
+ char c = 0;
+ charsetIdx--;
+ while (charsetIdx >= 0) {
+ c = contentType.charAt(charsetIdx);
+ if (!Character.isWhitespace(c)) break;
+ charsetIdx--;
+ }
+ if (charsetIdx == -1 || c == ';') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Extracts the MIME type without the charset specifier or other such extras.
+ */
+ private String getMimeType() {
+ int scIdx = httpHeaderValue.indexOf(';');
+ return (scIdx == -1 ? httpHeaderValue : httpHeaderValue.substring(0, scIdx)).trim();
+ }
+
+ }
+
private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] enumValues) {
for (T enumValue : enumValues) {
String enumInitParamValue = enumValue.getInitParamValue();
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/43fd9737/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index b95cda5..f008280 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -53,6 +53,7 @@ public class FreemarkerServletTest {
private static final String OUTPUT_FORMAT_HEADER_FTL = "outputFormatHeader.ftl";
private static final String CONTENT_TYPE_ATTR_FTL = "contentTypeAttr.ftl";
+ private static final String CONTENT_TYPE_ATTR_WITH_CHARSET_FTL = "contentTypeAttrWithCharset.ftl";
private static final String FOO_FTL = "foo.ftl";
private static final String FOO_SRC_UTF8_FTL = "foo-src-utf8.ftl";
private static final String FOO_OUT_UTF8_FTL = "foo-out-utf8.ftl";
@@ -61,6 +62,7 @@ public class FreemarkerServletTest {
private static final String CFG_DEFAULT_ENCODING = "US-ASCII";
/** According to the Servlet Specification */
private static final String SERVLET_RESPONSE_DEFAULT_CHARSET = "ISO-8859-1";
+ private static final String DEFAULT_CONTENT_TYPE = "text/html";
private MockServletContext servletContext;
@@ -182,42 +184,55 @@ public class FreemarkerServletTest {
@Test
public void testResponseOutputCharsetInitParam() throws Exception {
- // Legacy mode is not aware of the outputEncoding, thus it doesn't set it:
- assertOutputEncodingEquals(
- CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
- null, // <- init-param
- FOO_FTL);
- assertOutputEncodingEquals(
- CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
- FreemarkerServlet.INIT_PARAM_VALUE_LEGACY, // <- init-param
- FOO_FTL);
- // Legacy mode follows the source encoding of the template:
- assertOutputEncodingEquals(
- "UTF-8", // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
- null, // <- init-param
- FOO_SRC_UTF8_FTL);
- // Legacy mode doesn't deal with outputEncoding, but it's inherited by the Environment from the Template:
- assertOutputEncodingEquals(
- CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
- "UTF-8", // <- expected env.outputEncoding
- null, // <- init-param
- FOO_OUT_UTF8_FTL);
- // Charset in content type is the strongest:
- assertOutputEncodingEquals(
- "ISO-8859-2", // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
- null, // <- init-param
- "text/html; charset=ISO-8859-2", // ContentType init-param
- FOO_FTL);
- assertOutputEncodingEquals(
- "ISO-8859-2", // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
- null, // <- init-param
- "text/html; charset=ISO-8859-2", // ContentType init-param
- FOO_SRC_UTF8_FTL);
+ for (String initParamValue : new String[] { null, FreemarkerServlet.INIT_PARAM_VALUE_LEGACY }) {
+ // Legacy mode is not aware of the outputEncoding, thus it doesn't set it:
+ assertOutputEncodingEquals(
+ CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ FOO_FTL);
+ assertOutputEncodingEquals(
+ CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ FOO_FTL);
+ // Legacy mode follows the source encoding of the template:
+ assertOutputEncodingEquals(
+ "UTF-8", // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ FOO_SRC_UTF8_FTL);
+ // Legacy mode doesn't deal with outputEncoding, but it's inherited by the Environment from the Template:
+ assertOutputEncodingEquals(
+ CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+ "UTF-8", // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ FOO_OUT_UTF8_FTL);
+ // Charset in content type is the strongest:
+ assertOutputEncodingEquals(
+ "ISO-8859-2", // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ "text/html; charset=ISO-8859-2", // ContentType init-param
+ FOO_FTL);
+ assertOutputEncodingEquals(
+ "ISO-8859-2", // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ "text/html; charset=ISO-8859-2", // ContentType init-param
+ FOO_SRC_UTF8_FTL);
+ assertOutputEncodingEquals(
+ "UTF-8", // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
+ assertOutputEncodingEquals(
+ "UTF-8", // <- expected response.characterEncoding
+ null, // <- expected env.outputEncoding
+ initParamValue, // <- init-param
+ "text/html; charset=ISO-8859-2", // ContentType init-param
+ CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
+ }
// Non-legacy mode always keeps env.outputEncoding in sync. with the Servlet response encoding:
assertOutputEncodingEquals(
@@ -249,6 +264,12 @@ public class FreemarkerServletTest {
} catch (ServletException e) {
assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
}
+ // But the legacy content_type template attribute can still set the output charset:
+ assertOutputEncodingEquals(
+ "UTF-8", // <- expected response.characterEncoding
+ "UTF-8", // <- expected env.outputEncoding
+ FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+ CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
// Do not set mode:
assertOutputEncodingEquals(
@@ -269,8 +290,8 @@ public class FreemarkerServletTest {
// Not allowed to specify the charset in the contentType init-param:
try {
assertOutputEncodingEquals(
- null, // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
+ SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+ SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
"text/html; charset=ISO-8859-2", // ContentType init-param
FOO_FTL);
@@ -278,6 +299,12 @@ public class FreemarkerServletTest {
} catch (ServletException e) {
assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
}
+ // The legacy content_type template attribute can still specify an output charset, though it will be ignored:
+ assertOutputEncodingEquals(
+ SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+ SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
+ FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+ CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
// Forced mode:
assertOutputEncodingEquals(
@@ -308,8 +335,8 @@ public class FreemarkerServletTest {
// Not allowed to specify the charset in the contentType init-param:
try {
assertOutputEncodingEquals(
- null, // <- expected response.characterEncoding
- null, // <- expected env.outputEncoding
+ "UTF-16LE", // <- expected response.characterEncoding
+ "UTF-16LE", // <- expected env.outputEncoding
FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
"text/html; charset=ISO-8859-2", // ContentType init-param
FOO_FTL);
@@ -317,6 +344,12 @@ public class FreemarkerServletTest {
} catch (ServletException e) {
assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
}
+ // The legacy content_type template attribute can still specify an output charset, though it will be overridden:
+ assertOutputEncodingEquals(
+ "UTF-16LE", // <- expected response.characterEncoding
+ "UTF-16LE", // <- expected env.outputEncoding
+ FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+ CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
}
private void assertResponseContentTypeEquals(
@@ -495,6 +528,7 @@ public class FreemarkerServletTest {
tl.putTemplate(FOO_SRC_UTF8_FTL, "foo");
tl.putTemplate(FOO_OUT_UTF8_FTL, "foo");
tl.putTemplate(CONTENT_TYPE_ATTR_FTL, "<#ftl attributes={ 'content_type': 'text/plain' }>foo");
+ tl.putTemplate(CONTENT_TYPE_ATTR_WITH_CHARSET_FTL, "<#ftl attributes={ 'content_type': 'text/plain; charset=UTF-8' }>foo");
tl.putTemplate(OUTPUT_FORMAT_HEADER_FTL, "<#ftl outputFormat='plainText'>foo");
return tl;
} else {