Skip to content

Commit bf50b7d

Browse files
committed
Internal refactoring
- Set version of SPDX to avoid odd issue on Java 8 - Reuse BoundedInputStream - Add Archive tests - Add Segment tests
1 parent d497645 commit bf50b7d

File tree

8 files changed

+179
-40
lines changed

8 files changed

+179
-40
lines changed

pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
6363
javax.crypto.*;resolution:=optional,
6464
org.apache.commons.commons-codec;resolution:=optional,
6565
org.apache.commons.commons-io;resolution:=optional,
66+
org.apache.commons.lang3.reflect;resolution:=optional,
6667
*
6768
</commons.osgi.import>
6869

@@ -78,6 +79,8 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
7879
<slf4j.version>2.0.12</slf4j.version>
7980
<asm.version>9.6</asm.version>
8081
<project.build.outputTimestamp>2024-01-01T00:00:00Z</project.build.outputTimestamp>
82+
<!-- spdx 0.6.0 can require Java 11 depending on undocumented behavior which kicks in for us here. -->
83+
<commons.spdx.version>0.5.5</commons.spdx.version>
8184
</properties>
8285

8386
<issueManagement>
@@ -211,7 +214,6 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
211214
<groupId>org.apache.commons</groupId>
212215
<artifactId>commons-lang3</artifactId>
213216
<version>3.14.0</version>
214-
<scope>test</scope>
215217
</dependency>
216218
<dependency>
217219
<groupId>org.osgi</groupId>

