Set output_encoding in FreeMarkerView implementations
According to the official FreeMarker documentation, Spring's FreeMarkerView implementations should be configuring the output_encoding for template rendering. To address that, this commit modifies the FreeMarkerView implementations in Web MVC and WebFlux to explicitly set the output_encoding for template rendering. See https://freemarker.apache.org/docs/pgui_misc_charset.html#autoid_53 See gh-33071 Closes gh-33106
This commit is contained in:
parent
95887c81b9
commit
8b95697c8d
|
@ -26,6 +26,7 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.core.ParseException;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
|
@ -333,7 +334,9 @@ public class FreeMarkerView extends AbstractUrlBasedView {
|
|||
FastByteArrayOutputStream bos = new FastByteArrayOutputStream();
|
||||
Charset charset = getCharset(contentType);
|
||||
Writer writer = new OutputStreamWriter(bos, charset);
|
||||
template.process(freeMarkerModel, writer);
|
||||
Environment env = template.createProcessingEnvironment(freeMarkerModel, writer);
|
||||
env.setOutputEncoding(charset.name());
|
||||
env.process();
|
||||
byte[] bytes = bos.toByteArrayUnsafe();
|
||||
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
|
||||
return Mono.just(buffer);
|
||||
|
|
|
@ -58,12 +58,20 @@ class WebFluxViewResolutionIntegrationTests {
|
|||
|
||||
private static final MediaType TEXT_HTML_ISO_8859_1 = MediaType.parseMediaType("text/html;charset=ISO-8859-1");
|
||||
|
||||
private static final String EXPECTED_BODY = "<html><body>Hello, Java Café</body></html>";
|
||||
|
||||
|
||||
@Nested
|
||||
class FreeMarkerTests {
|
||||
|
||||
private static final String EXPECTED_BODY = """
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, Java Café</h1>
|
||||
<p>output_encoding: %s</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
private static final ClassTemplateLoader classTemplateLoader =
|
||||
new ClassTemplateLoader(WebFluxViewResolutionIntegrationTests.class, "");
|
||||
|
||||
|
@ -77,21 +85,21 @@ class WebFluxViewResolutionIntegrationTests {
|
|||
@Test
|
||||
void freemarkerWithDefaults() throws Exception {
|
||||
MockServerHttpResponse response = runTest(FreeMarkerWebFluxConfig.class);
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify();
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY.formatted("UTF-8")).expectComplete().verify();
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_UTF8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void freemarkerWithExplicitDefaultEncoding() throws Exception {
|
||||
MockServerHttpResponse response = runTest(ExplicitDefaultEncodingConfig.class);
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify();
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY.formatted("UTF-8")).expectComplete().verify();
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_UTF8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void freemarkerWithExplicitDefaultEncodingAndContentType() throws Exception {
|
||||
MockServerHttpResponse response = runTest(ExplicitDefaultEncodingAndContentTypeConfig.class);
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY).expectComplete().verify();
|
||||
StepVerifier.create(response.getBodyAsString()).expectNext(EXPECTED_BODY.formatted("ISO-8859-1")).expectComplete().verify();
|
||||
// When the Content-Type (supported media type) is explicitly set on the view resolver, it should be used.
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML_ISO_8859_1);
|
||||
}
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
<html><body>${hello}, Java Café</body></html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>${hello}, Java Café</h1>
|
||||
<p>output_encoding: ${.output_encoding}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
<html><body>${hello}, Java Café</body></html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>${hello}, Java Café</h1>
|
||||
<p>output_encoding: ${.output_encoding}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.nio.charset.Charset;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.core.ParseException;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
|
@ -364,19 +365,26 @@ public class FreeMarkerView extends AbstractTemplateView {
|
|||
}
|
||||
|
||||
/**
|
||||
* Process the FreeMarker template to the servlet response.
|
||||
* Process the FreeMarker template and write the result to the response.
|
||||
* <p>As of Spring Framework 6.2, this method sets the
|
||||
* {@linkplain Environment#setOutputEncoding(String) output encoding} of the
|
||||
* FreeMarker {@link Environment} to the character encoding of the supplied
|
||||
* {@link HttpServletResponse}.
|
||||
* <p>Can be overridden to customize the behavior.
|
||||
* @param template the template to process
|
||||
* @param model the model for the template
|
||||
* @param response servlet response (use this to get the OutputStream or Writer)
|
||||
* @throws IOException if the template file could not be retrieved
|
||||
* @throws TemplateException if thrown by FreeMarker
|
||||
* @see freemarker.template.Template#process(Object, java.io.Writer)
|
||||
* @see freemarker.template.Template#createProcessingEnvironment(Object, java.io.Writer)
|
||||
* @see freemarker.core.Environment#process()
|
||||
*/
|
||||
protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response)
|
||||
throws IOException, TemplateException {
|
||||
|
||||
template.process(model, response.getWriter());
|
||||
Environment env = template.createProcessingEnvironment(model, response.getWriter());
|
||||
env.setOutputEncoding(response.getCharacterEncoding());
|
||||
env.process();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,9 +48,6 @@ import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
|||
*/
|
||||
class ViewResolutionIntegrationTests {
|
||||
|
||||
private static final String EXPECTED_BODY = "<html><body>Hello, Java Café</body></html>";
|
||||
|
||||
|
||||
@BeforeAll
|
||||
static void verifyDefaultFileEncoding() {
|
||||
assertThat(System.getProperty("file.encoding")).as("JVM default file encoding").isEqualTo("UTF-8");
|
||||
|
@ -60,6 +57,15 @@ class ViewResolutionIntegrationTests {
|
|||
@Nested
|
||||
class FreeMarkerTests {
|
||||
|
||||
private static final String EXPECTED_BODY = """
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, Java Café</h1>
|
||||
<p>output_encoding: %s</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
@Test
|
||||
void freemarkerWithInvalidConfig() {
|
||||
assertThatRuntimeException()
|
||||
|
@ -69,45 +75,49 @@ class ViewResolutionIntegrationTests {
|
|||
|
||||
@Test
|
||||
void freemarkerWithDefaults() throws Exception {
|
||||
String encoding = "ISO-8859-1";
|
||||
MockHttpServletResponse response = runTest(FreeMarkerWebConfig.class);
|
||||
assertThat(response.isCharset()).as("character encoding set in response").isTrue();
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY);
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY.formatted(encoding));
|
||||
// Prior to Spring Framework 6.2, the charset is not updated in the Content-Type.
|
||||
// Thus, we expect ISO-8859-1 instead of UTF-8.
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo("ISO-8859-1");
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=ISO-8859-1");
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo(encoding);
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=" + encoding);
|
||||
}
|
||||
|
||||
@Test // gh-16629, gh-33071
|
||||
void freemarkerWithExistingViewResolver() throws Exception {
|
||||
String encoding = "ISO-8859-1";
|
||||
MockHttpServletResponse response = runTest(ExistingViewResolverConfig.class);
|
||||
assertThat(response.isCharset()).as("character encoding set in response").isTrue();
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY);
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY.formatted(encoding));
|
||||
// Prior to Spring Framework 6.2, the charset is not updated in the Content-Type.
|
||||
// Thus, we expect ISO-8859-1 instead of UTF-8.
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo("ISO-8859-1");
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=ISO-8859-1");
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo(encoding);
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=" + encoding);
|
||||
}
|
||||
|
||||
@Test // gh-33071
|
||||
void freemarkerWithExplicitDefaultEncoding() throws Exception {
|
||||
String encoding = "ISO-8859-1";
|
||||
MockHttpServletResponse response = runTest(ExplicitDefaultEncodingConfig.class);
|
||||
assertThat(response.isCharset()).as("character encoding set in response").isTrue();
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY);
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY.formatted(encoding));
|
||||
// Prior to Spring Framework 6.2, the charset is not updated in the Content-Type.
|
||||
// Thus, we expect ISO-8859-1 instead of UTF-8.
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo("ISO-8859-1");
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=ISO-8859-1");
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo(encoding);
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=" + encoding);
|
||||
}
|
||||
|
||||
@Test // gh-33071
|
||||
void freemarkerWithExplicitDefaultEncodingAndContentType() throws Exception {
|
||||
String encoding = "UTF-16";
|
||||
MockHttpServletResponse response = runTest(ExplicitDefaultEncodingAndContentTypeConfig.class);
|
||||
assertThat(response.isCharset()).as("character encoding set in response").isTrue();
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY);
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY.formatted(encoding));
|
||||
// When the Content-Type is explicitly set on the view resolver, it should be used.
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo("UTF-16");
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=UTF-16");
|
||||
assertThat(response.getCharacterEncoding()).isEqualTo(encoding);
|
||||
assertThat(response.getContentType()).isEqualTo("text/html;charset=" + encoding);
|
||||
}
|
||||
|
||||
|
||||
|
@ -202,7 +212,7 @@ class ViewResolutionIntegrationTests {
|
|||
@Test
|
||||
void groovyMarkup() throws Exception {
|
||||
MockHttpServletResponse response = runTest(GroovyMarkupWebConfig.class);
|
||||
assertThat(response.getContentAsString()).isEqualTo(EXPECTED_BODY);
|
||||
assertThat(response.getContentAsString()).isEqualTo("<html><body>Hello, Java Café</body></html>");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
<html><body>${hello}, Java Café</body></html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>${hello}, Java Café</h1>
|
||||
<p>output_encoding: ${.output_encoding}</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue