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

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

commit f79b6856754874c8f3246bb2459bca48dcd7d37d
Author: opencode <[email protected]>
AuthorDate: Mon Oct 12 20:31:49 2026 +0200

    Avoid exceptions when indexing malformed archive entry names
    
    JarContents.processEntry() assumed that every entry name was either
    non-empty or followed the multi-release naming convention. A JAR
    containing an entry with an empty name caused a
    StringIndexOutOfBoundsException and an entry below a version directory
    whose name is not a valid version number, for example
    META-INF/versions/9x/, caused a NumberFormatException. Either exception
    prevented the archive contents filter being built, which in turn
    prevented the archive resource set being created.
    
    Skip entries with an empty name, since a lookup of the archive root is
    handled before the filter is consulted. Treat a version directory that
    does not parse as a version number as a regular directory, recording the
    entry under its full name, which is the only name such an entry can be
    requested under. Entry names that do parse are processed exactly as
    before, keeping the filter consistent with how JarWarResourceSet indexes
    the same entries.
    
    Add a unit test for the malformed names.
---
 .../apache/catalina/webresources/JarContents.java  | 20 ++++++-
 .../catalina/webresources/TestJarContents.java     | 65 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/catalina/webresources/JarContents.java 
b/java/org/apache/catalina/webresources/JarContents.java
index ea30f9e386..e6ab5093c0 100644
--- a/java/org/apache/catalina/webresources/JarContents.java
+++ b/java/org/apache/catalina/webresources/JarContents.java
@@ -76,6 +76,14 @@ public final class JarContents {
 
     private void processEntry(JarEntry entry) {
         String name = entry.getName();
+
+        // A resource set lookup of the archive root is handled before the
+        // filter is consulted, so an entry with an empty name never needs to
+        // be recorded in the filter.
+        if (name.isEmpty()) {
+            return;
+        }
+
         int startPos = 0;
 
         // If the path starts with a slash, that's not useful information.
@@ -90,9 +98,15 @@ public final class JarContents {
         if (name.startsWith("META-INF/versions/", startPos)) {
             int i = name.indexOf('/', 18 + startPos);
             if (i > 0) {
-                int version = Integer.parseInt(name.substring(18 + startPos, 
i));
-                if (version <= Runtime.version().feature()) {
-                    startPos = i + 1;
+                try {
+                    int version = Integer.parseInt(name.substring(18 + 
startPos, i));
+                    if (version <= Runtime.version().feature()) {
+                        startPos = i + 1;
+                    }
+                } catch (NumberFormatException e) {
+                    // The segment is not a valid version number, so the entry
+                    // is not a versioned entry that can be resolved under its
+                    // base name and is added to the table under its full name.
                 }
             }
             if (startPos == name.length()) {
diff --git a/test/org/apache/catalina/webresources/TestJarContents.java 
b/test/org/apache/catalina/webresources/TestJarContents.java
index 8fff9bb6f2..af7fda2982 100644
--- a/test/org/apache/catalina/webresources/TestJarContents.java
+++ b/test/org/apache/catalina/webresources/TestJarContents.java
@@ -17,7 +17,11 @@
 package org.apache.catalina.webresources;
 
 import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -94,4 +98,65 @@ public class TestJarContents {
     public void testNullPointerExceptions() {
         testJarContentsObject.mightContainResource(null, 
jar.getAbsolutePath());
     }
+
+    @Test
+    public void testMalformedEntryNames() throws Exception {
+        // Malformed entry names must not prevent the filter being built.
+        // Entries below a version directory that does not parse as a version
+        // number are treated as regular entries, hashed under their full name.
+        // Entries below a version directory that does parse are hashed under
+        // their base name, as they are when the archive entries are processed
+        // by JarWarResourceSet. Entries with an empty name are skipped. A
+        // previously well-formed entry must still be resolvable.
+        File f = File.createTempFile("test-jar-contents-malformed", ".jar");
+        f.deleteOnExit();
+        try (ZipOutputStream out = new ZipOutputStream(new 
FileOutputStream(f))) {
+            out.putNextEntry(new ZipEntry(""));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("META-INF/versions/9x/foo.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("META-INF/versions//bar.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new 
ZipEntry("META-INF/versions/99999999999999/ovf.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("/META-INF/versions/9x/lead.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("META-INF/versions/+9/plus.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("META-INF/versions/-9/minus.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("META-INF/versions/9/ok.txt"));
+            out.write("x".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+            out.putNextEntry(new ZipEntry("real.txt"));
+            out.write("y".getBytes(StandardCharsets.UTF_8));
+            out.closeEntry();
+        }
+
+        try (JarFile jarFile = new JarFile(f)) {
+            JarContents jarContents = new JarContents(jarFile);
+
+            Assert.assertTrue(jarContents.mightContainResource("real.txt", 
""));
+            Assert.assertTrue(
+                    
jarContents.mightContainResource("META-INF/versions/9x/foo.txt", ""));
+            Assert.assertTrue(
+                    
jarContents.mightContainResource("META-INF/versions//bar.txt", ""));
+            Assert.assertTrue(jarContents.mightContainResource(
+                    "META-INF/versions/99999999999999/ovf.txt", ""));
+            // A leading slash is not significant
+            Assert.assertTrue(
+                    
jarContents.mightContainResource("/META-INF/versions/9x/lead.txt", ""));
+            // Signed and valid version segments record the base name
+            Assert.assertTrue(jarContents.mightContainResource("plus.txt", 
""));
+            Assert.assertTrue(jarContents.mightContainResource("minus.txt", 
""));
+            Assert.assertTrue(jarContents.mightContainResource("ok.txt", ""));
+        }
+    }
 }


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

Reply via email to