Skip to content

Commit a0c2104

Browse files
committed
Use response encoding when escaping HTML
With SPR-9293, it is now possible to HTML escape text while taking into account the current response encoding. When using UTF-* encodings, only XML markup significant characters are escaped, since UTF-* natively support those characters. This commit adds a new servlet context parameter to enable this fix by default in a Spring MVC application: <context-param> <param-name>responseEncodedHtmlEscape</param-name> <param-value>true</param-value> </context-param> Issue: SPR-12350, SPR-12132
1 parent 73e398a commit a0c2104

File tree

9 files changed

+124
-17
lines changed

9 files changed

+124
-17
lines changed

spring-web/src/main/java/org/springframework/web/util/WebUtils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ public abstract class WebUtils {
106106
*/
107107
public static final String HTML_ESCAPE_CONTEXT_PARAM = "defaultHtmlEscape";
108108

109+
/**
110+
* Use of response encoding for HTML escaping parameter at the servlet context level
111+
* (i.e. a context-param in {@code web.xml}): "responseEncodedHtmlEscape".
112+
*/
113+
public static final String RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM = "responseEncodedHtmlEscape";
114+
109115
/**
110116
* Web app root key parameter at the servlet context level
111117
* (i.e. a context-param in {@code web.xml}): "webAppRootKey".
@@ -175,7 +181,9 @@ public static void removeWebAppRootSystemProperty(ServletContext servletContext)
175181
* (if any). Falls back to {@code false} in case of no explicit default given.
176182
* @param servletContext the servlet context of the web application
177183
* @return whether default HTML escaping is enabled (default is false)
184+
* @deprecated as of Spring 4.1, in favor of {@link #getDefaultHtmlEscape}
178185
*/
186+
@Deprecated
179187
public static boolean isDefaultHtmlEscape(ServletContext servletContext) {
180188
if (servletContext == null) {
181189
return false;
@@ -202,6 +210,26 @@ public static Boolean getDefaultHtmlEscape(ServletContext servletContext) {
202210
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
203211
}
204212

213+
/**
214+
* Return whether response encoding should be used when HTML escaping characters,
215+
* thus only escaping XML markup significant characters with UTF-* encodings.
216+
* This option is enabled for the web application with a ServletContext param,
217+
* i.e. the value of the "responseEncodedHtmlEscape" context-param in {@code web.xml}
218+
* (if any).
219+
* <p>This method differentiates between no param specified at all and
220+
* an actual boolean value specified, allowing to have a context-specific
221+
* default in case of no setting at the global level.
222+
* @param servletContext the servlet context of the web application
223+
* @return whether response encoding is used for HTML escaping (null = no explicit default)
224+
*/
225+
public static Boolean getResponseEncodedHtmlEscape(ServletContext servletContext) {
226+
if (servletContext == null) {
227+
return null;
228+
}
229+
String param = servletContext.getInitParameter(RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM);
230+
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
231+
}
232+
205233
/**
206234
* Return the temporary directory for the current web application,
207235
* as provided by the servlet container.

spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -115,6 +115,8 @@ public class RequestContext {
115115

116116
private Boolean defaultHtmlEscape;
117117

118+
private Boolean responseEncodedHtmlEscape;
119+
118120
private UrlPathHelper urlPathHelper;
119121

120122
private RequestDataValueProcessor requestDataValueProcessor;
@@ -263,6 +265,8 @@ else if (localeResolver != null) {
263265
// context-param in web.xml, if any.
264266
this.defaultHtmlEscape = WebUtils.getDefaultHtmlEscape(this.webApplicationContext.getServletContext());
265267

268+
this.responseEncodedHtmlEscape = WebUtils.getResponseEncodedHtmlEscape(this.webApplicationContext.getServletContext());
269+
266270
this.urlPathHelper = new UrlPathHelper();
267271

268272
if (this.webApplicationContext.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
@@ -484,6 +488,27 @@ public Boolean getDefaultHtmlEscape() {
484488
return this.defaultHtmlEscape;
485489
}
486490

491+
/**
492+
* Is HTML escaping using the response encoding by default?
493+
* If enabled, only XML markup significant characters will be escaped with UTF-* encodings.
494+
* <p>Falls back to {@code false} in case of no explicit default given.
495+
* @since 4.1.2
496+
*/
497+
public boolean isResponseEncodedHtmlEscape() {
498+
return (this.responseEncodedHtmlEscape != null && this.responseEncodedHtmlEscape.booleanValue());
499+
}
500+
501+
/**
502+
* Return the default setting about use of response encoding for HTML escape setting,
503+
* differentiating between no default specified and an explicit value.
504+
* @return whether default use of response encoding HTML escaping is enabled (null = no explicit default)
505+
* @since 4.1.2
506+
*/
507+
public Boolean getResponseEncodedHtmlEscape() {
508+
return this.responseEncodedHtmlEscape;
509+
}
510+
511+
487512
/**
488513
* Set the UrlPathHelper to use for context path and request URI decoding.
489514
* Can be used to pass a shared UrlPathHelper instance in.

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/EscapeBodyTag.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
2121
import javax.servlet.jsp.tagext.BodyContent;
2222
import javax.servlet.jsp.tagext.BodyTag;
2323

24-
import org.springframework.web.util.HtmlUtils;
2524
import org.springframework.web.util.JavaScriptUtils;
2625

2726
/**
@@ -79,7 +78,7 @@ public int doAfterBody() throws JspException {
7978
try {
8079
String content = readBodyContent();
8180
// HTML and/or JavaScript escape, if demanded
82-
content = isHtmlEscape() ? HtmlUtils.htmlEscape(content) : content;
81+
content = htmlEscape(content);
8382
content = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(content) : content;
8483
writeBodyContent(content);
8584
}

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/EvalTag.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,7 +34,6 @@
3434
import org.springframework.expression.spel.support.StandardEvaluationContext;
3535
import org.springframework.expression.spel.support.StandardTypeConverter;
3636
import org.springframework.util.ObjectUtils;
37-
import org.springframework.web.util.HtmlUtils;
3837
import org.springframework.web.util.JavaScriptUtils;
3938
import org.springframework.web.util.TagUtils;
4039

@@ -121,7 +120,7 @@ public int doEndTag() throws JspException {
121120
try {
122121
String result = this.expression.getValue(evaluationContext, String.class);
123122
result = ObjectUtils.getDisplayString(result);
124-
result = (isHtmlEscape() ? HtmlUtils.htmlEscape(result) : result);
123+
result = htmlEscape(result);
125124
result = (this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(result) : result);
126125
this.pageContext.getOut().print(result);
127126
}

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/HtmlEscapingAwareTag.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
1818

1919
import javax.servlet.jsp.JspException;
2020

21+
import org.springframework.web.util.HtmlUtils;
22+
2123
/**
2224
* Superclass for tags that output content that might get HTML-escaped.
2325
*
@@ -31,7 +33,8 @@
3133
* @see #setHtmlEscape
3234
* @see HtmlEscapeTag
3335
* @see org.springframework.web.servlet.support.RequestContext#isDefaultHtmlEscape
34-
* @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
36+
* @see org.springframework.web.util.WebUtils#getDefaultHtmlEscape
37+
* @see org.springframework.web.util.WebUtils#getResponseEncodedHtmlEscape
3538
*/
3639
@SuppressWarnings("serial")
3740
public abstract class HtmlEscapingAwareTag extends RequestContextAwareTag {
@@ -72,4 +75,37 @@ protected boolean isDefaultHtmlEscape() {
7275
return getRequestContext().isDefaultHtmlEscape();
7376
}
7477

78+
/**
79+
* Return the applicable default for the use of response encoding with HTML escape for this tag.
80+
* <p>The default implementation checks the RequestContext's setting,
81+
* falling back to {@code false} in case of no explicit default given.
82+
* @see #getRequestContext()
83+
* @since 4.1.2
84+
*/
85+
protected boolean isResponseEncodedHtmlEscape() {
86+
return getRequestContext().isResponseEncodedHtmlEscape();
87+
}
88+
89+
/**
90+
* HTML encodes the given string, only if the htmlEscape setting is enabled.
91+
* The response encoding will be taken into account if the responseEncodedHtmlEscape setting is enabled.
92+
* @param content
93+
* @return
94+
* @see #isHtmlEscape()
95+
* @see #isResponseEncodedHtmlEscape()
96+
* @since 4.1.2
97+
*/
98+
protected String htmlEscape(String content) {
99+
String out = content;
100+
if(isHtmlEscape()) {
101+
if(isResponseEncodedHtmlEscape()) {
102+
out = HtmlUtils.htmlEscape(content, this.pageContext.getResponse().getCharacterEncoding());
103+
}
104+
else {
105+
out = HtmlUtils.htmlEscape(content);
106+
}
107+
}
108+
return out;
109+
}
110+
75111
}

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/MessageTag.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@
2828
import org.springframework.context.NoSuchMessageException;
2929
import org.springframework.util.ObjectUtils;
3030
import org.springframework.util.StringUtils;
31-
import org.springframework.web.util.HtmlUtils;
3231
import org.springframework.web.util.JavaScriptUtils;
3332
import org.springframework.web.util.TagUtils;
3433

@@ -181,7 +180,7 @@ public int doEndTag() throws JspException {
181180
String msg = resolveMessage();
182181

183182
// HTML and/or JavaScript escape, if demanded.
184-
msg = isHtmlEscape() ? HtmlUtils.htmlEscape(msg) : msg;
183+
msg = htmlEscape(msg);
185184
msg = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(msg) : msg;
186185

187186
// Expose as variable, if demanded, else write to the page.

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/TransformTag.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ protected final int doStartTagInternal() throws JspException {
105105
// Else, just do a toString.
106106
result = this.value.toString();
107107
}
108-
result = isHtmlEscape() ? HtmlUtils.htmlEscape(result) : result;
108+
result = htmlEscape(result);
109109
if (this.var != null) {
110110
pageContext.setAttribute(this.var, result, TagUtils.getScope(this.scope));
111111
}

spring-webmvc/src/main/java/org/springframework/web/servlet/tags/UrlTag.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ private String createUrl() throws JspException {
226226
}
227227

228228
// HTML and/or JavaScript escape, if demanded.
229-
urlStr = isHtmlEscape() ? HtmlUtils.htmlEscape(urlStr) : urlStr;
229+
urlStr = htmlEscape(urlStr);
230230
urlStr = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(urlStr) : urlStr;
231231

232232
return urlStr;

spring-webmvc/src/test/java/org/springframework/web/servlet/tags/MessageTagTests.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import org.springframework.web.context.ConfigurableWebApplicationContext;
3030
import org.springframework.web.servlet.support.RequestContext;
3131
import org.springframework.web.servlet.support.RequestContextUtils;
32+
import org.springframework.web.util.WebUtils;
3233

3334
/**
3435
* Tests for {@link MessageTag}.
@@ -266,11 +267,31 @@ protected void writeMessage(String msg) {
266267
}
267268
};
268269
tag.setPageContext(pc);
269-
tag.setText("test & text");
270+
tag.setText("test & text é");
270271
tag.setHtmlEscape(true);
271272
assertTrue("Correct doStartTag return value", tag.doStartTag() == Tag.EVAL_BODY_INCLUDE);
272273
assertEquals("Correct doEndTag return value", Tag.EVAL_PAGE, tag.doEndTag());
273-
assertEquals("Correct message", "test &amp; text", message.toString());
274+
assertEquals("Correct message", "test &amp; text &eacute;", message.toString());
275+
}
276+
277+
@SuppressWarnings("serial")
278+
public void testMessageTagWithTextEncodingEscaped() throws JspException {
279+
PageContext pc = createPageContext();
280+
pc.getServletContext().setInitParameter(WebUtils.RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM, "true");
281+
pc.getResponse().setCharacterEncoding("UTF-8");
282+
final StringBuffer message = new StringBuffer();
283+
MessageTag tag = new MessageTag() {
284+
@Override
285+
protected void writeMessage(String msg) {
286+
message.append(msg);
287+
}
288+
};
289+
tag.setPageContext(pc);
290+
tag.setText("test <&> é");
291+
tag.setHtmlEscape(true);
292+
assertTrue("Correct doStartTag return value", tag.doStartTag() == Tag.EVAL_BODY_INCLUDE);
293+
assertEquals("Correct doEndTag return value", Tag.EVAL_PAGE, tag.doEndTag());
294+
assertEquals("Correct message", "test &lt;&amp;&gt; é", message.toString());
274295
}
275296

276297
@SuppressWarnings("serial")

0 commit comments

Comments
 (0)