src/main/java/org/apache/commons/compress/harmony/pack200/Codec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public int[] decodeInts(final int n, final InputStream in) throws IOException, P
144144
* @throws Pack200Exception if there is a problem decoding the value or that the value is invalid
145145
*/
146146
public int[] decodeInts(final int n, final InputStream in, final int firstValue) throws IOException, Pack200Exception {
147-
final int[] result = new int[check(n + 1, in)];
147+
final int[] result = new int[check(n, in) + 1];
148148
result[0] = firstValue;
149149
int last = firstValue;
150150
for (int i = 1; i < n + 1; i++) {

src/main/java/org/apache/commons/compress/harmony/unpack200/Archive.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.io.IOException;
2525
import java.io.InputStream;
2626
import java.io.OutputStream;
27-
import java.io.UncheckedIOException;
2827
import java.nio.file.Files;
2928
import java.nio.file.Path;
3029
import java.nio.file.Paths;
@@ -69,18 +68,15 @@ public class Archive {
6968
* Creates an Archive with streams for the input and output files. Note: If you use this method then calling {@link #setRemovePackFile(boolean)} will have
7069
* no effect.
7170
*
72-
* @param inputStream TODO
73-
* @param outputStream TODO
71+
* @param inputStream the input stream, preferably a {@link BoundedInputStream}. The bound can the the file size.
72+
* @param outputStream the JAR output stream.
73+
* @throws IOException if an I/O error occurs
7474
*/
75-
public Archive(final InputStream inputStream, final JarOutputStream outputStream) {
76-
this.inputStream = inputStream instanceof BoundedInputStream ? (BoundedInputStream) inputStream : new BoundedInputStream(inputStream);
75+
public Archive(final InputStream inputStream, final JarOutputStream outputStream) throws IOException {
76+
this.inputStream = Pack200UnpackerAdapter.newBoundedInputStream(inputStream);
7777
this.outputStream = outputStream;
7878
if (inputStream instanceof FileInputStream) {
79-
try {
80-
inputPath = Paths.get(((FileInputStream) inputStream).getFD().toString());
81-
} catch (final IOException e) {
82-
throw new UncheckedIOException(e);
83-
}
79+
inputPath = Paths.get(Pack200UnpackerAdapter.readPath((FileInputStream) inputStream));
8480
} else {
8581
inputPath = null;
8682
}
@@ -90,18 +86,18 @@ public Archive(final InputStream inputStream, final JarOutputStream outputStream
9086
/**
9187
* Creates an Archive with the given input and output file names.
9288
*
93-
* @param inputFileName TODO
94-
* @param outputFileName TODO
89+
* @param inputFileName the input file name.
90+
* @param outputFileName the output file name
9591
* @throws FileNotFoundException if the input file does not exist
96-
* @throws FileNotFoundException TODO
97-
* @throws IOException TODO
92+
* @throws IOException if an I/O error occurs
9893
*/
94+
@SuppressWarnings("resource")
9995
public Archive(final String inputFileName, final String outputFileName) throws FileNotFoundException, IOException {
10096
this.inputPath = Paths.get(inputFileName);
101-
this.outputFileName = outputFileName;
10297
this.inputSize = Files.size(this.inputPath);
10398
this.inputStream = new BoundedInputStream(Files.newInputStream(inputPath), inputSize);
10499
this.outputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(outputFileName)));
100+
this.outputFileName = outputFileName;
105101
}
106102

107103
private boolean available(final InputStream inputStream) throws IOException {

src/main/java/org/apache/commons/compress/harmony/unpack200/Pack200UnpackerAdapter.java

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818

1919
import java.io.BufferedInputStream;
2020
import java.io.File;
21+
import java.io.FileInputStream;
22+
import java.io.FilterInputStream;
2123
import java.io.IOException;
2224
import java.io.InputStream;
25+
import java.net.URISyntaxException;
26+
import java.net.URL;
2327
import java.nio.file.Files;
2428
import java.nio.file.Path;
2529
import java.nio.file.Paths;
@@ -29,25 +33,124 @@
2933
import org.apache.commons.compress.harmony.pack200.Pack200Exception;
3034
import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
3135
import org.apache.commons.io.input.BoundedInputStream;
36+
import org.apache.commons.lang3.reflect.FieldUtils;
3237

3338
/**
3439
* This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
3540
*/
3641
public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
3742

43+
/**
44+
* Creates a new BoundedInputStream bound by the size of the given file.
45+
* <p>
46+
* The new BoundedInputStream wraps a new {@link BufferedInputStream}.
47+
* </p>
48+
*
49+
* @param file The file.
50+
* @return a new BoundedInputStream
51+
* @throws IOException if an I/O error occurs
52+
*/
3853
static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
3954
return newBoundedInputStream(file.toPath());
4055
}
4156

57+
private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
58+
return newBoundedInputStream(readPath(fileInputStream));
59+
}
60+
61+
static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
62+
if (inputStream instanceof BoundedInputStream) {
63+
return (BoundedInputStream) inputStream;
64+
}
65+
if (inputStream instanceof FilterInputStream) {
66+
return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
67+
}
68+
if (inputStream instanceof FileInputStream) {
69+
return newBoundedInputStream((FileInputStream) inputStream);
70+
}
71+
// No limit
72+
return new BoundedInputStream(inputStream);
73+
}
74+
75+
/**
76+
* Creates a new BoundedInputStream bound by the size of the given path.
77+
* <p>
78+
* The new BoundedInputStream wraps a new {@link BufferedInputStream}.
79+
* </p>
80+
*
81+
* @param path The path.
82+
* @return a new BoundedInputStream
83+
* @throws IOException if an I/O error occurs
84+
*/
4285
@SuppressWarnings("resource")
43-
static BoundedInputStream newBoundedInputStream(final Path inputPath) throws IOException {
44-
return new BoundedInputStream(new BufferedInputStream(Files.newInputStream(inputPath)), Files.size(inputPath));
86+
static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
87+
return new BoundedInputStream(new BufferedInputStream(Files.newInputStream(path)), Files.size(path));
4588
}
4689

90+
/**
91+
* Creates a new BoundedInputStream bound by the size of the given file.
92+
* <p>
93+
* The new BoundedInputStream wraps a new {@link BufferedInputStream}.
94+
* </p>
95+
*
96+
* @param first the path string or initial part of the path string.
97+
* @param more additional strings to be joined to form the path string.
98+
* @return a new BoundedInputStream
99+
* @throws IOException if an I/O error occurs
100+
*/
47101
static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
48102
return newBoundedInputStream(Paths.get(first, more));
49103
}
50104

105+
/**
106+
* Creates a new BoundedInputStream bound by the size of the given URL to a file.
107+
* <p>
108+
* The new BoundedInputStream wraps a new {@link BufferedInputStream}.
109+
* </p>
110+
*
111+
* @param path The URL.
112+
* @return a new BoundedInputStream
113+
* @throws IOException if an I/O error occurs
114+
* @throws URISyntaxException
115+
*/
116+
static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
117+
return newBoundedInputStream(Paths.get(url.toURI()));
118+
}
119+
120+
@SuppressWarnings("unchecked")
121+
private static <T> T readField(final Object object, final String fieldName) {
122+
try {
123+
return (T) FieldUtils.readField(object, fieldName, true);
124+
} catch (final IllegalAccessException e) {
125+
return null;
126+
}
127+
}
128+
129+
static String readPath(final FileInputStream fis) {
130+
return readField(fis, "path");
131+
}
132+
133+
/**
134+
* Unwraps the given FilterInputStream to return its wrapped InputStream.
135+
*
136+
* @param filterInputStream The FilterInputStream to unwrap.
137+
* @return The wrapped InputStream
138+
*/
139+
static InputStream unwrap(final FilterInputStream filterInputStream) {
140+
return readField(filterInputStream, "in");
141+
}
142+
143+
/**
144+
* Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
145+
*
146+
* @param filterInputStream The FilterInputStream to unwrap.
147+
* @return The wrapped InputStream
148+
*/
149+
@SuppressWarnings("resource")
150+
static InputStream unwrap(final InputStream inputStream) {
151+
return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
152+
}
153+
51154
@Override
52155
public void unpack(final File file, final JarOutputStream out) throws IOException {
53156
if (file == null || out == null) {

src/main/java/org/apache/commons/compress/harmony/unpack200/Segment.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
5050
import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute;
5151
import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute;
52+
import org.apache.commons.io.input.BoundedInputStream;
5253

5354
/**
5455
* A Pack200 archive consists of one or more segments. Each segment is stand-alone, in the sense that every segment has the magic number header; thus, every
@@ -462,7 +463,7 @@ public void setPreRead(final boolean value) {
462463
/**
463464
* Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding JarOuputStream.
464465
*
465-
* @param inputStream a packed input stream.
466+
* @param inputStream a packed input stream, preferably a {@link BoundedInputStream}.
466467
* @param out output stream.
467468
* @throws Pack200Exception if there is a problem unpacking
468469
* @throws IOException if there is a problem with I/O during unpacking
@@ -484,7 +485,8 @@ void unpackProcess() throws IOException, Pack200Exception {
484485
* Package-private accessors for unpacking stages
485486
*/
486487
void unpackRead(final InputStream inputStream) throws IOException, Pack200Exception {
487-
final InputStream in = inputStream.markSupported() ? inputStream : new BufferedInputStream(inputStream);
488+
@SuppressWarnings("resource")
489+
final InputStream in = Pack200UnpackerAdapter.newBoundedInputStream(inputStream);
488490

489491
header = new SegmentHeader(this);
490492
header.read(in);

src/main/java/org/apache/commons/compress/java/util/jar/Pack200.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.jar.JarOutputStream;
3131

3232
import org.apache.commons.compress.harmony.archive.internal.nls.Messages;
33+
import org.apache.commons.io.input.BoundedInputStream;
3334

3435
/**
3536
* Class factory for {@link Pack200.Packer} and {@link Pack200.Unpacker}.
@@ -230,7 +231,7 @@ public interface Unpacker {
230231
/**
231232
* Unpacks the contents of the specified {@code File} to the specified JAR output stream.
232233
*
233-
* @param in file to be uncompressed.
234+
* @param in file to uncompress.
234235
* @param out JAR output stream of uncompressed data.
235236
* @throws IOException if I/O exception occurs.
236237
*/
@@ -239,7 +240,7 @@ public interface Unpacker {
239240
/**
240241
* Unpacks the specified stream to the specified JAR output stream.
241242
*
242-
* @param in stream to uncompressed.
243+
* @param in stream to uncompress, preferably a {@link BoundedInputStream}.
243244
* @param out JAR output stream of uncompressed data.
244245
* @throws IOException if I/O exception occurs.
245246
*/

src/test/java/org/apache/commons/compress/harmony/unpack200/ArchiveTest.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@
3333
import java.io.InputStream;
3434
import java.io.InputStreamReader;
3535
import java.net.URL;
36-
import java.nio.file.Files;
37-
import java.nio.file.Path;
38-
import java.nio.file.Paths;
3936
import java.util.Enumeration;
4037
import java.util.jar.JarEntry;
4138
import java.util.jar.JarFile;
@@ -196,18 +193,39 @@ public void testLoggingOptions() throws Exception {
196193
"references_oom.pack",
197194
"segment_header_oom.pack",
198195
"signatures_oom.pack"
199-
// @formatter:on
196+
// @formatter:on
200197
})
201198
// Tests of various files that can cause out of memory errors
202-
public void testParsingOOM(final String testFileName) throws Exception {
199+
public void testParsingOOMBounded(final String testFileName) throws Exception {
203200
final URL url = Segment.class.getResource("/org/apache/commons/compress/pack/" + testFileName);
204-
final Path path = Paths.get(url.toURI());
205-
try (InputStream in = new BoundedInputStream(new BufferedInputStream(Files.newInputStream(path)), Files.size(path));
201+
try (BoundedInputStream in = Pack200UnpackerAdapter.newBoundedInputStream(url);
206202
JarOutputStream out = new JarOutputStream(NullOutputStream.INSTANCE)) {
207203
assertThrows(IOException.class, () -> new Archive(in, out).unpack());
208204
}
209205
}
210206

207+
@ParameterizedTest
208+
@ValueSource(strings = {
209+
// @formatter:off
210+
"bandint_oom.pack",
211+
"cpfloat_oom.pack",
212+
"cputf8_oom.pack",
213+
"favoured_oom.pack",
214+
"filebits_oom.pack",
215+
"flags_oom.pack",
216+
"references_oom.pack",
217+
"segment_header_oom.pack",
218+
"signatures_oom.pack"
219+
// @formatter:on
220+
})
221+
// Tests of various files that can cause out of memory errors
222+
public void testParsingOOMUnbounded(final String testFileName) throws Exception {
223+
try (InputStream is = Segment.class.getResourceAsStream("/org/apache/commons/compress/pack/" + testFileName);
224+
JarOutputStream out = new JarOutputStream(NullOutputStream.INSTANCE)) {
225+
assertThrows(IOException.class, () -> new Archive(is, out).unpack());
226+
}
227+
}
228+
211229
@Test
212230
public void testRemovePackFile() throws Exception {
213231
final File original = new File(Archive.class.getResource("/pack200/sql.pack.gz").toURI());
@@ -221,10 +239,10 @@ public void testRemovePackFile() throws Exception {
221239
read = inputStream.read(bytes);
222240
}
223241
}
224-
final String inputFile = copy.getPath();
242+
final String inputFileName = copy.getPath();
225243
final File file = createTempFile("sqlout", ".jar");
226-
final String outputFile = file.getPath();
227-
final Archive archive = new Archive(inputFile, outputFile);
244+
final String outputFileName = file.getPath();
245+
final Archive archive = new Archive(inputFileName, outputFileName);
228246
archive.setRemovePackFile(true);
229247
archive.unpack();
230248
assertFalse(copy.exists());

0 commit comments

Comments
 (0)