proguard icon indicating copy to clipboard operation
proguard copied to clipboard

Proguard omits directory entries in output JAR, causing `ClassLoader#getResources` to fail

Open Rhython opened this issue 11 months ago • 2 comments

When processing JAR files through Proguard, directory entries in the output JAR are not properly preserved. This results in ClassLoader#getResources(String) being unable to locate resources when queried using directory paths.

Environment Proguard 7.6.1, jdk-21, Windows 11.

Steps to Reproduce

  1. Create a project with directory structure: src/main/resources/META-INF/data/
  2. Add sample files to the directory
  3. Configure Proguard to keep everything
  4. Build project with Proguard processing

Attempt to load resources using:

Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/data");

Expected Behavior The directory resource URL should be returned when querying existing directory paths.

Actual Behavior Empty enumeration is returned because the JAR lacks directory entries, even though files exist in those directories.

This breaks resource loading patterns commonly used in Resource discovery mechanisms and Framework classpath scanning implementations.

Rhython avatar Jan 29 '25 07:01 Rhython

And this looks very similar to #381.

Rhython avatar Jan 29 '25 07:01 Rhython

It is hard to understand how proguard write data entries to jar file, so I did a very hack fix, maybe helpful:

diff --git a/base/src/main/java/proguard/OutputWriter.java b/base/src/main/java/proguard/OutputWriter.java
--- a/base/src/main/java/proguard/OutputWriter.java	(revision fbcf41fd670cc6ffdf2db6cfddc07f5a527142e2)
+++ b/base/src/main/java/proguard/OutputWriter.java	(date 1738083442253)
@@ -41,6 +41,9 @@
 import java.security.*;
 import java.security.cert.X509Certificate;
 import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
 
 
 /**
@@ -397,6 +400,48 @@
 
             // Close all output entries.
             writer.close();
+
+            // read all the entries from the output jar and write them back to the jar
+            Map<String, byte[]> entries = new TreeMap<>();
+            File file = classPath.get(toOutputIndex - 1).getFile();
+            try (JarInputStream jis = new JarInputStream(new FileInputStream(file))) {
+                JarEntry entry;
+                while ((entry = jis.getNextJarEntry()) != null) {
+                    if (!entry.isDirectory()) {
+                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                        byte[] buffer = new byte[1024];
+                        int bytesRead;
+                        while ((bytesRead = jis.read(buffer)) != -1) {
+                            baos.write(buffer, 0, bytesRead);
+                        }
+                        entries.put(entry.getName(), baos.toByteArray());
+                    }
+                }
+            }
+            // add missing directories
+            Set<String> dirs = new HashSet<>();
+            for (String entry : entries.keySet()) {
+                while (entry.contains("/")) {
+                    entry = entry.substring(0, entry.lastIndexOf('/'));
+                    dirs.add(entry + "/");
+                }
+            }
+            for (String dir : dirs) {
+                if (!entries.containsKey(dir)) {
+                    entries.put(dir, null);
+                }
+            }
+
+            // write the entries back to the jar
+            try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(file))) {
+                for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
+                    jos.putNextEntry(new JarEntry(entry.getKey()));
+                    if (entry.getValue() != null) {
+                        jos.write(entry.getValue());
+                    }
+                    jos.closeEntry();
+                }
+            }
         }
         catch (IOException ex)
         {

It works for me.

Rhython avatar Jan 29 '25 07:01 Rhython