Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 2a20225

Browse files
committed
Fix various issues with JarFileScanner.
Overview: - Previously JarFileScanner assumed backslashes to be the platform independent file separator for zip files. This is incorrect (and in fact, the associated unit tests did assume forward slashes, using "javax/ws/rs" to denote the package "javax.ws.rs"). See [1] for details. - As a result the non-recursive scanning mode was broken: failing to find the backslash delimiter, the scanner would return classes from subpackages even when it was instructed not to do so. This has been fixed. - The scanner also did not verify that "matching" classes were in a proper subdirectory of the indicated package. As a result a class such as javax.ws.rs.GET was matched for the following "packages": - javax/ws/r (too short, misses an "s") - javax/ws/rs/GE (too long, includes class name prefix) This has been fixed as well. The class' unit tests were extended to cover the problematic cases. Of the five new unit tests, three fail prior to the fix. [1] https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
1 parent 5976f56 commit 2a20225

File tree

2 files changed

+72
-34
lines changed

2 files changed

+72
-34
lines changed

core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarFileScanner.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public final class JarFileScanner implements ResourceFinder {
5858

5959
private static final Logger LOGGER = Logger.getLogger(JarFileScanner.class.getName());
6060
// platform independent file separator within the jar file
61-
private static final char JAR_FILE_SEPARATOR = '\\';
61+
private static final char JAR_FILE_SEPARATOR = '/';
6262

6363
private final JarInputStream jarInputStream;
6464
private final String parent;
@@ -91,14 +91,17 @@ public boolean hasNext() {
9191
break;
9292
}
9393
if (!next.isDirectory() && next.getName().startsWith(parent)) {
94+
final String suffix = next.getName().substring(parent.length());
9495
if (recursive) {
9596
// accept any entries with the prefix
96-
break;
97-
}
98-
// accept only entries directly in the folder.
99-
final String suffix = next.getName().substring(parent.length());
100-
if (suffix.lastIndexOf(JAR_FILE_SEPARATOR) <= 0) {
101-
break;
97+
if (parent.isEmpty() || suffix.indexOf(JAR_FILE_SEPARATOR) == 0) {
98+
break;
99+
}
100+
} else {
101+
// accept only entries directly in the folder.
102+
if (suffix.lastIndexOf(JAR_FILE_SEPARATOR) == (parent.isEmpty() ? -1 : 0)) {
103+
break;
104+
}
102105
}
103106
}
104107
} while (true);

core-server/src/test/java/org/glassfish/jersey/server/internal/scanning/JarFileScannerTest.java

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,33 @@
4040
package org.glassfish.jersey.server.internal.scanning;
4141

4242
import java.io.FileInputStream;
43+
import java.io.IOException;
4344
import java.io.InputStream;
4445
import java.util.Enumeration;
4546
import java.util.jar.JarEntry;
4647
import java.util.jar.JarFile;
48+
import java.util.regex.Pattern;
4749

4850
import org.junit.Before;
4951
import org.junit.Test;
52+
import org.junit.experimental.theories.DataPoint;
53+
import org.junit.experimental.theories.Theories;
54+
import org.junit.experimental.theories.Theory;
55+
import org.junit.runner.RunWith;
5056
import static org.hamcrest.CoreMatchers.equalTo;
57+
import static org.junit.Assert.assertFalse;
5158
import static org.junit.Assert.assertThat;
5259
import static org.junit.Assert.fail;
5360

5461
/**
5562
* @author Martin Snyder
5663
*/
64+
@RunWith(Theories.class)
5765
public class JarFileScannerTest {
66+
@DataPoint
67+
public static final boolean RECURSIVE = true;
68+
@DataPoint
69+
public static final boolean NON_RECURSIVE = false;
5870

5971
private String jaxRsApiPath;
6072

@@ -75,55 +87,78 @@ public void setUp() throws Exception {
7587
}
7688
}
7789

