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
This commit is contained in:
parent
73e398a165
commit
a0c210457b
|
@ -106,6 +106,12 @@ public abstract class WebUtils {
|
|||
*/
|
||||
public static final String HTML_ESCAPE_CONTEXT_PARAM = "defaultHtmlEscape";
|
||||
|
||||
/**
|
||||
* Use of response encoding for HTML escaping parameter at the servlet context level
|
||||
* (i.e. a context-param in {@code web.xml}): "responseEncodedHtmlEscape".
|
||||
*/
|
||||
public static final String RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM = "responseEncodedHtmlEscape";
|
||||
|
||||
/**
|
||||
* Web app root key parameter at the servlet context level
|
||||
* (i.e. a context-param in {@code web.xml}): "webAppRootKey".
|
||||
|
@ -175,7 +181,9 @@ public abstract class WebUtils {
|
|||
* (if any). Falls back to {@code false} in case of no explicit default given.
|
||||
* @param servletContext the servlet context of the web application
|
||||
* @return whether default HTML escaping is enabled (default is false)
|
||||
* @deprecated as of Spring 4.1, in favor of {@link #getDefaultHtmlEscape}
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isDefaultHtmlEscape(ServletContext servletContext) {
|
||||
if (servletContext == null) {
|
||||
return false;
|
||||
|
@ -202,6 +210,26 @@ public abstract class WebUtils {
|
|||
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether response encoding should be used when HTML escaping characters,
|
||||
* thus only escaping XML markup significant characters with UTF-* encodings.
|
||||
* This option is enabled for the web application with a ServletContext param,
|
||||
* i.e. the value of the "responseEncodedHtmlEscape" context-param in {@code web.xml}
|
||||
* (if any).
|
||||
* <p>This method differentiates between no param specified at all and
|
||||
* an actual boolean value specified, allowing to have a context-specific
|
||||
* default in case of no setting at the global level.
|
||||
* @param servletContext the servlet context of the web application
|
||||
* @return whether response encoding is used for HTML escaping (null = no explicit default)
|
||||
*/
|
||||
public static Boolean getResponseEncodedHtmlEscape(ServletContext servletContext) {
|
||||
if (servletContext == null) {
|
||||
return null;
|
||||
}
|
||||
String param = servletContext.getInitParameter(RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM);
|
||||
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the temporary directory for the current web application,
|
||||
* as provided by the servlet container.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -115,6 +115,8 @@ public class RequestContext {
|
|||
|
||||
private Boolean defaultHtmlEscape;
|
||||
|
||||
private Boolean responseEncodedHtmlEscape;
|
||||
|
||||
private UrlPathHelper urlPathHelper;
|
||||
|
||||
private RequestDataValueProcessor requestDataValueProcessor;
|
||||
|
@ -263,6 +265,8 @@ public class RequestContext {
|
|||
// context-param in web.xml, if any.
|
||||
this.defaultHtmlEscape = WebUtils.getDefaultHtmlEscape(this.webApplicationContext.getServletContext());
|
||||
|
||||
this.responseEncodedHtmlEscape = WebUtils.getResponseEncodedHtmlEscape(this.webApplicationContext.getServletContext());
|
||||
|
||||
this.urlPathHelper = new UrlPathHelper();
|
||||
|
||||
if (this.webApplicationContext.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
|
||||
|
@ -484,6 +488,27 @@ public class RequestContext {
|
|||
return this.defaultHtmlEscape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is HTML escaping using the response encoding by default?
|
||||
* If enabled, only XML markup significant characters will be escaped with UTF-* encodings.
|
||||
* <p>Falls back to {@code false} in case of no explicit default given.
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public boolean isResponseEncodedHtmlEscape() {
|
||||
return (this.responseEncodedHtmlEscape != null && this.responseEncodedHtmlEscape.booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default setting about use of response encoding for HTML escape setting,
|
||||
* differentiating between no default specified and an explicit value.
|
||||
* @return whether default use of response encoding HTML escaping is enabled (null = no explicit default)
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public Boolean getResponseEncodedHtmlEscape() {
|
||||
return this.responseEncodedHtmlEscape;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the UrlPathHelper to use for context path and request URI decoding.
|
||||
* Can be used to pass a shared UrlPathHelper instance in.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,6 @@ import javax.servlet.jsp.JspException;
|
|||
import javax.servlet.jsp.tagext.BodyContent;
|
||||
import javax.servlet.jsp.tagext.BodyTag;
|
||||
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import org.springframework.web.util.JavaScriptUtils;
|
||||
|
||||
/**
|
||||
|
@ -79,7 +78,7 @@ public class EscapeBodyTag extends HtmlEscapingAwareTag implements BodyTag {
|
|||
try {
|
||||
String content = readBodyContent();
|
||||
// HTML and/or JavaScript escape, if demanded
|
||||
content = isHtmlEscape() ? HtmlUtils.htmlEscape(content) : content;
|
||||
content = htmlEscape(content);
|
||||
content = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(content) : content;
|
||||
writeBodyContent(content);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,7 +34,6 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import org.springframework.web.util.JavaScriptUtils;
|
||||
import org.springframework.web.util.TagUtils;
|
||||
|
||||
|
@ -121,7 +120,7 @@ public class EvalTag extends HtmlEscapingAwareTag {
|
|||
try {
|
||||
String result = this.expression.getValue(evaluationContext, String.class);
|
||||
result = ObjectUtils.getDisplayString(result);
|
||||
result = (isHtmlEscape() ? HtmlUtils.htmlEscape(result) : result);
|
||||
result = htmlEscape(result);
|
||||
result = (this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(result) : result);
|
||||
this.pageContext.getOut().print(result);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,6 +18,8 @@ package org.springframework.web.servlet.tags;
|
|||
|
||||
import javax.servlet.jsp.JspException;
|
||||
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
/**
|
||||
* Superclass for tags that output content that might get HTML-escaped.
|
||||
*
|
||||
|
@ -31,7 +33,8 @@ import javax.servlet.jsp.JspException;
|
|||
* @see #setHtmlEscape
|
||||
* @see HtmlEscapeTag
|
||||
* @see org.springframework.web.servlet.support.RequestContext#isDefaultHtmlEscape
|
||||
* @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
|
||||
* @see org.springframework.web.util.WebUtils#getDefaultHtmlEscape
|
||||
* @see org.springframework.web.util.WebUtils#getResponseEncodedHtmlEscape
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class HtmlEscapingAwareTag extends RequestContextAwareTag {
|
||||
|
@ -72,4 +75,37 @@ public abstract class HtmlEscapingAwareTag extends RequestContextAwareTag {
|
|||
return getRequestContext().isDefaultHtmlEscape();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the applicable default for the use of response encoding with HTML escape for this tag.
|
||||
* <p>The default implementation checks the RequestContext's setting,
|
||||
* falling back to {@code false} in case of no explicit default given.
|
||||
* @see #getRequestContext()
|
||||
* @since 4.1.2
|
||||
*/
|
||||
protected boolean isResponseEncodedHtmlEscape() {
|
||||
return getRequestContext().isResponseEncodedHtmlEscape();
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML encodes the given string, only if the htmlEscape setting is enabled.
|
||||
* The response encoding will be taken into account if the responseEncodedHtmlEscape setting is enabled.
|
||||
* @param content
|
||||
* @return
|
||||
* @see #isHtmlEscape()
|
||||
* @see #isResponseEncodedHtmlEscape()
|
||||
* @since 4.1.2
|
||||
*/
|
||||
protected String htmlEscape(String content) {
|
||||
String out = content;
|
||||
if(isHtmlEscape()) {
|
||||
if(isResponseEncodedHtmlEscape()) {
|
||||
out = HtmlUtils.htmlEscape(content, this.pageContext.getResponse().getCharacterEncoding());
|
||||
}
|
||||
else {
|
||||
out = HtmlUtils.htmlEscape(content);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -28,7 +28,6 @@ import org.springframework.context.MessageSourceResolvable;
|
|||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import org.springframework.web.util.JavaScriptUtils;
|
||||
import org.springframework.web.util.TagUtils;
|
||||
|
||||
|
@ -181,7 +180,7 @@ public class MessageTag extends HtmlEscapingAwareTag implements ArgumentAware {
|
|||
String msg = resolveMessage();
|
||||
|
||||
// HTML and/or JavaScript escape, if demanded.
|
||||
msg = isHtmlEscape() ? HtmlUtils.htmlEscape(msg) : msg;
|
||||
msg = htmlEscape(msg);
|
||||
msg = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(msg) : msg;
|
||||
|
||||
// Expose as variable, if demanded, else write to the page.
|
||||
|
|
|
@ -105,7 +105,7 @@ public class TransformTag extends HtmlEscapingAwareTag {
|
|||
// Else, just do a toString.
|
||||
result = this.value.toString();
|
||||
}
|
||||
result = isHtmlEscape() ? HtmlUtils.htmlEscape(result) : result;
|
||||
result = htmlEscape(result);
|
||||
if (this.var != null) {
|
||||
pageContext.setAttribute(this.var, result, TagUtils.getScope(this.scope));
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ public class UrlTag extends HtmlEscapingAwareTag implements ParamAware {
|
|||
}
|
||||
|
||||
// HTML and/or JavaScript escape, if demanded.
|
||||
urlStr = isHtmlEscape() ? HtmlUtils.htmlEscape(urlStr) : urlStr;
|
||||
urlStr = htmlEscape(urlStr);
|
||||
urlStr = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(urlStr) : urlStr;
|
||||
|
||||
return urlStr;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -29,6 +29,7 @@ import org.springframework.context.support.DefaultMessageSourceResolvable;
|
|||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.servlet.support.RequestContext;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link MessageTag}.
|
||||
|
@ -266,11 +267,31 @@ public class MessageTagTests extends AbstractTagTests {
|
|||
}
|
||||
};
|
||||
tag.setPageContext(pc);
|
||||
tag.setText("test & text");
|
||||
tag.setText("test & text é");
|
||||
tag.setHtmlEscape(true);
|
||||
assertTrue("Correct doStartTag return value", tag.doStartTag() == Tag.EVAL_BODY_INCLUDE);
|
||||
assertEquals("Correct doEndTag return value", Tag.EVAL_PAGE, tag.doEndTag());
|
||||
assertEquals("Correct message", "test & text", message.toString());
|
||||
assertEquals("Correct message", "test & text é", message.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public void testMessageTagWithTextEncodingEscaped() throws JspException {
|
||||
PageContext pc = createPageContext();
|
||||
pc.getServletContext().setInitParameter(WebUtils.RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM, "true");
|
||||
pc.getResponse().setCharacterEncoding("UTF-8");
|
||||
final StringBuffer message = new StringBuffer();
|
||||
MessageTag tag = new MessageTag() {
|
||||
@Override
|
||||
protected void writeMessage(String msg) {
|
||||
message.append(msg);
|
||||
}
|
||||
};
|
||||
tag.setPageContext(pc);
|
||||
tag.setText("test <&> é");
|
||||
tag.setHtmlEscape(true);
|
||||
assertTrue("Correct doStartTag return value", tag.doStartTag() == Tag.EVAL_BODY_INCLUDE);
|
||||
assertEquals("Correct doEndTag return value", Tag.EVAL_PAGE, tag.doEndTag());
|
||||
assertEquals("Correct message", "test <&> é", message.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
|
|
Loading…
Reference in New Issue