UIBeanTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.components;
import org.apache.struts2.ActionContext;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.components.template.Template;
import org.apache.struts2.components.template.TemplateEngine;
import org.apache.struts2.components.template.TemplateEngineManager;
import org.apache.struts2.dispatcher.SessionMap;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import java.util.Collections;
import java.util.Map;
import static org.apache.struts2.security.DefaultNotExcludedAcceptedPatternsCheckerTest.NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER;
public class UIBeanTest extends StrutsInternalTestCase {
private UIBean bean;
@Override
public void setUp() throws Exception {
super.setUp();
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
bean = new UIBean(stack, req, res) {
@Override
protected String getDefaultTemplate() {
return null;
}
};
}
public void testPopulateComponentHtmlId1() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.getAttributes().put("id", "formId");
TextField txtFld = new TextField(stack, req, res);
txtFld.setId("txtFldId");
txtFld.populateComponentHtmlId(form);
assertEquals("txtFldId", txtFld.getAttributes().get("id"));
}
public void testPopulateComponentHtmlIdWithOgnl() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.getAttributes().put("id", "formId");
TextField txtFld = new TextField(stack, req, res);
txtFld.setName("txtFldName%{'1'}");
txtFld.populateComponentHtmlId(form);
assertEquals("formId_txtFldName1", txtFld.getAttributes().get("id"));
}
public void testPopulateComponentHtmlId2() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.getAttributes().put("id", "formId");
TextField txtFld = new TextField(stack, req, res);
txtFld.setName("txtFldName");
txtFld.populateComponentHtmlId(form);
assertEquals("formId_txtFldName", txtFld.getAttributes().get("id"));
}
public void testPopulateComponentHtmlWithoutNameAndId() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.getAttributes().put("id", "formId");
TextField txtFld = new TextField(stack, req, res);
txtFld.populateComponentHtmlId(form);
assertNull(txtFld.getAttributes().get("id"));
}
public void testEscape() {
assertEquals(bean.escape("hello[world"), "hello_world");
assertEquals(bean.escape("hello.world"), "hello_world");
assertEquals(bean.escape("hello]world"), "hello_world");
assertEquals(bean.escape("hello!world"), "hello_world");
assertEquals(bean.escape("hello!@#$%^&*()world"), "hello__________world");
}
public void testEscapeId() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.getAttributes().put("id", "formId");
TextField txtFld = new TextField(stack, req, res);
txtFld.setName("foo/bar");
txtFld.populateComponentHtmlId(form);
assertEquals("formId_foo_bar", txtFld.getAttributes().get("id"));
}
public void testGetThemeFromForm() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Form form = new Form(stack, req, res);
form.setTheme("foo");
TextField txtFld = new TextField(stack, req, res);
assertEquals("foo", txtFld.getTheme());
}
public void testMergeTemplateNullEngineException() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
//templateEngineManager that returns null as TemplateEngine
TemplateEngineManager templateEngineManager = new TemplateEngineManager() {
public TemplateEngine getTemplateEngine(Template template, String templateTypeOverride) {
return null;
}
};
TextField txtFld = new TextField(stack, req, res);
txtFld.setTemplateEngineManager(templateEngineManager);
try {
txtFld.mergeTemplate(null, new Template(null, null, null));
fail("Exception not thrown");
} catch (final Exception e) {
assertTrue(e instanceof ConfigurationException);
}
}
public void testBuildTemplate() {
String defaultTemplateName = "default";
String customTemplateName = "custom";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
TextField txtFld = new TextField(stack, req, res);
Template defaultTemplate = txtFld.buildTemplateName(null, defaultTemplateName);
Template customTemplate = txtFld.buildTemplateName(customTemplateName, defaultTemplateName);
assertEquals(defaultTemplateName, defaultTemplate.getName());
assertEquals(customTemplateName, customTemplate.getName());
}
public void testGetTemplateDirExplicit() {
String explicitTemplateDir = "explicitTemplateDirectory";
String attrTemplateDir = "attrTemplateDirectory";
String defaultTemplateDir = "defaultTemplateDirectory";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
Map<String, Object> context = Collections.singletonMap("templateDir", attrTemplateDir);
ActionContext.getContext().put("attr", context);
TextField txtFld = new TextField(stack, req, res);
txtFld.setTemplateDir(explicitTemplateDir);
txtFld.setDefaultTemplateDir(defaultTemplateDir);
assertEquals(explicitTemplateDir, txtFld.getTemplateDir());
}
public void testGetTemplateDirDefault() {
String defaultTemplateDir = "defaultTemplateDirectory";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
TextField txtFld = new TextField(stack, req, res);
txtFld.setDefaultTemplateDir(defaultTemplateDir);
assertEquals(defaultTemplateDir, txtFld.getTemplateDir());
}
public void testGetTemplateDirNoneSet() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
TextField txtFld = new TextField(stack, req, res);
assertEquals("template", txtFld.getTemplateDir());
}
public void testSetAccesskey() {
String accesskeyValue = "myAccesskey";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
TextField txtFld = new TextField(stack, req, res);
txtFld.setAccesskey(accesskeyValue);
txtFld.evaluateParams();
assertEquals(accesskeyValue, txtFld.getAttributes().get("accesskey"));
}
public void testValueParameterEvaluation() {
String value = "myValue";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
TextField txtFld = new TextField(stack, req, res);
txtFld.addParameter("value", value);
txtFld.evaluateParams();
assertEquals(value, txtFld.getAttributes().get("nameValue"));
}
public void testValueParameterRecursion() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
stack.push(new Object() {
public String getMyValue() {
return "%{myBad}";
}
public String getMyBad() {
throw new IllegalStateException("Recursion detected!");
}
});
TextField txtFld = new TextField(stack, req, res);
container.inject(txtFld);
txtFld.setName("%{myValue}");
txtFld.evaluateParams();
assertEquals("%{myBad}", txtFld.getAttributes().get("nameValue"));
assertEquals("%{myBad}", txtFld.getAttributes().get("name"));
}
public void testValueNameParameterNotAccepted() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
stack.push(new Object() {
public String getMyValueName() {
return "getMyValue()";
}
public String getMyValue() {
return "value";
}
});
TextField txtFld = new TextField(stack, req, res);
container.inject(txtFld);
txtFld.setName("%{myValueName}");
txtFld.evaluateParams();
assertEquals("getMyValue()", txtFld.getAttributes().get("name"));
assertEquals("getMyValue()", txtFld.getAttributes().get("nameValue"));
txtFld.setNotExcludedAcceptedPatterns(NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER);
txtFld.evaluateParams();
assertEquals("getMyValue()", txtFld.getAttributes().get("name"));
assertEquals("value", txtFld.getAttributes().get("nameValue"));
}
public void testValueNameParameterGetterAccepted() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
stack.push(new Object() {
public String getMyValue() {
return "value";
}
});
TextField txtFld = new TextField(stack, req, res);
container.inject(txtFld);
txtFld.setName("getMyValue()");
txtFld.evaluateParams();
assertEquals("getMyValue()", txtFld.getAttributes().get("name"));
assertEquals("value", txtFld.getAttributes().get("nameValue"));
}
public void testSetClass() {
String cssClass = "insertCssClassHere";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
TextField txtFld = new TextField(stack, req, res);
txtFld.setCssClass(cssClass);
txtFld.evaluateParams();
assertEquals(cssClass, txtFld.getAttributes().get("cssClass"));
}
public void testSetStyle() {
String cssStyle = "insertCssStyleHere";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext.getContext().withServletRequest(req);
TextField txtFld = new TextField(stack, req, res);
txtFld.setStyle(cssStyle);
txtFld.evaluateParams();
assertEquals(cssStyle, txtFld.getAttributes().get("cssStyle"));
}
public void testNonce() {
String nonceVal = "r4nd0m";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext actionContext = stack.getActionContext();
actionContext.withServletRequest(req);
MockHttpSession session = new MockHttpSession();
session.setAttribute("nonce", nonceVal);
req.setSession(session);
actionContext.withSession(new SessionMap(req));
DoubleSelect dblSelect = new DoubleSelect(stack, req, res);
dblSelect.evaluateParams();
assertEquals(nonceVal, dblSelect.getAttributes().get("nonce"));
}
public void testNonceOfInvalidSession() {
String nonceVal = "r4nd0m";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
ActionContext actionContext = stack.getActionContext();
actionContext.withServletRequest(req);
MockHttpSession session = new MockHttpSession();
session.setAttribute("nonce", nonceVal);
req.setSession(session);
actionContext.withSession(new SessionMap(req));
session.invalidate();
DoubleSelect dblSelect = new DoubleSelect(stack, req, res);
dblSelect.evaluateParams();
assertNull(dblSelect.getAttributes().get("nonce"));
}
public void testSetNullUiStaticContentPath() {
// given
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
TextField field = new TextField(stack, req, res);
// when
field.setStaticContentPath(null);
// then
assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, field.uiStaticContentPath);
// when
field.setStaticContentPath(" ");
// then
assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, field.uiStaticContentPath);
// when
field.setStaticContentPath("content");
// then
assertEquals("/content", field.uiStaticContentPath);
// when
field.setStaticContentPath("/content");
// then
assertEquals("/content", field.uiStaticContentPath);
// when
field.setStaticContentPath("/content/");
// then
assertEquals("/content", field.uiStaticContentPath);
}
/**
* The {@code name} attribute of a {@link UIBean} is evaluated to determine the {@value UIBean#ATTR_NAME_VALUE}
* parameter value. Thus, it is imperative that the {@code name} attribute is not derived from user input as it will
* otherwise result in a critical SSTI vulnerability.
* <p>
* When using FreeMarker, if the {@code name} attribute is a templating variable that corresponds to a getter which
* returns user-controlled input, it will usually resolve to {@code null} when loading the corresponding Action,
* which results in a rendering error, giving developers strong feedback that the attribute is not set correctly.
* <p>
* In the case of Velocity, templating variables which resolve to {@code null} do not cause rendering errors, making
* this potentially critical mistake sometimes undetectable. By logging a prominent warning, Velocity developers are
* also given a clear indication that the {@code name} attribute is not set correctly.
* <p>
* If the name attribute should definitely correspond to a variable (it is NOT derived from user input), the warning
* can be suppressed by using the Struts OGNL expression syntax instead ( %{expr} ). This may be appropriate when
* defining Struts components within an Iterator or loop.
*/
public void testPotentialDoubleEvaluationWarning() {
bean.setName("${someVar}");
assertNull(bean.name);
}
}