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 dc4eec120bc1e8cfa5aad3929176f9e27d9079db 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 e2674cb9fc..923217ce1e 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]
