/* * 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.catalina.core; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import javax.servlet.AsyncContext; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.apache.catalina.Context; import org.apache.catalina.Wrapper; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; import org.apache.catalina.util.URLEncoder; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.UDecoder; @RunWith(value = Parameterized.class) public class TestApplicationContextGetRequestDispatcher extends TomcatBaseTest { private final boolean useAsync; public TestApplicationContextGetRequestDispatcher(boolean useAsync) { this.useAsync = useAsync; } @Parameters(name = "{index}: useAsync[{0}]") public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {Boolean.TRUE}, {Boolean.FALSE} }); } @Test public void testGetRequestDispatcherNullPath01() throws Exception { doTestGetRequestDispatcher(true, "/start", null, null, "/target", DispatcherServlet.NULL); } @Test public void testGetRequestDispatcherNullPath02() throws Exception { doTestGetRequestDispatcher(false, "/start", null, null, "/target", DispatcherServlet.NULL); } @Test public void testGetRequestDispatcherOutsideContextRoot01() throws Exception { doTestGetRequestDispatcher( true, "/start", null, "../outside", "/target", DispatcherServlet.NULL); } @Test public void testGetRequestDispatcherOutsideContextRoot02() throws Exception { doTestGetRequestDispatcher( false, "/start", null, "../outside", "/target", DispatcherServlet.NULL); } @Test public void testGetRequestDispatcherEncodedTraversal() throws Exception { doTestGetRequestDispatcher( true, "/prefix/start", null, "%2E%2E/target", "/target", DispatcherServlet.NULL); } @Test public void testGetRequestDispatcherTraversal01() throws Exception { doTestGetRequestDispatcher( true, "/prefix/start", null, "../target", "/target", TargetServlet.OK); } @Test public void testGetRequestDispatcherTraversal02() throws Exception { doTestGetRequestDispatcher( false, "/prefix/start", null, "../target", "/target", TargetServlet.OK); } @Test public void testGetRequestDispatcherTraversal03() throws Exception { doTestGetRequestDispatcher( true, "/prefix/start", null, "../target?a=b", "/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcherTraversal04() throws Exception { doTestGetRequestDispatcher( false, "/prefix/start", null, "../target?a=b", "/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcherTraversal05() throws Exception { doTestGetRequestDispatcher( true, "/prefix/start", "a=b", "../target", "/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcherTraversal06() throws Exception { doTestGetRequestDispatcher( false, "/prefix/start", "a=b", "../target", "/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher01() throws Exception { doTestGetRequestDispatcher( true, "/prefix/start", null, "target", "/prefix/target", TargetServlet.OK); } @Test public void testGetRequestDispatcher02() throws Exception { doTestGetRequestDispatcher( false, "/prefix/start", null, "target", "/prefix/target", TargetServlet.OK); } @Test public void testGetRequestDispatcher03() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", null, "target?a=b", "/prefix/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher04() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", null, "target?a=b", "/prefix/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher05() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", "a=b", "target", "/prefix/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher06() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", "a=b", "target", "/prefix/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher11() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Fbb%3Dcc/start", null, "target", "/aa%3Fbb%3Dcc/target", TargetServlet.OK); } @Test public void testGetRequestDispatcher12() throws Exception { // Expected to fail because when the RD processes this as unencoded it // sees /aa?bb=cc/target which it thinks is a query string. This is why // Tomcat encodes by default. doTestGetRequestDispatcher(false, "/aa%3Fbb%3Dcc/start", null, "target", "/aa%3Fbb%3Dcc/target", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher13() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Fbb%3Dcc/start", null, "target?a=b", "/aa%3Fbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher14() throws Exception { // Expected to fail because when the RD processes this as unencoded it // sees /aa?bb=cc/target which it thinks is a query string. This is why // Tomcat encodes by default. doTestGetRequestDispatcher(false, "/aa%3Fbb%3Dcc/start", null, "target?a=b", "/aa%3Fbb%3Dcc/target", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher15() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Fbb%3Dcc/start", "a=b", "target", "/aa%3Fbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher16() throws Exception { // Expected to fail because when the RD processes this as unencoded it // sees /aa?bb=cc/target which it thinks is a query string. This is why // Tomcat encodes by default. doTestGetRequestDispatcher(false, "/aa%3Fbb%3Dcc/start", "a=b", "target", "/aa%3Fbb%3Dcc/target", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher21() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Dbb%3Dcc/start", null, "target", "/aa%3Dbb%3Dcc/target", TargetServlet.OK); } @Test public void testGetRequestDispatcher22() throws Exception { doTestGetRequestDispatcher(false, "/aa%3Dbb%3Dcc/start", null, "target", "/aa%3Dbb%3Dcc/target", TargetServlet.OK); } @Test public void testGetRequestDispatcher23() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Dbb%3Dcc/start", null, "target?a=b", "/aa%3Dbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher24() throws Exception { doTestGetRequestDispatcher(false, "/aa%3Dbb%3Dcc/start", null, "target?a=b", "/aa%3Dbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher25() throws Exception { doTestGetRequestDispatcher(true, "/aa%3Dbb%3Dcc/start", "a=b", "target", "/aa%3Dbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher26() throws Exception { doTestGetRequestDispatcher(false, "/aa%3Dbb%3Dcc/start", "a=b", "target", "/aa%3Dbb%3Dcc/target", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher31() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", null, "aa%3Fbb%3Dcc", "/prefix/aa%3Fbb%3Dcc", TargetServlet.OK); } @Test public void testGetRequestDispatcher32() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", null, "aa%3Fbb%3Dcc", "/prefix/aa%3Fbb%3Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher33() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", null, "aa%3Fbb%3Dcc?a=b", "/prefix/aa%3Fbb%3Dcc", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher34() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", null, "aa%3Fbb%3Dcc?a=b", "/prefix/aa%3Fbb%3Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher35() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", "a=b", "aa%3Fbb%3Dcc", "/prefix/aa%3Fbb%3Dcc", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher36() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", "a=b", "aa%3Fbb%3Dcc", "/prefix/aa%3Fbb%3Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher41() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", null, "aa%3Fbb%3Dcc", "/prefix/aa%253Fbb%253Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher42() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", null, "aa%3Fbb%3Dcc", "/prefix/aa%253Fbb%253Dcc", TargetServlet.OK); } @Test public void testGetRequestDispatcher43() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", null, "aa%3Fbb%3Dcc?a=b", "/prefix/aa%253Fbb%253Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher44() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", null, "aa%3Fbb%3Dcc?a=b", "/prefix/aa%253Fbb%253Dcc", TargetServlet.OK + "a=b"); } @Test public void testGetRequestDispatcher45() throws Exception { doTestGetRequestDispatcher(true, "/prefix/start", "a=b", "aa%3Fbb%3Dcc", "/prefix/aa%253Fbb%253Dcc", Default404Servlet.DEFAULT_404); } @Test public void testGetRequestDispatcher46() throws Exception { doTestGetRequestDispatcher(false, "/prefix/start", "a=b", "aa%3Fbb%3Dcc", "/prefix/aa%253Fbb%253Dcc", TargetServlet.OK + "a=b"); } private void doTestGetRequestDispatcher(boolean useEncodedDispatchPaths, String startPath, String startQueryString, String dispatchPath, String targetPath, String expectedBody) throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); // No file system docBase required Context ctx = tomcat.addContext("/test", null); ctx.setDispatchersUseEncodedPaths(useEncodedDispatchPaths); // Add a default servlet to return 404 for not found resources Tomcat.addServlet(ctx, "Default", new Default404Servlet()); ctx.addServletMappingDecoded("/*", "Default"); // Add a target servlet to dispatch to Tomcat.addServlet(ctx, "target", new TargetServlet()); ctx.addServletMappingDecoded( UDecoder.URLDecode(targetPath, StandardCharsets.UTF_8), "target"); if (useAsync) { Wrapper w = Tomcat.addServlet( ctx, "rd", new AsyncDispatcherServlet(dispatchPath, useEncodedDispatchPaths)); w.setAsyncSupported(true); } else { Tomcat.addServlet(ctx, "rd", new DispatcherServlet(dispatchPath)); } ctx.addServletMappingDecoded(UDecoder.URLDecode(startPath, StandardCharsets.UTF_8), "rd"); tomcat.start(); StringBuilder url = new StringBuilder("http://localhost:"); url.append(getPort()); url.append("/test"); url.append(startPath); if (startQueryString != null) { url.append('?'); url.append(startQueryString); } ByteChunk bc = getUrl(url.toString()); String body = bc.toString(); Assert.assertEquals(expectedBody, body); } private static class Default404Servlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String DEFAULT_404 = "DEFAULT-404"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().print(DEFAULT_404); resp.setStatus(404); } } private static class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String NULL = "RD-NULL"; private final String dispatchPath; public DispatcherServlet(String dispatchPath) { this.dispatchPath = dispatchPath; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { RequestDispatcher rd = req.getRequestDispatcher(dispatchPath); if (rd == null) { resp.setContentType("text/plain"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().print(NULL); } else { rd.forward(req, resp); } } } private static class TargetServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String OK = "OK"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().print(OK); String qs = req.getQueryString(); if (qs != null) { resp.getWriter().print(qs); } } } private static class AsyncDispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String NULL = "RD-NULL"; private final String dispatchPath; private final boolean encodePath; public AsyncDispatcherServlet(String dispatchPath, boolean encodePath) { this.dispatchPath = dispatchPath; this.encodePath = encodePath; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext ac = req.startAsync(); // Quick and dirty. Sufficient for this test but ignores lots of // edge cases. String target = null; if (dispatchPath != null) { target = req.getServletPath(); int lastSlash = target.lastIndexOf('/'); target = target.substring(0, lastSlash + 1); if (encodePath) { target = URLEncoder.DEFAULT.encode(target, StandardCharsets.UTF_8); } target += dispatchPath; } try { ac.dispatch(target); } catch (UnsupportedOperationException uoe) { ac.complete(); resp.setContentType("text/plain"); resp.setCharacterEncoding("UTF-8"); resp.getWriter().print(NULL); } } } }