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 99083290a8016ce45b4e398c404cf9677b6c96d4 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 8e989aa67b..152a94b37d 100644 --- a/test/org/apache/jasper/servlet/TestTldScanner.java +++ b/test/org/apache/jasper/servlet/TestTldScanner.java @@ -39,6 +39,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; @@ -138,6 +139,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]
