This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
commit 1ecd39bc5bda8b3b529aabd0a80384bed41a4984 Author: Gary Gregory <[email protected]> AuthorDate: Sun Aug 16 10:46:52 2026 -0400 [GZip] GzipCompressorInputStream now throws CompressorException instead of IllegalArgumetException/IllegalStateException. --- src/changes/changes.xml | 2 +- .../compress/compressors/gzip/GzipParameters.java | 17 ++++--- .../gzip/GzipCompressorInputStreamTest.java | 58 ++++++++++++++++++++++ .../compressors/gzip/GzipParametersTest.java | 9 ++-- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3ab6c4fe2..f4e45f82a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -149,7 +149,7 @@ The <action> type attribute can be add,update,fix,remove. <!-- FIX gzip --> <action type="fix" dev="ggregory" due-to="Gary Gregory">[GZip] GzipParameters.setOperatingSystem(int) now throws CompressorException on illegal input.</action> <action type="fix" issue="COMPRESS-705" dev="ggregory" due-to="Mario Fredenhagen, Gary Gregory">[GZip] GZip IOException: Extra subfield length exceeds remaining bytes in extra field; use new option GzipCompressorInputStream.Builder.setIgnoreExtraField(boolean).</action> - <action type="fix" dev="ggregory" due-to="Gary Gregory">[GZip] GzipCompressorInputStream now throws CompressorException instead of IllegalArgumetException/IllegalStateException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory, KALI 834X">[GZip] GzipCompressorInputStream now throws CompressorException instead of IllegalArgumetException/IllegalStateException.</action> <!-- FIX snappy --> <action type="fix" dev="ggregory" due-to="Stanislav Fort, Gary Gregory">[Snappy] Fix for when a valid raw Snappy stream with uncompressed size > 2 GiB used to decompress and then fail at physical EOF with a “Premature end of stream” exception instead of completing cleanly.</action> <!-- FIX deflate64 --> diff --git a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipParameters.java b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipParameters.java index d02ff11cf..eed5a87cf 100644 --- a/src/main/java/org/apache/commons/compress/compressors/gzip/GzipParameters.java +++ b/src/main/java/org/apache/commons/compress/compressors/gzip/GzipParameters.java @@ -26,6 +26,7 @@ import java.util.zip.Deflater; import org.apache.commons.compress.CompressException; +import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -509,9 +510,9 @@ public int hashCode() { operatingSystem, trailerCrc, trailerISize); } - private String requireNonNulByte(final String text) { + private String requireNonNulByte(final String text) throws CompressorException { if (StringUtils.isNotEmpty(text) && ArrayUtils.contains(text.getBytes(fileNameCharset), (byte) 0)) { - throw new IllegalArgumentException("String encoded in Charset '" + fileNameCharset + "' contains the nul byte 0 which is not supported in gzip."); + throw new CompressorException("String encoded in Charset '" + fileNameCharset + "' contains the nul byte 0 which is not supported in gzip."); } return text; } @@ -534,9 +535,9 @@ public void setBufferSize(final int bufferSize) { * Sets an arbitrary user-defined comment. * * @param comment A user-defined comment. - * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. + * @throws CompressorException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. */ - public void setComment(final String comment) { + public void setComment(final String comment) throws CompressorException { this.comment = requireNonNulByte(comment); } @@ -586,11 +587,11 @@ void setExtraFieldXlen(final int extraFieldXlen) { * Sets the name of the compressed file. * * @param fileName The name of the file without the directory path. - * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. + * @throws CompressorException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. * @deprecated Use {@link #setFileName(String)}. */ @Deprecated - public void setFilename(final String fileName) { + public void setFilename(final String fileName) throws CompressorException { setFileName(fileName); } @@ -598,9 +599,9 @@ public void setFilename(final String fileName) { * Sets the name of the compressed file. * * @param fileName The name of the file without the directory path. - * @throws IllegalArgumentException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. + * @throws CompressorException if the encoded bytes would contain a nul byte '\0' reserved for gzip field termination. */ - public void setFileName(final String fileName) { + public void setFileName(final String fileName) throws CompressorException { this.fileName = requireNonNulByte(fileName); } diff --git a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStreamTest.java index 67301ec88..4b218a147 100644 --- a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorInputStreamTest.java @@ -21,10 +21,14 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -35,9 +39,13 @@ import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.io.IOUtils; import org.apache.commons.io.file.PathUtils; import org.apache.commons.io.function.IOStream; @@ -353,4 +361,54 @@ void testReadGzipFileCreatedByCli() throws IOException { } } + private static byte[] gzipMemberWithFileName(final byte[] fileNameField) throws IOException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(0x1f); + bos.write(0x8b); // magic + bos.write(0x08); // deflate + bos.write(0x08); // FLG: FNAME present + bos.write(new byte[] { 0, 0, 0, 0 }); // MTIME + bos.write(0x00); // XFL + bos.write(0xff); // OS unknown + bos.write(fileNameField); + bos.write(0x00); // NUL terminator for FNAME + final ByteArrayOutputStream deflated = new ByteArrayOutputStream(); + try (DeflaterOutputStream dos = new DeflaterOutputStream(deflated, new Deflater(Deflater.DEFAULT_COMPRESSION, true))) { + // empty payload + } + bos.write(deflated.toByteArray()); + final long crc = new CRC32().getValue(); + bos.write((int) (crc & 0xff)); + bos.write((int) (crc >> 8 & 0xff)); + bos.write((int) (crc >> 16 & 0xff)); + bos.write((int) (crc >> 24 & 0xff)); + bos.write(new byte[] { 0, 0, 0, 0 }); // ISIZE + return bos.toByteArray(); + } + + /** + * A member whose FNAME field cannot be round-tripped through the configured charset must fail with a CompressorException rather than a raw + * IllegalArgumentException escaping the IOException contract of read(). + */ + @Test + void testUnencodableFileNameThrowsCompressorException() throws IOException { + // Under UTF-32BE a single stray byte decodes to U+FFFD, which re-encodes to bytes containing a NUL. + final byte[] gz = gzipMemberWithFileName(new byte[] { 0x41 }); + assertThrows(CompressorException.class, () -> { + try (GzipCompressorInputStream gis = GzipCompressorInputStream.builder() + .setInputStream(new ByteArrayInputStream(gz)) + .setFileNameCharset(Charset.forName("UTF-32BE")) + .get()) { + IOUtils.toString(gis, StandardCharsets.ISO_8859_1); + } + }); + // A member that round-trips cleanly still parses. + final byte[] ok = gzipMemberWithFileName("name.txt".getBytes(StandardCharsets.ISO_8859_1)); + try (GzipCompressorInputStream gis = GzipCompressorInputStream.builder() + .setInputStream(new ByteArrayInputStream(ok)) + .get()) { + assertEquals("", IOUtils.toString(gis, StandardCharsets.ISO_8859_1)); + assertEquals("name.txt", gis.getMetaData().getFileName()); + } + } } diff --git a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipParametersTest.java b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipParametersTest.java index ecc5a466b..3d7194ed6 100644 --- a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipParametersTest.java +++ b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipParametersTest.java @@ -30,6 +30,7 @@ import java.util.zip.Deflater; import org.apache.commons.compress.CompressException; +import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.compress.compressors.gzip.GzipParameters.OS; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -91,11 +92,11 @@ void testIllegalCommentOrFileName(final Charset charset, final String text) { final GzipParameters gzipParameters = new GzipParameters(); // null resets to default value gzipParameters.setFileNameCharset(charset); - assertThrows(IllegalArgumentException.class, () -> gzipParameters.setComment(text)); + assertThrows(CompressorException.class, () -> gzipParameters.setComment(text)); assertNull(gzipParameters.getComment()); - assertThrows(IllegalArgumentException.class, () -> gzipParameters.setFilename(text)); + assertThrows(CompressorException.class, () -> gzipParameters.setFilename(text)); assertNull(gzipParameters.getFileName()); - assertThrows(IllegalArgumentException.class, () -> gzipParameters.setFileName(text)); + assertThrows(CompressorException.class, () -> gzipParameters.setFileName(text)); assertNull(gzipParameters.getFileName()); assertEquals(gzipParameters, gzipParameters); } @@ -111,7 +112,7 @@ void testIllegalCommentOrFileName(final Charset charset, final String text) { "UTF-8 , helloéworld" }) // @formatter:on - void testLegalCommentOrFileName(final Charset charset, final String text) { + void testLegalCommentOrFileName(final Charset charset, final String text) throws CompressorException { final GzipParameters gzipParameters = new GzipParameters(); // null resets to default value gzipParameters.setFileNameCharset(charset);
