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 437c0be2b6ddd420fafc94a9d72bfa18b3258db5
Author: opencode <[email protected]>
AuthorDate: Mon Oct 19 16:00:44 2026 +0200

    Strip a trailing slash from the context in resolveUrl
    
    The JSTL standard handlers strip a single trailing slash from the
    context path before joining it with a URL that starts with a slash,
    so a c:url or c:redirect context of /app/ with a value of /p.jsp
    resolves to /app/p.jsp. The shared tag plugin helper only recognized
    the exact root context / and otherwise concatenated the two strings
    directly, so any other context ending in a slash resolved to a path
    with a doubled slash in it.
    
    The helper now reproduces the handler rule, stripping one trailing
    slash from any context that ends in one, which reduces to the
    previous root context behavior for the context /. The change covers
    both plugins that share the helper, c:url and c:redirect. Neither
    plugin is registered by default, so only the web applications that
    explicitly enable them observe the difference.
---
 java/org/apache/jasper/tagplugins/jstl/Util.java   | 13 +++--
 .../jasper/tagplugins/jstl/core/TestUrl.java       | 59 ++++++++++++++++++++++
 .../jasper/tagplugins/jstl/core/url-tagPlugins.xml | 23 +++++++++
 test/webapp/jstl/url-context-slash.jsp             | 25 +++++++++
 webapps/docs/changelog.xml                         |  8 +++
 5 files changed, 123 insertions(+), 5 deletions(-)

diff --git a/java/org/apache/jasper/tagplugins/jstl/Util.java 
b/java/org/apache/jasper/tagplugins/jstl/Util.java
index a57dfe823e..f453c4a497 100644
--- a/java/org/apache/jasper/tagplugins/jstl/Util.java
+++ b/java/org/apache/jasper/tagplugins/jstl/Util.java
@@ -285,11 +285,14 @@ public class Util {
             if (!context.startsWith("/") || !url.startsWith("/")) {
                 throw new 
JspTagException(Localizer.getMessage("jstl.urlMustStartWithSlash"));
             }
-            if (context.equals("/")) {
-                // Don't produce string starting with '//', many
-                // browsers interpret this as host name, not as
-                // path on same host.
-                return url;
+            if (context.endsWith("/")) {
+                // Strip the trailing slash of the context, as the standard
+                // handler does, so that the joined path does not carry a
+                // doubled slash. For the root context this reduces to the
+                // URL itself, which also avoids producing a string starting
+                // with '//', as many browsers interpret this as host name,
+                // not as path on same host.
+                return context.substring(0, context.length() - 1) + url;
             } else {
                 return context + url;
             }
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java 
b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
new file mode 100644
index 0000000000..3ac0d1c396
--- /dev/null
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.tagplugins.jstl.core;
+
+import java.io.File;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TestUrl extends AbstractTestTag {
+
+    @Override
+    protected File getTagPluginsFile() {
+        return new 
File("test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml");
+    }
+
+
+    /*
+     * A context path with a trailing slash must be joined with the value
+     * without producing a doubled slash, as the standard handler strips the
+     * trailing slash from the context before concatenation. Only the exact
+     * context "/" was special cased before, so any other context ending in a
+     * slash resolved to a path with "//" in it.
+     */
+    @Test
+    public void testContextWithTrailingSlash() throws Exception {
+        ByteChunk res = new ByteChunk();
+
+        int rc = getUrl("http://localhost:"; + getPort() +
+                "/test/jstl/url-context-slash.jsp", res, null);
+
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+
+        String body = res.toString();
+        Assert.assertTrue(body, 
body.contains("TRAILING=[/jstltest/index.jsp]"));
+        // The root context keeps resolving to just the value
+        Assert.assertTrue(body, body.contains("ROOT=[/index.jsp]"));
+        // A context without a trailing slash is simply concatenated
+        Assert.assertTrue(body, body.contains("PLAIN=[/jstltest/index.jsp]"));
+    }
+}
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml 
b/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml
new file mode 100644
index 0000000000..55860d0587
--- /dev/null
+++ b/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<tag-plugins>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.UrlTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Url</plugin-class>
+  </tag-plugin>
+</tag-plugins>
diff --git a/test/webapp/jstl/url-context-slash.jsp 
b/test/webapp/jstl/url-context-slash.jsp
new file mode 100644
index 0000000000..a964623ecb
--- /dev/null
+++ b/test/webapp/jstl/url-context-slash.jsp
@@ -0,0 +1,25 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+<%@ page session="false" contentType="text/html; charset=UTF-8"
+         pageEncoding="UTF-8" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core"; prefix="c" %>
+<c:url var="trailing" context="/jstltest/" value="/index.jsp"/>
+<p>TRAILING=[${trailing}]</p>
+<c:url var="root" context="/" value="/index.jsp"/>
+<p>ROOT=[${root}]</p>
+<c:url var="plain" context="/jstltest" value="/index.jsp"/>
+<p>PLAIN=[${plain}]</p>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9d8f8e2c8d..a719cb403f 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -477,6 +477,14 @@
         invalid use fails as it does with the handler instead of being
         silently ignored. (remm)
       </fix>
+      <fix>
+        The tag plugins for <code>c:url</code> and <code>c:redirect</code>
+        now strip a trailing slash from a <code>context</code> path before
+        joining it with the URL, as the standard tag handlers do, so that a
+        context path ending in a slash no longer produces a resolved path
+        with a doubled slash. Previously only the root context was handled.
+        (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="WebSocket">


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

Reply via email to