/** * This file Copyright (c) 2007-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.cms.filters; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.getCurrentArguments; import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.same; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import info.magnolia.cms.core.AggregationState; import info.magnolia.cms.util.CustomFilterConfig; import info.magnolia.cms.util.ServletUtils; import info.magnolia.content2bean.Content2BeanException; import info.magnolia.context.MgnlContext; import info.magnolia.context.WebContext; import info.magnolia.jcr.node2bean.Node2BeanException; import info.magnolia.jcr.node2bean.impl.Node2BeanProcessorImpl; import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl; import info.magnolia.jcr.node2bean.impl.TypeMappingImpl; import info.magnolia.test.ComponentsTestUtil; import info.magnolia.test.MgnlTestCase; import info.magnolia.test.mock.MockAggregationState; import info.magnolia.test.mock.MockHierarchyManager; import info.magnolia.test.mock.MockUtil; import info.magnolia.voting.DefaultVoting; import info.magnolia.voting.Voting; import java.io.IOException; import java.lang.reflect.Field; import javax.jcr.RepositoryException; import javax.servlet.FilterChain; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.junit.Before; import org.junit.Test; import com.mockrunner.mock.web.MockHttpServletRequest; /** * @version $Id$ */ public class ServletDispatchingFilterTest extends MgnlTestCase { @Override @Before public void setUp() throws Exception { super.setUp(); ComponentsTestUtil.setImplementation(WebContainerResources.class, WebContainerResourcesImpl.class); } @Test public void testEscapeMetaCharacters() { assertEquals("a\\{b\\)c\\(d\\}e\\^f\\]g\\[h\\*i\\$j\\+kl", Mapping.escapeMetaCharacters("a{b)c(d}e^f]g[h*i$j+kl")); } @Test public void testSupportsDefaultMapping() throws Exception { shouldMatch("/dms/some-doc.pdf", "", "/dms/some-doc.pdf", "/"); shouldMatch(null, "", "/", "/"); } @Test public void testSupportsPathMapping() throws Exception { shouldMatch("/hey/ho/lets/go", "/ramones", "/ramones/hey/ho/lets/go", "/ramones/*"); shouldMatch(null, "/ramones", "/ramones/", "/ramones/*"); shouldMatch("/wesh", "/yo", "/yo/wesh", "/yo/*"); // path mapping end with /*, and as such /my/path should not match for /my/path/* shouldBypass("/ramones", "/ramones/*"); } @Test public void testSupportsExtensionMapping() throws Exception { shouldMatch(null, "/dms/some-doc.pdf", "/dms/some-doc.pdf", "*.pdf"); } @Test public void testSupportsRegexMappings() throws Exception { // the part of the request following the pattern match is the path info: shouldMatch("/123/456", "/test", "/test/123/456", "regex:/[a-z]{4}"); // if pattern ends with a $, extra path info in the request uri does not match: shouldMatch(null, "/test", "/test", "regex:^/[a-z]{4}$"); shouldBypass("/test/blah", "regex:^/[a-z]{4}$"); shouldBypass("/testx", "regex:^/[a-z]{4}$"); shouldMatch(null, "/foo/bar473", "/foo/bar473", "regex:^/foo/bar[0-9]{3}[A-Za-z]*"); shouldMatch(null, "/foo/bar473wEsh", "/foo/bar473wEsh", "regex:^/foo/bar[0-9]{3}[A-Za-z]*"); shouldMatch("/wEsh", "/foo/bar473", "/foo/bar473/wEsh", "regex:^/foo/bar[0-9]{3}"); //shouldMatch("/wEsh", "/foo/bar473", "/foo/bar473wEsh", "regex:^/foo/bar[0-9]{3}"); shouldBypass("/foo/bar473wEsh", "regex:^/foo/bar[0-9]{3}"); // to avoid the previous case: shouldMatch(null, "/foo/bar473", "/foo/bar473", "regex:^/foo/bar[0-9]{3}"); shouldMatch("/wEsh", "/foo/bar473", "/foo/bar473/wEsh", "regex:^/foo/bar[0-9]{3}"); } @Test public void testShouldNotBypassWhenPathMappingMatches() throws Exception { shouldMatch("/some-doc.pdf", "/$d}^(m)+{s*", "/$d}^(m)+{s*/some-doc.pdf", "/$d}^(m)+{s*/*"); } @Test public void testShouldNotBypassWhenExactMappingMatches() throws Exception { shouldMatch(null, "/exactMatch", "/exactMatch", "/exactMatch"); shouldMatch(null, "/somepath*", "/somepath*", "/somepath*"); } @Test public void testShouldBypassWhenMappingDoesNotMatch() throws Exception { shouldBypass("/bleh/foo.bar", "/dms/*"); shouldBypass("/exactMatch/rightPathInfo", "/exactMatch"); shouldBypass("/nonSpecConformantMapping.html", "/nonSpecConformantMapping*"); shouldBypass("/nonSpecConformantMapping*/somePage.html", "/nonSpecConformantMapping*"); } @Test public void testShouldBypassWhenMappingDoesNotMatchMAGNOLIA1984() throws Exception { shouldBypass("/modules/dms/managingdocs.html", "/dms/*"); } @Test public void testPathInfoShouldAdhereToServletSpec() throws Exception { shouldMatch("/pathInfo.html", "/servlet", "/servlet/pathInfo.html", "/servlet/*"); // The following doesn't work - spec isn't clear as to whether it should or not //shouldMatch("/test", "/some-some.foo", "/some-some.foo/test", "*.foo"); } @Test public void testPathInfoShouldStateWhateverIsAfterTheRegexMapping() throws Exception { shouldMatch("/test", "/some-doc.pdf", "/some-doc.pdf/test", "regex:.*\\.pdf"); } private void shouldBypass(String requestPath, String mapping) throws Exception { doTestBypassAndPathInfo(true, null, null, requestPath, mapping, false); } private void shouldMatch(final String expectedPathInfo, final String expectedServletPath, final String requestPath, String mapping) throws Exception { doTestBypassAndPathInfo(false, expectedPathInfo, expectedServletPath, requestPath, mapping, true); } private void doTestBypassAndPathInfo(final boolean shouldBypass, final String expectedPathInfo, final String expectedServletPath, final String requestPath, String mapping, boolean shouldCheckPathInfoAndServletPath) throws Exception { ComponentsTestUtil.setInstance(Voting.class, new DefaultVoting()); WebContext ctx = createStrictMock(WebContext.class); MgnlContext.setInstance(ctx); final AggregationState state = new MockAggregationState(); expect(ctx.getContextPath()).andReturn("/magnoliaAuthor").anyTimes(); final FilterChain chain = createNiceMock(FilterChain.class); final HttpServletResponse res = createNiceMock(HttpServletResponse.class); final HttpServletRequest req = createMock(HttpServletRequest.class); expect(req.getAttribute(EasyMock.<String>anyObject())).andReturn(null).anyTimes(); expect(ctx.getAggregationState()).andReturn(state).anyTimes(); expect(req.getRequestURI()).andReturn("/magnoliaAuthor" + requestPath).anyTimes(); expect(req.getContextPath()).andReturn("/magnoliaAuthor").anyTimes(); expect(req.getServletPath()).andReturn("/magnoliaAuthor" + requestPath).anyTimes(); expect(req.getPathInfo()).andReturn(null).anyTimes(); expect(req.getQueryString()).andReturn(null).anyTimes(); final Servlet servlet = createStrictMock(Servlet.class); if (!shouldBypass) { servlet.service(isA(HttpServletRequestWrapper.class), same(res)); expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { final HttpServletRequestWrapper requestWrapper = (HttpServletRequestWrapper) getCurrentArguments()[0]; final String pathInfo = requestWrapper.getPathInfo(); final String servletPath = requestWrapper.getServletPath(); assertEquals("pathInfo does not match", expectedPathInfo, pathInfo); assertEquals("servletPath does not match", expectedServletPath, servletPath); return null; } }); } replay(chain, res, req, servlet, ctx); state.setCurrentURI(requestPath); final AbstractMgnlFilter filter = new ServletDispatchingFilter(); final Field servletField = ServletDispatchingFilter.class.getDeclaredField("servlet"); servletField.setAccessible(true); servletField.set(filter, servlet); filter.addMapping(mapping); assertEquals("Should " + (shouldBypass ? "" : "not ") + "have bypassed", shouldBypass, !filter.matches(req)); if (!shouldBypass) { filter.doFilter(req, res, chain); } verify(chain, res, req, servlet, ctx); } @Test public void testWrapperRespectsForwards() throws RepositoryException, IOException, Content2BeanException, ServletException, Node2BeanException { String testContent = "" + "/server/filters/servlets/test.class=" + ServletDispatchingFilter.class.getName() + "\n" + "/server/filters/servlets/test.servletClass=" + TestServlet.class.getName() + "\n" + "/server/filters/servlets/test.servletName=TestServlet\n" + "/server/filters/servlets/test.comment=TestComment\n" + "/server/filters/servlets/test.parameters.param1=value1\n" + "/server/filters/servlets/test.parameters.param2=value2\n" + "/server/filters/servlets/test.mappings.mapping.pattern=/mapping/*"; MockHierarchyManager hm = MockUtil.createHierarchyManager(testContent); Node2BeanProcessorImpl n2b = new Node2BeanProcessorImpl(new TypeMappingImpl(), new Node2BeanTransformerImpl()); ServletDispatchingFilter filter = (ServletDispatchingFilter) n2b.toBean(hm.getContent("/server/filters/servlets/test").getJCRNode()); assertEquals("Wrapper for TestServlet servlet", filter.getName()); assertEquals("TestComment", filter.getComment()); assertEquals(2, filter.getParameters().size()); assertEquals("value1", filter.getParameters().get("param1")); assertEquals("value2", filter.getParameters().get("param2")); assertEquals(1, filter.getMappings().size()); filter.init(new CustomFilterConfig("test", null)); MockHttpServletRequest mock = new MockHttpServletRequest(); mock.setRequestURI("/magnoliaAuthor/mapping/test.html"); mock.setContextPath("/magnoliaAuthor"); mock.setServletPath(""); mock.setPathInfo("/mapping/test.html"); MgnlContext.getWebContext().getAggregationState().setCurrentURI(StringUtils.substringAfter(mock.getRequestURI(), mock.getContextPath())); filter.doFilter(mock, null, null); filter.destroy(); } public static class TestServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { assertEquals("/magnoliaAuthor/mapping/test.html", req.getRequestURI()); assertEquals("/magnoliaAuthor", req.getContextPath()); assertEquals("/mapping", req.getServletPath()); assertEquals("/test.html", req.getPathInfo()); assertEquals(null, req.getQueryString()); // simulate a forward MockHttpServletRequest mock = ServletUtils.getWrappedRequest(req, MockHttpServletRequest.class); mock.setRequestURI("/magnoliaAuthor/.magnolia/somepage.html"); mock.setContextPath("/magnoliaAuthor"); mock.setServletPath(""); mock.setPathInfo("/.magnolia/somepage.html"); // test that the ServletDispatcherFitler wrapper lets the new values through assertEquals("/magnoliaAuthor/.magnolia/somepage.html", req.getRequestURI()); assertEquals("/magnoliaAuthor", req.getContextPath()); assertEquals("", req.getServletPath()); assertEquals("/.magnolia/somepage.html", req.getPathInfo()); assertEquals(null, req.getQueryString()); } } }