This is an automated email from the ASF dual-hosted git repository. markt-asf pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit b08b71e054c83ec3fbcec845ae062cad47fb5ac8 Author: Mark Thomas <[email protected]> AuthorDate: Mon Aug 10 21:59:48 2026 +0100 Add test cases for the new URL / URL pattern encoded/decoded option Generated with a combination of - Claude + Opus 5 - CoPilot + ChatGPT-5.6 --- build.xml | 1 + .../core/TestApplicationFilterRegistration.java | 52 ++++++++++++ .../core/TestApplicationServletRegistration.java | 48 +++++++++++ .../startup/TestContextConfigAnnotation.java | 27 +++++++ .../TestUrlPatternsProvidedInDecodedForm.java | 94 ++++++++++++++++++++++ .../apache/catalina/startup/UrlPatternFilter.java | 38 +++++++++ .../apache/catalina/startup/UrlPatternServlet.java | 26 ++++++ .../util/descriptor/web/TestWebXmlParser-web.xml | 63 +++++++++++++++ .../util/descriptor/web/TestWebXmlParser.java | 67 +++++++++++++++ test/webapp-url-patterns/WEB-INF/web.xml | 56 +++++++++++++ 10 files changed, 472 insertions(+) diff --git a/build.xml b/build.xml index 2ad873f3e4..eeae5bdaca 100644 --- a/build.xml +++ b/build.xml @@ -1950,6 +1950,7 @@ <include name="**/service-config.txt"/> <include name="**/logging-non-rotatable.properties"/> <include name="**/TestStrings.properties"/> + <include name="**/*-web.xml"/> </fileset> </copy> </target> diff --git a/test/org/apache/catalina/core/TestApplicationFilterRegistration.java b/test/org/apache/catalina/core/TestApplicationFilterRegistration.java new file mode 100644 index 0000000000..5f73fe6aff --- /dev/null +++ b/test/org/apache/catalina/core/TestApplicationFilterRegistration.java @@ -0,0 +1,52 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; + +public class TestApplicationFilterRegistration { + + @Test + public void testUrlPatternEncoded() { + doTestUrlPattern(false, "/filter%"); + } + + @Test + public void testUrlPatternDecoded() { + doTestUrlPattern(true, "/filter%25"); + } + + private void doTestUrlPattern(boolean urlPatternsProvidedInDecodedForm, String expectedPattern) { + StandardContext context = new StandardContext(); + context.setUrlPatternsProvidedInDecodedForm(urlPatternsProvidedInDecodedForm); + + FilterDef filterDef = new FilterDef(); + filterDef.setFilterName("filter"); + context.addFilterDef(filterDef); + + ApplicationFilterRegistration registration = new ApplicationFilterRegistration(filterDef, context); + registration.addMappingForUrlPatterns(null, true, "/filter%25"); + + FilterMap[] filterMaps = context.findFilterMaps(); + Assert.assertEquals(1, filterMaps.length); + Assert.assertArrayEquals(new String[] { expectedPattern }, filterMaps[0].getURLPatterns()); + } +} diff --git a/test/org/apache/catalina/core/TestApplicationServletRegistration.java b/test/org/apache/catalina/core/TestApplicationServletRegistration.java new file mode 100644 index 0000000000..4452a0baf9 --- /dev/null +++ b/test/org/apache/catalina/core/TestApplicationServletRegistration.java @@ -0,0 +1,48 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Wrapper; + +public class TestApplicationServletRegistration { + + @Test + public void testUrlPatternEncoded() { + doTestUrlPattern(false, "/servlet%"); + } + + @Test + public void testUrlPatternDecoded() { + doTestUrlPattern(true, "/servlet%25"); + } + + private void doTestUrlPattern(boolean urlPatternsProvidedInDecodedForm, String expectedPattern) { + StandardContext context = new StandardContext(); + context.setUrlPatternsProvidedInDecodedForm(urlPatternsProvidedInDecodedForm); + + Wrapper wrapper = context.createWrapper(); + wrapper.setName("servlet"); + context.addChild(wrapper); + + ApplicationServletRegistration registration = new ApplicationServletRegistration(wrapper, context); + Assert.assertTrue(registration.addMapping("/servlet%25").isEmpty()); + Assert.assertEquals("servlet", context.findServletMapping(expectedPattern)); + } +} diff --git a/test/org/apache/catalina/startup/TestContextConfigAnnotation.java b/test/org/apache/catalina/startup/TestContextConfigAnnotation.java index ba2b80ac09..585a19a4f8 100644 --- a/test/org/apache/catalina/startup/TestContextConfigAnnotation.java +++ b/test/org/apache/catalina/startup/TestContextConfigAnnotation.java @@ -268,6 +268,33 @@ public class TestContextConfigAnnotation { Assert.assertNull(filterDef); } + @Test + public void testUrlPatternsEncoded() throws Exception { + doTestUrlPatterns(false, "%"); + } + + @Test + public void testUrlPatternsDecoded() throws Exception { + doTestUrlPatterns(true, "%25"); + } + + @SuppressWarnings("deprecation") + private void doTestUrlPatterns(boolean urlPatternsProvidedInDecodedForm, String expectedSuffix) throws Exception { + WebXml webXml = new WebXml(urlPatternsProvidedInDecodedForm); + Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>(); + ContextConfig config = new ContextConfig(); + + File servletFile = paramClassResource("org/apache/catalina/startup/UrlPatternServlet"); + config.processAnnotationsFile(servletFile, webXml, false, javaClassCache); + Assert.assertEquals("urlPatternServlet", + webXml.getServletMappings().get("/servlet" + expectedSuffix)); + + File filterFile = paramClassResource("org/apache/catalina/startup/UrlPatternFilter"); + config.processAnnotationsFile(filterFile, webXml, false, javaClassCache); + FilterMap filterMap = webXml.getFilterMappings().iterator().next(); + Assert.assertArrayEquals(new String[] { "/filter" + expectedSuffix }, filterMap.getURLPatterns()); + } + @Test public void testCheckHandleTypes() throws Exception { Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>(); diff --git a/test/org/apache/catalina/startup/TestUrlPatternsProvidedInDecodedForm.java b/test/org/apache/catalina/startup/TestUrlPatternsProvidedInDecodedForm.java new file mode 100644 index 0000000000..ff3a2c5d7e --- /dev/null +++ b/test/org/apache/catalina/startup/TestUrlPatternsProvidedInDecodedForm.java @@ -0,0 +1,94 @@ +/* + * 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.startup; + +import java.io.File; + +import jakarta.servlet.http.HttpServletResponse; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.tomcat.util.buf.ByteChunk; + +/** + * End to end tests for the <code>urlPatternsProvidedInDecodedForm</code> Context attribute. The web application maps a + * Servlet at <code>/servlet%25</code> and protects <code>/secure%25</code> with a security constraint that denies all + * access. Whether those patterns are stored as <code>%</code> or as <code>%25</code> determines which request URI + * reaches them once the URI has been decoded. + */ +public class TestUrlPatternsProvidedInDecodedForm extends TomcatBaseTest { + + /* + * The URI that decodes to "%". + */ + private static final String ENCODED_FORM_URI_SUFFIX = "%25"; + + /* + * The URI that decodes to "%25". + */ + private static final String DECODED_FORM_URI_SUFFIX = "%2525"; + + + @Test + public void testEncodedForm() throws Exception { + // Patterns are decoded when read so the Servlet is mapped at "/servlet%" + doTestUrlPatterns(false, ENCODED_FORM_URI_SUFFIX, DECODED_FORM_URI_SUFFIX); + } + + + @Test + public void testDecodedForm() throws Exception { + // Patterns are used as provided so the Servlet is mapped at "/servlet%25" + doTestUrlPatterns(true, DECODED_FORM_URI_SUFFIX, ENCODED_FORM_URI_SUFFIX); + } + + + @SuppressWarnings("deprecation") + private void doTestUrlPatterns(boolean urlPatternsProvidedInDecodedForm, String matchingSuffix, + String nonMatchingSuffix) throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + File appDir = new File("test/webapp-url-patterns"); + Context ctx = tomcat.addWebapp(null, "/test", appDir.getAbsolutePath()); + ctx.setUrlPatternsProvidedInDecodedForm(urlPatternsProvidedInDecodedForm); + + tomcat.start(); + + // The Servlet mapping is only reachable via the URI that decodes to the configured pattern + ByteChunk body = new ByteChunk(); + Assert.assertEquals(HttpServletResponse.SC_OK, getUrl(uri("/test/servlet", matchingSuffix), body, null)); + Assert.assertEquals("OK", body.toString()); + + Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, + getUrl(uri("/test/servlet", nonMatchingSuffix), new ByteChunk(), null)); + + // The security constraint follows the same pattern so it only protects the matching URI + Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, + getUrl(uri("/test/secure", matchingSuffix), new ByteChunk(), null)); + + Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, + getUrl(uri("/test/secure", nonMatchingSuffix), new ByteChunk(), null)); + } + + + private String uri(String path, String suffix) { + return "http://localhost:" + getPort() + path + suffix; + } +} diff --git a/test/org/apache/catalina/startup/UrlPatternFilter.java b/test/org/apache/catalina/startup/UrlPatternFilter.java new file mode 100644 index 0000000000..4327dc035c --- /dev/null +++ b/test/org/apache/catalina/startup/UrlPatternFilter.java @@ -0,0 +1,38 @@ +/* + * 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.startup; + +import java.io.IOException; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.GenericFilter; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; + +@WebFilter(value = "/filter%25", filterName = "urlPatternFilter") +public class UrlPatternFilter extends GenericFilter { + + private static final long serialVersionUID = 1L; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + } +} diff --git a/test/org/apache/catalina/startup/UrlPatternServlet.java b/test/org/apache/catalina/startup/UrlPatternServlet.java new file mode 100644 index 0000000000..a656628754 --- /dev/null +++ b/test/org/apache/catalina/startup/UrlPatternServlet.java @@ -0,0 +1,26 @@ +/* + * 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.startup; + +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; + +@WebServlet(value = "/servlet%25", name = "urlPatternServlet") +public class UrlPatternServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; +} diff --git a/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser-web.xml b/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser-web.xml new file mode 100644 index 0000000000..e9be914953 --- /dev/null +++ b/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser-web.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<!-- + Every URL and URL pattern in this file uses %25 so that the two forms are + easy to tell apart. If the patterns are treated as being provided in encoded + form they will be decoded to "%". If they are treated as being provided in + decoded form they will be left as "%25". +--> +<web-app> + <error-page> + <error-code>404</error-code> + <location>/error%25</location> + </error-page> + <filter> + <filter-name>filter</filter-name> + <filter-class>org.apache.tomcat.TestFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>filter</filter-name> + <url-pattern>/filter%25</url-pattern> + </filter-mapping> + <servlet> + <servlet-name>servlet</servlet-name> + <servlet-class>org.apache.tomcat.TestServlet</servlet-class> + </servlet> + <servlet-mapping> + <servlet-name>servlet</servlet-name> + <url-pattern>/servlet%25</url-pattern> + </servlet-mapping> + <jsp-config> + <jsp-property-group> + <url-pattern>/jsp%25</url-pattern> + </jsp-property-group> + </jsp-config> + <login-config> + <auth-method>FORM</auth-method> + <form-login-config> + <form-login-page>/login%25</form-login-page> + <form-error-page>/login-error%25</form-error-page> + </form-login-config> + </login-config> + <security-constraint> + <web-resource-collection> + <web-resource-name>resource</web-resource-name> + <url-pattern>/secure%25</url-pattern> + </web-resource-collection> + </security-constraint> +</web-app> diff --git a/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser.java b/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser.java new file mode 100644 index 0000000000..5bcf23a10a --- /dev/null +++ b/test/org/apache/tomcat/util/descriptor/web/TestWebXmlParser.java @@ -0,0 +1,67 @@ +/* + * 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.tomcat.util.descriptor.web; + +import java.net.URL; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +public class TestWebXmlParser { + + private static final String WEB_XML = "TestWebXmlParser-web.xml"; + + @Test + public void testUrlPatternsEncoded() throws Exception { + doTestUrlPatterns(false, "%"); + } + + @Test + public void testUrlPatternsDecoded() throws Exception { + doTestUrlPatterns(true, "%25"); + } + + @SuppressWarnings("deprecation") + private void doTestUrlPatterns(boolean urlPatternsProvidedInDecodedForm, String expectedSuffix) throws Exception { + URL webXmlUrl = TestWebXmlParser.class.getResource(WEB_XML); + Assert.assertNotNull("Could not locate " + WEB_XML, webXmlUrl); + + WebXml webXml = new WebXml(urlPatternsProvidedInDecodedForm); + WebXmlParser parser = new WebXmlParser(false, false, true, urlPatternsProvidedInDecodedForm); + Assert.assertTrue(parser.parseWebXml(webXmlUrl, webXml, false)); + + Assert.assertEquals("servlet", webXml.getServletMappings().get("/servlet" + expectedSuffix)); + + FilterMap filterMap = webXml.getFilterMappings().iterator().next(); + Assert.assertArrayEquals(new String[] { "/filter" + expectedSuffix }, filterMap.getURLPatterns()); + + JspPropertyGroup jspPropertyGroup = webXml.getJspPropertyGroups().iterator().next(); + Assert.assertEquals(Set.of("/jsp" + expectedSuffix), jspPropertyGroup.getUrlPatterns()); + + LoginConfig loginConfig = webXml.getLoginConfig(); + Assert.assertEquals("/login" + expectedSuffix, loginConfig.getLoginPage()); + Assert.assertEquals("/login-error" + expectedSuffix, loginConfig.getErrorPage()); + + ErrorPage errorPage = webXml.getErrorPages().values().iterator().next(); + Assert.assertEquals("/error" + expectedSuffix, errorPage.getLocation()); + + SecurityConstraint securityConstraint = webXml.getSecurityConstraints().iterator().next(); + SecurityCollection securityCollection = securityConstraint.findCollection("resource"); + Assert.assertArrayEquals(new String[] { "/secure" + expectedSuffix }, securityCollection.findPatterns()); + } +} diff --git a/test/webapp-url-patterns/WEB-INF/web.xml b/test/webapp-url-patterns/WEB-INF/web.xml new file mode 100644 index 0000000000..f1a64bcdab --- /dev/null +++ b/test/webapp-url-patterns/WEB-INF/web.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee + https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" + version="6.0" + metadata-complete="true"> + + <!-- + Used by TestUrlPatternsProvidedInDecodedForm. The %25 in the URL patterns + is decoded to "%" if the patterns are treated as being provided in encoded + form and left as "%25" if they are treated as being provided in decoded + form. The request URI required to reach each mapping therefore differs + between the two modes. + --> + + <servlet> + <servlet-name>tester</servlet-name> + <servlet-class>org.apache.catalina.startup.TesterServlet</servlet-class> + </servlet> + + <servlet-mapping> + <servlet-name>tester</servlet-name> + <url-pattern>/servlet%25</url-pattern> + </servlet-mapping> + + <servlet-mapping> + <servlet-name>tester</servlet-name> + <url-pattern>/secure%25</url-pattern> + </servlet-mapping> + + <security-constraint> + <web-resource-collection> + <web-resource-name>secure</web-resource-name> + <url-pattern>/secure%25</url-pattern> + </web-resource-collection> + <auth-constraint/> + </security-constraint> + +</web-app> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
