WW-5021 Adds validation of the provided static content path

This commit is contained in:
Lukasz Lenart
2021-01-06 17:03:17 +01:00
parent ef72783f50
commit f956910d20
7 changed files with 169 additions and 21 deletions
@@ -30,6 +30,7 @@ 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.components.template.TemplateRenderingContext;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.apache.struts2.util.TextProviderHelper;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import org.apache.struts2.views.util.ContextUtil;
@@ -526,8 +527,8 @@ public abstract class UIBean extends Component {
}
@Inject(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH)
public void setUiStaticContentPath(String uiStaticContentPath) {
this.uiStaticContentPath = uiStaticContentPath;
public void setStaticContentPath(String uiStaticContentPath) {
this.uiStaticContentPath = StaticContentLoader.Validator.validateStaticContentPath(uiStaticContentPath);
}
@Inject
@@ -29,6 +29,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.StaticContentLoader;
public class ConstantConfig {
private Boolean devMode;
@@ -274,7 +275,7 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_LOCALIZED_TEXT_PROVIDER, beanConfToString(localizedTextProvider));
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
return map;
}
@@ -1352,6 +1353,6 @@ public class ConstantConfig {
}
public void setStaticContentPath(String staticContentPath) {
this.staticContentPath = staticContentPath;
this.staticContentPath = StaticContentLoader.Validator.validateStaticContentPath(staticContentPath);
}
}
@@ -48,7 +48,7 @@ import java.util.StringTokenizer;
*
* <p>
* This class is used to serve common static content needed when using various parts of Struts, such as JavaScript
* files, CSS files, etc. It works by looking for requests to {@link #staticContentPath}/* and then mapping the value
* files, CSS files, etc. It works by looking for requests to {@link #uiStaticContentPath}/* and then mapping the value
* after to common packages in Struts and, optionally, in your class path. By default, the following packages are
* automatically searched:
* </p>
@@ -60,7 +60,7 @@ import java.util.StringTokenizer;
* </ul>
*
* <p>
* This means that you can simply request {@link #staticContentPath}/xhtml/styles.css and the XHTML UI theme's default stylesheet
* This means that you can simply request {@link #uiStaticContentPath}/xhtml/styles.css and the XHTML UI theme's default stylesheet
* will be returned. Likewise, many of the AJAX UI components require various JavaScript files, which are found in the
* org.apache.struts2.static package. If you wish to add additional packages to be searched, you can add a comma
* separated (space, tab and new line will do as well) list in the filter init parameter named "packages". <b>Be
@@ -86,9 +86,9 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
protected boolean serveStatic;
/**
* Store state of StrutsConstants.STRUTS_STATIC_CONTENT_PATH setting.
* Store state of {@link StrutsConstants#STRUTS_UI_STATIC_CONTENT_PATH} setting.
*/
protected String staticContentPath;
protected String uiStaticContentPath;
/**
* Store state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting.
@@ -118,8 +118,8 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
}
@Inject(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH)
public void setStaticContentPath(String staticContentPath) {
this.staticContentPath = staticContentPath;
public void setStaticContentPath(String uiStaticContentPath) {
this.uiStaticContentPath = StaticContentLoader.Validator.validateStaticContentPath(uiStaticContentPath);
}
/**
@@ -238,7 +238,7 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
LOG.warn("Unable to send error response, code: {};", HttpServletResponse.SC_NOT_FOUND, e1);
} catch (IllegalStateException ise) {
// Log illegalstate instead of passing unrecoverable exception to calling thread
LOG.warn("Unable to send error response, code: {}; isCommited: {};", HttpServletResponse.SC_NOT_FOUND, response.isCommitted(), ise);
LOG.warn("Unable to send error response, code: {}; isCommitted: {};", HttpServletResponse.SC_NOT_FOUND, response.isCommitted(), ise);
}
}
@@ -298,7 +298,7 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
* Look for a static resource in the classpath.
*
* @param path The resource path
* @return The inputstream of the resource
* @return The URL of the resource
* @throws IOException If there is a problem locating the resource
*/
protected URL findResource(String path) throws IOException {
@@ -368,16 +368,16 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
}
public boolean canHandle(String resourcePath) {
return serveStatic && resourcePath.startsWith(staticContentPath + "/");
return serveStatic && resourcePath.startsWith(uiStaticContentPath + "/");
}
/**
* @param path requested path
* @return path without leading {@link #staticContentPath}
* @return path without leading {@link #uiStaticContentPath}
*/
protected String cleanupPath(String path) {
if (path.startsWith(staticContentPath)) {
return path.substring(staticContentPath.length());
if (path.startsWith(uiStaticContentPath)) {
return path.substring(uiStaticContentPath.length());
} else {
return path;
}
@@ -18,6 +18,10 @@
*/
package org.apache.struts2.dispatcher;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.config.StrutsBeanSelectionProvider;
import javax.servlet.http.HttpServletRequest;
@@ -35,6 +39,12 @@ import java.io.IOException;
*/
public interface StaticContentLoader {
/**
* Default path at which static content is served, can be changed
* by using {@link org.apache.struts2.StrutsConstants#STRUTS_UI_STATIC_CONTENT_PATH}
*/
String DEFAULT_STATIC_CONTENT_PATH = "/static";
/**
* @param path Requested resource path
* @return true if this loader is able to load this type of resource, false otherwise
@@ -57,4 +67,32 @@ public interface StaticContentLoader {
*/
void findStaticResource(String path, HttpServletRequest request, HttpServletResponse response) throws IOException;
class Validator {
private static final Logger LOG = LogManager.getLogger(DefaultStaticContentLoader.class);
public static String validateStaticContentPath(String uiStaticContentPath) {
if (StringUtils.isBlank(uiStaticContentPath)) {
LOG.warn("\"{}\" has been set to \"{}\", falling back into default value \"{}\"",
StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH,
uiStaticContentPath,
StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH);
return StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH;
} else if ("/".equals(uiStaticContentPath)) {
LOG.warn("\"{}\" cannot be set to \"{}\", falling back into default value \"{}\"",
StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH,
uiStaticContentPath,
StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH);
return StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH;
} else if(!uiStaticContentPath.startsWith("/")) {
LOG.warn("\"{}\" must start with \"/\", but has been set to \"{}\", prepending the missing \"/\"!",
StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH,
uiStaticContentPath);
return "/" + uiStaticContentPath;
} else {
LOG.debug("\"{}\" has been set to \"{}\"", StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, uiStaticContentPath);
return uiStaticContentPath;
}
}
}
}
@@ -25,6 +25,8 @@ 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.DefaultStaticContentLoader;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -343,4 +345,34 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(nonceVal, dblSelect.getParameters().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);
}
}
@@ -18,11 +18,17 @@
*/
package org.apache.struts2.config.entities;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.TestBean;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.components.TextField;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
@@ -109,4 +115,30 @@ public class ConstantConfigTest {
map.get(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES));
}
@Test
public void testSettingStaticContentPath() {
// given
ConstantConfig config = new ConstantConfig();
// when
config.setStaticContentPath(null);
// then
Assert.assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, config.getStaticContentPath());
// when
config.setStaticContentPath(" ");
// then
Assert.assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, config.getStaticContentPath());
// when
config.setStaticContentPath("content");
// then
Assert.assertEquals("/content", config.getStaticContentPath());
// when
config.setStaticContentPath("/content");
// then
Assert.assertEquals("/content", config.getStaticContentPath());
}
}
@@ -30,13 +30,12 @@ import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
public class DefaultStaticContentLoaderTest extends StrutsInternalTestCase {
private HttpServletRequest requestMock;
private HttpServletResponse responseMock;
private HostConfig hostConfigMock;
private DefaultStaticContentLoader defaultStaticContentLoader;
public void testParsePackages() throws Exception {
public void testParsePackages() {
DefaultStaticContentLoader filterDispatcher = new DefaultStaticContentLoader();
List<String> result1 = filterDispatcher.parse("foo.bar.package1 foo.bar.package2 foo.bar.package3");
List<String> result2 = filterDispatcher.parse("foo.bar.package1\tfoo.bar.package2\tfoo.bar.package3");
@@ -102,10 +101,55 @@ public class DefaultStaticContentLoaderTest extends StrutsInternalTestCase {
}
}
protected void setUp() {
public void testSetNullUiStaticContentPath() {
// given
DefaultStaticContentLoader loader = new DefaultStaticContentLoader();
// when
loader.setStaticContentPath(null);
// then
assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, loader.uiStaticContentPath);
}
public void testSetEmptyUiStaticContentPath() {
// given
DefaultStaticContentLoader loader = new DefaultStaticContentLoader();
// when
loader.setStaticContentPath(" ");
// then
assertEquals(StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH, loader.uiStaticContentPath);
}
public void testSetUiStaticContentPathWithoutLeadingSlash() {
// given
DefaultStaticContentLoader loader = new DefaultStaticContentLoader();
// when
loader.setStaticContentPath("content");
// then
assertEquals("/content", loader.uiStaticContentPath);
}
public void testSetUiStaticContentPath() {
// given
DefaultStaticContentLoader loader = new DefaultStaticContentLoader();
// when
loader.setStaticContentPath("/content");
// then
assertEquals("/content", loader.uiStaticContentPath);
}
protected void setUp() throws Exception {
super.setUp();
requestMock = createMock(HttpServletRequest.class);
responseMock = createMock(HttpServletResponse.class);
hostConfigMock = createMock(HostConfig.class);
HostConfig hostConfigMock = createMock(HostConfig.class);
expect(hostConfigMock.getInitParameter("packages")).andStubReturn(null);
expect(hostConfigMock.getInitParameter("loggerFactory")).andStubReturn(null);
defaultStaticContentLoader = new DefaultStaticContentLoader();