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

rmaucher pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 73388ae1bb50eddcc6f597a12724a7cf81ca485f
Author: opencode <[email protected]>
AuthorDate: Mon Oct 19 12:17:45 2026 +0200

    Avoid registering TLD listeners twice for a duplicate taglib URI
    
    TldScanner.parseTld() only skipped a TLD whose exact TldResourcePath
    had already been parsed. TldResourcePath.equals() includes the URL, so
    the same tag library discovered through a second resource path, for
    example a TLD declared in jsp-config of web.xml that an IDE also
    unpacked into WEB-INF/classes/META-INF, or the same TLD shipped in two
    JARs, passed the guard and its listeners were appended to the scanner's
    listener list again. JasperInitializer.onStartup() then invoked
    context.addListener() for each list entry, instantiating the listener
    class twice so that it received every container event twice. The
    taglib URI map already resolves such duplicates with first-wins
    semantics (JSP.7.3.6.1) but the listener list was not deduplicated.
    
    Detect the duplicate by taglib URI in parseTld() and skip the listener
    registration for it, while continuing to cache the parsed TLD under its
    own resource path since it may be referenced directly by that path.
    Add a unit test covering the duplicate URI scan.
    
    Found by code review.
---
 java/org/apache/jasper/servlet/TldScanner.java     | 12 ++++-
 test/org/apache/jasper/servlet/TestTldScanner.java | 61 ++++++++++++++++++++++
 2 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/jasper/servlet/TldScanner.java 
b/java/org/apache/jasper/servlet/TldScanner.java
index c9da0a5e99..da2fb0ac0d 100644
--- a/java/org/apache/jasper/servlet/TldScanner.java
+++ b/java/org/apache/jasper/servlet/TldScanner.java
@@ -309,8 +309,14 @@ public class TldScanner {
             tld = tldParser.parse(path);
         }
         String uri = tld.getUri();
+        // JSP.7.3.6.1: the first mapping for a taglib URI wins. A TLD with a
+        // previously seen URI found via a different resource path (for
+        // example, an unpacked JAR in WEB-INF/classes/META-INF or the same
+        // TLD shipped in two JARs) is a duplicate.
+        boolean duplicateUri = false;
         if (uri != null) {
-            if (!uriTldResourcePathMap.containsKey(uri)) {
+            duplicateUri = uriTldResourcePathMap.containsKey(uri);
+            if (!duplicateUri) {
                 uriTldResourcePathMap.put(uri, path);
             }
         }
@@ -321,7 +327,9 @@ public class TldScanner {
         }
 
         tldResourcePathTaglibXmlMap.put(path, tld);
-        if (tld.getListeners() != null) {
+        if (tld.getListeners() != null && !duplicateUri) {
+            // The listeners of a duplicate tag library have already been
+            // registered when the tag library was first discovered.
             listeners.addAll(tld.getListeners());
         }
     }
diff --git a/test/org/apache/jasper/servlet/TestTldScanner.java 
b/test/org/apache/jasper/servlet/TestTldScanner.java
index 5816831dea..2b7d61ffcc 100644
--- a/test/org/apache/jasper/servlet/TestTldScanner.java
+++ b/test/org/apache/jasper/servlet/TestTldScanner.java
@@ -40,6 +40,7 @@ import org.apache.catalina.startup.TomcatBaseTest;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.Jar;
 import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
 import org.apache.tomcat.util.scan.JarFactory;
 import org.apache.tomcat.util.scan.StandardJarScanner;
 import org.easymock.EasyMock;
@@ -139,6 +140,66 @@ public class TestTldScanner extends TomcatBaseTest {
     }
 
 
+    @Test
+    public void testListenersNotDuplicatedForDuplicateTaglibUri() throws 
Exception {
+        // The same tag library may be discovered more than once, for example
+        // a TLD declared in web.xml that is also found unpacked in
+        // WEB-INF/classes/META-INF or shipped in two JARs. The first mapping
+        // wins for the taglib URI and the listeners of the duplicate must not
+        // be registered again, otherwise the listener class is instantiated
+        // twice and receives each event twice.
+        ServletContext mockContext = EasyMock.createMock(ServletContext.class);
+        EasyMock.replay(mockContext);
+
+        TldScanner scanner = new TldScanner(mockContext, true, true, true);
+
+        File appDir = new File(getTemporaryDirectory(), "duplicate-listeners");
+        TldResourcePath first = createTld(appDir, "one",
+                "<uri>http://tomcat.apache.org/duplicate</uri>\n" +
+                "  <listener>\n" +
+                "    
<listener-class>com.example.DuplicateListener</listener-class>\n" +
+                "  </listener>");
+        TldResourcePath second = createTld(appDir, "two",
+                "<uri>http://tomcat.apache.org/duplicate</uri>\n" +
+                "  <listener>\n" +
+                "    
<listener-class>com.example.DuplicateListener</listener-class>\n" +
+                "  </listener>");
+
+        scanner.parseTld(first);
+        scanner.parseTld(second);
+
+        EasyMock.verify(mockContext);
+        Assert.assertEquals(1, scanner.getListeners().size());
+        Assert.assertEquals("com.example.DuplicateListener", 
scanner.getListeners().get(0));
+        // The first mapping wins for the taglib URI.
+        Assert.assertEquals(1, scanner.getUriTldResourcePathMap().size());
+        Assert.assertSame(first,
+                
scanner.getUriTldResourcePathMap().get("http://tomcat.apache.org/duplicate";));
+        // Both parsed TLDs remain cached since they may also be referenced
+        // directly by resource path.
+        Assert.assertEquals(2, 
scanner.getTldResourcePathTaglibXmlMap().size());
+    }
+
+
+    private static TldResourcePath createTld(File appDir, String name,
+            String extraElements) throws Exception {
+        File tld = new File(appDir, name + "/example.tld");
+        Files.createDirectories(tld.getParentFile().toPath());
+        String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<taglib xmlns=\"http://java.sun.com/xml/ns/j2ee\"\n"; +
+                "    
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"; +
+                "    xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee\n"; +
+                "        
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd\"\n"; +
+                "    version=\"2.0\">\n" +
+                "  <tlib-version>1.0</tlib-version>\n" +
+                "  <short-name>example</short-name>\n" +
+                "  " + extraElements + "\n" +
+                "</taglib>\n";
+        Files.writeString(tld.toPath(), content, StandardCharsets.UTF_8);
+        return new TldResourcePath(tld.toURI().toURL(), "/" + name + 
"/example.tld");
+    }
+
+
     private static void createImplicitTld(File appDir, String extraElements) 
throws Exception {
         File implicitTld = new File(appDir, 
"WEB-INF/tags/display/implicit.tld");
         Files.createDirectories(implicitTld.getParentFile().toPath());


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to