Skip to content

Commit c0627bd

Browse files
committed
Rewrite JAR/ZIP file parsing
1 parent aa79cc9 commit c0627bd

File tree

3 files changed

+98
-83
lines changed

3 files changed

+98
-83
lines changed

src/main/java/de/thetaphi/forbiddenapis/Checker.java

+17-5
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,11 @@ public void setSignaturesSeverity(Collection<String> signatures, ViolationSeveri
370370
forbiddenSignatures.setSignaturesSeverity(signatures, severity);
371371
}
372372

373-
/** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)! Does not log anything. */
374-
public void addClassToCheck(final InputStream in, String name) throws IOException {
373+
/** Parses and adds a class from the given stream to the list of classes to check. Does not log anything. */
374+
public void streamReadClassToCheck(final InputStream in, String name) throws IOException {
375375
final ClassReader reader;
376-
try (final InputStream in_ = in) {
377-
reader = AsmUtils.readAndPatchClass(in_);
376+
try {
377+
reader = AsmUtils.readAndPatchClass(in);
378378
} catch (IllegalArgumentException iae) {
379379
throw new IllegalArgumentException(String.format(Locale.ENGLISH,
380380
"The class file format of '%s' is too recent to be parsed by ASM.", name));
@@ -383,9 +383,21 @@ public void addClassToCheck(final InputStream in, String name) throws IOExceptio
383383
classesToCheck.put(metadata.getBinaryClassName(), metadata);
384384
}
385385

386+
/** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)!
387+
* Does not log anything.
388+
* @deprecated Do not use anymore, use {@link #streamReadClassToCheck(InputStream,String)} */
389+
@Deprecated
390+
public void addClassToCheck(final InputStream in, String name) throws IOException {
391+
try (InputStream _in = in) {
392+
streamReadClassToCheck(_in, name);
393+
}
394+
}
395+
386396
/** Parses and adds a class from the given file to the list of classes to check. Does not log anything. */
387397
public void addClassToCheck(File f) throws IOException {
388-
addClassToCheck(new FileInputStream(f), f.toString());
398+
try (InputStream in = new FileInputStream(f)) {
399+
streamReadClassToCheck(in, f.toString());
400+
}
389401
}
390402

391403
/** Parses and adds a multiple class files. */

src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.io.File;
2424
import java.io.IOException;
25+
import java.io.InputStream;
2526
import java.util.Collection;
2627
import java.util.EnumSet;
2728
import java.util.Iterator;
@@ -209,7 +210,9 @@ public void debug(String msg) {
209210
if (restrictClassFilename && name != null && !name.endsWith(".class")) {
210211
continue;
211212
}
212-
checker.addClassToCheck(r.getInputStream(), r.getName());
213+
try (InputStream in = r.getInputStream()) {
214+
checker.streamReadClassToCheck(in, r.getName());
215+
}
213216
foundClass = true;
214217
}
215218
if (!foundClass) {

src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java

+77-77
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@
1919
import static de.thetaphi.forbiddenapis.Checker.Option.*;
2020

2121
import java.io.File;
22+
import java.io.FileInputStream;
2223
import java.io.IOException;
23-
import java.io.InputStream;
24-
import java.io.OutputStream;
25-
import java.nio.file.Files;
26-
import java.nio.file.Path;
27-
import java.util.*;
2824
import java.net.JarURLConnection;
29-
import java.net.URLConnection;
30-
import java.net.URLClassLoader;
25+
import java.net.MalformedURLException;
3126
import java.net.URISyntaxException;
3227
import java.net.URL;
33-
import java.net.MalformedURLException;
34-
import java.util.jar.JarEntry;
35-
import java.util.jar.JarFile;
28+
import java.net.URLClassLoader;
29+
import java.net.URLConnection;
30+
import java.util.Arrays;
31+
import java.util.EnumSet;
32+
import java.util.LinkedHashSet;
33+
import java.util.Locale;
34+
import java.util.zip.ZipEntry;
35+
import java.util.zip.ZipInputStream;
3636

3737
import org.apache.commons.cli.CommandLine;
3838
import org.apache.commons.cli.DefaultParser;
@@ -41,6 +41,7 @@
4141
import org.apache.commons.cli.OptionGroup;
4242
import org.apache.commons.cli.Options;
4343
import org.codehaus.plexus.util.DirectoryScanner;
44+
import org.codehaus.plexus.util.SelectorUtils;
4445

4546
import de.thetaphi.forbiddenapis.AsmUtils;
4647
import de.thetaphi.forbiddenapis.Checker;
@@ -217,50 +218,11 @@ private void printHelp(Options options) {
217218
}
218219

219220
public void run() throws ExitException {
220-
File firstClassesDirectory = new File(cmd.getOptionValue(dirOpt.getLongOpt())).getAbsoluteFile();
221-
222-
if (!firstClassesDirectory.exists()) {
223-
throw new ExitException(EXIT_ERR_OTHER, "Directory with class files does not exist: " + firstClassesDirectory);
221+
final File classesDirectory = new File(cmd.getOptionValue(dirOpt.getLongOpt())).getAbsoluteFile();
222+
if (!classesDirectory.exists()) {
223+
throw new ExitException(EXIT_ERR_OTHER, "Directory with class files does not exist: " + classesDirectory);
224224
}
225225

226-
try {
227-
if (!firstClassesDirectory.isDirectory() && firstClassesDirectory.getName().endsWith(".jar")) {
228-
// Create a temporary directory
229-
Path tempDir = Files.createTempDirectory("jar_extract_");
230-
tempDir.toFile().deleteOnExit();
231-
System.out.println("Directory is a jar - temporary extracting to " + tempDir);
232-
233-
// Extract JAR contents
234-
try (JarFile jarFile = new JarFile(firstClassesDirectory)) {
235-
Enumeration<JarEntry> entries = jarFile.entries();
236-
while (entries.hasMoreElements()) {
237-
JarEntry entry = entries.nextElement();
238-
Path entryDestination = tempDir.resolve(entry.getName());
239-
240-
if (entry.isDirectory()) {
241-
Files.createDirectories(entryDestination);
242-
} else {
243-
Files.createDirectories(entryDestination.getParent());
244-
try (InputStream in = jarFile.getInputStream(entry);
245-
OutputStream out = Files.newOutputStream(entryDestination)) {
246-
byte[] buffer = new byte[8192];
247-
int len;
248-
while ((len = in.read(buffer)) > 0) {
249-
out.write(buffer, 0, len);
250-
}
251-
}
252-
}
253-
entryDestination.toFile().deleteOnExit();
254-
}
255-
}
256-
firstClassesDirectory = tempDir.toFile();
257-
}
258-
} catch (IOException e) {
259-
throw new ExitException(EXIT_ERR_OTHER, "Could not unpack jar file: " + e);
260-
}
261-
262-
final File classesDirectory = firstClassesDirectory;
263-
264226
// parse classpath given as argument; add -d to classpath, too
265227
final String[] classpath = cmd.getOptionValues(classpathOpt.getLongOpt());
266228
final URL[] urls;
@@ -305,27 +267,6 @@ public void run() throws ExitException {
305267
checker.addSuppressAnnotation(a);
306268
}
307269

308-
logger.info("Scanning for classes to check...");
309-
310-
String[] includes = cmd.getOptionValues(includesOpt.getLongOpt());
311-
if (includes == null || includes.length == 0) {
312-
includes = new String[] { "**/*.class" };
313-
}
314-
final String[] excludes = cmd.getOptionValues(excludesOpt.getLongOpt());
315-
final DirectoryScanner ds = new DirectoryScanner();
316-
ds.setBasedir(classesDirectory);
317-
ds.setCaseSensitive(true);
318-
ds.setIncludes(includes);
319-
ds.setExcludes(excludes);
320-
ds.addDefaultExcludes();
321-
ds.scan();
322-
final String[] files = ds.getIncludedFiles();
323-
if (files.length == 0) {
324-
throw new ExitException(EXIT_ERR_OTHER, String.format(Locale.ENGLISH,
325-
"No classes found in directory %s (includes=%s, excludes=%s).",
326-
classesDirectory, Arrays.toString(includes), Arrays.toString(excludes)));
327-
}
328-
329270
try {
330271
final String[] bundledSignatures = cmd.getOptionValues(bundledsignaturesOpt.getLongOpt());
331272
if (bundledSignatures != null) for (String bs : new LinkedHashSet<>(Arrays.asList(bundledSignatures))) {
@@ -362,13 +303,72 @@ public void run() throws ExitException {
362303
return;
363304
}
364305
}
306+
307+
logger.info("Scanning for classes to check...");
365308

366-
try {
367-
checker.addClassesToCheck(classesDirectory, files);
368-
} catch (IOException ioe) {
369-
throw new ExitException(EXIT_ERR_OTHER, "Failed to load one of the given class files: " + ioe);
309+
String[] includes = cmd.getOptionValues(includesOpt.getLongOpt());
310+
if (includes == null || includes.length == 0) {
311+
includes = new String[] { "**/*.class" };
370312
}
313+
final String[] excludes = cmd.getOptionValues(excludesOpt.getLongOpt());
314+
315+
if (classesDirectory.isDirectory()) {
316+
final DirectoryScanner ds = new DirectoryScanner();
317+
ds.setBasedir(classesDirectory);
318+
ds.setCaseSensitive(true);
319+
ds.setIncludes(includes);
320+
ds.setExcludes(excludes);
321+
ds.addDefaultExcludes();
322+
ds.scan();
323+
final String[] files = ds.getIncludedFiles();
324+
if (files.length == 0) {
325+
throw new ExitException(EXIT_ERR_OTHER, String.format(Locale.ENGLISH,
326+
"No classes found in directory %s (includes=%s, excludes=%s).",
327+
classesDirectory, Arrays.toString(includes), Arrays.toString(excludes)));
328+
}
329+
try {
330+
checker.addClassesToCheck(classesDirectory, files);
331+
} catch (IOException ioe) {
332+
throw new ExitException(EXIT_ERR_OTHER, "Failed to load one of the given class files: " + ioe);
333+
}
334+
} else if (classesDirectory.getName().endsWith(".jar") || classesDirectory.getName().endsWith(".zip")) {
335+
int filesFound = 0;
336+
try (final ZipInputStream zipin = new ZipInputStream(new FileInputStream(classesDirectory))) {
337+
ZipEntry entry;
338+
while ((entry = zipin.getNextEntry()) != null) {
339+
// cleanup name in the zip file (fix trailing slash and windows separators):
340+
final String name = entry.getName().replace('\\', '/').replaceFirst("^/+", "");
341+
next:
342+
for (String ipattern : includes) {
343+
if (SelectorUtils.match(ipattern, name)) {
344+
if (excludes != null) {
345+
for (String epattern : excludes) {
346+
if (SelectorUtils.match(epattern, name)) {
347+
break next;
348+
}
349+
}
350+
}
351+
try {
352+
checker.streamReadClassToCheck(zipin, name);
353+
filesFound++;
354+
} catch (IOException ioe) {
355+
throw new ExitException(EXIT_ERR_OTHER, String.format(Locale.ENGLISH, "Failed to load class file '%s' from jar/zip: %s", name, ioe));
356+
}
357+
break next;
358+
}
359+
}
360+
}
361+
}
362+
if (filesFound == 0) {
363+
throw new ExitException(EXIT_ERR_OTHER, String.format(Locale.ENGLISH,
364+
"No classes found in jar/zip file %s (includes=%s, excludes=%s).",
365+
classesDirectory, Arrays.toString(includes), Arrays.toString(excludes)));
366+
}
371367

368+
} else {
369+
throw new ExitException(EXIT_ERR_OTHER, "Classes directory parameter is neither a directory or a jar/zip file.");
370+
}
371+
372372
try {
373373
checker.run();
374374
} catch (ForbiddenApiException fae) {

0 commit comments

Comments
 (0)