diff --git a/core/src/main/java/org/apache/struts2/components/Text.java b/core/src/main/java/org/apache/struts2/components/Text.java index c7935ac7c..a5ea21715 100644 --- a/core/src/main/java/org/apache/struts2/components/Text.java +++ b/core/src/main/java/org/apache/struts2/components/Text.java @@ -19,6 +19,7 @@ package org.apache.struts2.components; import com.opensymphony.xwork2.util.ValueStack; +import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -58,6 +59,10 @@ import java.util.List; * * * * @@ -119,6 +124,10 @@ public class Text extends ContextBean implements Param.UnnamedParametric { protected String actualName; protected String name; protected String searchStack; + private boolean escapeHtml = false; + private boolean escapeJavaScript = false; + private boolean escapeXml = false; + private boolean escapeCsv = false; public Text(ValueStack stack) { super(stack); @@ -134,6 +143,26 @@ public class Text extends ContextBean implements Param.UnnamedParametric { this.searchStack = searchStack; } + @StrutsTagAttribute(description="Whether to escape HTML", type="Boolean", defaultValue="false") + public void setEscapeHtml(boolean escape) { + this.escapeHtml = escape; + } + + @StrutsTagAttribute(description="Whether to escape Javascript", type="Boolean", defaultValue="false") + public void setEscapeJavaScript(boolean escapeJavaScript) { + this.escapeJavaScript = escapeJavaScript; + } + + @StrutsTagAttribute(description="Whether to escape XML", type="Boolean", defaultValue="false") + public void setEscapeXml(boolean escapeXml) { + this.escapeXml = escapeXml; + } + + @StrutsTagAttribute(description="Whether to escape CSV (useful to escape a value for a column)", type="Boolean", defaultValue="false") + public void setEscapeCsv(boolean escapeCsv) { + this.escapeCsv = escapeCsv; + } + public boolean usesBody() { // overriding this to true such that EVAL_BODY_BUFFERED is return and // bodyContent will be valid hence, text between start & end tag will @@ -161,7 +190,7 @@ public class Text extends ContextBean implements Param.UnnamedParametric { if (msg != null) { try { if (getVar() == null) { - writer.write(msg); + writer.write(prepare(msg)); } else { putInContext(msg); } @@ -184,4 +213,22 @@ public class Text extends ContextBean implements Param.UnnamedParametric { values.add(value); } + + private String prepare(String value) { + String result = value; + if (escapeHtml) { + result = StringEscapeUtils.escapeHtml4(result); + } + if (escapeJavaScript) { + result = StringEscapeUtils.escapeEcmaScript(result); + } + if (escapeXml) { + result = StringEscapeUtils.escapeXml(result); + } + if (escapeCsv) { + result = StringEscapeUtils.escapeCsv(result); + } + + return result; + } } diff --git a/core/src/main/java/org/apache/struts2/views/jsp/TextTag.java b/core/src/main/java/org/apache/struts2/views/jsp/TextTag.java index 6407954fa..c0420dfad 100644 --- a/core/src/main/java/org/apache/struts2/views/jsp/TextTag.java +++ b/core/src/main/java/org/apache/struts2/views/jsp/TextTag.java @@ -35,6 +35,10 @@ public class TextTag extends ContextBeanTag { protected String name; protected String searchValueStack; + private boolean escapeHtml = false; + private boolean escapeJavaScript = false; + private boolean escapeXml = false; + private boolean escapeCsv = false; public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new Text(stack); @@ -46,6 +50,10 @@ public class TextTag extends ContextBeanTag { Text text = (Text) component; text.setName(name); text.setSearchValueStack(searchValueStack); + text.setEscapeHtml(escapeHtml); + text.setEscapeJavaScript(escapeJavaScript); + text.setEscapeXml(escapeXml); + text.setEscapeCsv(escapeCsv); } public void setName(String name) { @@ -55,4 +63,21 @@ public class TextTag extends ContextBeanTag { public void setSearchValueStack(String searchStack) { this.searchValueStack = searchStack; } + + public void setEscapeHtml(boolean escapeHtml) { + this.escapeHtml = escapeHtml; + } + + public void setEscapeJavaScript(boolean escapeJavaScript) { + this.escapeJavaScript = escapeJavaScript; + } + + public void setEscapeXml(boolean escapeXml) { + this.escapeXml = escapeXml; + } + + public void setEscapeCsv(boolean escapeCsv) { + this.escapeCsv = escapeCsv; + } + } diff --git a/core/src/site/resources/tags/text.html b/core/src/site/resources/tags/text.html index bd2e51115..f068caddb 100644 --- a/core/src/site/resources/tags/text.html +++ b/core/src/site/resources/tags/text.html @@ -33,6 +33,38 @@ Please do not edit it directly.

Type

Description

+ + escapeCsv + false + false + false + Boolean + Whether to escape CSV (useful to escape a value for a column) + + + escapeHtml + false + false + false + Boolean + Whether to escape HTML + + + escapeJavaScript + false + false + false + Boolean + Whether to escape Javascript + + + escapeXml + false + false + false + Boolean + Whether to escape XML + name true diff --git a/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java index dae0d532a..856b9335b 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/TextTagTest.java @@ -305,6 +305,46 @@ public class TextTagTest extends AbstractTagTest { assertEquals("No foo here", stack.findString("myId")); // is in stack now } + public void testEscapeHtml() throws Exception { + final String key = "foo.escape.html"; + final String value = "1 < 2"; + tag.setName(key); + tag.setEscapeHtml(true); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(value, writer.toString()); + } + + public void testEscapeXml() throws Exception { + final String key = "foo.escape.xml"; + final String value = "<>'"&"; + tag.setName(key); + tag.setEscapeXml(true); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(value, writer.toString()); + } + + public void testEscapeJavaScript() throws Exception { + final String key = "foo.escape.javascript"; + final String value = "\\t\\b\\n\\f\\r\\\"\\\'\\/\\\\"; + tag.setName(key); + tag.setEscapeJavaScript(true); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(value, writer.toString()); + } + + public void testEscapeCsv() throws Exception { + final String key = "foo.escape.csv"; + final String value = "\"something,\"\",\"\"\""; + tag.setName(key); + tag.setEscapeCsv(true); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(value, writer.toString()); + } + /** * todo remove ActionContext set after LocalizedTextUtil is fixed to not use ThreadLocal * diff --git a/core/src/test/resources/org/apache/struts2/TestAction.properties b/core/src/test/resources/org/apache/struts2/TestAction.properties index 1ecb4cdfc..cd2ce8914 100644 --- a/core/src/test/resources/org/apache/struts2/TestAction.properties +++ b/core/src/test/resources/org/apache/struts2/TestAction.properties @@ -21,4 +21,8 @@ expressionKey=Foo is ${foo} messageFormatKey=Params are {0} {1} {2} foo.bar.baz=This should start with foo bar.baz=No foo here -some.image.from.properties=some.gif \ No newline at end of file +some.image.from.properties=some.gif +foo.escape.html=1 < 2 +foo.escape.xml=<>''\"& +foo.escape.javascript=\u0009\u0008\n\u000C\u000D\"''/\u005C +foo.escape.csv=something,","