SPR-5605 spring:url tag should use htmlEscape instead of escapeXml for entity encoding

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@825 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Scott Andrews 2009-03-26 04:37:26 +00:00
parent 10f6f3733c
commit 2d2924e8ce
9 changed files with 95 additions and 98 deletions

View File

@ -14,6 +14,6 @@ ex.printStackTrace(new java.io.PrintWriter(out));
<p/> <p/>
<br/> <br/>
<a href="<spring:url value="/" escapeXml="true" />">Home</a> <a href="<spring:url value="/" htmlEscape="true" />">Home</a>
<%@ include file="/WEB-INF/jsp/footer.jsp" %> <%@ include file="/WEB-INF/jsp/footer.jsp" %>

View File

@ -1,8 +1,8 @@
<table class="footer"> <table class="footer">
<tr> <tr>
<td><a href="<spring:url value="/" escapeXml="true" />">Home</a></td> <td><a href="<spring:url value="/" htmlEscape="true" />">Home</a></td>
<td align="right"><img src="<spring:url value="/static/images/springsource-logo.png" escapeXml="true" />" alt="Sponsored by SpringSource"/></td> <td align="right"><img src="<spring:url value="/static/images/springsource-logo.png" htmlEscape="true" />" alt="Sponsored by SpringSource"/></td>
</tr> </tr>
</table> </table>

View File

@ -5,7 +5,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="<spring:url value="/static/styles/petclinic.css" escapeXml="true" />" type="text/css"/> <link rel="stylesheet" href="<spring:url value="/static/styles/petclinic.css" htmlEscape="true" />" type="text/css"/>
<title>PetClinic :: a Spring Framework demonstration</title> <title>PetClinic :: a Spring Framework demonstration</title>
</head> </head>

View File

@ -21,6 +21,6 @@
</form:form> </form:form>
<br/> <br/>
<a href='<spring:url value="/owners/new" escapeXml="true"/>'>Add Owner</a> <a href='<spring:url value="/owners/new" htmlEscape="true"/>'>Add Owner</a>
<%@ include file="/WEB-INF/jsp/footer.jsp" %> <%@ include file="/WEB-INF/jsp/footer.jsp" %>

View File

@ -23,7 +23,7 @@
<table class="table-buttons"> <table class="table-buttons">
<tr> <tr>
<td> <td>
<a href="<spring:url value="/vets.xml" escapeXml="true" />">View as XML</a> <a href="<spring:url value="/vets.xml" htmlEscape="true" />">View as XML</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -1,13 +1,13 @@
<%@ include file="/WEB-INF/jsp/includes.jsp" %> <%@ include file="/WEB-INF/jsp/includes.jsp" %>
<%@ include file="/WEB-INF/jsp/header.jsp" %> <%@ include file="/WEB-INF/jsp/header.jsp" %>
<img src="<spring:url value="/static/images/pets.png" escapeXml="true" />" align="right" style="position:relative;right:30px;"> <img src="<spring:url value="/static/images/pets.png" htmlEscape="true" />" align="right" style="position:relative;right:30px;">
<h2><fmt:message key="welcome"/></h2> <h2><fmt:message key="welcome"/></h2>
<ul> <ul>
<li><a href="<spring:url value="/owners/search" escapeXml="true" />">Find owner</a></li> <li><a href="<spring:url value="/owners/search" htmlEscape="true" />">Find owner</a></li>
<li><a href="<spring:url value="/vets" escapeXml="true" />">Display all veterinarians</a></li> <li><a href="<spring:url value="/vets" htmlEscape="true" />">Display all veterinarians</a></li>
<li><a href="<spring:url value="/static/html/tutorial.html" escapeXml="true" />">Tutorial</a></li> <li><a href="<spring:url value="/static/html/tutorial.html" htmlEscape="true" />">Tutorial</a></li>
</ul> </ul>
<p>&nbsp;</p> <p>&nbsp;</p>

View File

