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]

Reply via email to