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

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

commit 607192fa116ac6e25afdea3439a25c5908bc3bb2
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 109044d833..9253b1195b 100644
--- a/java/org/apache/jasper/runtime/PageContextImpl.java
+++ b/java/org/apache/jasper/runtime/PageContextImpl.java
@@ -460,8 +460,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