78-
/**
79-
* Test case for JERSEY-2197, JERSEY-2175.
80-
*/
8190
@Test
82-
public void testClassEnumeration() throws Exception {
83-
// Count actual entries.
84-
final JarFile jarFile = new JarFile(jaxRsApiPath);
91+
public void testRecursiveResourceEnumerationOfAllPackages() throws IOException {
92+
final int actualEntries = countJarEntriesByPattern(Pattern.compile(".*\\.(class|properties|xml)"));
93+
final int scannedEntries = countJarEntriesUsingScanner("", true);
94+
assertThat("Failed to enumerate all contents of javax.ws.rs-api", scannedEntries, equalTo(actualEntries));
95+
}
8596

86-
int actualEntries = 0;
87-
try {
88-
final Enumeration<JarEntry> entries = jarFile.entries();
97+
@Test
98+
public void testRecursiveClassEnumerationWithExistantPackage() throws IOException {
99+
final int actualEntries = countJarEntriesByPattern(Pattern.compile("javax/ws/rs/.*\\.class"));
100+
final int scannedEntries = countJarEntriesUsingScanner("javax/ws/rs", true);
101+
assertThat("Failed to enumerate all contents of javax.ws.rs-api", scannedEntries, equalTo(actualEntries));
102+
}
89103

104+
@Test
105+
public void testNonRecursiveClassEnumerationWithExistantPackage() throws IOException {
106+
final int actualEntries = countJarEntriesByPattern(Pattern.compile("javax/ws/rs/[^/]*\\.class"));
107+
final int scannedEntries = countJarEntriesUsingScanner("javax/ws/rs", false);
108+
assertThat("Failed to enumerate package 'javax.ws.rs' of javax.ws.rs-api", scannedEntries, equalTo(actualEntries));
109+
}
110+
111+
private int countJarEntriesByPattern(final Pattern pattern) throws IOException {
112+
int matchingEntries = 0;
113+
114+
try (final JarFile jarFile = new JarFile(this.jaxRsApiPath)) {
115+
final Enumeration<JarEntry> entries = jarFile.entries();
90116
while (entries.hasMoreElements()) {
91117
final JarEntry entry = entries.nextElement();
92-
93-
if (entry.getName().endsWith(".class")) {
94-
actualEntries++;
118+
if (pattern.matcher(entry.getName()).matches()) {
119+
matchingEntries++;
95120
}
96121
}
97-
} finally {
98-
jarFile.close();
99122
}
100123

101-
// Scan entries using Jersey scanner.
102-
final InputStream jaxRsApi = new FileInputStream(jaxRsApiPath);
124+
return matchingEntries;
125+
}
103126

104-
try {
105-
final JarFileScanner jarFileScanner = new JarFileScanner(jaxRsApi, "javax/ws/rs", true);
127+
private int countJarEntriesUsingScanner(final String parent, final boolean recursive) throws IOException {
128+
int scannedEntryCount = 0;
106129

107-
int scannedEntryCount = 0;
130+
try (final InputStream jaxRsApi = new FileInputStream(this.jaxRsApiPath)) {
131+
final JarFileScanner jarFileScanner = new JarFileScanner(jaxRsApi, parent, recursive);
108132
while (jarFileScanner.hasNext()) {
109133
// Fetch next entry.
110134
jarFileScanner.next();
111135

136+
// JERSEY-2175 and JERSEY-2197:
112137
// This test doesn't actually do anything with the input stream, but it is important that it
113138
// open/close the stream to simulate actual usage. The reported defect is only exposed if you
114139
// call open/close in some fashion.
115-
final InputStream classStream = jarFileScanner.open();
116-
117-
try {
140+
try (final InputStream classStream = jarFileScanner.open()) {
118141
scannedEntryCount++;
119-
} finally {
120-
classStream.close();
121142
}
122143
}
144+
}
145+
146+
return scannedEntryCount;
147+
}
148+
149+
@Theory
150+
public void testClassEnumerationWithNonexistentPackage(final boolean recursive) throws IOException {
151+
try (final InputStream jaxRsApi = new FileInputStream(this.jaxRsApiPath)) {
152+
final JarFileScanner jarFileScanner = new JarFileScanner(jaxRsApi, "javax/ws/r", recursive);
153+
assertFalse("Unexpectedly found package 'javax.ws.r' in javax.ws.rs-api", jarFileScanner.hasNext());
154+
}
155+
}
123156

124-
assertThat("Failed to enumerate all contents of javax.ws.rs-api.", scannedEntryCount, equalTo(actualEntries));
125-
} finally {
126-
jaxRsApi.close();
157+
@Theory
158+
public void testClassEnumerationWithClassPrefix(final boolean recursive) throws IOException {
159+
try (final InputStream jaxRsApi = new FileInputStream(this.jaxRsApiPath)) {
160+
final JarFileScanner jarFileScanner = new JarFileScanner(jaxRsApi, "javax/ws/rs/GE", recursive);
161+
assertFalse("Unexpectedly found package 'javax.ws.rs.GE' in javax.ws.rs-api", jarFileScanner.hasNext());
127162
}
128163
}
129164
}

0 commit comments

Comments
 (0)