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]
