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 3923e2fd7dc81e10094cef253de6b3f657a83337
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Nov 3 14:23:20 2026 +0000

    Port the utility method for the undecoded context path for a request
---
 .../catalina/connector/LocalStrings.properties     |   1 -
 .../apache/catalina/util/LocalStrings.properties   |   2 +
 java/org/apache/catalina/util/RequestUtil.java     | 125 +++++++++++++++++++++
 3 files changed, 127 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/connector/LocalStrings.properties 
b/java/org/apache/catalina/connector/LocalStrings.properties
index c61445b4d4..25500521d4 100644
--- a/java/org/apache/catalina/connector/LocalStrings.properties
+++ b/java/org/apache/catalina/connector/LocalStrings.properties
@@ -54,7 +54,6 @@ coyoteRequest.changeSessionId=Cannot change session ID. There 
is no session asso
 coyoteRequest.chunkedPostTooLarge=Parameters were not parsed because the size 
of the posted data was too big. Because this request was a chunked request, it 
could not be processed further. Use the maxPostSize attribute of the connector 
to resolve this if the application should accept large POSTs.
 coyoteRequest.deletePartFailed=Failed to delete temporary file used for part 
[{0}]
 coyoteRequest.filterAsyncSupportUnknown=Unable to determine if any filters do 
not support async processing
-coyoteRequest.getContextPath.ise=Unable to find match between the canonical 
context path [{0}] and the URI presented by the user agent [{1}]
 coyoteRequest.getInputStream.ise=getReader() has already been called for this 
request
 coyoteRequest.getReader.ise=getInputStream() has already been called for this 
request
 coyoteRequest.gssLifetimeFail=Failed to obtain remaining lifetime for user 
principal [{0}]
diff --git a/java/org/apache/catalina/util/LocalStrings.properties 
b/java/org/apache/catalina/util/LocalStrings.properties
index 7f59be4eb6..dcb53ddf9f 100644
--- a/java/org/apache/catalina/util/LocalStrings.properties
+++ b/java/org/apache/catalina/util/LocalStrings.properties
@@ -43,6 +43,8 @@ netmaskSet.invalidNetMask=One or more netmasks provided are 
invalid: {0}
 
 parameterMap.locked=No modifications are allowed to a locked ParameterMap
 
+requestUtil.getContextPath.ise=Unable to find match between the canonical 
context path [{0}] and the URI presented by the user agent [{1}]
+
 resourceSet.locked=No modifications are allowed to a locked ResourceSet
 
 sessionIdGeneratorBase.createRandom=Creation of SecureRandom instance for 
session ID generation using [{0}] took [{1}] milliseconds.
diff --git a/java/org/apache/catalina/util/RequestUtil.java 
b/java/org/apache/catalina/util/RequestUtil.java
index 5a46a95786..332e93afce 100644
--- a/java/org/apache/catalina/util/RequestUtil.java
+++ b/java/org/apache/catalina/util/RequestUtil.java
@@ -22,13 +22,20 @@ import java.util.Enumeration;
 
 import jakarta.servlet.http.HttpServletRequest;
 
+import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
 import org.apache.catalina.connector.Request;
+import org.apache.tomcat.util.buf.EncodedSolidusHandling;
 import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.res.StringManager;
 
 /**
  * General purpose request parsing and encoding utility methods.
  */
 public final class RequestUtil {
+
+    private static final StringManager sm = 
StringManager.getManager(RequestUtil.class);
+
     /**
      * Default constructor.
      */
@@ -199,4 +206,122 @@ public final class RequestUtil {
         }
         return result.toString();
     }
