Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Rhython opened this issue Jan 29, 2025 · 2 comments

Comments

@Rhython
Copy link

Rhython commented Jan 29, 2025

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
Copy link
Author

Rhython commented Jan 29, 2025

And this looks very similar to #381.

@Rhython
Copy link
Author

Rhython commented Jan 29, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant