| /* |
| * 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.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.CyclicBarrier; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import jakarta.servlet.FilterChain; |
| import jakarta.servlet.GenericFilter; |
| import jakarta.servlet.HttpConstraintElement; |
| import jakarta.servlet.HttpMethodConstraintElement; |
| import jakarta.servlet.MultipartConfigElement; |
| import jakarta.servlet.Servlet; |
| import jakarta.servlet.ServletContainerInitializer; |
| import jakarta.servlet.ServletContext; |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.ServletRegistration; |
| import jakarta.servlet.ServletRequest; |
| import jakarta.servlet.ServletResponse; |
| import jakarta.servlet.ServletSecurityElement; |
| import jakarta.servlet.annotation.HttpConstraint; |
| import jakarta.servlet.annotation.HttpMethodConstraint; |
| import jakarta.servlet.annotation.MultipartConfig; |
| import jakarta.servlet.annotation.ServletSecurity; |
| import jakarta.servlet.annotation.ServletSecurity.TransportGuarantee; |
| import jakarta.servlet.http.HttpServlet; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.hamcrest.CoreMatchers; |
| import org.hamcrest.MatcherAssert; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| import static org.apache.catalina.startup.SimpleHttpClient.CRLF; |
| import org.apache.catalina.ContainerEvent; |
| import org.apache.catalina.ContainerListener; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Engine; |
| import org.apache.catalina.Host; |
| import org.apache.catalina.Lifecycle; |
| import org.apache.catalina.LifecycleEvent; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.LifecycleListener; |
| import org.apache.catalina.LifecycleState; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.authenticator.BasicAuthenticator; |
| import org.apache.catalina.loader.WebappLoader; |
| import org.apache.catalina.startup.SimpleHttpClient; |
| import org.apache.catalina.startup.TesterMapRealm; |
| import org.apache.catalina.startup.TesterServlet; |
| import org.apache.catalina.startup.Tomcat; |
| import org.apache.catalina.startup.TomcatBaseTest; |
| import org.apache.jasper.servlet.JasperInitializer; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.descriptor.web.FilterDef; |
| import org.apache.tomcat.util.descriptor.web.FilterMap; |
| import org.apache.tomcat.util.descriptor.web.LoginConfig; |
| import org.apache.tomcat.util.http.Method; |
| |
| |
| public class TestStandardContext extends TomcatBaseTest { |
| |
| // @formatter:off |
| private static final String REQUEST = |
| "GET / HTTP/1.1" + CRLF + |
| "Host: anything" + CRLF + |
| "Connection: close" + CRLF + |
| CRLF; |
| // @formatter:on |
| |
| @Test |
| public void testBug46243() throws Exception { |
| // This tests that if a Filter init() fails then the web application |
| // is not put into service. (BZ 46243) |
| // This also tests that if the cause of the failure is gone, |
| // the context can be started without a need to redeploy it. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| |
| File docBase = new File(tomcat.getHost().getAppBaseFile(), "ROOT"); |
| if (!docBase.mkdirs() && !docBase.isDirectory()) { |
| Assert.fail("Unable to create docBase"); |
| } |
| |
| Context root = tomcat.addContext("", "ROOT"); |
| configureTest46243Context(root, true); |
| tomcat.start(); |
| |
| // Configure the client |
| Bug46243Client client = new Bug46243Client(tomcat.getConnector().getLocalPort()); |
| client.setRequest(new String[] { REQUEST }); |
| |
| client.connect(); |
| client.processRequest(); |
| Assert.assertTrue(client.isResponse404()); |
| |
| // Context failed to start. This checks that automatic transition |
| // from FAILED to STOPPED state was successful. |
| Assert.assertEquals(LifecycleState.STOPPED, root.getState()); |
| |
| // Prepare context for the second attempt |
| // Configuration was cleared on stop() thanks to |
| // StandardContext.resetContext(), so we need to configure it again |
| // from scratch. |
| configureTest46243Context(root, false); |
| root.start(); |
| // The same request is processed successfully |
| client.connect(); |
| client.processRequest(); |
| Assert.assertTrue(client.isResponse200()); |
| Assert.assertEquals(Bug46243Filter.class.getName() + HelloWorldServlet.RESPONSE_TEXT, client.getResponseBody()); |
| } |
| |
| private static void configureTest46243Context(Context context, boolean fail) { |
| // Add a test filter that fails |
| FilterDef filterDef = new FilterDef(); |
| filterDef.setFilterClass(Bug46243Filter.class.getName()); |
| filterDef.setFilterName("Bug46243"); |
| filterDef.addInitParameter("fail", Boolean.toString(fail)); |
| context.addFilterDef(filterDef); |
| FilterMap filterMap = new FilterMap(); |
| filterMap.setFilterName("Bug46243"); |
| filterMap.addURLPatternDecoded("*"); |
| context.addFilterMap(filterMap); |
| |
| // Add a test servlet so there is something to generate a response if |
| // it works (although it shouldn't) |
| Tomcat.addServlet(context, "Bug46243", new HelloWorldServlet()); |
| context.addServletMappingDecoded("/", "Bug46243"); |
| } |
| |
| private static final class Bug46243Client extends SimpleHttpClient { |
| |
| Bug46243Client(int port) { |
| setPort(port); |
| } |
| |
| @Override |
| public boolean isResponseBodyOK() { |
| // Don't care about the body in this test |
| return true; |
| } |
| } |
| |
| public static final class Bug46243Filter extends GenericFilter { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| PrintWriter out = response.getWriter(); |
| out.print(getClass().getName()); |
| chain.doFilter(request, response); |
| } |
| |
| @Override |
| public void init() throws ServletException { |
| boolean fail = getInitParameter("fail").equals("true"); |
| if (fail) { |
| throw new ServletException("Init fail (test)", new ClassNotFoundException()); |
| } |
| } |
| } |
| |
| @Test |
| public void testWebappLoaderStartFail() throws Exception { |
| // Test that if WebappLoader start() fails and if the cause of |
| // the failure is gone, the context can be started without |
| // a need to redeploy it. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| tomcat.start(); |
| // To not start Context automatically, as we have to configure it first |
| ((ContainerBase) tomcat.getHost()).setStartChildren(false); |
| |
| FailingWebappLoader loader = new FailingWebappLoader(); |
| File root = new File("test/webapp"); |
| Context context = tomcat.addWebapp("", root.getAbsolutePath()); |
| context.setLoader(loader); |
| |
| try { |
| context.start(); |
| Assert.fail(); |
| } catch (LifecycleException ex) { |
| // As expected |
| } |
| Assert.assertEquals(LifecycleState.FAILED, context.getState()); |
| |
| // The second attempt |
| loader.setFail(false); |
| context.start(); |
| Assert.assertEquals(LifecycleState.STARTED, context.getState()); |
| |
| // Using a test from testBug49922() to check that the webapp is running |
| ByteChunk result = getUrl("http://localhost:" + getPort() + "/bug49922/target"); |
| Assert.assertEquals("Target", result.toString()); |
| } |
| |
| @Test |
| public void testWebappListenerConfigureFail() throws Exception { |
| // Test that if LifecycleListener on webapp fails during |
| // configure_start event and if the cause of the failure is gone, |
| // the context can be started without a need to redeploy it. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| tomcat.start(); |
| // To not start Context automatically, as we have to configure it first |
| ((ContainerBase) tomcat.getHost()).setStartChildren(false); |
| |
| FailingLifecycleListener listener = new FailingLifecycleListener(); |
| File root = new File("test/webapp"); |
| Context context = tomcat.addWebapp("", root.getAbsolutePath()); |
| context.addLifecycleListener(listener); |
| |
| try { |
| context.start(); |
| Assert.fail(); |
| } catch (LifecycleException ex) { |
| // As expected |
| } |
| Assert.assertEquals(LifecycleState.FAILED, context.getState()); |
| |
| // The second attempt |
| listener.setFail(false); |
| context.start(); |
| Assert.assertEquals(LifecycleState.STARTED, context.getState()); |
| |
| // Using a test from testBug49922() to check that the webapp is running |
| ByteChunk result = getUrl("http://localhost:" + getPort() + "/bug49922/target"); |
| Assert.assertEquals("Target", result.toString()); |
| } |
| |
| private static class FailingWebappLoader extends WebappLoader { |
| private boolean fail = true; |
| |
| protected void setFail(boolean fail) { |
| this.fail = fail; |
| } |
| |
| @Override |
| protected void startInternal() throws LifecycleException { |
| if (fail) { |
| throw new RuntimeException("Start fail (test)"); |
| } |
| super.startInternal(); |
| } |
| } |
| |
| private static class FailingLifecycleListener implements LifecycleListener { |
| private static final String failEvent = Lifecycle.CONFIGURE_START_EVENT; |
| private boolean fail = true; |
| |
| protected void setFail(boolean fail) { |
| this.fail = fail; |
| } |
| |
| @Override |
| public void lifecycleEvent(LifecycleEvent event) { |
| if (fail && event.getType().equals(failEvent)) { |
| throw new RuntimeException(failEvent + " fail (test)"); |
| } |
| } |
| } |
| |
| @Test |
| public void testBug49922() throws Exception { |
| // Test that filter mapping works. Test that the same filter is |
| // called only once, even if is selected by several mapping |
| // url-patterns or by both a url-pattern and a servlet-name. |
| |
| getTomcatInstanceTestWebapp(false, true); |
| |
| ByteChunk result = new ByteChunk(); |
| |
| // Check filter and servlet aren't called |
| int rc = getUrl("http://localhost:" + getPort() + "/test/bug49922/foo", result, null); |
| Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc); |
| Assert.assertTrue(result.getLength() > 0); |
| |
| // Check extension mapping works |
| result = getUrl("http://localhost:" + getPort() + "/test/foo.do"); |
| Assert.assertEquals("FilterServlet", result.toString()); |
| |
| // Check path mapping works |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/servlet"); |
| Assert.assertEquals("FilterServlet", result.toString()); |
| |
| // Check servlet name mapping works |
| result = getUrl("http://localhost:" + getPort() + "/test/foo.od"); |
| Assert.assertEquals("FilterServlet", result.toString()); |
| |
| // Check filter is only called once |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/servlet/foo.do"); |
| Assert.assertEquals("FilterServlet", result.toString()); |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/servlet/foo.od"); |
| Assert.assertEquals("FilterServlet", result.toString()); |
| |
| // Check dispatcher mapping |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/target"); |
| Assert.assertEquals("Target", result.toString()); |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/forward"); |
| Assert.assertEquals("FilterTarget", result.toString()); |
| result = getUrl("http://localhost:" + getPort() + "/test/bug49922/include"); |
| Assert.assertEquals("IncludeFilterTarget", result.toString()); |
| } |
| |
| |
| public static final class Bug49922Filter extends GenericFilter { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| response.setContentType("text/plain"); |
| response.getWriter().print("Filter"); |
| chain.doFilter(request, response); |
| } |
| } |
| |
| public static final class Bug49922ForwardServlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| req.getRequestDispatcher("/bug49922/target").forward(req, resp); |
| } |
| |
| } |
| |
| public static final class Bug49922IncludeServlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| resp.setContentType("text/plain"); |
| resp.getWriter().print("Include"); |
| req.getRequestDispatcher("/bug49922/target").include(req, resp); |
| } |
| |
| } |
| |
| public static final class Bug49922TargetServlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| resp.setContentType("text/plain"); |
| resp.getWriter().print("Target"); |
| } |
| |
| } |
| |
| public static final class Bug49922Servlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| resp.setContentType("text/plain"); |
| resp.getWriter().print("Servlet"); |
| } |
| |
| } |
| |
| @Test |
| public void testBug50015() throws Exception { |
| // Test that configuring servlet security constraints programmatically |
| // does work. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| |
| // No file system docBase required |
| Context ctx = getProgrammaticRootContext(); |
| |
| // Setup realm |
| TesterMapRealm realm = new TesterMapRealm(); |
| realm.addUser("tomcat", "tomcat"); |
| realm.addUserRole("tomcat", "tomcat"); |
| ctx.setRealm(realm); |
| |
| // Configure app for BASIC auth |
| LoginConfig lc = new LoginConfig(); |
| lc.setAuthMethod("BASIC"); |
| ctx.setLoginConfig(lc); |
| ctx.getPipeline().addValve(new BasicAuthenticator()); |
| |
| // Add ServletContainerInitializer |
| ServletContainerInitializer sci = new Bug50015SCI(); |
| ctx.addServletContainerInitializer(sci, null); |
| |
| // Start the context |
| tomcat.start(); |
| |
| // Request the first servlet |
| ByteChunk bc = new ByteChunk(); |
| int rc = getUrl("http://localhost:" + getPort() + "/bug50015", bc, null); |
| |
| // Check for a 401 |
| Assert.assertNotSame("OK", bc.toString()); |
| Assert.assertEquals(401, rc); |
| } |
| |
| public static final class Bug50015SCI implements ServletContainerInitializer { |
| |
| @Override |
| public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { |
| // Register and map servlet |
| Servlet s = new TesterServlet(); |
| ServletRegistration.Dynamic sr = ctx.addServlet("bug50015", s); |
| sr.addMapping("/bug50015"); |
| |
| // Limit access to users in the Tomcat role |
| HttpConstraintElement hce = new HttpConstraintElement(TransportGuarantee.NONE, "tomcat"); |
| ServletSecurityElement sse = new ServletSecurityElement(hce); |
| sr.setServletSecurity(sse); |
| } |
| } |
| |
| @Test |
| public void testDenyUncoveredHttpMethodsSCITrue() throws Exception { |
| doTestDenyUncoveredHttpMethodsSCI(true); |
| } |
| |
| @Test |
| public void testDenyUncoveredHttpMethodsSCIFalse() throws Exception { |
| doTestDenyUncoveredHttpMethodsSCI(false); |
| } |
| |
| private void doTestDenyUncoveredHttpMethodsSCI(boolean enableDeny) throws Exception { |
| // Test that denying uncovered HTTP methods when adding servlet security |
| // constraints programmatically does work. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| |
| // No file system docBase required |
| Context ctx = getProgrammaticRootContext(); |
| |
| ctx.setDenyUncoveredHttpMethods(enableDeny); |
| |
| // Setup realm |
| TesterMapRealm realm = new TesterMapRealm(); |
| realm.addUser("tomcat", "tomcat"); |
| realm.addUserRole("tomcat", "tomcat"); |
| ctx.setRealm(realm); |
| |
| // Configure app for BASIC auth |
| LoginConfig lc = new LoginConfig(); |
| lc.setAuthMethod("BASIC"); |
| ctx.setLoginConfig(lc); |
| ctx.getPipeline().addValve(new BasicAuthenticator()); |
| |
| // Add ServletContainerInitializer |
| ServletContainerInitializer sci = new DenyUncoveredHttpMethodsSCI(); |
| ctx.addServletContainerInitializer(sci, null); |
| |
| // Start the context |
| tomcat.start(); |
| |
| // Request the first servlet |
| ByteChunk bc = new ByteChunk(); |
| int rc = getUrl("http://localhost:" + getPort() + "/test", bc, null); |
| |
| // Check for a 401 |
| if (enableDeny) { |
| // Should be default error page |
| Assert.assertTrue(bc.toString().contains("403")); |
| Assert.assertEquals(403, rc); |
| } else { |
| Assert.assertEquals("OK", bc.toString()); |
| Assert.assertEquals(200, rc); |
| } |
| } |
| |
| public static final class DenyUncoveredHttpMethodsSCI implements ServletContainerInitializer { |
| |
| @Override |
| public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { |
| // Register and map servlet |
| Servlet s = new TesterServlet(); |
| ServletRegistration.Dynamic sr = ctx.addServlet("test", s); |
| sr.addMapping("/test"); |
| |
| // Add a constraint with uncovered methods |
| HttpConstraintElement hce = new HttpConstraintElement(TransportGuarantee.NONE, "tomcat"); |
| HttpMethodConstraintElement hmce = new HttpMethodConstraintElement(Method.POST, hce); |
| Set<HttpMethodConstraintElement> hmces = new HashSet<>(); |
| hmces.add(hmce); |
| ServletSecurityElement sse = new ServletSecurityElement(hmces); |
| sr.setServletSecurity(sse); |
| } |
| } |
| |
| @Test |
| public void testBug51376a() throws Exception { |
| doTestBug51376(false); |
| } |
| |
| @Test |
| public void testBug51376b() throws Exception { |
| doTestBug51376(true); |
| } |
| |
| private void doTestBug51376(boolean loadOnStartUp) throws Exception { |
| // Test that for a servlet that was added programmatically its |
| // loadOnStartup property is honored and its init() and destroy() |
| // methods are called. |
| |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| |
| // No file system docBase required |
| Context ctx = getProgrammaticRootContext(); |
| |
| // Add ServletContainerInitializer |
| Bug51376SCI sci = new Bug51376SCI(loadOnStartUp); |
| ctx.addServletContainerInitializer(sci, null); |
| |
| // Start the context |
| tomcat.start(); |
| |
| // Stop the context |
| ctx.stop(); |
| |
| // Make sure that init() and destroy() were called correctly |
| Assert.assertTrue(sci.getServlet().isOk()); |
| Assert.assertTrue(loadOnStartUp == sci.getServlet().isInitCalled()); |
| } |
| |
| public static final class Bug51376SCI implements ServletContainerInitializer { |
| |
| private Bug51376Servlet s = null; |
| private boolean loadOnStartUp; |
| |
| public Bug51376SCI(boolean loadOnStartUp) { |
| this.loadOnStartUp = loadOnStartUp; |
| } |
| |
| private Bug51376Servlet getServlet() { |
| return s; |
| } |
| |
| @Override |
| public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { |
| // Register and map servlet |
| s = new Bug51376Servlet(); |
| ServletRegistration.Dynamic sr = ctx.addServlet("bug51376", s); |
| sr.addMapping("/bug51376"); |
| if (loadOnStartUp) { |
| sr.setLoadOnStartup(1); |
| } |
| } |
| } |
| |
| public static final class Bug51376Servlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private Boolean initOk = null; |
| private Boolean destroyOk = null; |
| |
| @Override |
| public void init() { |
| if (initOk == null && destroyOk == null) { |
| initOk = Boolean.TRUE; |
| } else { |
| initOk = Boolean.FALSE; |
| } |
| } |
| |
| @Override |
| public void destroy() { |
| if (initOk.booleanValue() && destroyOk == null) { |
| destroyOk = Boolean.TRUE; |
| } else { |
| destroyOk = Boolean.FALSE; |
| } |
| } |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| resp.setContentType("text/plain"); |
| resp.getWriter().write("OK"); |
| } |
| |
| protected boolean isOk() { |
| if (initOk != null && initOk.booleanValue() && destroyOk != null && destroyOk.booleanValue()) { |
| return true; |
| } else if (initOk == null && destroyOk == null) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| protected boolean isInitCalled() { |
| return initOk != null && initOk.booleanValue(); |
| } |
| } |
| |
| /** |
| * Test case for bug 49711: HttpServletRequest.getParts does not work in a filter. |
| */ |
| @Test |
| public void testBug49711() { |
| Bug49711Client client = new Bug49711Client(); |
| |
| // Make sure non-multipart works properly |
| client.doRequest("/regular", false, false); |
| |
| // Servlet attempts to read parts which will trigger an ISE |
| Assert.assertTrue(client.isResponse500()); |
| |
| client.reset(); |
| |
| // Make sure regular multipart works properly |
| client.doRequest("/multipart", false, true); // send multipart request |
| |
| Assert.assertEquals("Regular multipart doesn't work", "parts=1", client.getResponseBody()); |
| |
| client.reset(); |
| |
| // Make casual multipart request to "regular" servlet w/o config |
| // We expect an error |
| client.doRequest("/regular", false, true); // send multipart request |
| |
| // Servlet attempts to read parts which will trigger an ISE |
| Assert.assertTrue(client.isResponse500()); |
| |
| client.reset(); |
| |
| // Make casual multipart request to "regular" servlet w/config |
| // We expect that the server /will/ parse the parts, even though |
| // there is no @MultipartConfig |
| client.doRequest("/regular", true, true); // send multipart request |
| |
| Assert.assertEquals("Incorrect response for configured casual multipart request", "parts=1", |
| client.getResponseBody()); |
| |
| client.reset(); |
| } |
| |
| private static class Bug49711Servlet extends HttpServlet { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| // Just echo the parameters and values back as plain text |
| resp.setContentType("text/plain"); |
| resp.setCharacterEncoding("UTF-8"); |
| |
| PrintWriter out = resp.getWriter(); |
| |
| out.print("parts=" + (null == req.getParts() ? "null" : Integer.valueOf(req.getParts().size()))); |
| } |
| } |
| |
| @MultipartConfig |
| private static class Bug49711Servlet_multipart extends Bug49711Servlet { |
| private static final long serialVersionUID = 1L; |
| } |
| |
| /** |
| * Bug 49711 test client: test for casual getParts calls. |
| */ |
| private class Bug49711Client extends SimpleHttpClient { |
| |
| private boolean init; |
| private Context context; |
| |
| private synchronized void init() throws Exception { |
| if (init) { |
| return; |
| } |
| |
| Tomcat tomcat = getTomcatInstance(); |
| context = tomcat.addContext("", TEMP_DIR); |
| Tomcat.addServlet(context, "regular", new Bug49711Servlet()); |
| Wrapper w = Tomcat.addServlet(context, "multipart", new Bug49711Servlet_multipart()); |
| |
| // Tomcat.addServlet does not respect annotations, so we have |
| // to set our own MultipartConfigElement. |
| w.setMultipartConfigElement(new MultipartConfigElement("")); |
| |
| context.addServletMappingDecoded("/regular", "regular"); |
| context.addServletMappingDecoded("/multipart", "multipart"); |
| tomcat.start(); |
| |
| setPort(tomcat.getConnector().getLocalPort()); |
| |
| init = true; |
| } |
| |
| private Exception doRequest(String uri, boolean allowCasualMultipart, boolean makeMultipartRequest) { |
| try { |
| init(); |
| |
| context.setAllowCasualMultipartParsing(allowCasualMultipart); |
| |
| // Open connection |
| connect(); |
| |
| // Send specified request body using method |
| String[] request; |
| |
| if (makeMultipartRequest) { |
| String boundary = "--simpleboundary"; |
| |
| String content = "--" + boundary + CRLF + "Content-Disposition: form-data; name=\"name\"" + CRLF + |
| CRLF + "value" + CRLF + "--" + boundary + "--" + CRLF; |
| |
| // Re-encode the content so that bytes = characters |
| content = new String(content.getBytes("UTF-8"), "ASCII"); |
| |
| request = new String[] { "POST http://localhost:" + getPort() + uri + " HTTP/1.1" + CRLF + |
| "Host: localhost:" + getPort() + CRLF + "Connection: close" + CRLF + |
| "Content-Type: multipart/form-data; boundary=" + boundary + CRLF + "Content-Length: " + |
| content.length() + CRLF + CRLF + content + CRLF }; |
| } else { |
| request = new String[] { "GET http://localhost:" + getPort() + uri + " HTTP/1.1" + CRLF + |
| "Host: localhost:" + getPort() + CRLF + "Connection: close" + CRLF + CRLF }; |
| } |
| |
| setRequest(request); |
| processRequest(); // blocks until response has been read |
| |
| // Close the connection |
| disconnect(); |
| } catch (Exception e) { |
| return e; |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public boolean isResponseBodyOK() { |
| return false; // Don't care |
| } |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPostConstructMethodNullClassName() { |
| new StandardContext().addPostConstructMethod(null, ""); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPostConstructMethodNullMethodName() { |
| new StandardContext().addPostConstructMethod("", null); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPostConstructMethodConflicts() { |
| StandardContext standardContext = new StandardContext(); |
| standardContext.addPostConstructMethod("a", "a"); |
| standardContext.addPostConstructMethod("a", "b"); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPreDestroyMethodNullClassName() { |
| new StandardContext().addPreDestroyMethod(null, ""); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPreDestroyMethodNullMethodName() { |
| new StandardContext().addPreDestroyMethod("", null); |
| } |
| |
| @Test(expected = IllegalArgumentException.class) |
| public void testAddPreDestroyMethodConflicts() { |
| StandardContext standardContext = new StandardContext(); |
| standardContext.addPreDestroyMethod("a", "a"); |
| standardContext.addPreDestroyMethod("a", "b"); |
| } |
| |
| @Test |
| public void testTldListener() throws Exception { |
| // Set up a container |
| Tomcat tomcat = getTomcatInstance(); |
| |
| File docBase = new File("test/webapp-3.0"); |
| Context ctx = tomcat.addContext("", docBase.getAbsolutePath()); |
| ctx.addServletContainerInitializer(new JasperInitializer(), null); |
| |
| // Start the context |
| tomcat.start(); |
| |
| // Stop the context |
| ctx.stop(); |
| |
| String log = TesterTldListener.getLog(); |
| Assert.assertTrue(log, log.contains("PASS-01")); |
| Assert.assertTrue(log, log.contains("PASS-02")); |
| Assert.assertFalse(log, log.contains("FAIL")); |
| } |
| |
| @Test |
| public void testFlagFailCtxIfServletStartFails() throws Exception { |
| Tomcat tomcat = getTomcatInstance(); |
| File docBase = new File(System.getProperty("java.io.tmpdir")); |
| StandardContext context = (StandardContext) tomcat.addContext("", docBase.getAbsolutePath()); |
| |
| // first we test the flag itself, which can be set on the Host and |
| // Context |
| Assert.assertFalse(context.getComputedFailCtxIfServletStartFails()); |
| |
| StandardHost host = (StandardHost) tomcat.getHost(); |
| host.setFailCtxIfServletStartFails(true); |
| Assert.assertTrue(context.getComputedFailCtxIfServletStartFails()); |
| context.setFailCtxIfServletStartFails(Boolean.FALSE); |
| Assert.assertFalse("flag on Context should override Host config", |
| context.getComputedFailCtxIfServletStartFails()); |
| |
| // second, we test the actual effect of the flag on the startup |
| Wrapper servlet = Tomcat.addServlet(context, "myservlet", new FailingStartupServlet()); |
| servlet.setLoadOnStartup(1); |
| |
| tomcat.start(); |
| Assert.assertTrue("flag false should not fail deployment", context.getState().isAvailable()); |
| |
| tomcat.stop(); |
| Assert.assertFalse(context.getState().isAvailable()); |
| |
| host.removeChild(context); |
| context = (StandardContext) tomcat.addContext("", docBase.getAbsolutePath()); |
| servlet = Tomcat.addServlet(context, "myservlet", new FailingStartupServlet()); |
| servlet.setLoadOnStartup(1); |
| tomcat.start(); |
| Assert.assertFalse("flag true should fail deployment", context.getState().isAvailable()); |
| } |
| |
| private static class FailingStartupServlet extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void init() throws ServletException { |
| throw new ServletException("failing on purpose"); |
| } |
| |
| } |
| |
| @Test |
| public void testBug56085() throws Exception { |
| Tomcat tomcat = getTomcatInstanceTestWebapp(false, true); |
| |
| String realPath = ((Context) tomcat.getHost().findChildren()[0]).getRealPath("\\"); |
| |
| Assert.assertNull(realPath); |
| } |
| |
| /* |
| * Check real path for directories ends with File.separator for consistency with previous major versions. |
| */ |
| @Test |
| public void testBug57556a() throws Exception { |
| Tomcat tomcat = getTomcatInstanceTestWebapp(false, true); |
| Context testContext = ((Context) tomcat.getHost().findChildren()[0]); |
| |
| File f = new File(testContext.getDocBase()); |
| if (!f.isAbsolute()) { |
| f = new File(((Host) testContext.getParent()).getAppBaseFile(), f.getPath()); |
| } |
| String base = f.getCanonicalPath(); |
| |
| |
| doTestBug57556(testContext, "", base + File.separatorChar); |
| doTestBug57556(testContext, "/", base + File.separatorChar); |
| doTestBug57556(testContext, "/jsp", base + File.separatorChar + "jsp"); |
| doTestBug57556(testContext, "/jsp/", base + File.separatorChar + "jsp" + File.separatorChar); |
| doTestBug57556(testContext, "/index.html", base + File.separatorChar + "index.html"); |
| doTestBug57556(testContext, "/foo", base + File.separatorChar + "foo"); |
| doTestBug57556(testContext, "/foo/", base + File.separatorChar + "foo" + File.separatorChar); |
| } |
| |
| @Test |
| public void testBug57556b() throws Exception { |
| Tomcat tomcat = getTomcatInstance(); |
| File docBase = new File("/"); |
| Context testContext = tomcat.addContext("", docBase.getAbsolutePath()); |
| tomcat.start(); |
| |
| File f = new File(testContext.getDocBase()); |
| if (!f.isAbsolute()) { |
| f = new File(((Host) testContext.getParent()).getAppBaseFile(), f.getPath()); |
| } |
| String base = f.getCanonicalPath(); |
| |
| doTestBug57556(testContext, "", base); |
| doTestBug57556(testContext, "/", base); |
| } |
| |
| private void doTestBug57556(Context testContext, String path, String expected) throws Exception { |
| String realPath = testContext.getRealPath(path); |
| Assert.assertNotNull(realPath); |
| Assert.assertEquals(expected, realPath); |
| } |
| |
| @Test |
| public void testBug56903() { |
| Context context = new StandardContext(); |
| |
| context.setResourceOnlyServlets("a,b,c"); |
| MatcherAssert.assertThat(Arrays.asList(context.getResourceOnlyServlets().split(",")), |
| CoreMatchers.hasItems("a", "b", "c")); |
| } |
| |
| @Test |
| public void testSetPath() { |
| testSetPath("", ""); |
| testSetPath("/foo", "/foo"); |
| testSetPath("/foo/bar", "/foo/bar"); |
| testSetPath(null, ""); |
| testSetPath("/", ""); |
| testSetPath("foo", "/foo"); |
| testSetPath("/foo/bar/", "/foo/bar"); |
| testSetPath("foo/bar/", "/foo/bar"); |
| } |
| |
| private void testSetPath(String value, String expectedValue) { |
| StandardContext context = new StandardContext(); |
| context.setPath(value); |
| Assert.assertEquals(expectedValue, context.getPath()); |
| } |
| |
| |
| @Test |
| public void testUncoveredMethods() throws Exception { |
| // Setup Tomcat instance |
| Tomcat tomcat = getTomcatInstance(); |
| |
| // No file system docBase required |
| Context ctx = tomcat.addContext("/test", null); |
| ctx.setDenyUncoveredHttpMethods(true); |
| |
| ServletContainerInitializer sci = new SCI(); |
| ctx.addServletContainerInitializer(sci, null); |
| |
| tomcat.start(); |
| |
| ByteChunk bc = new ByteChunk(); |
| int rc; |
| |
| rc = getUrl("http://localhost:" + getPort() + "/test/foo", bc, false); |
| |
| Assert.assertEquals(403, rc); |
| } |
| |
| |
| public static class SCI implements ServletContainerInitializer { |
| @Override |
| public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { |
| ServletRegistration.Dynamic sr = ctx.addServlet("Foo", Foo.class.getName()); |
| sr.addMapping("/foo"); |
| } |
| } |
| |
| |
| @ServletSecurity(value = @HttpConstraint(ServletSecurity.EmptyRoleSemantic.DENY), httpMethodConstraints = @HttpMethodConstraint(Method.POST)) |
| public static class Foo extends HttpServlet { |
| |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| resp.getWriter().print("OK"); |
| } |
| } |
| |
| |
| @Test |
| public void testNamingContextName() throws Exception { |
| Engine engine = new StandardEngine(); |
| engine.setName("engine"); |
| |
| Host host = new StandardHost(); |
| host.setName("host"); |
| host.setParent(engine); |
| |
| Context context = new StandardContext(); |
| context.setName("context"); |
| context.setParent(host); |
| |
| java.lang.reflect.Method m = StandardContext.class.getDeclaredMethod("getNamingContextName"); |
| m.setAccessible(true); |
| String result = (String) m.invoke(context); |
| |
| Assert.assertEquals("/engine/hostcontext", result); |
| } |
| |
| @Test |
| public void testWrapperListeners() throws Exception { |
| // Setup Tomcat instance |
| Tomcat tomcat = getTomcatInstance(); |
| |
| // No file system docBase required |
| Context ctx = tomcat.addContext("/test", null); |
| ctx.setWrapperClass("org.apache.catalina.core.TestStandardContext$MyWrapperClass"); |
| ctx.addWrapperLifecycle("org.apache.catalina.core.TestStandardContext$MyWrapperLifecycleListener"); |
| ctx.addWrapperListener("org.apache.catalina.core.TestStandardContext$MyWrapperContainerListener"); |
| Wrapper w = Tomcat.addServlet(ctx, "something", "org.apache.catalina.core.TestStandardContext$Bug51376Servlet"); |
| |
| tomcat.start(); |
| |
| w.addInitParameter("foo", "bar"); |
| |
| Assert.assertTrue(customWrapperClassOk); |
| Assert.assertTrue(containerListenerOk); |
| Assert.assertTrue(lifecycleListenerOk); |
| } |
| |
| @Test |
| public void testGetServletContextReturnsSameInstanceUnderConcurrency() throws Exception { |
| Tomcat tomcat = getTomcatInstance(); |
| File appDir = new File(tomcat.getHost().getAppBaseFile(), "ROOT"); |
| if (!appDir.mkdirs() && !appDir.isDirectory()) { |
| Assert.fail("Unable to create appDir"); |
| } |
| |
| StandardContext standardContext = (StandardContext) tomcat.addContext("", appDir.getAbsolutePath()); |
| tomcat.start(); |
| |
| // Null the context field to simulate the window during reload |
| standardContext.context = null; |
| |
| int numThreads = 20; |
| CyclicBarrier barrier = new CyclicBarrier(numThreads); |
| List<Thread> threads = new ArrayList<>(); |
| ServletContext[] results = new ServletContext[numThreads]; |
| AtomicReference<Throwable> failure = new AtomicReference<>(); |
| |
| for (int numOfThread = 0; numOfThread < numThreads; numOfThread++) { |
| final int index = numOfThread; |
| Thread thread = new Thread(() -> { |
| try { |
| barrier.await(); |
| results[index] = standardContext.getServletContext(); |
| } catch (Throwable ex) { |
| failure.set(ex); |
| } |
| }); |
| thread.start(); |
| threads.add(thread); |
| } |
| |
| for (Thread thread : threads) { |
| thread.join(5000); |
| } |
| |
| if (failure.get() != null) { |
| Assert.fail("Thread failed: " + failure.get()); |
| } |
| |
| ServletContext first = results[0]; |
| Assert.assertNotNull(first); |
| for (int i = 1; i < numThreads; i++) { |
| Assert.assertSame(first, results[i]); |
| } |
| } |
| |
| |
| private static boolean customWrapperClassOk = false; |
| |
| public static class MyWrapperClass extends StandardWrapper { |
| @Override |
| protected void startInternal() throws LifecycleException { |
| super.startInternal(); |
| customWrapperClassOk = true; |
| } |
| } |
| |
| private static boolean containerListenerOk = false; |
| |
| public static class MyWrapperContainerListener implements ContainerListener { |
| @Override |
| public void containerEvent(ContainerEvent event) { |
| containerListenerOk = true; |
| } |
| } |
| |
| private static boolean lifecycleListenerOk = false; |
| |
| public static class MyWrapperLifecycleListener implements LifecycleListener { |
| @Override |
| public void lifecycleEvent(LifecycleEvent event) { |
| lifecycleListenerOk = true; |
| } |
| } |
| } |