This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new a55bc496d7 Review "Share the Url and Redirect tag plugin URLs with
their params"
a55bc496d7 is described below
commit a55bc496d78ff3c39b333291780bdbf592265e46
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Nov 6 14:24:49 2026 +0000
Review "Share the Url and Redirect tag plugin URLs with their params"
Fix various edge cases for parameter handling with the plugins. Handling
all of the edge cases made the plugs sufficiently complex that there was
little benefit to using them. Therefore there is a tradeoff where the
plugins fall back to the standard tag for the cases that would have
required more complex handling since that handling would have affected
all calls.
Co-authored by: Claude / Sonnet 5.5
---
.../apache/jasper/compiler/TagPluginManager.java | 18 +++
.../compiler/tagplugin/TagPluginContext.java | 17 +++
.../apache/jasper/tagplugins/jstl/core/Import.java | 22 +---
.../apache/jasper/tagplugins/jstl/core/Param.java | 87 +++++++--------
.../jasper/tagplugins/jstl/core/Redirect.java | 9 +-
.../apache/jasper/tagplugins/jstl/core/Url.java | 9 +-
.../jasper/tagplugins/jstl/core/UrlParameters.java | 124 +++++++++++++++++++++
.../jasper/tagplugins/jstl/core/TestImport.java | 2 +-
.../jasper/tagplugins/jstl/core/TestRedirect.java | 2 +-
.../jasper/tagplugins/jstl/core/TestUrl.java | 22 ++--
test/webapp/jstl/url-param-fallback-nested.jsp | 20 ++++
webapps/docs/changelog.xml | 10 ++
12 files changed, 253 insertions(+), 89 deletions(-)
diff --git a/java/org/apache/jasper/compiler/TagPluginManager.java
b/java/org/apache/jasper/compiler/TagPluginManager.java
index 0e4fd620a4..8632872d29 100644
--- a/java/org/apache/jasper/compiler/TagPluginManager.java
+++ b/java/org/apache/jasper/compiler/TagPluginManager.java
@@ -189,6 +189,24 @@ public class TagPluginManager {
return ((Node.CustomTag) parent).getTagPluginContext();
}
+ @Override
+ public TagPluginContext getParentContext(boolean crossTagHandlers) {
+ if (!crossTagHandlers) {
+ return getParentContext();
+ }
+ Node parent = node.getParent();
+ while (parent != null) {
+ if (parent instanceof Node.CustomTag) {
+ TagPluginContext parentContext = ((Node.CustomTag)
parent).getTagPluginContext();
+ if (parentContext != null) {
+ return parentContext;
+ }
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
@Override
public void setPluginAttribute(String key, Object value) {
pluginAttributes.put(key, value);
diff --git a/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
b/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
index 848775da93..eb6686daa4 100644
--- a/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
+++ b/java/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
@@ -128,6 +128,23 @@ public interface TagPluginContext {
*/
TagPluginContext getParentContext();
+ /**
+ * Get the PluginContext of the closest enclosing custom tag that has one,
optionally looking beyond tags whose
+ * body is generated by the standard tag handler mechanism. The same
restrictions as for
+ * {@link #getParentContext()} apply to the context obtained.
+ * <p>
+ * Tags that abandoned their plugin are not skipped. Their body is
generated in a separate method of the
+ * generated servlet, so local variables of outer tags are out of scope
inside it and the result must not be used
+ * to share such variables. It is suitable for finding a tag that must be
told to abandon its plugin.
+ *
+ * @param crossTagHandlers {@code true} to continue past tags that
abandoned their plugin
+ *
+ * @return the pluginContext of the closest enclosing tag with a plugin,
or {@code null} if there is none
+ */
+ default TagPluginContext getParentContext(boolean crossTagHandlers) {
+ return getParentContext();
+ }
+
/**
* Associate the attribute with a value in the current tagplugin context.
The plugin attributes can be used for
* communication among tags that must work together as a group. See
<c:when> for an example.
diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Import.java
b/java/org/apache/jasper/tagplugins/jstl/core/Import.java
index 3bfe051689..b0e76a58fa 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/Import.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/Import.java
@@ -25,18 +25,6 @@ import org.apache.jasper.tagplugins.jstl.Util;
*/
public class Import implements TagPlugin {
- /**
- * Key of the plugin attribute through which a URL tag shares its working
- * URL with the {@link Param} tags in its body. The value is the name of
- * 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. 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";
-
/**
* Creates a new instance of the Import tag plugin.
*/
@@ -295,14 +283,8 @@ public class Import implements TagPlugin {
// if the varReader is not specified
else {
- // 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(URL_VARIABLE_ATTRIBUTE, urlName);
- ctxt.generateBody();
+ // add params
+ UrlParameters.generateBody(ctxt, urlName);
String strScope = "page";
if (hasScope) {
diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Param.java
b/java/org/apache/jasper/tagplugins/jstl/core/Param.java
index 0cf10748d9..6a1ea36499 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/Param.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/Param.java
@@ -37,42 +37,35 @@ public class Param implements TagPlugin {
// don't support the body content
+ // Append the parameter to the working URL of the enclosing URL tag.
+ // The URL plugins share the names of the local variables holding
+ // that URL with the Param plugins in their bodies, so that nested URL
+ // tags cannot interfere with each other. 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. It stops at tags that no plugin
+ // handled, since their bodies are generated in separate methods where
+ // the local variables are out of scope.
+ TagPluginContext urlContext = UrlParameters.findUrlContext(ctxt);
+ if (urlContext == null) {
+ // The parameter cannot be appended inline. Let the standard tag
+ // handler process it, and return the enclosing URL tag to its
+ // standard handler as well, since only that can receive it. If
+ // there is no URL tag, the standard handler reports the misplaced
+ // tag.
+ UrlParameters.fallBack(ctxt);
+ return;
+ }
+ UrlParameters.register(urlContext, ctxt);
+ String urlTargetName = UrlParameters.getUrlVariable(urlContext);
+ String paramEnd = UrlParameters.getParamEndVariable(urlContext);
+
// define names of all the temp variables
String nameName = ctxt.getTemporaryVariableName();
String valueName = ctxt.getTemporaryVariableName();
String encName = ctxt.getTemporaryVariableName();
String index = ctxt.getTemporaryVariableName();
-
- // if the param tag has no parents, throw an exception
- TagPluginContext parent = ctxt.getParentContext();
- if (parent == null) {
- ctxt.generateJavaSource(
- " throw new JspTagException" + "(\"<param> outside
<import> or <urlEncode>\");");
- return;
- }
-
- // 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);
+ String paramName = ctxt.getTemporaryVariableName();
// get the value of "name"
ctxt.generateJavaSource("String " + nameName + " = ");
@@ -89,20 +82,26 @@ public class Param implements TagPlugin {
ctxt.generateJavaSource(" if(" + valueName + " == null) " +
valueName + " = \"\";");
ctxt.generateJavaSource(
" String " + encName + " =
_jspx_page_context.getResponse().getCharacterEncoding();");
- ctxt.generateJavaSource(
- " " + nameName + " = java.net.URLEncoder.encode(" +
nameName + ", " + encName + ");");
- ctxt.generateJavaSource(
- " " + valueName + " = java.net.URLEncoder.encode(" +
valueName + ", " + encName + ");");
- ctxt.generateJavaSource(" int " + index + ";");
- ctxt.generateJavaSource(" " + index + " = " + urlTargetName +
".indexOf(\'?\');");
- // if the current param is the first one, add a "?" ahead of it
- // else add a "&" ahead of it
- ctxt.generateJavaSource(" if(" + index + " == -1){");
- ctxt.generateJavaSource(" " + urlTargetName + " = " +
urlTargetName + " + \"?\" + " + nameName +
- " + \"=\" + " + valueName + ";");
+ ctxt.generateJavaSource(" String " + paramName + " =
java.net.URLEncoder.encode(" + nameName + ", " +
+ encName + ") + \"=\" + java.net.URLEncoder.encode(" +
valueName + ", " + encName + ");");
+ // The parameters precede any query string already in the URL. The
+ // first one is inserted after the "?", or appended with a new "?" if
+ // there is no query string. Later ones follow the previous parameter.
+ ctxt.generateJavaSource(" if(" + paramEnd + " < 0){");
+ ctxt.generateJavaSource(" int " + index + " = " + urlTargetName
+ ".indexOf('?');");
+ ctxt.generateJavaSource(" if(" + index + " == -1){");
+ ctxt.generateJavaSource(" " + urlTargetName + " = " +
urlTargetName + " + \"?\" + " + paramName +
+ ";");
+ ctxt.generateJavaSource(" " + paramEnd + " = " +
urlTargetName + ".length();");
+ ctxt.generateJavaSource(" }else{");
+ ctxt.generateJavaSource(" " + urlTargetName + " = " +
urlTargetName + ".substring(0, " + index +
+ " + 1) + " + paramName + " + \"&\" + " + urlTargetName +
".substring(" + index + " + 1);");
+ ctxt.generateJavaSource(" " + paramEnd + " = " + index + "
+ 1 + " + paramName + ".length();");
+ ctxt.generateJavaSource(" }");
ctxt.generateJavaSource(" }else{");
- ctxt.generateJavaSource(" " + urlTargetName + " = " +
urlTargetName + " + \"&\" + " + nameName +
- " + \"=\" + " + valueName + ";");
+ ctxt.generateJavaSource(" " + urlTargetName + " = " +
urlTargetName + ".substring(0, " + paramEnd +
+ ") + \"&\" + " + paramName + " + " + urlTargetName +
".substring(" + paramEnd + ");");
+ ctxt.generateJavaSource(" " + paramEnd + " += 1 + " + paramName
+ ".length();");
ctxt.generateJavaSource(" }");
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 59e0069d09..0a45235611 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/Redirect.java
@@ -60,15 +60,8 @@ public class Redirect implements TagPlugin {
ctxt.generateJavaSource("String " + baseUrlName + " = " +
"org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" +
urlName + ", " + contextName + ", _jspx_page_context);");
- // 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();
+ UrlParameters.generateBody(ctxt, baseUrlName);
// get the response object
ctxt.generateJavaSource("HttpServletResponse " + responseName + " = " +
diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Url.java
b/java/org/apache/jasper/tagplugins/jstl/core/Url.java
index 198b211f2e..30035901b2 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/Url.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/Url.java
@@ -72,15 +72,8 @@ public class Url implements TagPlugin {
ctxt.generateJavaSource("String " + baseUrlName + " = " +
"org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" +
valueName + ", " + contextName + ", _jspx_page_context);");
- // 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();
+ UrlParameters.generateBody(ctxt, baseUrlName);
// if the url is relative, encode it
ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl("
+ baseUrlName + ")){");
diff --git a/java/org/apache/jasper/tagplugins/jstl/core/UrlParameters.java
b/java/org/apache/jasper/tagplugins/jstl/core/UrlParameters.java
new file mode 100644
index 0000000000..961babb516
--- /dev/null
+++ b/java/org/apache/jasper/tagplugins/jstl/core/UrlParameters.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+/**
+ * Translation time protocol shared by the URL tag plugins and the {@link
Param} plugin.
+ * <p>
+ * A URL tag plugin shares the name of the generated local variable that holds
its working URL, and the name of a
+ * second local variable that records where the parameters end, with the
{@link Param} plugins in its body. The
+ * parameters are inserted ahead of any query string that is already part of
the URL, as the standard tag handler does.
+ * <p>
+ * A {@link Param} plugin can only append to those variables when it is
generated in the same method as the URL tag. If
+ * it cannot, the URL tag and all the parameters of the tag that were already
generated inline are returned to the
+ * standard tag handlers, which exchange parameters at runtime.
+ */
+final class UrlParameters {
+
+ /**
+ * Plugin attribute of a URL tag context: the name of the local variable
that holds the working URL.
+ */
+ static final String URL_VARIABLE_ATTRIBUTE = "urlVariable";
+
+ private static final String PARAM_END_ATTRIBUTE = "urlParamEnd";
+ private static final String PARAMS_ATTRIBUTE = "urlParams";
+ private static final String ABANDONED_ATTRIBUTE = "urlAbandoned";
+
+ private UrlParameters() {
+ }
+
+ /**
+ * Generate the body of a URL tag whose working URL is held in a local
variable.
+ *
+ * @param ctxt the context of the URL tag
+ * @param urlName the name of the local variable that holds the working URL
+ */
+ static void generateBody(TagPluginContext ctxt, String urlName) {
+ if (ctxt.hasBody()) {
+ String paramEnd = ctxt.getTemporaryVariableName();
+ // Index in the URL at which the next parameter is inserted; -1
until the first one is added.
+ ctxt.generateJavaSource("int " + paramEnd + " = -1;");
+ ctxt.setPluginAttribute(URL_VARIABLE_ATTRIBUTE, urlName);
+ ctxt.setPluginAttribute(PARAM_END_ATTRIBUTE, paramEnd);
+ ctxt.setPluginAttribute(PARAMS_ATTRIBUTE, new
ArrayList<TagPluginContext>());
+ }
+ ctxt.generateBody();
+ }
+
+ /**
+ * Find the context of the URL tag that a parameter generated inline can
append to.
+ *
+ * @param param the context of the parameter
+ *
+ * @return the context of the URL tag, or {@code null} if the parameter
cannot be generated inline
+ */
+ static TagPluginContext findUrlContext(TagPluginContext param) {
+ TagPluginContext context = param.getParentContext();
+ while (context != null) {
+ if (context.getPluginAttribute(ABANDONED_ATTRIBUTE) != null) {
+ return null;
+ }
+ if (context.getPluginAttribute(URL_VARIABLE_ATTRIBUTE) != null) {
+ return context;
+ }
+ context = context.getParentContext();
+ }
+ return null;
+ }
+
+ static String getUrlVariable(TagPluginContext url) {
+ return (String) url.getPluginAttribute(URL_VARIABLE_ATTRIBUTE);
+ }
+
+ static String getParamEndVariable(TagPluginContext url) {
+ return (String) url.getPluginAttribute(PARAM_END_ATTRIBUTE);
+ }
+
+ @SuppressWarnings("unchecked")
+ static void register(TagPluginContext url, TagPluginContext param) {
+ ((List<TagPluginContext>)
url.getPluginAttribute(PARAMS_ATTRIBUTE)).add(param);
+ }
+
+ /**
+ * Return a parameter to the standard tag handler, together with the
closest enclosing URL tag and the parameters
+ * of that tag that were already generated inline. The standard handler of
a parameter adds it to the closest
+ * ancestor handler at runtime, which requires the URL tag to be a
standard handler as well.
+ *
+ * @param param the context of the parameter
+ */
+ @SuppressWarnings("unchecked")
+ static void fallBack(TagPluginContext param) {
+ param.dontUseTagPlugin();
+ TagPluginContext url = param.getParentContext(true);
+ while (url != null && url.getPluginAttribute(URL_VARIABLE_ATTRIBUTE)
== null) {
+ url = url.getParentContext(true);
+ }
+ if (url == null || url.getPluginAttribute(ABANDONED_ATTRIBUTE) !=
null) {
+ return;
+ }
+ url.setPluginAttribute(ABANDONED_ATTRIBUTE, Boolean.TRUE);
+ url.dontUseTagPlugin();
+ for (TagPluginContext inline : (List<TagPluginContext>)
url.getPluginAttribute(PARAMS_ATTRIBUTE)) {
+ inline.dontUseTagPlugin();
+ }
+ }
+}
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestImport.java
b/test/org/apache/jasper/tagplugins/jstl/core/TestImport.java
index 2c59d5ce37..c4bad6c942 100644
--- a/test/org/apache/jasper/tagplugins/jstl/core/TestImport.java
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestImport.java
@@ -70,7 +70,7 @@ public class TestImport extends AbstractTestTag {
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
String body = res.toString();
- Assert.assertTrue(body, body.contains("QS=[a=b&sp=x+y&u=%26%3D]"));
+ Assert.assertTrue(body, body.contains("QS=[sp=x+y&u=%26%3D&a=b]"));
}
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java
b/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java
index 473e3ca8c3..a293072288 100644
--- a/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestRedirect.java
@@ -132,6 +132,6 @@ public class TestRedirect extends AbstractTestTag {
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"));
+ Assert.assertTrue(location,
location.endsWith("/test/jstl/redirect-target.jsp?sp=x+y&u=%26%3D&a=b"));
}
}
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
index 84ca506899..29dbc49320 100644
--- a/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestUrl.java
@@ -73,7 +73,7 @@ public class TestUrl extends AbstractTestTag {
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]"));
+ Assert.assertTrue(body,
body.contains("U=[/test/a.jsp?sp=x+y&u2=%26%3D&x=1]"));
}
@@ -153,9 +153,11 @@ public class TestUrl extends AbstractTestTag {
* 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.
+ * plugin URL tag is out of scope. The enclosing URL tag, and the
+ * parameters of that tag already generated inline, must then use the
+ * standard handlers, which exchange the parameters at runtime. The
+ * parameters precede the existing query string, as in the standard
+ * handler.
*/
@Test
public void testParamNestedInPluginFallback() throws Exception {
@@ -165,8 +167,14 @@ public class TestUrl extends AbstractTestTag {
"/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);
+ Assert.assertEquals(body, HttpServletResponse.SC_OK, rc);
+ Assert.assertTrue(body, body.contains("U=[/test/a.jsp?p=q]"));
+ // Parameters before, within and after the fallen back tag
+ Assert.assertTrue(body,
body.contains("MIXED=[/test/a.jsp?a=1&b=2&c=3&q=1]"));
+ // Only the closest URL tag returns to the standard handler
+ Assert.assertTrue(body, body.contains("OUTER=[/test/o.jsp?x=1&z=3]"));
+ Assert.assertTrue(body, body.contains("INNER=[/test/i.jsp?y=2]"));
+ // Several parameters keep their order ahead of the query string
+ Assert.assertTrue(body,
body.contains("ORDERED=[/test/a.jsp?a=1&b=2&q=1&r=2]"));
}
}
diff --git a/test/webapp/jstl/url-param-fallback-nested.jsp
b/test/webapp/jstl/url-param-fallback-nested.jsp
index 66a826f658..a0db4d8778 100644
--- a/test/webapp/jstl/url-param-fallback-nested.jsp
+++ b/test/webapp/jstl/url-param-fallback-nested.jsp
@@ -20,3 +20,23 @@
<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>
+<c:url var="mixed" value="/a.jsp?q=1">
+ <c:param name="a" value="1"/>
+ <c:forEach var="i" varStatus="s" begin="1" end="1"><c:param name="b"
value="2"/></c:forEach>
+ <c:param name="c" value="3"/>
+</c:url>
+<p>MIXED=[${mixed}]</p>
+<c:url var="outer" value="/o.jsp">
+ <c:param name="x" value="1"/>
+ <c:url var="inner" value="/i.jsp">
+ <c:forEach var="i" varStatus="s" begin="1" end="1"><c:param name="y"
value="2"/></c:forEach>
+ </c:url>
+ <c:param name="z" value="3"/>
+</c:url>
+<p>OUTER=[${outer}]</p>
+<p>INNER=[${inner}]</p>
+<c:url var="ordered" value="/a.jsp?q=1&r=2">
+ <c:param name="a" value="1"/>
+ <c:param name="b" value="2"/>
+</c:url>
+<p>ORDERED=[${ordered}]</p>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index eda24df27b..470b6442f5 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -655,6 +655,16 @@
keeps the parameters of each tag to itself and never touches page
scope attributes. (remm)
</fix>
+ <fix>
+ The tag plugins for <code>c:url</code>, <code>c:redirect</code> and
+ <code>c:import</code> now insert the parameters of nested
+ <code>c:param</code> tags ahead of any query string already present in
+ the URL, as the standard tag handlers do. A <code>c:param</code> that
+ the plugin cannot append to inline, because it is nested in a tag that
+ is generated by its standard tag handler, now returns the enclosing
+ URL tag to its standard handler instead of failing at runtime.
+ (markt)
+ </fix>
<fix>
<code>RecordELResolver</code> no longer prevents
<code>BeanELResolver</code> from resolving bean-style properties (such
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]