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

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

commit 8e2ab5cce2114fe8f1cda41ad71198163b5154da
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 7a0a876e8b..2044f7a497 100644
--- a/java/org/apache/jasper/runtime/PageContextImpl.java
+++ b/java/org/apache/jasper/runtime/PageContextImpl.java
@@ -449,8 +449,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 def4b63a1b..719390d575 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