This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 6e16bd9857 Fix BZ 70001: _jspx_dependants contains non-deterministic 
absolute JAR paths for taglibs resolved from external JARs
6e16bd9857 is described below

commit 6e16bd9857f7507631d6bb2eecd2fc2f058f36f8
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 69c6605f17..e43c13414c 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;
 
 /**
@@ -474,7 +475,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 8bde741fd8..dab1355464 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 9012262696..a9caf237fa 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 jakarta.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]

Reply via email to