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

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

commit 0426ba125492b669f9d43f7adb764bbdb432951c
Author: opencode <[email protected]>
AuthorDate: Mon Oct 19 11:52:19 2026 +0200

    Avoid StringIndexOutOfBoundsException forwarding a relative path from
    a slash-less servlet path
    
    PageContextImpl.getAbsolutePathRelativeToContext, used by
    pageContext.forward to resolve a relative path, truncated the current
    servlet path at its last '/' without checking that a '/' is present.
    When the servlet path contains no '/', for example for a servlet or JSP
    mapped with the empty url-pattern where the servlet path is the empty
    string, the substring call threw a StringIndexOutOfBoundsException,
    turning the forward into an uncontrolled 500 while the sibling include
    logic in JspRuntimeLibrary.getContextRelativePath, which guards the
    truncation, resolved the same request successfully.
    
    Skip the truncation when the servlet path has no '/' so forward and
    include resolve slash-less paths identically against the web
    application root.
    
    Found by code review.
---
 .../org/apache/jasper/runtime/PageContextImpl.java |  6 ++-
 .../apache/jasper/runtime/TestPageContextImpl.java | 62 ++++++++++++++++++++++
 2 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/jasper/runtime/PageContextImpl.java 
b/java/org/apache/jasper/runtime/PageContextImpl.java
index b58634d427..d3bd96369c 100644
--- a/java/org/apache/jasper/runtime/PageContextImpl.java
+++ b/java/org/apache/jasper/runtime/PageContextImpl.java
@@ -446,8 +446,10 @@ public class PageContextImpl extends PageContext {
             if (uri == null) {
                 uri = ((HttpServletRequest) request).getServletPath();
             }
-            String baseURI = uri.substring(0, uri.lastIndexOf('/'));
-            path = baseURI + '/' + path;
+            if (uri.lastIndexOf('/') >= 0) {
+                uri = uri.substring(0, uri.lastIndexOf('/'));
+            }
+            path = uri + '/' + path;
         }
 
         return path;
diff --git a/test/org/apache/jasper/runtime/TestPageContextImpl.java 
b/test/org/apache/jasper/runtime/TestPageContextImpl.java
index 9883009335..f8cd9a500f 100644
--- a/test/org/apache/jasper/runtime/TestPageContextImpl.java
+++ b/test/org/apache/jasper/runtime/TestPageContextImpl.java
@@ -122,6 +122,68 @@ public class TestPageContextImpl extends TomcatBaseTest {
     }
 
 
+    @Test
+    public void testForwardRelativePathWithEmptyServletPath() throws Exception 
{
+        Tomcat tomcat = getTomcatInstanceTestWebapp(false, false);
+
+        Context context = (Context) tomcat.getHost().findChild("/test");
+
+        // The forwarder is mapped to the context root using the empty url
+        // pattern so the servlet path in the request handled by it is the
+        // empty string, which contains no '/' to use as the base for
+        // resolving the relative forward path.
+        Tomcat.addServlet(context, "emptyPathForwarder", new 
EmptyPathForwarder());
+        context.addServletMapping("", "emptyPathForwarder");
+
+        Tomcat.addServlet(context, "forwardTarget", new ForwardTarget());
+        context.addServletMapping("/forwardTarget", "forwardTarget");
+
+        tomcat.start();
+
+        // The empty pattern matches the context root exactly.
+        ByteChunk res = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + "/test/", res, null);
+
+        Assert.assertEquals(res.toString(), HttpServletResponse.SC_OK, rc);
+        Assert.assertTrue(res.toString(), 
res.toString().contains("FORWARDED"));
+    }
+
+
+    public static class EmptyPathForwarder extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+
+            PageContext pageContext = 
JspFactory.getDefaultFactory().getPageContext(
+                    this, req, resp, null, false, JspWriter.DEFAULT_BUFFER, 
true);
+            try {
+                // Relative path resolution against the empty servlet path
+                // must not throw StringIndexOutOfBoundsException.
+                pageContext.forward("forwardTarget");
+            } finally {
+                JspFactory.getDefaultFactory().releasePageContext(pageContext);
+            }
+        }
+    }
+
+
+    public static class ForwardTarget extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+
+            resp.setContentType("text/plain;charset=UTF-8");
+            resp.getWriter().println("FORWARDED");
+        }
+    }
+
+
     @Test
     public void testIncludeThenForward() throws Exception {
         getTomcatInstanceTestWebapp(false, true);


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

Reply via email to