@ -27,9 +27,10 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext; import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import org.springframework.util.StringUtils; import org.springframework.web.util.ExpressionEvaluationUtils;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.JavaScriptUtils;
import org.springframework.web.util.TagUtils; import org.springframework.web.util.TagUtils;
/** /**
@ -39,7 +40,8 @@ import org.springframework.web.util.TagUtils;
* <p>Enhancements to the JSTL functionality include: * <p>Enhancements to the JSTL functionality include:
* <ul> * <ul>
* <li>URL encoded template URI variables</li> * <li>URL encoded template URI variables</li>
* <li>XML escaping of URLs</li> * <li>HTML/XML escaping of URLs</li>
* <li>JavaScipt escaping of URLs</li>
* </ul> * </ul>
* *
* <p>Template URI variables are indicated in the {@link #setValue(String) 'value'} * <p>Template URI variables are indicated in the {@link #setValue(String) 'value'}
@ -53,8 +55,10 @@ import org.springframework.web.util.TagUtils;
* over direct EL substitution as the values are URL encoded. Failure to properly * over direct EL substitution as the values are URL encoded. Failure to properly
* encode URL can leave an application vulnerable to XSS and other injection attacks. * encode URL can leave an application vulnerable to XSS and other injection attacks.
* *
* <p>URLs can be XML escaped by setting the {@link #setEscapeXml(String) * <p>URLs can be HTML/XML escaped by setting the {@link #setHtmlEscape(String)
* 'escapeXml'} attribute to 'true', the default is 'false'. * 'htmlEscape'} attribute to 'true'. Detects an HTML escaping setting, either on
* this tag instance, the page level, or the <code>web.xml</code> level. The default
* is 'false'. When setting the URL value into a variable, escaping is not recommended.
* *
* <p>Example usage: * <p>Example usage:
* <pre>&lt;spring:url value="/url/path/{variableName}"&gt; * <pre>&lt;spring:url value="/url/path/{variableName}"&gt;
@ -67,7 +71,7 @@ import org.springframework.web.util.TagUtils;
* @since 3.0 * @since 3.0
* @see ParamTag * @see ParamTag
*/ */
public class UrlTag extends TagSupport implements ParamAware { public class UrlTag extends HtmlEscapingAwareTag implements ParamAware {
private static final String URL_TEMPLATE_DELIMITER_PREFIX = "{"; private static final String URL_TEMPLATE_DELIMITER_PREFIX = "{";
@ -75,8 +79,6 @@ public class UrlTag extends TagSupport implements ParamAware {
private static final String URL_TYPE_ABSOLUTE = "://"; private static final String URL_TYPE_ABSOLUTE = "://";
private static final char[] XML_CHARS = { '&', '<', '>', '"', '\'' };
private List<Param> params; private List<Param> params;
@ -92,7 +94,7 @@ public class UrlTag extends TagSupport implements ParamAware {
private int scope = PageContext.PAGE_SCOPE; private int scope = PageContext.PAGE_SCOPE;
private boolean escapeXml = false; private boolean javaScriptEscape = false;
/** /**
@ -142,13 +144,12 @@ public class UrlTag extends TagSupport implements ParamAware {
} }
/** /**
* Instructs the tag to XML entity encode the resulting URL. * Set JavaScript escaping for this tag, as boolean value.
* <p>Defaults to false to maintain compatibility with the JSTL c:url tag. * Default is "false".
* <p><b>NOTE:</b> Strongly recommended to set as 'true' when rendering
* directly to the JspWriter in an XML or HTML based file.
*/ */
public void setEscapeXml(String escapeXml) { public void setJavaScriptEscape(String javaScriptEscape) throws JspException {
this.escapeXml = Boolean.valueOf(escapeXml); this.javaScriptEscape =
ExpressionEvaluationUtils.evaluateBoolean("javaScriptEscape", javaScriptEscape, pageContext);
} }
public void addParam(Param param) { public void addParam(Param param) {
@ -157,7 +158,7 @@ public class UrlTag extends TagSupport implements ParamAware {
@Override @Override
public int doStartTag() throws JspException { public int doStartTagInternal() throws JspException {
this.params = new LinkedList<Param>(); this.params = new LinkedList<Param>();
this.templateParams = new HashSet<String>(); this.templateParams = new HashSet<String>();
return EVAL_BODY_INCLUDE; return EVAL_BODY_INCLUDE;
@ -213,9 +214,11 @@ public class UrlTag extends TagSupport implements ParamAware {
// (Do not embed the session identifier in a remote link!) // (Do not embed the session identifier in a remote link!)
urlStr = response.encodeURL(urlStr); urlStr = response.encodeURL(urlStr);
} }
if (this.escapeXml) {
urlStr = escapeXml(urlStr); // HTML and/or JavaScript escape, if demanded.
} urlStr = isHtmlEscape() ? HtmlUtils.htmlEscape(urlStr) : urlStr;
urlStr = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(urlStr) : urlStr;
return urlStr; return urlStr;
} }
@ -295,40 +298,11 @@ public class UrlTag extends TagSupport implements ParamAware {
} }
} }
/**
* XML entity encode the provided string. &#38;, &#60;, &#62;, &#39; and
* &#34; are encoded to their entity values.
* @param xml the value to escape
* @return the escaped value
*/
protected String escapeXml(String xml) {
if (xml == null) {
return null;
}
String escapedXml = xml;
for (char xmlChar : XML_CHARS) {
escapedXml = StringUtils.replace(escapedXml, String.valueOf(xmlChar), entityValue(xmlChar));
}
return escapedXml;
}
/**
* Convert a character value to a XML entity value.
* The decimal value of the character is used.
* <p>For example, 'A' is converted to "&amp;#65;".
* @param xmlChar the character to encode
* @return the entity value
*/
protected String entityValue(char xmlChar) {
return new StringBuilder().append("&#").append(Integer.toString(xmlChar)).append(";").toString();
}
/** /**
* Internal enum that classifies URLs by type. * Internal enum that classifies URLs by type.
*/ */
private enum UrlType { private enum UrlType {
CONTEXT_RELATIVE, RELATIVE, ABSOLUTE CONTEXT_RELATIVE, RELATIVE, ABSOLUTE
} }

View File

@ -379,13 +379,18 @@
effect unless the var attribute is also defined.</description> effect unless the var attribute is also defined.</description>
</attribute> </attribute>
<attribute> <attribute>
<name>escapeXml</name> <name>htmlEscape</name>
<required>false</required> <required>false</required>
<rtexprvalue>true</rtexprvalue> <rtexprvalue>true</rtexprvalue>
<description>Escape XML special characters in the resulting URL. 'true' and <description>Set HTML escaping for this tag, as boolean value. Overrides the
'false' are supported. Defaults to 'false' to maintain compatibility with default HTML escaping setting for the current page.</description>
the JSTL c:url tag. Strongly recommended to set as 'true' when rendering </attribute>
directly to the JspWriter in an XML or HTML based document.</description> <attribute>
<name>javaScriptEscape</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>Set JavaScript escaping for this tag, as boolean value.
Default is false.</description>
</attribute> </attribute>
</tag> </tag>

View File

@ -90,7 +90,7 @@ public class UrlTagTests extends AbstractTagTests {
PageContext.REQUEST_SCOPE)); PageContext.REQUEST_SCOPE));
} }
public void testSetEscapeXmlDefault() throws JspException { public void testSetHtmlEscapeDefault() throws JspException {
tag.setValue("url/path"); tag.setValue("url/path");
tag.setVar("var"); tag.setVar("var");
@ -112,10 +112,10 @@ public class UrlTagTests extends AbstractTagTests {
.getAttribute("var")); .getAttribute("var"));
} }
public void testSetEscapeXmlFalse() throws JspException { public void testSetHtmlEscapeFalse() throws JspException {
tag.setValue("url/path"); tag.setValue("url/path");
tag.setVar("var"); tag.setVar("var");
tag.setEscapeXml("false"); tag.setHtmlEscape("false");
tag.doStartTag(); tag.doStartTag();
@ -135,10 +135,10 @@ public class UrlTagTests extends AbstractTagTests {
.getAttribute("var")); .getAttribute("var"));
} }
public void testSetEscapeXmlTrue() throws JspException { public void testSetHtmlEscapeTrue() throws JspException {
tag.setValue("url/path"); tag.setValue("url/path");
tag.setVar("var"); tag.setVar("var");
tag.setEscapeXml("true"); tag.setHtmlEscape("true");
tag.doStartTag(); tag.doStartTag();
@ -154,7 +154,54 @@ public class UrlTagTests extends AbstractTagTests {
tag.doEndTag(); tag.doEndTag();
assertEquals("url/path?n+me=v%26l%3De&#38;name=value2", context assertEquals("url/path?n+me=v%26l%3De&amp;name=value2", context
.getAttribute("var"));
}
public void testSetJavaScriptEscapeTrue() throws JspException {
tag.setValue("url/path");
tag.setVar("var");
tag.setJavaScriptEscape("true");
tag.doStartTag();
Param param = new Param();
param.setName("n me");
param.setValue("v&l=e");
tag.addParam(param);
param = new Param();
param.setName("name");
param.setValue("value2");
tag.addParam(param);
tag.doEndTag();
assertEquals("url\\/path?n+me=v%26l%3De&name=value2", context
.getAttribute("var"));
}
public void testSetHtmlAndJavaScriptEscapeTrue() throws JspException {
tag.setValue("url/path");
tag.setVar("var");
tag.setHtmlEscape("true");
tag.setJavaScriptEscape("true");
tag.doStartTag();
Param param = new Param();
param.setName("n me");
param.setValue("v&l=e");
tag.addParam(param);
param = new Param();
param.setName("name");
param.setValue("value2");
tag.addParam(param);
tag.doEndTag();
assertEquals("url\\/path?n+me=v%26l%3De&amp;name=value2", context
.getAttribute("var")); .getAttribute("var"));
} }
@ -521,35 +568,6 @@ public class UrlTagTests extends AbstractTagTests {
} }
} }
public void testEscapeXml() {
assertEquals("&#60;script type=&#34;text/javascript&#34;&#62;", tag
.escapeXml("<script type=\"text/javascript\">"));
}
public void testEscapeXmlNull() {
assertNull(tag.escapeXml(null));
}
public void testEntityValueAmpersand() {
assertEquals("&#38;", tag.entityValue('&'));
}
public void testEntityValueLessThan() {
assertEquals("&#60;", tag.entityValue('<'));
}
public void testEntityValueGreaterThan() {
assertEquals("&#62;", tag.entityValue('>'));
}
public void testEntityValueSingleQuote() {
assertEquals("&#39;", tag.entityValue('\''));
}
public void testEntityValueDoubleQuote() {
assertEquals("&#34;", tag.entityValue('"'));
}
public void testJspWriterOutput() { public void testJspWriterOutput() {
// TODO assert that the output to the JspWriter is the expected output // TODO assert that the output to the JspWriter is the expected output
} }