This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new ccc22ba156 Back-port new test cases and fix exposed edge case
ccc22ba156 is described below

commit ccc22ba15654b4edbfc86796b39b2745ac90f5ea
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Nov 3 16:15:12 2026 +0000

    Back-port new test cases and fix exposed edge case
    
    Also rename new utility method
---
 java/org/apache/catalina/connector/Request.java    |  2 +-
 .../org/apache/catalina/core/AsyncContextImpl.java |  9 +++
 java/org/apache/catalina/util/RequestUtil.java     |  2 +-
 .../apache/catalina/core/TestAsyncContextImpl.java | 71 ++++++++++++++++++++++
 4 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/catalina/connector/Request.java 
b/java/org/apache/catalina/connector/Request.java
index f48f8798b1..12568b2d8d 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -1995,7 +1995,7 @@ public class Request implements HttpServletRequest {
 
     @Override
     public String getContextPath() {
-        return RequestUtil.getContextPathUndecoded(this);
+        return RequestUtil.getContextPathEncoded(this);
     }
 
 
diff --git a/java/org/apache/catalina/core/AsyncContextImpl.java 
b/java/org/apache/catalina/core/AsyncContextImpl.java
index a5694fe368..e087970758 100644
--- a/java/org/apache/catalina/core/AsyncContextImpl.java
+++ b/java/org/apache/catalina/core/AsyncContextImpl.java
@@ -230,6 +230,15 @@ public class AsyncContextImpl implements AsyncContext, 
AsyncContextCallback {
             path = request.getRequestURI();
             cpath = request.getContextPath();
         }
+        int pos = 0;
+        if (!context.getAllowMultipleLeadingForwardSlashInPath()) {
+            do {
+                pos++;
+            } while (pos < path.length() && path.charAt(pos) == '/');
+            pos--;
+            path = path.substring(pos);
+
+        }
         if (cpath.length() > 1) {
             path = path.substring(cpath.length());
         }
diff --git a/java/org/apache/catalina/util/RequestUtil.java 
b/java/org/apache/catalina/util/RequestUtil.java
index 332e93afce..b59638f67a 100644
--- a/java/org/apache/catalina/util/RequestUtil.java
+++ b/java/org/apache/catalina/util/RequestUtil.java
@@ -208,7 +208,7 @@ public final class RequestUtil {
     }
 
 
-    public static String getContextPathUndecoded(Request request) {
+    public static String getContextPathEncoded(Request request) {
 
         int lastSlash = request.getMappingData().contextSlashCount;
         // Special case handling for the root context
diff --git a/test/org/apache/catalina/core/TestAsyncContextImpl.java 
b/test/org/apache/catalina/core/TestAsyncContextImpl.java
index ebf1a214d6..9f0bd5afca 100644
--- a/test/org/apache/catalina/core/TestAsyncContextImpl.java
+++ b/test/org/apache/catalina/core/TestAsyncContextImpl.java
@@ -66,11 +66,13 @@ import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.catalina.valves.TesterAccessLogValve;
 import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.unittest.TesterContext;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.EncodedSolidusHandling;
 import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
 import org.apache.tomcat.util.descriptor.web.ErrorPage;
+import org.apache.tomcat.util.scan.StandardJarScanner;
 import org.easymock.EasyMock;
 
 public class TestAsyncContextImpl extends TomcatBaseTest {
@@ -2520,6 +2522,73 @@ public class TestAsyncContextImpl extends TomcatBaseTest 
{
     }
 
 
+    /*
+     * A %3b in the request URI is an encoded semi-colon that is part of the 
path, not a path parameter delimiter. After
+     * AsyncContext.dispatch() the servlet should see exactly the same path 
info as it saw for the original request.
+     */
+    @Test
+    public void testAsyncDispatchEncodedSemicolon() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        Context context = tomcat.addContext("/test", null);
+        // Disable class path scanning - it slows the tests down by almost an 
order of magnitude
+        ((StandardJarScanner) context.getJarScanner()).setScanClassPath(false);
+
+        Wrapper w = Tomcat.addServlet(context, "semicolon", new 
AsyncDispatchPathInfoServlet());
+        w.setAsyncSupported(true);
+        context.addServletMapping("/semicolon/*", "semicolon");
+        tomcat.start();
+
+        ByteChunk responseBody = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + 
"/a/b/../../test/semicolon/foo%3bbar", responseBody, null);
+
+        Assert.assertEquals(200, rc);
+        // Same pathInfo before and after the dispatch
+        Assert.assertEquals("/foo;bar|/foo;bar", responseBody.toString());
+    }
+
+
+    /*
+     * Multiple leading slashes in the request URI must not break 
AsyncContext.dispatch(). The servlet should see
+     * exactly the same path info before and after the dispatch.
+     */
+    @Test
+    public void testAsyncDispatchMultipleLeadingSlashes() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        Context context = tomcat.addContext("/test", null);
+        // Disable class path scanning - it slows the tests down by almost an 
order of magnitude
+        ((StandardJarScanner) context.getJarScanner()).setScanClassPath(false);
+
+        Wrapper w = Tomcat.addServlet(context, "slashes", new 
AsyncDispatchPathInfoServlet());
+        w.setAsyncSupported(true);
+        context.addServletMapping("/slashes/*", "slashes");
+        tomcat.start();
+
+        ByteChunk responseBody = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + 
"//test/slashes/foo", responseBody, null);
+
+        Assert.assertEquals(200, rc);
+        Assert.assertEquals("/foo|/foo", responseBody.toString());
+    }
+
+
+    private static class AsyncDispatchPathInfoServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+            if (req.getAttribute("first") == null) {
+                req.setAttribute("first", req.getPathInfo());
+                AsyncContext ac = req.startAsync();
+                ac.dispatch();
+            } else {
+                resp.setContentType("text/plain");
+                resp.getWriter().print(req.getAttribute("first") + "|" + 
req.getPathInfo());
+            }
+        }
+    }
+
+
     private static class AsyncDispatchUrlWithSpacesServlet extends HttpServlet 
{
 
         private static final long serialVersionUID = 1L;
@@ -2665,6 +2734,8 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
 
 
     private void doTestAsyncIoEnd(boolean useThread, boolean useComplete) 
throws Exception {
+        // Make sure the logger exists before setting the level
+        LogFactory.getLog("org.apache.coyote");
         
LogManager.getLogManager().getLogger("org.apache.coyote").setLevel(Level.ALL);
         try {
             Tomcat tomcat = getTomcatInstance();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to