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;
*
*
* - name* (String) - the i18n message key
+ * - escapeHtml (Boolean) - Escape HTML. Defaults to false
+ * - escapeJavaScript (Boolean) - Escape JavaScript. Defaults to false
+ * - escapeXml (Boolean) - Escape XML. Defaults to false
+ * - escapeCsv (Boolean) - Escape CSV. Defaults to false
*
*
*
@@ -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,","