mirror of
https://github.com/apache/struts.git
synced 2026-08-05 14:47:09 +00:00
Proposed fix for WW-5022 (escape html tag body control flag)
- Added escapeHtmlBody parameter to s:a and s:submit tags. - No other tags appear to require this feature (but can be added to any component). - Added new unit tests for escapeHtmlBody (and usesBody for component). - Fixed broken s:a tags in ShowCase app.
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><s:a value="%{home}"><i class="glyphicon glyphicon-home"></i> Home</s:a></li>
|
||||
<li><s:a value="%{home}" escapeHtmlBody="false"><i class="glyphicon glyphicon-home"></i> Home</s:a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="glyphicon glyphicon-cog"></i> Configuration
|
||||
@@ -233,7 +233,7 @@
|
||||
</li>
|
||||
<li><s:a value="/person/index.html">Person Manager</s:a></li>
|
||||
<li><s:a value="/skill/index.html">CRUD</s:a></li>
|
||||
<li><s:a value="/wait/index.html">Execute & Wait</s:a></li>
|
||||
<li><s:a value="/wait/index.html" escapeHtmlBody="false">Execute & Wait</s:a></li>
|
||||
<li><s:a value="/token/index.html">Token</s:a></li>
|
||||
<li><s:url var="url" namespace="/modelDriven" action="modelDriven"/><s:a
|
||||
href="%{url}">Model Driven</s:a></li>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<s:url var="url" action="download"/>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-picture"></i> Download image file.</s:a>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info" escapeHtmlBody="false"><i class="icon-picture"></i> Download image file.</s:a>
|
||||
</div>
|
||||
<div class="col-md-6" style="text-align: center;">
|
||||
<div class="alert alert-info">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
<s:url var="url" action="download2"/>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-download-alt"></i> Download ZIP file.</s:a>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info" escapeHtmlBody="false"><i class="icon-download-alt"></i> Download ZIP file.</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
</s:iterator>
|
||||
|
||||
<s:url var="url" action="showAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info" escapeHtmlBody="false"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@
|
||||
|
||||
|
||||
<s:url var="url" action="showGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info" escapeHtmlBody="false"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@
|
||||
</s:iterator>
|
||||
|
||||
<s:url var="url" action="showMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info" escapeHtmlBody="false"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
</s:subset>
|
||||
|
||||
<s:url var="url" action="showSubsetTagDemo" namespace="/tags/non-ui/subsetIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info" escapeHtmlBody="false"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +69,7 @@ public class Anchor extends ClosingUIBean {
|
||||
protected UrlProvider urlProvider;
|
||||
protected UrlRenderer urlRenderer;
|
||||
protected boolean processingTagBody = false;
|
||||
protected boolean escapeHtmlBody = true;
|
||||
|
||||
//these params are passed by the Param tag
|
||||
protected Map urlParameters = new LinkedHashMap();
|
||||
@@ -95,6 +96,18 @@ public class Anchor extends ClosingUIBean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to set if body content should be HTML-escaped.
|
||||
*
|
||||
* @return true if body should be HTML-escaped, false otherwise.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
@Override
|
||||
public boolean escapeHtmlBody() {
|
||||
return escapeHtmlBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void evaluateExtraParams() {
|
||||
super.evaluateExtraParams();
|
||||
@@ -262,4 +275,9 @@ public class Anchor extends ClosingUIBean {
|
||||
public void setForceAddSchemeHostAndPort(boolean forceAddSchemeHostAndPort) {
|
||||
urlProvider.setForceAddSchemeHostAndPort(forceAddSchemeHostAndPort);
|
||||
}
|
||||
|
||||
@StrutsTagAttribute(required = false, description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "true")
|
||||
public void setEscapeHtmlBody(boolean escapeHtmlBody) {
|
||||
this.escapeHtmlBody = escapeHtmlBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +521,17 @@ public class Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to set if body content should be HTML-escaped.
|
||||
*
|
||||
* @return always true (default) for this component.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
public boolean escapeHtmlBody() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if provided name is a valid tag's attribute
|
||||
*
|
||||
|
||||
@@ -54,6 +54,7 @@ public class Submit extends FormButton {
|
||||
final public static String OPEN_TEMPLATE = "submit";
|
||||
final public static String TEMPLATE = "submit-close";
|
||||
protected String src;
|
||||
protected boolean escapeHtmlBody = true;
|
||||
|
||||
public Submit(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
|
||||
super(stack, request, response);
|
||||
@@ -100,12 +101,28 @@ public class Submit extends FormButton {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
@StrutsTagAttribute(required = false, description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "true")
|
||||
public void setEscapeHtmlBody(boolean escapeHtmlBody) {
|
||||
this.escapeHtmlBody = escapeHtmlBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesBody() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to set if body content should be HTML-escaped.
|
||||
*
|
||||
* @return true if body should be HTML-escaped, false otherwise.
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
@Override
|
||||
public boolean escapeHtmlBody() {
|
||||
return escapeHtmlBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides to be able to render body in a template rather than always before the template
|
||||
*/
|
||||
|
||||
@@ -47,7 +47,8 @@ public class AnchorTag extends AbstractClosingTag {
|
||||
protected String portletUrlType;
|
||||
protected String anchor;
|
||||
protected String forceAddSchemeHostAndPort;
|
||||
|
||||
protected boolean escapeHtmlBody = true; // Default - escape HTML body
|
||||
|
||||
public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
|
||||
return new Anchor(stack, req, res);
|
||||
}
|
||||
@@ -77,11 +78,12 @@ public class AnchorTag extends AbstractClosingTag {
|
||||
if (escapeAmp != null) {
|
||||
tag.setEscapeAmp(BooleanUtils.toBoolean(escapeAmp));
|
||||
}
|
||||
if (forceAddSchemeHostAndPort != null) {
|
||||
if (forceAddSchemeHostAndPort != null) {
|
||||
tag.setForceAddSchemeHostAndPort(BooleanUtils.toBoolean(forceAddSchemeHostAndPort));
|
||||
}
|
||||
tag.setEscapeHtmlBody(escapeHtmlBody);
|
||||
}
|
||||
|
||||
|
||||
public void setHref(String href) {
|
||||
this.href = href;
|
||||
}
|
||||
@@ -141,6 +143,17 @@ public class AnchorTag extends AbstractClosingTag {
|
||||
public void setForceAddSchemeHostAndPort(String forceAddSchemeHostAndPort) {
|
||||
this.forceAddSchemeHostAndPort = forceAddSchemeHostAndPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set via parameter to control if body content should be HTML-escaped.
|
||||
*
|
||||
* @param escapeHtmlBody
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
public void setEscapeHtmlBody(boolean escapeHtmlBody) {
|
||||
this.escapeHtmlBody = escapeHtmlBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ public class SubmitTag extends AbstractClosingTag {
|
||||
protected String method;
|
||||
protected String type;
|
||||
protected String src;
|
||||
protected boolean escapeHtmlBody = true; // Default - escape HTML body
|
||||
|
||||
public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
|
||||
return new Submit(stack, req, res);
|
||||
@@ -50,6 +51,7 @@ public class SubmitTag extends AbstractClosingTag {
|
||||
submit.setMethod(method);
|
||||
submit.setType(type);
|
||||
submit.setSrc(src);
|
||||
submit.setEscapeHtmlBody(escapeHtmlBody);
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
@@ -71,4 +73,15 @@ public class SubmitTag extends AbstractClosingTag {
|
||||
public void setSrc(String src) {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set via parameter to control if body content should be HTML-escaped.
|
||||
*
|
||||
* @param escapeHtmlBody
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
public void setEscapeHtmlBody(boolean escapeHtmlBody) {
|
||||
this.escapeHtmlBody = escapeHtmlBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,4 @@
|
||||
<#include "/${parameters.templateDir}/${parameters.expandTheme}/scripting-events.ftl" />
|
||||
<#include "/${parameters.templateDir}/${parameters.expandTheme}/common-attributes.ftl" />
|
||||
<#include "/${parameters.templateDir}/${parameters.expandTheme}/dynamic-attributes.ftl" />
|
||||
>${parameters.body}</a>
|
||||
>${tag.escapeHtmlBody()?then(parameters.body, parameters.body?no_esc)}</a>
|
||||
@@ -19,8 +19,8 @@
|
||||
*/
|
||||
-->
|
||||
<#if parameters.type?? && parameters.type=="button">
|
||||
<#if parameters.body?length gt 0>${parameters.body}<#elseif parameters.label??><@s.property value="parameters.label"/><#rt/></#if>
|
||||
<#if parameters.body?length gt 0>${tag.escapeHtmlBody()?then(parameters.body, parameters.body?no_esc)}<#elseif parameters.label??><@s.property value="parameters.label"/><#rt/></#if>
|
||||
</button>
|
||||
<#else>
|
||||
${parameters.body}<#rt/>
|
||||
</#if>
|
||||
${tag.escapeHtmlBody()?then(parameters.body, parameters.body?no_esc)}<#rt/>
|
||||
</#if>
|
||||
@@ -479,7 +479,6 @@ public class ComponentTest extends AbstractTagTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// updownselect
|
||||
public void testUpDownSelectDisposeItselfFromComponentStack() throws Exception {
|
||||
TextFieldTag t = new TextFieldTag();
|
||||
@@ -505,4 +504,42 @@ public class ComponentTest extends AbstractTagTest {
|
||||
fail(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the expected default behaviour for component body state methods.
|
||||
*/
|
||||
public void testComponentDefaultBodyStates() {
|
||||
Component component = new Component(stack);
|
||||
// Test expected default results for: usesBody(), escapeHtmlBody().
|
||||
assertFalse("Component default usesBody not false ?", component.usesBody());
|
||||
assertTrue("Component default htmlEscapeBody not true ?", component.escapeHtmlBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour for Anchor body state methods.
|
||||
*/
|
||||
public void testAnchorBodyStates() {
|
||||
Anchor anchor = new Anchor(stack, request, response);
|
||||
// Test expected default results for: usesBody(), escapeHtmlBody().
|
||||
assertTrue("Anchor default usesBody not true ?", anchor.usesBody());
|
||||
assertTrue("Anchor default htmlEscapeBody not true ?", anchor.escapeHtmlBody());
|
||||
anchor.setEscapeHtmlBody(false);
|
||||
assertFalse("Anchor htmlEscapeBody not false after set false ?", anchor.escapeHtmlBody());
|
||||
anchor.setEscapeHtmlBody(true);
|
||||
assertTrue("Anchor htmlEscapeBody not true after set true ?", anchor.escapeHtmlBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour for Submit body state methods.
|
||||
*/
|
||||
public void testAnchortBodyStates() {
|
||||
Submit submit = new Submit(stack, request, response);
|
||||
// Test expected default results for: usesBody(), escapeHtmlBody().
|
||||
assertTrue("Submit default usesBody not true ?", submit.usesBody());
|
||||
assertTrue("Submit default htmlEscapeBody not true ?", submit.escapeHtmlBody());
|
||||
submit.setEscapeHtmlBody(false);
|
||||
assertFalse("Submit htmlEscapeBody not false after set false ?", submit.escapeHtmlBody());
|
||||
submit.setEscapeHtmlBody(true);
|
||||
assertTrue("Submit htmlEscapeBody not true after set true ?", submit.escapeHtmlBody());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,4 +101,67 @@ public class AnchorTest extends AbstractUITagTest {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test anchor tag body supported
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testSimpleWithBody() throws Exception {
|
||||
createAction();
|
||||
|
||||
AnchorTag tag = createTag();
|
||||
tag.setHref("a");
|
||||
|
||||
StrutsBodyContent body = new StrutsBodyContent(null);
|
||||
body.print("normal body text with nothing to escape");
|
||||
tag.setBodyContent(body);
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
verifyResource("href-3.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that by default anchor tag body is HTML-escaped.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testSimpleWithBodyHTMLEscaped() throws Exception {
|
||||
createAction();
|
||||
|
||||
AnchorTag tag = createTag();
|
||||
tag.setHref("a");
|
||||
|
||||
StrutsBodyContent body = new StrutsBodyContent(null);
|
||||
body.print("should HTML escape: < & >");
|
||||
tag.setBodyContent(body);
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
verifyResource("href-4.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that with htmlEscapeBody false anchor tag body is not HTML-escaped.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testSimpleWithBodyNotHTMLEscaped() throws Exception {
|
||||
createAction();
|
||||
|
||||
AnchorTag tag = createTag();
|
||||
tag.setHref("a");
|
||||
tag.setEscapeHtmlBody(false);
|
||||
|
||||
StrutsBodyContent body = new StrutsBodyContent(null);
|
||||
body.print("should not HTML escape: < & >");
|
||||
tag.setBodyContent(body);
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
verifyResource("href-5.txt");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,4 +260,56 @@ public class SubmitTest extends AbstractUITagTest {
|
||||
verifyGenericProperties(tag, "xhtml", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that by default submit tag body is HTML-escaped.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testSubmitWithBodyHTMLEscaped() throws Exception {
|
||||
TestAction testAction = (TestAction) action;
|
||||
testAction.setFoo("bar");
|
||||
|
||||
SubmitTag tag = new SubmitTag();
|
||||
tag.setTheme("simple");
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setType("button");
|
||||
tag.setName("myname");
|
||||
tag.setLabel("yoyoyoyoy");
|
||||
tag.setValue("%{foo}");
|
||||
|
||||
StrutsBodyContent body = new StrutsBodyContent(null);
|
||||
body.print("should HTML escape: < & >");
|
||||
tag.setBodyContent(body);
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
verify(TextFieldTag.class.getResource("Submit-11.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that with htmlEscapeBody false submit tag body is not HTML-escaped.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testSubmitWithBodyNotHTMLEscaped() throws Exception {
|
||||
TestAction testAction = (TestAction) action;
|
||||
testAction.setFoo("bar");
|
||||
|
||||
SubmitTag tag = new SubmitTag();
|
||||
tag.setTheme("simple");
|
||||
tag.setPageContext(pageContext);
|
||||
tag.setType("button");
|
||||
tag.setName("myname");
|
||||
tag.setLabel("yoyoyoyoy");
|
||||
tag.setValue("%{foo}");
|
||||
tag.setEscapeHtmlBody(false);
|
||||
|
||||
StrutsBodyContent body = new StrutsBodyContent(null);
|
||||
body.print("should not HTML escape: < & >");
|
||||
tag.setBodyContent(body);
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
verify(TextFieldTag.class.getResource("Submit-12.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<button type="submit" id="myname" name="myname" value="bar">should HTML escape: < & ></button>
|
||||
@@ -0,0 +1 @@
|
||||
<button type="submit" id="myname" name="myname" value="bar">should not HTML escape: < & ></button>
|
||||
@@ -0,0 +1 @@
|
||||
<a id="mylink" href="a">normal body text with nothing to escape</a>
|
||||
@@ -0,0 +1 @@
|
||||
<a id="mylink" href="a">should HTML escape: < & ></a>
|
||||
@@ -0,0 +1 @@
|
||||
<a id="mylink" href="a">should not HTML escape: < & ></a>
|
||||
Reference in New Issue
Block a user