+
+
+    public static String getContextPathUndecoded(Request request) {
+
+        int lastSlash = request.getMappingData().contextSlashCount;
+        // Special case handling for the root context
+        if (lastSlash == 0) {
+            return "";
+        }
+
+        String uri = request.getRequestURI();
+        Context context = request.getContext();
+        String canonicalContextPath = 
request.getServletContext().getContextPath();
+        Connector connector = request.getConnector();
+        EncodedSolidusHandling encodedSolidusHandling = 
connector.getEncodedSolidusHandlingInternal();
+
+        int pos = 0;
+        if (!context.getAllowMultipleLeadingForwardSlashInPath()) {
+            // Ensure that the returned value only starts with a single '/'.
+            // This prevents the value being misinterpreted as a 
protocol-relative
+            // URI if used with sendRedirect().
+            do {
+                pos++;
+            } while (pos < uri.length() && uri.charAt(pos) == '/');
+            pos--;
+            uri = uri.substring(pos);
+        }
+
+        char[] uriChars = uri.toCharArray();
+        // Need at least the number of slashes in the context path
+        while (lastSlash > 0) {
+            pos = nextSlash(uriChars, pos + 1, encodedSolidusHandling);
+            if (pos == -1) {
+                break;
+            }
+            lastSlash--;
+        }
+        // Now allow for path parameters, normalization and/or encoding.
+        // Essentially, keep extending the candidate path up to the next slash
+        // until the decoded and normalized candidate path (with the path
+        // parameters removed) is the same as the canonical path.
+        String candidate;
+        if (pos == -1) {
+            candidate = uri;
+        } else {
+            candidate = uri.substring(0, pos);
+        }
+        candidate = removePathParameters(candidate);
+        candidate = UDecoder.URLDecode(candidate, connector.getURICharset());
+        candidate = 
org.apache.tomcat.util.http.RequestUtil.normalize(candidate);
+        boolean match = canonicalContextPath.equals(candidate);
+        while (!match && pos != -1) {
+            pos = nextSlash(uriChars, pos + 1, encodedSolidusHandling);
+            if (pos == -1) {
+                candidate = uri;
+            } else {
+                candidate = uri.substring(0, pos);
+            }
+            candidate = removePathParameters(candidate);
+            candidate = UDecoder.URLDecode(candidate, 
connector.getURICharset());
+            candidate = 
org.apache.tomcat.util.http.RequestUtil.normalize(candidate);
+            match = canonicalContextPath.equals(candidate);
+        }
+        if (match) {
+            if (pos == -1) {
+                return uri;
+            } else {
+                return uri.substring(0, pos);
+            }
+        } else {
+            // Should never happen
+            throw new IllegalStateException(
+                    sm.getString("requestUtil.getContextPath.ise", 
canonicalContextPath, uri));
+        }
+    }
+
+
+    private static String removePathParameters(String input) {
+        int nextSemiColon = input.indexOf(';');
+        // Shortcut
+        if (nextSemiColon == -1) {
+            return input;
+        }
+        StringBuilder result = new StringBuilder(input.length());
+        result.append(input, 0, nextSemiColon);
+        while (true) {
+            int nextSlash = input.indexOf('/', nextSemiColon);
+            if (nextSlash == -1) {
+                break;
+            }
+            nextSemiColon = input.indexOf(';', nextSlash);
+            if (nextSemiColon == -1) {
+                result.append(input.substring(nextSlash));
+                break;
+            } else {
+                result.append(input, nextSlash, nextSemiColon);
+            }
+        }
+
+        return result.toString();
+    }
+
+
+    private static int nextSlash(char[] uri, int startPos, 
EncodedSolidusHandling encodedSolidusHandling) {
+        int len = uri.length;
+        int pos = startPos;
+        while (pos < len) {
+            if (uri[pos] == '/') {
+                return pos;
+            } else if (encodedSolidusHandling == EncodedSolidusHandling.DECODE 
&&
+                    uri[pos] == '%' && pos + 2 < len && uri[pos + 1] == '2' &&
+                    (uri[pos + 2] == 'f' || uri[pos + 2] == 'F')) {
+                return pos;
+            }
+            pos++;
+        }
+        return -1;
+    }
 }


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

Reply via email to