This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 954b82366e Fix BZ 70001: _jspx_dependants contains non-deterministic
absolute JAR paths for taglibs resolved from external JARs
954b82366e is described below
commit 954b82366e393d8dab7e8bb65cc3b8ccd5fb1a25
Author: jluehe <[email protected]>
AuthorDate: Tue Apr 14 13:53:21 2026 -0700
Fix BZ 70001: _jspx_dependants contains non-deterministic absolute JAR
paths for taglibs resolved from external JARs
---
java/org/apache/jasper/compiler/Compiler.java | 36 ++++++-
.../apache/jasper/compiler/TagLibraryInfoImpl.java | 18 +++-
.../jasper/compiler/TestTagLibraryInfoImpl.java | 120 ++++++++++++++++++++-
test/webapp/jsp/generator/external-taglib.jsp | 19 ++++
4 files changed, 187 insertions(+), 6 deletions(-)
diff --git a/java/org/apache/jasper/compiler/Compiler.java
b/java/org/apache/jasper/compiler/Compiler.java
index 027b1c0822..ec8b882b20 100644
--- a/java/org/apache/jasper/compiler/Compiler.java
+++ b/java/org/apache/jasper/compiler/Compiler.java
@@ -37,6 +37,7 @@ import org.apache.jasper.servlet.JspServletWrapper;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.Jar;
+import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
import org.apache.tomcat.util.scan.JarFactory;
/**
@@ -472,7 +473,40 @@ public abstract class Compiler {
String key = include.getKey();
URL includeUrl;
long includeLastModified;
- if (key.startsWith("jar:jar:")) {
+ if (key.startsWith("uri:")) {
+ // Key is a stable taglib URI used for TLDs in JARs outside
+ // the web application (avoids baking absolute paths into
the
+ // generated code). Two forms exist:
+ // "uri:<taglib-uri>" – the JAR file itself
+ // "uri:<taglib-uri>!/<entry>" – a TLD entry within the
JAR
+ int bangSlash = key.indexOf("!/");
+ String tagUri = bangSlash < 0
+ ? key.substring(4)
+ : key.substring(4, bangSlash);
+ TldCache tldCache = ctxt.getOptions().getTldCache();
+ TldResourcePath tldPath =
tldCache.getTldResourcePath(tagUri);
+ if (tldPath == null) {
+ return true;
+ }
+ if (bangSlash < 0) {
+ // JAR-level key: check the JAR file's last-modified
+ URLConnection urlConn =
tldPath.getUrl().openConnection();
+ try {
+ includeLastModified = urlConn.getLastModified();
+ } finally {
+ urlConn.getInputStream().close();
+ }
+ } else {
+ // TLD-entry key: check the entry's last-modified
within the JAR
+ String entryName = key.substring(bangSlash + 2);
+ try (Jar jar = tldPath.openJar()) {
+ if (jar == null) {
+ return true;
+ }
+ includeLastModified =
jar.getLastModified(entryName);
+ }
+ }
+ } else if (key.startsWith("jar:jar:")) {
// Assume we constructed this correctly
int entryStart = key.lastIndexOf("!/");
String entry = key.substring(entryStart + 2);
diff --git a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
index 21e326fd4a..37af797e50 100644
--- a/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
+++ b/java/org/apache/jasper/compiler/TagLibraryInfoImpl.java
@@ -133,7 +133,10 @@ class TagLibraryInfoImpl extends TagLibraryInfo implements
TagConstants {
}
if (jar != null) {
if (path == null) {
- // JAR not in the web application so add it directly
+ // JAR not in the web application so add it directly.
Use the
+ // stable taglib URI as the dependency key instead of
the
+ // absolute JAR URL to keep the generated servlet code
+ // deterministic across build environments.
URL jarUrl = jar.getJarFileURL();
long lastMod;
URLConnection urlConn = null;
@@ -151,12 +154,19 @@ class TagLibraryInfoImpl extends TagLibraryInfo
implements TagConstants {
}
}
}
- pageInfo.addDependant(jarUrl.toExternalForm(),
Long.valueOf(lastMod));
+ pageInfo.addDependant("uri:" + uriIn,
Long.valueOf(lastMod));
}
- // Add TLD within the JAR to the dependency list
+ // Add TLD within the JAR to the dependency list. For
external
+ // JARs (path == null) use a stable "uri:...!/entryName"
key
+ // instead of the absolute jar.getURL(entryName) to keep
the
+ // generated servlet code deterministic across build
environments.
String entryName = tldResourcePath.getEntryName();
try {
- pageInfo.addDependant(jar.getURL(entryName),
Long.valueOf(jar.getLastModified(entryName)));
+ String tldKey = path != null
+ ? jar.getURL(entryName)
+ : "uri:" + uriIn + "!/" + entryName;
+ pageInfo.addDependant(tldKey,
+ Long.valueOf(jar.getLastModified(entryName)));
} catch (IOException ioe) {
throw new JasperException(ioe);
}
diff --git a/test/org/apache/jasper/compiler/TestTagLibraryInfoImpl.java
b/test/org/apache/jasper/compiler/TestTagLibraryInfoImpl.java
index f7e5e5e5c6..b9f512a5a9 100644
--- a/test/org/apache/jasper/compiler/TestTagLibraryInfoImpl.java
+++ b/test/org/apache/jasper/compiler/TestTagLibraryInfoImpl.java
@@ -16,13 +16,29 @@
*/
package org.apache.jasper.compiler;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Test;
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.jasper.servlet.JspServlet;
import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.scan.StandardJarScanFilter;
+import org.apache.tomcat.util.scan.StandardJarScanner;
/**
* Test case for {@link TagLibraryInfoImpl}.
@@ -39,7 +55,6 @@ public class TestTagLibraryInfoImpl extends TomcatBaseTest {
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
}
-
/*
* https://bz.apache.org/bugzilla/show_bug.cgi?id=64373
*/
@@ -53,4 +68,107 @@ public class TestTagLibraryInfoImpl extends TomcatBaseTest {
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
}
+ /*
+ * https://bz.apache.org/bugzilla/show_bug.cgi?id=70001
+ *
+ * Verify that taglib directives referencing a TLD in a JAR that is outside
+ * the web application (i.e. on the classpath but not in WEB-INF/lib)
produce
+ * a stable, environment-independent key in the generated servlet's
+ * {@code _jspx_dependants} map.
+ *
+ * Before the fix, the key was an absolute {@code jar:file:/...} URL that
+ * encoded the build-environment-specific JAR location, making JSP
compilation
+ * non-deterministic. After the fix the key must use the {@code "uri:"}
prefix
+ * followed by the taglib URI from the JSP directive.
+ */
+ @Test
+ public void testExternalTaglibDependantUsesUri() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+ File appDir = new File("test/webapp");
+ Context ctx = tomcat.addWebapp(null, "/test",
appDir.getAbsolutePath());
+
+ StandardJarScanner scanner = (StandardJarScanner) ctx.getJarScanner();
+ StandardJarScanFilter filter = (StandardJarScanFilter)
scanner.getJarScanFilter();
+ filter.setTldSkip(filter.getTldSkip() + ",testclasses");
+ filter.setPluggabilitySkip(filter.getPluggabilitySkip() +
",testclasses");
+
+ // Add a JAR containing the test TLD to the *parent* classloader rather
+ // than to WEB-INF/lib. The TLD scanner then sees it as an external JAR
+ // (TldResourcePath.getWebappPath() == null), which is the code path
that
+ // the fix for non-deterministic _jspx_dependants addresses.
+ File jar = createExternalTaglibJar();
+ ClassLoader parent = Thread.currentThread().getContextClassLoader();
+ ctx.setParentClassLoader(new URLClassLoader(new URL[] {
jar.toURI().toURL() }, parent));
+
+ tomcat.start();
+
+ ByteChunk body = new ByteChunk();
+ int rc = getUrl("http://localhost:" + getPort() +
+ "/test/jsp/generator/external-taglib.jsp", body, null);
+ Assert.assertEquals(body.toString(), HttpServletResponse.SC_OK, rc);
+
+ // Retrieve the _jspx_dependants map from the compiled servlet via the
+ // JspServletWrapper.
+ Context webCtx = (Context) tomcat.getHost().findChild("/test");
+ Wrapper jspWrapper = (Wrapper) webCtx.findChild("jsp");
+ JspServlet jspServlet = (JspServlet) jspWrapper.getServlet();
+ Field rctxtField = JspServlet.class.getDeclaredField("rctxt");
+ rctxtField.setAccessible(true);
+ JspRuntimeContext rctxt = (JspRuntimeContext)
rctxtField.get(jspServlet);
+ Map<String,Long> dependants = rctxt.getWrapper(
+ "/jsp/generator/external-taglib.jsp").getDependants();
+
+ Assert.assertNotNull("Expected non-null _jspx_dependants map",
dependants);
+
+ // No key in _jspx_dependants should be an absolute file/jar URL.
+ // Such URLs embed environment-specific paths and make JSP compilation
+ // non-deterministic.
+ for (String key : dependants.keySet()) {
+ Assert.assertFalse(
+ "_jspx_dependants must not contain absolute paths for
external taglib JARs, got: " + key,
+ key.startsWith("jar:file:") || key.startsWith("file:"));
+ }
+
+ // The external taglib JAR and its TLD entry must each be recorded with
+ // a stable "uri:" key rather than an absolute path.
+ Assert.assertTrue(
+ "Expected 'uri:http://tomcat.apache.org/test/external-taglib'
key in _jspx_dependants",
+
dependants.containsKey("uri:http://tomcat.apache.org/test/external-taglib"));
+ Assert.assertTrue(
+ "Expected
'uri:http://tomcat.apache.org/test/external-taglib!/META-INF/external-taglib-test.tld'"
+
+ " key in _jspx_dependants",
+ dependants.containsKey(
+
"uri:http://tomcat.apache.org/test/external-taglib!/META-INF/external-taglib-test.tld"));
+ }
+
+ /**
+ * Creates a temporary JAR containing a minimal TLD with URI
+ * {@code http://tomcat.apache.org/test/external-taglib}. The TLD has no
+ * validator and no tag-handler classes so the JAR itself is the only
+ * dependency required to compile a JSP that references it.
+ */
+ private static File createExternalTaglibJar() throws Exception {
+ String tld =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<taglib xmlns=\"http://java.sun.com/xml/ns/javaee\"\n" +
+ "
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+ "
xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee " +
+
"http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd\"\n" +
+ " version=\"2.1\">\n" +
+ " <tlib-version>1.0</tlib-version>\n" +
+ " <short-name>ext</short-name>\n" +
+ "
<uri>http://tomcat.apache.org/test/external-taglib</uri>\n" +
+ "</taglib>\n";
+
+ File jar = File.createTempFile("external-taglib-test", ".jar");
+ jar.deleteOnExit();
+
+ try (JarOutputStream jos = new JarOutputStream(new
FileOutputStream(jar))) {
+ jos.putNextEntry(new
JarEntry("META-INF/external-taglib-test.tld"));
+ jos.write(tld.getBytes(StandardCharsets.UTF_8));
+ jos.closeEntry();
+ }
+
+ return jar;
+ }
}
diff --git a/test/webapp/jsp/generator/external-taglib.jsp
b/test/webapp/jsp/generator/external-taglib.jsp
new file mode 100644
index 0000000000..b2a6936ce0
--- /dev/null
+++ b/test/webapp/jsp/generator/external-taglib.jsp
@@ -0,0 +1,19 @@
+<%--
+ 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 contentType="text/plain" %>
+<%@ taglib prefix="ext" uri="http://tomcat.apache.org/test/external-taglib" %>
+OK
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]