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 64c4b06524536d7f9825c69f34ff13c50dccca66 Author: opencode <[email protected]> AuthorDate: Mon Oct 19 16:27:19 2026 +0200 Share the Url and Redirect tag plugin URLs with their params The Url and Redirect tag plugins exchanged the working URL of the tag with the Param plugin through the page scope attribute url_without_param: the URL plugin wrote the attribute before generating the tag body, the Param plugin read it back, appended to it and wrote it again, and the URL plugin finally read it back and removed it. The fixed name overwrote any application page attribute of the same name while the body was processed and removed it at the end. It also let nested URL tags interfere: a c:url nested in the body of a plugin enabled c:url, a nesting the JSTL tag library validator accepts, removed the attribute, so the outer tag read null and silently rendered the literal string null. Share the name of the generated local variable that holds the working URL through the plugin context instead, the mechanism the Choose plugin already uses and that the Import plugin adopted earlier. The Param plugin appends directly to that variable and looks for the closest URL tag ancestor by walking up the parent context chain, as the standard handler at runtime walks up the tag ancestors, so a parameter nested within an intermediate tag still reaches the URL of its enclosing URL tag. A parent context whose tag abandoned the plugin ends the search, since the body of such a tag is generated in a separate method of the generated servlet where the outer local variables are out of scope; the parameter then falls back to the standard handler. This matches the reference implementation, which keeps the parameters of each tag to itself through a per-tag parameter manager and never touches page scope attributes. Add tests for the nested and clobbering pages, the parameters of both URL plugins, parameters nested in a plugin processed or a fallen back tag, and the closest ancestor semantics. Found by code review. --- .../apache/jasper/compiler/TagPluginManager.java | 9 ++ .../compiler/tagplugin/TagPluginContext.java | 5 +- .../apache/jasper/tagplugins/jstl/core/Import.java | 4 +- .../apache/jasper/tagplugins/jstl/core/Param.java | 38 +++---- .../jasper/tagplugins/jstl/core/Redirect.java | 19 ++-- .../apache/jasper/tagplugins/jstl/core/Url.java | 21 ++-- .../jasper/tagplugins/jstl/core/TestRedirect.java | 27 +++++ .../jasper/tagplugins/jstl/core/TestUrl.java | 113 +++++++++++++++++++++ .../tagplugins/jstl/core/redirect-tagPlugins.xml | 4 + .../jasper/tagplugins/jstl/core/url-tagPlugins.xml | 12 +++ test/webapp/jstl/redirect-params.jsp | 22 ++++ test/webapp/jstl/url-clobber.jsp | 23 +++++ test/webapp/jstl/url-nested.jsp | 20 ++++ test/webapp/jstl/url-param-fallback-nested.jsp | 22 ++++ test/webapp/jstl/url-param-nested-if.jsp | 25 +++++ test/webapp/jstl/url-params.jsp | 22 ++++ webapps/docs/changelog.xml | 12 +++ 17 files changed, 359 insertions(+), 39 deletions(-) diff --git a/java/org/apache/jasper/compiler/TagPluginManager.java b/java/org/apache/jasper/compiler/TagPluginManager.java index f499327c51..867ceb9f7e 100644 --- a/java/org/apache/jasper/compiler/TagPluginManager.java +++ b/java/org/apache/jasper/compiler/TagPluginManager.java @@ -192,6 +192,15 @@ public class TagPluginManager { @Override public TagPluginContext getParentContext() { + // If the tag of this context no longer uses the plugin, its body + // is generated by the standard tag mechanism, which places + // scriptless body content in a separate method of the generated + // servlet. The local variables of the outer tags are then out of + // scope inside that body, so do not let a child walk through this + // context into an outer scope. + if (!node.useTagPlugin()) { + return null; + } Node parent = node.getParent(); if (!(parent instanceof Node.CustomTag)) { return null; diff --git a/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java b/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java index 519c732f3d..e17897e1f1 100644 --- a/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java +++ b/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java @@ -111,8 +111,9 @@ public interface TagPluginContext { * obtained is limited to getPluginAttribute and setPluginAttribute, and queries (e.g. isScriptless()). There should * be no calls to generate*(). * - * @return The pluginContext for the parent node. null if the parent is not a custom tag, or if the parent custom - * tag has no plugin context. + * @return The pluginContext for the parent node. null if the parent is not a custom tag, if the parent custom tag + * has no plugin context, or if the tag associated with this context has abandoned the plugin, in which + * case the body is not generated inline and the scopes of the generated code are disconnected. */ TagPluginContext getParentContext(); diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Import.java b/java/org/apache/jasper/tagplugins/jstl/core/Import.java index 706cb48fb1..e4d2ca2dc4 100644 --- a/java/org/apache/jasper/tagplugins/jstl/core/Import.java +++ b/java/org/apache/jasper/tagplugins/jstl/core/Import.java @@ -31,7 +31,9 @@ public class Import implements TagPlugin { * the generated local variable that holds the URL and to which the * parameters are appended. Sharing the variable rather than a page scope * attribute with a fixed name makes the protocol re-entrant: a nested URL - * tag in the body cannot overwrite or remove the outer tag's URL. + * tag in the body cannot overwrite or remove the outer tag's URL. The + * Param plugin walks up the parent context chain, so a parameter nested + * in an intermediate tag finds the closest URL tag ancestor. */ static final String URL_VARIABLE_ATTRIBUTE = "urlVariable"; diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Param.java b/java/org/apache/jasper/tagplugins/jstl/core/Param.java index 76e0d680f4..0cf10748d9 100644 --- a/java/org/apache/jasper/tagplugins/jstl/core/Param.java +++ b/java/org/apache/jasper/tagplugins/jstl/core/Param.java @@ -40,7 +40,6 @@ public class Param implements TagPlugin { // define names of all the temp variables String nameName = ctxt.getTemporaryVariableName(); String valueName = ctxt.getTemporaryVariableName(); - String urlName = ctxt.getTemporaryVariableName(); String encName = ctxt.getTemporaryVariableName(); String index = ctxt.getTemporaryVariableName(); @@ -52,20 +51,28 @@ public class Param implements TagPlugin { return; } - // Append the parameter to the parent tag's working URL. The Import - // plugin shares the name of the local variable holding that URL - // through the plugin context, so that nested URL tags cannot - // interfere with it through a shared page scope attribute. The Url - // and Redirect plugins still use such an attribute. - Object urlVariable = parent.getPluginAttribute(Import.URL_VARIABLE_ATTRIBUTE); - String urlTargetName; - if (urlVariable != null) { - urlTargetName = (String) urlVariable; - } else { - ctxt.generateJavaSource( - "String " + urlName + " = " + "(String)_jspx_page_context.getAttribute(\"url_without_param\");"); - urlTargetName = urlName; + // Append the parameter to the working URL of the enclosing URL tag. + // The Import, Url and Redirect plugins share the name of the local + // variable holding that URL through the plugin context, so that + // nested URL tags cannot interfere with each other through a shared + // page scope attribute. As in the standard handler, which at runtime + // adds the parameter to the closest URL tag ancestor, the search + // walks up the plugin context chain until a context provides the + // working URL, stopping at parents that no plugin handled. + TagPluginContext urlContext = parent; + while (urlContext != null && + urlContext.getPluginAttribute(Import.URL_VARIABLE_ATTRIBUTE) == null) { + urlContext = urlContext.getParentContext(); + } + if (urlContext == null) { + // No plugin handled an enclosing URL tag, so there is no working + // URL variable to append to. Let the standard tag handler process + // the parameter. A standard URL tag then takes it as usual, and + // the handler reports the error for a misplaced tag. + ctxt.dontUseTagPlugin(); + return; } + String urlTargetName = (String) urlContext.getPluginAttribute(Import.URL_VARIABLE_ATTRIBUTE); // get the value of "name" ctxt.generateJavaSource("String " + nameName + " = "); @@ -97,9 +104,6 @@ public class Param implements TagPlugin { ctxt.generateJavaSource(" " + urlTargetName + " = " + urlTargetName + " + \"&\" + " + nameName + " + \"=\" + " + valueName + ";"); ctxt.generateJavaSource(" }"); - if (urlVariable == null) { - ctxt.generateJavaSource(" _jspx_page_context.setAttribute(\"url_without_param\"," + urlName + ");"); - } ctxt.generateJavaSource("}"); } } diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java b/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java index fabad2c995..59e0069d09 100644 --- a/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java +++ b/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java @@ -41,7 +41,6 @@ public class Redirect implements TagPlugin { String urlName = ctxt.getTemporaryVariableName(); String contextName = ctxt.getTemporaryVariableName(); String baseUrlName = ctxt.getTemporaryVariableName(); - String resultName = ctxt.getTemporaryVariableName(); String responseName = ctxt.getTemporaryVariableName(); // get context @@ -60,27 +59,29 @@ public class Redirect implements TagPlugin { // get the raw url according to "url" and "context" ctxt.generateJavaSource("String " + baseUrlName + " = " + "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + urlName + ", " + contextName + ", _jspx_page_context);"); - ctxt.generateJavaSource("_jspx_page_context.setAttribute" + "(\"url_without_param\", " + baseUrlName + ");"); + + // Share the working URL with the <c:param> tags in the body by + // handing them the name of the local variable that holds it. + // Appending to the variable directly is re-entrant: a nested URL tag + // in the body of this tag must not be able to overwrite or remove it, + // which a page scope attribute with a fixed name would allow. + ctxt.setPluginAttribute(Import.URL_VARIABLE_ATTRIBUTE, baseUrlName); // add params ctxt.generateBody(); - ctxt.generateJavaSource( - "String " + resultName + " = " + "(String)_jspx_page_context.getAttribute(\"url_without_param\");"); - ctxt.generateJavaSource("_jspx_page_context.removeAttribute" + "(\"url_without_param\");"); - // get the response object ctxt.generateJavaSource("HttpServletResponse " + responseName + " = " + "((HttpServletResponse) _jspx_page_context.getResponse());"); // if the url is relative, encode it - ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){"); - ctxt.generateJavaSource(" " + resultName + " = " + responseName + ".encodeRedirectURL(" + resultName + ");"); + ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + baseUrlName + ")){"); + ctxt.generateJavaSource(" " + baseUrlName + " = " + responseName + ".encodeRedirectURL(" + baseUrlName + ");"); ctxt.generateJavaSource("}"); // do redirect ctxt.generateJavaSource("try{"); - ctxt.generateJavaSource(" " + responseName + ".sendRedirect(" + resultName + ");"); + ctxt.generateJavaSource(" " + responseName + ".sendRedirect(" + baseUrlName + ");"); // Avoid unreachable code error in compiler ctxt.generateJavaSource(" if (true) {"); ctxt.generatePageTermination(); diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Url.java b/java/org/apache/jasper/tagplugins/jstl/core/Url.java index 8772218a75..198b211f2e 100644 --- a/java/org/apache/jasper/tagplugins/jstl/core/Url.java +++ b/java/org/apache/jasper/tagplugins/jstl/core/Url.java @@ -46,7 +46,6 @@ public class Url implements TagPlugin { String valueName = ctxt.getTemporaryVariableName(); String contextName = ctxt.getTemporaryVariableName(); String baseUrlName = ctxt.getTemporaryVariableName(); - String resultName = ctxt.getTemporaryVariableName(); String responseName = ctxt.getTemporaryVariableName(); // get the scope @@ -72,32 +71,34 @@ public class Url implements TagPlugin { // get the raw url ctxt.generateJavaSource("String " + baseUrlName + " = " + "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + valueName + ", " + contextName + ", _jspx_page_context);"); - ctxt.generateJavaSource("_jspx_page_context.setAttribute" + "(\"url_without_param\", " + baseUrlName + ");"); + + // Share the working URL with the <c:param> tags in the body by + // handing them the name of the local variable that holds it. + // Appending to the variable directly is re-entrant: a nested URL tag + // in the body of this tag must not be able to overwrite or remove it, + // which a page scope attribute with a fixed name would allow. + ctxt.setPluginAttribute(Import.URL_VARIABLE_ATTRIBUTE, baseUrlName); // add params ctxt.generateBody(); - ctxt.generateJavaSource( - "String " + resultName + " = " + "(String)_jspx_page_context.getAttribute(\"url_without_param\");"); - ctxt.generateJavaSource("_jspx_page_context.removeAttribute(\"url_without_param\");"); - // if the url is relative, encode it - ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){"); + ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + baseUrlName + ")){"); ctxt.generateJavaSource(" HttpServletResponse " + responseName + " = " + "((HttpServletResponse) _jspx_page_context.getResponse());"); - ctxt.generateJavaSource(" " + resultName + " = " + responseName + ".encodeURL(" + resultName + ");"); + ctxt.generateJavaSource(" " + baseUrlName + " = " + responseName + ".encodeURL(" + baseUrlName + ");"); ctxt.generateJavaSource("}"); // if "var" is specified, the url string is stored in the attribute var if (hasVar) { String strVar = ctxt.getConstantAttribute("var"); ctxt.generateJavaSource( - "_jspx_page_context.setAttribute" + "(\"" + strVar + "\", " + resultName + ", " + iScope + ");"); + "_jspx_page_context.setAttribute" + "(\"" + strVar + "\", " + baseUrlName + ", " + iScope + ");"); // if var is not specified, just print out the url string } else { ctxt.generateJavaSource("try{"); - ctxt.generateJavaSource(" _jspx_page_context.getOut().print(" + resultName + ");"); + ctxt.generateJavaSource(" _jspx_page_context.getOut().print(" + baseUrlName + ");"); ctxt.generateJavaSource("}catch(java.io.IOException ex){"); ctxt.generateJavaSource(" throw new JspTagException(ex.toString(), ex);"); ctxt.generateJavaSource("}"); diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java b/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java index 99de0c16d1..473e3ca8c3 100644 --- a/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java +++ b/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java @@ -18,6 +18,9 @@ package org.apache.jasper.tagplugins.jstl.core; import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -30,6 +33,7 @@ import org.junit.Assert; import org.junit.Test; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.http.Method; public class TestRedirect extends AbstractTestTag { @@ -107,4 +111,27 @@ public class TestRedirect extends AbstractTestTag { Assert.assertTrue(pageStarted.await(1, TimeUnit.SECONDS)); Assert.assertFalse(pageProcessingContinued.await(1, TimeUnit.SECONDS)); } + + + /* + * The <c:param> plugin must append its parameters, URL-encoded, to the + * working URL of the enclosing <c:redirect> tag. The plugins used to + * exchange that URL through the page scope attribute "url_without_param" + * which interfered with nested URL tags and application attributes of the + * same name. The URL is now shared through the plugin context instead. + */ + @Test + public void testRedirectParams() throws Exception { + ByteChunk res = new ByteChunk(); + Map<String, List<String>> resHead = new HashMap<>(); + + int rc = methodUrl("http://localhost:" + getPort() + "/test/jstl/redirect-params.jsp", res, + DEFAULT_CLIENT_TIMEOUT_MS, null, resHead, Method.GET, false); + + Assert.assertEquals(HttpServletResponse.SC_FOUND, rc); + + String location = getSingleHeader("Location", resHead); + Assert.assertNotNull(location); + Assert.assertTrue(location, location.endsWith("/test/jstl/redirect-target.jsp?a=b&sp=x+y&u=%26%3D")); + } } diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java index 3ac0d1c396..84ca506899 100644 --- a/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java +++ b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java @@ -56,4 +56,117 @@ public class TestUrl extends AbstractTestTag { // A context without a trailing slash is simply concatenated Assert.assertTrue(body, body.contains("PLAIN=[/jstltest/index.jsp]")); } + + + /* + * The <c:param> plugin must add its parameters to the URL of its own + * <c:url> tag, URL-encoded, the first one following an existing query + * string with "&". + */ + @Test + public void testParams() throws Exception { + ByteChunk res = new ByteChunk(); + + int rc = getUrl("http://localhost:" + getPort() + + "/test/jstl/url-params.jsp", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + + String body = res.toString(); + Assert.assertTrue(body, body.contains("U=[/test/a.jsp?x=1&sp=x+y&u2=%26%3D]")); + } + + + /* + * A nested <c:url> in the body of another <c:url> (a nesting the JSTL tag + * library validator accepts) must not disturb the outer tag. The plugins + * used to exchange the working URL through the fixed page scope attribute + * "url_without_param": the inner tag removed that attribute, so the outer + * tag read it back as null and printed the literal "null". The working URL + * is now shared with the body through the plugin context, so the inner tag + * cannot interfere with the outer one. + */ + @Test + public void testNestedUrl() throws Exception { + ByteChunk res = new ByteChunk(); + + int rc = getUrl("http://localhost:" + getPort() + + "/test/jstl/url-nested.jsp", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + + String body = res.toString(); + // The inner tag renders its URL, then the outer tag renders its own, + // never the literal "null" + Assert.assertTrue(body, body.contains("NEXT=[/test/b.jsp/test/a.jsp]")); + } + + + /* + * The <c:url>/<c:param> plugin pair must not clobber an application page + * scope attribute named "url_without_param". The plugins used to store the + * working URL in such an attribute and remove it afterwards, overwriting + * and then deleting the application attribute. The URL is now shared with + * the body through the plugin context instead. + */ + @Test + public void testParamKeepsUserAttribute() throws Exception { + ByteChunk res = new ByteChunk(); + + int rc = getUrl("http://localhost:" + getPort() + + "/test/jstl/url-clobber.jsp", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + + String body = res.toString(); + Assert.assertTrue(body, body.contains("KEEP=[keepme]")); + // The parameter is still appended to the tag's own URL + Assert.assertTrue(body, body.contains("U=[/test/a.jsp?p=q]")); + } + + + /* + * A <c:param> nested in a plugin processed tag inside the <c:url> body, + * as the JSTL tag library validator allows, must still reach the working + * URL of the enclosing URL tag, as the standard handler adds it to the + * closest URL tag ancestor. The append must stay conditional on the + * enclosing tag having been executed. + */ + @Test + public void testParamNestedInIf() throws Exception { + ByteChunk res = new ByteChunk(); + + int rc = getUrl("http://localhost:" + getPort() + + "/test/jstl/url-param-nested-if.jsp", res, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + + String body = res.toString(); + // The parameter of the executed <c:if> reaches the URL + Assert.assertTrue(body, body.contains("U=[/test/a.jsp?p=q]")); + // The parameter of the skipped <c:if> does not + Assert.assertTrue(body, body.contains("U2=[/test/a.jsp]")); + } + + + /* + * A <c:param> nested in a tag whose plugin falls back to the standard + * handler, as <c:forEach> with a varStatus, is generated into the tag + * method of that handler, where the working URL variable of an enclosing + * plugin URL tag is out of scope. The page must still translate; the + * standard parameter handler then reports that no standard URL tag + * encloses it. + */ + @Test + public void testParamNestedInPluginFallback() throws Exception { + ByteChunk res = new ByteChunk(); + + int rc = getUrl("http://localhost:" + getPort() + + "/test/jstl/url-param-fallback-nested.jsp", res, null); + + String body = res.toString(); + // The page must not fail at translation time + Assert.assertFalse(body, body.contains("Unable to compile")); + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + } } diff --git a/test/org/apache/jasper/tagplugins/jstl/core/redirect-tagPlugins.xml b/test/org/apache/jasper/tagplugins/jstl/core/redirect-tagPlugins.xml index 94441bb2eb..610f944386 100644 --- a/test/org/apache/jasper/tagplugins/jstl/core/redirect-tagPlugins.xml +++ b/test/org/apache/jasper/tagplugins/jstl/core/redirect-tagPlugins.xml @@ -20,4 +20,8 @@ <tag-class>org.apache.taglibs.standard.tag.rt.core.RedirectTag</tag-class> <plugin-class>org.apache.jasper.tagplugins.jstl.core.Redirect</plugin-class> </tag-plugin> + <tag-plugin> + <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class> + <plugin-class>org.apache.jasper.tagplugins.jstl.core.Param</plugin-class> + </tag-plugin> </tag-plugins> diff --git a/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml b/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml index 55860d0587..6c22ed0665 100644 --- a/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml +++ b/test/org/apache/jasper/tagplugins/jstl/core/url-tagPlugins.xml @@ -20,4 +20,16 @@ <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-plugin> + <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class> + <plugin-class>org.apache.jasper.tagplugins.jstl.core.Param</plugin-class> + </tag-plugin> + <tag-plugin> + <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class> + <plugin-class>org.apache.jasper.tagplugins.jstl.core.If</plugin-class> + </tag-plugin> + <tag-plugin> + <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class> + <plugin-class>org.apache.jasper.tagplugins.jstl.core.ForEach</plugin-class> + </tag-plugin> </tag-plugins> diff --git a/test/webapp/jstl/redirect-params.jsp b/test/webapp/jstl/redirect-params.jsp new file mode 100644 index 0000000000..7c233b7ae8 --- /dev/null +++ b/test/webapp/jstl/redirect-params.jsp @@ -0,0 +1,22 @@ +<%-- + 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:redirect url="/jstl/redirect-target.jsp?a=b"><c:param name="sp" + value="x y"/><c:param name="u" value="${'&='}"/></c:redirect> +<p>NOTREACHED=[]</p> diff --git a/test/webapp/jstl/url-clobber.jsp b/test/webapp/jstl/url-clobber.jsp new file mode 100644 index 0000000000..2bccf15413 --- /dev/null +++ b/test/webapp/jstl/url-clobber.jsp @@ -0,0 +1,23 @@ +<%-- + 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:set var="url_without_param" value="keepme" scope="page" /> +<c:url value="/a.jsp" var="u"><c:param name="p" value="q"/></c:url> +<p>KEEP=[${url_without_param}]</p> +<p>U=[${u}]</p> diff --git a/test/webapp/jstl/url-nested.jsp b/test/webapp/jstl/url-nested.jsp new file mode 100644 index 0000000000..36d82e930c --- /dev/null +++ b/test/webapp/jstl/url-nested.jsp @@ -0,0 +1,20 @@ +<%-- + 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" %> +<p>NEXT=[<c:url value="/a.jsp"><c:url value="/b.jsp"/></c:url>]</p> diff --git a/test/webapp/jstl/url-param-fallback-nested.jsp b/test/webapp/jstl/url-param-fallback-nested.jsp new file mode 100644 index 0000000000..66a826f658 --- /dev/null +++ b/test/webapp/jstl/url-param-fallback-nested.jsp @@ -0,0 +1,22 @@ +<%-- + 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="u" value="/a.jsp"><c:forEach var="i" varStatus="s" + begin="1" end="1"><c:param name="p" value="q"/></c:forEach></c:url> +<p>U=[${u}]</p> diff --git a/test/webapp/jstl/url-param-nested-if.jsp b/test/webapp/jstl/url-param-nested-if.jsp new file mode 100644 index 0000000000..5155b4e514 --- /dev/null +++ b/test/webapp/jstl/url-param-nested-if.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="u" value="/a.jsp"><c:if test="${true}"><c:param + name="p" value="q"/></c:if></c:url> +<p>U=[${u}]</p> +<c:url var="u2" value="/a.jsp"><c:if test="${false}"><c:param + name="p" value="q"/></c:if></c:url> +<p>U2=[${u2}]</p> diff --git a/test/webapp/jstl/url-params.jsp b/test/webapp/jstl/url-params.jsp new file mode 100644 index 0000000000..f572ad70c9 --- /dev/null +++ b/test/webapp/jstl/url-params.jsp @@ -0,0 +1,22 @@ +<%-- + 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 value="/a.jsp?x=1" var="u"><c:param name="sp" + value="x y"/><c:param name="u2" value="${'&='}"/></c:url> +<p>U=[${u}]</p> diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index a719cb403f..1ff8129618 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -485,6 +485,18 @@ with a doubled slash. Previously only the root context was handled. (remm) </fix> + <fix> + The tag plugins for <code>c:url</code>, <code>c:redirect</code> and + <code>c:param</code> now share the working URL of the URL tag through + the plugin context rather than the page scope attribute + <code>url_without_param</code>. That fixed name overwrote an + application attribute of the same name while the tag body was + processed and removed it afterwards, and it let a nested URL tag + destroy the outer tag's URL, which then silently rendered as + <code>null</code>. This matches the reference implementation, which + keeps the parameters of each tag to itself and never touches page + scope attributes. (remm) + </fix> </changelog> </subsection> <subsection name="WebSocket"> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
