Skip to content
Commits on Source (5)
Apache Commons Compress
Copyright 2002-2018 The Apache Software Foundation
Copyright 2002-2019 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).
......
......@@ -5,6 +5,112 @@ compression and archive formats. These include: bzip2, gzip, pack200,
lzma, xz, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4,
Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
Release 1.19
------------
ZipArchiveInputStream and ZipFile will no longer throw an exception if
an extra field generally understood by Commons Compress is malformed
but rather turn them into UnrecognizedExtraField instances. You can
influence the way extra fields are parsed in more detail by using the
new getExtraFields(ExtraFieldParsingBehavior) method of
ZipArchiveEntry now.
Some of the ZIP extra fields related to strong encryption will now
throw ZipExceptions rather than ArrayIndexOutOfBoundsExceptions in
certain cases when used directly. There is no practical difference
when they are read via ZipArchiveInputStream or ZipFile.
New features:
o It is now possible to skip parsing of local file headers when
using ZipFile which may speed up reading the archive at the
cost of potentially missing important information. See the
javadocs of the ZipFile class for details.
Issue: COMPRESS-466.
o TarArchiveInputStream has a new constructor-arg lenient that
can be used to accept certain broken archives.
Issue: COMPRESS-469.
o ArjArchiveEntry and SevenZArchiveEntry now implement hashCode
and equals.
Issue: COMPRESS-475.
o Added a MultiReadOnlySeekableByteChannel class
that can be used to concatenate the parts of a multi volume 7z
archive so that SevenZFile can read them.
Issue: COMPRESS-231.
Thanks to Tim Underwood.
Fixed Bugs:
o ZipArchiveInputStream could forget the compression level has
changed under certain circumstances.
o Fixed another potential resource leak in
ParallelScatterZipCreator#writeTo.
Issue: COMPRESS-470.
o ArArchiveInputStream could think it had hit EOF prematurely.
Github Pull Request #74.
Thanks to Alex Bertram.
o Throw IOException rather than RuntimeExceptions for certain
malformed LZ4 or Snappy inputs.
Issue: COMPRESS-490.
o ZipArchiveInputStream failed to read stored entries with a
data descriptor if the data descriptor didn't use the
signature invented by InfoZIP.
Issue: COMPRESS-482.
Changes:
o SevenZFile now provides a way to cap memory consumption for
LZMA(2) compressed content.
Github Pull Request #76.
Issue: COMPRESS-481.
Thanks to Robin Schimpf.
o The ARJ package has been updated to contain constants for more
recent specifications.
Issue: COMPRESS-464.
Thanks to Rostislav Krasny.
o Update optional library zstd-jni from 1.3.3-3 to 1.4.0-1.
Issue: COMPRESS-484.
o ParallelScatterZipCreator now writes the entries to the
gathered output in the same order they have been added.
Github Pull Requests #78 and #79.
Issue: COMPRESS-485.
Thanks to Hervé Boutemy, Tibor Digana.
o The Expander and Archive example classes can leak resources
they have wrapped around passed in streams or channels. The
methods consuming streams and channels have been adapted to
give the calling code a chance to deal with those wrapper
resources.
Issue: COMPRESS-486.
o ZipArchiveInputStream and ZipFile no longer assume Commons
Compress would understand extra fields better than the writer
of the archive and silently turn extra fields that Commons
Compress should understand into UnrecognizedExtraFields if
parsing said fields fails.
It is now possible to take more control over the extra field
parsing process with a new overload of
ZipArchiveEntry#getExtraFields.
Issue: COMPRESS-479.
o ZipArchiveInputStream will now throw an exception if reading a
stored entry with a data descriptor and the data descriptor
doesn't match what it has actually read.
The most common case for a situation like this is a stored ZIP
archive inside of the archive ZipArchiveInputStream currently
reads. In such a case ZipArchiveInputStream would happily
extract the contained archive and stop once the central
directory of the inner archive has been hit. This is a case
where ZipArchiveInputStream simply can not be used and only
ZipFile is able to read the archive.
The only other explanation is a broken archive. So the
exception prevents users from thinking they had successfully
read the contents of the archive.
Issue: COMPRESS-483.
o The 7zip tools provide a default name for archive entries
without name; SevenZFile returns a null name for such
entries. A new method getDefaultName has been added to derive
the same name the 7zip tools would use and an option has been
added that sets SevenZArchiveEntry's name to the default name
if it is not contained inside the archive.
Issue: COMPRESS-478.
Release 1.18
------------
......
libcommons-compress-java (1.19-1) unstable; urgency=medium
* New upstream release
- Removed the CVE-2019-12402 patch (fixed upstream)
* Standards-Version updated to 4.5.0
-- Emmanuel Bourg <ebourg@apache.org> Mon, 27 Jan 2020 12:35:59 +0100
libcommons-compress-java (1.18-3) unstable; urgency=medium
* Team upload.
......
......@@ -17,7 +17,7 @@ Build-Depends:
libmaven-javadoc-plugin-java,
libxz-java (>= 1.5),
maven-debian-helper
Standards-Version: 4.4.0
Standards-Version: 4.5.0
Vcs-Git: https://salsa.debian.org/java-team/libcommons-compress-java.git
Vcs-Browser: https://salsa.debian.org/java-team/libcommons-compress-java
Homepage: https://commons.apache.org/proper/commons-compress/
......
Description: addresses CVE-2019-12402 (Debian: #939610)
From: Stefan Bodewig <bodewig@apache.org>
Date: Fri, 23 Aug 2019 14:12:05 +0000 (+0200)
Subject: unit tests for encoding logic
X-Git-Tag: 1.19-RC1~6
X-Git-Url: https://gitbox.apache.org/repos/asf?p=commons-compress.git;a=commitdiff_plain;h=4ad5d80a6272e007f64a6ac66829ca189a8093b9;hp=16a0c84e84b93cc8c107b7ff3080bd11317ab581
unit tests for encoding logic
---
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/NioZipEncoding.java b/src/main/java/org/apache/commons/compress/archivers/zip/NioZipEncoding.java
index 0a7581a..4ce9c20 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/NioZipEncoding.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/NioZipEncoding.java
@@ -112,6 +112,9 @@ class NioZipEncoding implements ZipEncoding, CharsetAccessor {
} else if (res.isOverflow()) {
int increment = estimateIncrementalEncodingSize(enc, cb.remaining());
out = ZipEncodingHelper.growBufferBy(out, increment);
+
+ } else if (res.isUnderflow() || res.isError()) {
+ break;
}
}
// tell the encoder we are done
diff --git a/src/test/java/org/apache/commons/compress/archivers/zip/NioZipEncodingTest.java b/src/test/java/org/apache/commons/compress/archivers/zip/NioZipEncodingTest.java
new file mode 100644
index 0000000..a04730c
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/archivers/zip/NioZipEncodingTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.commons.compress.archivers.zip;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NioZipEncodingTest {
+
+ private static final String UMLAUTS = "\u00e4\u00f6\u00fc";
+
+ @Test
+ public void umlautToUTF16BE() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.UTF_16BE, false);
+ ByteBuffer bb = e.encode(UMLAUTS);
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertArrayEquals(UMLAUTS.getBytes(StandardCharsets.UTF_16BE), result);
+ }
+
+ @Test
+ public void umlautToUTF8() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.UTF_8, true);
+ ByteBuffer bb = e.encode("\u00e4\u00f6\u00fc");
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertArrayEquals(UMLAUTS.getBytes(StandardCharsets.UTF_8), result);
+ }
+
+ @Test
+ public void umlautToISO88591() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.ISO_8859_1, true);
+ ByteBuffer bb = e.encode("\u00e4\u00f6\u00fc");
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertArrayEquals(UMLAUTS.getBytes(StandardCharsets.ISO_8859_1), result);
+ }
+
+ @Test
+ public void unmappableUmlauts() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.US_ASCII, false);
+ ByteBuffer bb = e.encode("\u00e4\u00f6\u00fc");
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertEquals("%U00E4%U00F6%U00FC", new String(result, StandardCharsets.US_ASCII));
+ }
+
+ private static final String RAINBOW_EMOJI = "\ud83c\udf08";
+
+ @Test
+ public void unmappableRainbowEmoji() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.US_ASCII, false);
+ ByteBuffer bb = e.encode(RAINBOW_EMOJI);
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertEquals("%UD83C%UDF08", new String(result, StandardCharsets.US_ASCII));
+ }
+
+ @Test
+ public void rainbowEmojiToSurrogatePairUTF16() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.UTF_16BE, false);
+ ByteBuffer bb = e.encode(RAINBOW_EMOJI);
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertArrayEquals(RAINBOW_EMOJI.getBytes(StandardCharsets.UTF_16BE), result);
+ }
+
+ @Test
+ public void partialSurrogatePair() {
+ NioZipEncoding e = new NioZipEncoding(StandardCharsets.US_ASCII, false);
+ ByteBuffer bb = e.encode("\ud83c");
+ final int off = bb.arrayOffset();
+ byte[] result = Arrays.copyOfRange(bb.array(), off, off + bb.limit() - bb.position());
+ Assert.assertEquals(0, result.length);
+ }
+}
disable-brotli.patch
disable-zstd.patch
disable-osgi-tests.patch
CVE-2019-12402-939610.patch
......@@ -20,11 +20,11 @@
<parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
<version>47</version>
<version>48</version>
</parent>
<artifactId>commons-compress</artifactId>
<version>1.18</version>
<version>1.19</version>
<name>Apache Commons Compress</name>
<url>https://commons.apache.org/proper/commons-compress/</url>
<!-- The description is not indented to make it look better in the release notes -->
......@@ -38,6 +38,10 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<!-- override parent as version used by parent requires Java 8 -->
<commons.felix.version>3.5.1</commons.felix.version>
<commons.componentid>compress</commons.componentid>
<commons.module.name>org.apache.commons.compress</commons.module.name>
<commons.jira.id>COMPRESS</commons.jira.id>
......@@ -45,8 +49,9 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<!-- configuration bits for cutting a release candidate -->
<commons.release.version>${project.version}</commons.release.version>
<commons.rc.version>RC1</commons.rc.version>
<powermock.version>1.7.3</powermock.version>
<commons.pmd-plugin.version>3.8</commons.pmd-plugin.version>
<commons.bc.version>1.19</commons.bc.version>
<powermock.version>1.7.4</powermock.version>
<commons.pmd-plugin.version>3.12.0</commons.pmd-plugin.version>
<commons.manifestlocation>${project.build.outputDirectory}/META-INF</commons.manifestlocation>
<commons.manifestfile>${commons.manifestlocation}/MANIFEST.MF</commons.manifestfile>
......@@ -63,11 +68,14 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<!-- generate report even if there are binary incompatible changes -->
<commons.japicmp.breakBuildOnBinaryIncompatibleModifications>false</commons.japicmp.breakBuildOnBinaryIncompatibleModifications>
<!-- 0.12.0 dies with a NullPointerException -->
<commons.japicmp.version>0.11.1</commons.japicmp.version>
<pax.exam.version>4.11.0</pax.exam.version>
<slf4j.version>1.7.21</slf4j.version>
<!-- definition uses commons.componentId starting with parent 47,
this doesn't work for us -->
<commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/${project.artifactId}</commons.scmPubUrl>
<japicmp.skip>false</japicmp.skip>
<pax.exam.version>4.13.1</pax.exam.version>
<slf4j.version>1.7.26</slf4j.version>
</properties>
<issueManagement>
......@@ -85,7 +93,7 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.3.3-3</version>
<version>1.4.0-1</version>
<optional>true</optional>
</dependency>
<dependency>
......@@ -141,7 +149,7 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>5.6.10</version>
<version>6.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
......@@ -251,9 +259,9 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
</contributors>
<scm>
<connection>scm:git:https://git-wip-us.apache.org/repos/asf/commons-compress.git</connection>
<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/commons-compress.git</developerConnection>
<url>https://git-wip-us.apache.org/repos/asf?p=commons-compress.git</url>
<connection>scm:git:https://gitbox.apache.org/repos/asf/commons-compress.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-compress.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=commons-compress.git</url>
</scm>
<build>
......@@ -319,14 +327,6 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
<artifactId>maven-bundle-plugin</artifactId>
<version>${commons.felix.version}</version>
</plugin>
<!-- override skip property of parent pom -->
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
......@@ -524,7 +524,6 @@ Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj.
</activation>
<properties>
<maven.compiler.release>9</maven.compiler.release>
<commons.jacoco.version>0.7.9</commons.jacoco.version>
<animal.sniffer.skip>true</animal.sniffer.skip>
<!-- coverall version 4.3.0 does not work with java 9, see https://github.com/trautonen/coveralls-maven-plugin/issues/112 -->
<coveralls.skip>true</coveralls.skip>
......
......@@ -39,10 +39,132 @@ The <action> type attribute can be add,update,fix,remove.
<document>
<properties>
<title>commons-compress</title>
<title>Apache Commons Compress Release Notes</title>
</properties>
<body>
<release version="1.18" date="not released, yet"
<release version="1.19" date="not released, yet"
description="Release 1.19
----------------------------------------
ZipArchiveInputStream and ZipFile will no longer throw an exception if
an extra field generally understood by Commons Compress is malformed
but rather turn them into UnrecognizedExtraField instances.
You can influence the way extra fields are parsed in more detail by
using the new getExtraFields(ExtraFieldParsingBehavior) method of
ZipArchiveEntry now.
Some of the ZIP extra fields related to strong encryption will now
throw ZipExceptions rather than ArrayIndexOutOfBoundsExceptions in
certain cases when used directly. There is no practical difference
when they are read via ZipArchiveInputStream or ZipFile.
">
<action type="fix" date="2018-09-07">
ZipArchiveInputStream could forget the compression level has
changed under certain circumstances.
</action>
<action issue="COMPRESS-466" type="add" date="2018-10-07">
It is now possible to skip parsing of local file headers when
using ZipFile which may speed up reading the archive at the
cost of potentially missing important information. See the
javadocs of the ZipFile class for details.
</action>
<action issue="COMPRESS-469" type="add" date="2018-11-10">
TarArchiveInputStream has a new constructor-arg lenient that
can be used to accept certain broken archives.
</action>
<action issue="COMPRESS-470" type="fix" date="2018-11-18">
Fixed another potential resource leak in
ParallelScatterZipCreator#writeTo.
</action>
<action issue="COMPRESS-475" type="add" date="2018-12-15">
ArjArchiveEntry and SevenZArchiveEntry now implement hashCode
and equals.
</action>
<action type="fix" date="2019-02-18" due-to="Alex Bertram">
ArArchiveInputStream could think it had hit EOF prematurely.
Github Pull Request #74.
</action>
<action type="update" date="2019-04-16" due-to="Robin Schimpf"
issue="COMPRESS-481">
SevenZFile now provides a way to cap memory consumption for
LZMA(2) compressed content.
Github Pull Request #76.
</action>
<action type="update" date="2019-04-18"
due-to="Rostislav Krasny" issue="COMPRESS-464">
The ARJ package has been updated to contain constants for more
recent specifications.
</action>
<action type="update" date="2019-04-30"
issue="COMPRESS-484">
Update optional library zstd-jni from 1.3.3-3 to 1.4.0-1.
</action>
<action type="update" date="2019-08-07"
due-to="Hervé Boutemy, Tibor Digana"
issue="COMPRESS-485">
ParallelScatterZipCreator now writes the entries to the
gathered output in the same order they have been added.
Github Pull Requests #78 and #79.
</action>
<action type="fix" date="2019-08-08" issue="COMPRESS-490">
Throw IOException rather than RuntimeExceptions for certain
malformed LZ4 or Snappy inputs.
</action>
<action type="update" date="2019-08-09" issue="COMPRESS-486">
The Expander and Archive example classes can leak resources
they have wrapped around passed in streams or channels. The
methods consuming streams and channels have been adapted to
give the calling code a chance to deal with those wrapper
resources.
</action>
<action type="update" date="2019-08-16" issue="COMPRESS-479">
ZipArchiveInputStream and ZipFile no longer assume Commons
Compress would understand extra fields better than the writer
of the archive and silently turn extra fields that Commons
Compress should understand into UnrecognizedExtraFields if
parsing said fields fails.
It is now possible to take more control over the extra field
parsing process with a new overload of
ZipArchiveEntry#getExtraFields.
</action>
<action type="fix" date="2019-08-18" issue="COMPRESS-482">
ZipArchiveInputStream failed to read stored entries with a
data descriptor if the data descriptor didn't use the
signature invented by InfoZIP.
</action>
<action type="update" date="2019-08-18" issue="COMPRESS-483">
ZipArchiveInputStream will now throw an exception if reading a
stored entry with a data descriptor and the data descriptor
doesn't match what it has actually read.
The most common case for a situation like this is a stored ZIP
archive inside of the archive ZipArchiveInputStream currently
reads. In such a case ZipArchiveInputStream would happily
extract the contained archive and stop once the central
directory of the inner archive has been hit. This is a case
where ZipArchiveInputStream simply can not be used and only
ZipFile is able to read the archive.
The only other explanation is a broken archive. So the
exception prevents users from thinking they had successfully
read the contents of the archive.
</action>
<action type="add" date="2019-08-18" issue="COMPRESS-231"
due-to="Tim Underwood">
Added a MultiReadOnlySeekableByteChannel class
that can be used to concatenate the parts of a multi volume 7z
archive so that SevenZFile can read them.
</action>
<action type="update" date="2019-08-20" issue="COMPRESS-478">
The 7zip tools provide a default name for archive entries
without name; SevenZFile returns a null name for such
entries. A new method getDefaultName has been added to derive
the same name the 7zip tools would use and an option has been
added that sets SevenZArchiveEntry's name to the default name
if it is not contained inside the archive.
</action>
</release>
<release version="1.18" date="2018-08-16"
description="Release 1.18">
<action type="fix" date="2018-06-15" due-to="DidierLoiseau">
The example Expander class has been vulnerable to a path
......
......@@ -551,7 +551,7 @@ public class ArchiveStreamFactory implements ArchiveStreamProvider {
if (tais.getNextTarEntry().isCheckSumOK()) {
return TAR;
}
} catch (final Exception e) { // NOPMD // NOSONAR
} catch (final Exception e) { // NOPMD NOSONAR
// can generate IllegalArgumentException as well
// as IOException
// autodetection, simply not a TAR
......
......@@ -23,7 +23,10 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Enumeration;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
/**
* Simple command line application that lists the contents of an archive.
......@@ -49,6 +52,8 @@ public final class Lister {
String format = args.length > 1 ? args[1] : detectFormat(f);
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
list7z(f);
} else if ("zipfile".equals(format)) {
listZipUsingZipFile(f);
} else {
listStream(f, args);
}
......@@ -84,13 +89,25 @@ public final class Lister {
System.out.println("Created " + z.toString());
ArchiveEntry ae;
while ((ae = z.getNextEntry()) != null) {
System.out.println(ae.getName());
String name = ae.getName() == null ? z.getDefaultName() + " (entry name was null)"
: ae.getName();
System.out.println(name);
}
}
}
private static void listZipUsingZipFile(File f) throws ArchiveException, IOException {
try (ZipFile z = new ZipFile(f)) {
System.out.println("Created " + z.toString());
for (Enumeration<ZipArchiveEntry> en = z.getEntries(); en.hasMoreElements(); ) {
System.out.println(en.nextElement().getName());
}
}
}
private static void usage() {
System.out.println("Parameters: archive-name [archive-type]");
System.out.println("Parameters: archive-name [archive-type]\n");
System.out.println("the magic archive-type 'zipfile' prefers ZipFile over ZipArchiveInputStream");
}
}
......@@ -21,6 +21,7 @@ package org.apache.commons.compress.archivers.ar;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
......@@ -104,12 +105,10 @@ public class ArArchiveInputStream extends ArchiveInputStream {
final int read = IOUtils.readFully(input, realized);
trackReadBytes(read);
if (read != expected.length) {
throw new IOException("failed to read header. Occured at byte: " + getBytesRead());
throw new IOException("Failed to read header. Occured at byte: " + getBytesRead());
}
for (int i = 0; i < expected.length; i++) {
if (expected[i] != realized[i]) {
throw new IOException("invalid header " + ArchiveUtils.toAsciiString(realized));
}
if (!Arrays.equals(expected, realized)) {
throw new IOException("Invalid header " + ArchiveUtils.toAsciiString(realized));
}
}
......@@ -121,15 +120,14 @@ public class ArArchiveInputStream extends ArchiveInputStream {
trackReadBytes(1);
}
if (input.available() == 0) {
return null;
}
{
final int read = IOUtils.readFully(input, metaData);
trackReadBytes(read);
if (read == 0) {
return null;
}
if (read < metaData.length) {
throw new IOException("truncated ar archive");
throw new IOException("Truncated ar archive");
}
}
......@@ -139,12 +137,10 @@ public class ArArchiveInputStream extends ArchiveInputStream {
final int read = IOUtils.readFully(input, realized);
trackReadBytes(read);
if (read != expected.length) {
throw new IOException("failed to read entry trailer. Occured at byte: " + getBytesRead());
throw new IOException("Failed to read entry trailer. Occured at byte: " + getBytesRead());
}
for (int i = 0; i < expected.length; i++) {
if (expected[i] != realized[i]) {
throw new IOException("invalid entry trailer. not read the content? Occured at byte: " + getBytesRead());
}
if (!Arrays.equals(expected, realized)) {
throw new IOException("Invalid entry trailer. not read the content? Occured at byte: " + getBytesRead());
}
}
......@@ -264,13 +260,11 @@ public class ArArchiveInputStream extends ArchiveInputStream {
if (currentEntry == null) {
throw new IllegalStateException("No current ar entry");
}
int toRead = len;
final long entryEnd = entryOffset + currentEntry.getLength();
if (len > 0 && entryEnd > offset) {
toRead = (int) Math.min(len, entryEnd - offset);
} else {
if (len < 0 || offset >= entryEnd) {
return -1;
}
final int toRead = (int) Math.min(len, entryEnd - offset);
final int ret = this.input.read(b, off, toRead);
trackReadBytes(ret);
return ret;
......@@ -354,16 +348,16 @@ public class ArArchiveInputStream extends ArchiveInputStream {
* Is this the name of the "Archive String Table" as used by
* SVR4/GNU to store long file names?
*
* <p>GNU ar stores multiple extended filenames in the data section
* <p>GNU ar stores multiple extended file names in the data section
* of a file with the name "//", this record is referred to by
* future headers.</p>
*
* <p>A header references an extended filename by storing a "/"
* followed by a decimal offset to the start of the filename in
* the extended filename data section.</p>
* <p>A header references an extended file name by storing a "/"
* followed by a decimal offset to the start of the file name in
* the extended file name data section.</p>
*
* <p>The format of the "//" file itself is simply a list of the
* long filenames, each separated by one or more LF
* long file names, each separated by one or more LF
* characters. Note that the decimal offsets are number of
* characters, not line or string number within the "//" file.</p>
*/
......
......@@ -94,7 +94,7 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
writeArchiveHeader();
} else {
if (prevEntry.getLength() != entryOffset) {
throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
throw new IOException("Length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
}
if (haveUnclosedEntry) {
......@@ -135,7 +135,7 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
final String n = pEntry.getName();
if (LONGFILE_ERROR == longFileMode && n.length() > 16) {
throw new IOException("filename too long, > 16 chars: "+n);
throw new IOException("File name too long, > 16 chars: "+n);
}
if (LONGFILE_BSD == longFileMode &&
(n.length() > 16 || n.contains(" "))) {
......@@ -149,28 +149,28 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
offset = fill(offset, 16, ' ');
final String m = "" + pEntry.getLastModified();
if (m.length() > 12) {
throw new IOException("modified too long");
throw new IOException("Last modified too long");
}
offset += write(m);
offset = fill(offset, 28, ' ');
final String u = "" + pEntry.getUserId();
if (u.length() > 6) {
throw new IOException("userid too long");
throw new IOException("User id too long");
}
offset += write(u);
offset = fill(offset, 34, ' ');
final String g = "" + pEntry.getGroupId();
if (g.length() > 6) {
throw new IOException("groupid too long");
throw new IOException("Group id too long");
}
offset += write(g);
offset = fill(offset, 40, ' ');
final String fm = "" + Integer.toString(pEntry.getMode(), 8);
if (fm.length() > 8) {
throw new IOException("filemode too long");
throw new IOException("Filemode too long");
}
offset += write(fm);
......@@ -179,7 +179,7 @@ public class ArArchiveOutputStream extends ArchiveOutputStream {
String.valueOf(pEntry.getLength()
+ (mustAppendName ? n.length() : 0));
if (s.length() > 10) {
throw new IOException("size too long");
throw new IOException("Size too long");
}
offset += write(s);
......
<html>
<!DOCTYPE html>
<html lang="en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
......@@ -17,6 +18,9 @@
limitations under the License.
-->
<head>
<title>ar package</title>
</head>
<body>
<p>Provides stream classes for reading and writing archives using
the AR format.</p>
......
......@@ -143,6 +143,24 @@ public class ArjArchiveEntry implements ArchiveEntry {
return localFileHeader.method;
}
@Override
public int hashCode() {
final String name = getName();
return name == null ? 0 : name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final ArjArchiveEntry other = (ArjArchiveEntry) obj;
return localFileHeader.equals(other.localFileHeader);
}
/**
* The known values for HostOs.
*/
......
......@@ -35,7 +35,9 @@ import org.apache.commons.compress.utils.IOUtils;
/**
* Implements the "arj" archive format as an InputStream.
* <p>
* <a href="http://farmanager.com/svn/trunk/plugins/multiarc/arc.doc/arj.txt">Reference</a>
* <a href="https://github.com/FarGroup/FarManager/blob/master/plugins/multiarc/arc.doc/arj.txt">Reference 1</a>
* <br>
* <a href="http://www.fileformat.info/format/arj/corion.htm">Reference 2</a>
* @NotThreadSafe
* @since 1.6
*/
......@@ -107,16 +109,17 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
}
private String readString(final DataInputStream dataIn) throws IOException {
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nextByte;
while ((nextByte = dataIn.readUnsignedByte()) != 0) {
buffer.write(nextByte);
}
if (charsetName != null) {
return new String(buffer.toByteArray(), charsetName);
try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
int nextByte;
while ((nextByte = dataIn.readUnsignedByte()) != 0) {
buffer.write(nextByte);
}
if (charsetName != null) {
return new String(buffer.toByteArray(), charsetName);
}
// intentionally using the default encoding as that's the contract for a null charsetName
return new String(buffer.toByteArray());
}
// intentionally using the default encoding as that's the contract for a null charsetName
return new String(buffer.toByteArray());
}
private void readFully(final DataInputStream dataIn, final byte[] b)
......@@ -259,7 +262,7 @@ public class ArjArchiveInputStream extends ArchiveInputStream {
}
extendedHeaders.add(extendedHeaderBytes);
}
localFileHeader.extendedHeaders = extendedHeaders.toArray(new byte[extendedHeaders.size()][]);
localFileHeader.extendedHeaders = extendedHeaders.toArray(new byte[0][]);
return localFileHeader;
}
......
......@@ -18,6 +18,7 @@
package org.apache.commons.compress.archivers.arj;
import java.util.Arrays;
import java.util.Objects;
class LocalFileHeader {
int archiverVersionNumber;
......@@ -57,6 +58,7 @@ class LocalFileHeader {
static class FileTypes {
static final int BINARY = 0;
static final int SEVEN_BIT_TEXT = 1;
static final int COMMENT_HEADER = 2;
static final int DIRECTORY = 3;
static final int VOLUME_LABEL = 4;
static final int CHAPTER_LABEL = 5;
......@@ -65,6 +67,8 @@ class LocalFileHeader {
static class Methods {
static final int STORED = 0;
static final int COMPRESSED_MOST = 1;
static final int COMPRESSED = 2;
static final int COMPRESSED_FASTER = 3;
static final int COMPRESSED_FASTEST = 4;
static final int NO_DATA_NO_CRC = 8;
static final int NO_DATA = 9;
......@@ -120,4 +124,44 @@ class LocalFileHeader {
builder.append("]");
return builder.toString();
}
@Override
public int hashCode() {
return name == null ? 0 : name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final LocalFileHeader other = (LocalFileHeader) obj;
return
archiverVersionNumber == other.archiverVersionNumber &&
minVersionToExtract == other.minVersionToExtract &&
hostOS == other.hostOS &&
arjFlags == other.arjFlags &&
method == other.method &&
fileType == other.fileType &&
reserved == other.reserved &&
dateTimeModified == other.dateTimeModified &&
compressedSize == other.compressedSize &&
originalSize == other.originalSize &&
originalCrc32 == other.originalCrc32 &&
fileSpecPosition == other.fileSpecPosition &&
fileAccessMode == other.fileAccessMode &&
firstChapter == other.firstChapter &&
lastChapter == other.lastChapter &&
extendedFilePosition == other.extendedFilePosition &&
dateTimeAccessed == other.dateTimeAccessed &&
dateTimeCreated == other.dateTimeCreated &&
originalSizeEvenForVolumes == other.originalSizeEvenForVolumes &&
Objects.equals(name, other.name) &&
Objects.equals(comment, other.comment) &&
Arrays.deepEquals(extendedHeaders, other.extendedHeaders);
}
}
......@@ -52,6 +52,20 @@ class MainHeader {
static final int ALTNAME = 0x80;
}
static class HostOS {
static final int MS_DOS = 0;
static final int PRIMOS = 1;
static final int UNIX = 2;
static final int AMIGA = 3;
static final int MAC_OS = 4;
static final int OS2 = 5;
static final int APPLE_GS = 6;
static final int ATARI_ST = 7;
static final int NeXT = 8;
static final int VAX_VMS = 9;
static final int WIN95 = 10;
static final int WIN32 = 11;
}
@Override
public String toString() {
......
<html>
<!DOCTYPE html>
<html lang="en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
......@@ -17,6 +18,9 @@
limitations under the License.
-->
<head>
<title>arj package</title>
</head>
<body>
<p>Provides stream classes for reading archives using
the ARJ format.</p>
......
......@@ -53,8 +53,8 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
* <h3>OLD FORMAT</h3>
*
* <p>Each file has a 76 (ascii) / 26 (binary) byte header, a variable
* length, NUL terminated filename, and variable length file data. A
* header for a filename "TRAILER!!!" indicates the end of the
* length, NUL terminated file name, and variable length file data. A
* header for a file name "TRAILER!!!" indicates the end of the
* archive.</p>
*
* <p>All the fields in the header are ISO 646 (approximately ASCII)
......@@ -88,7 +88,7 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
* apart from c_mtime and c_filesize which are 32-bit integer values
* </pre>
*
* <p>If necessary, the filename and file data are padded with a NUL byte to an even length</p>
* <p>If necessary, the file name and file data are padded with a NUL byte to an even length</p>
*
* <p>Special files, directories, and the trailer are recorded with
* the h_filesize field equal to 0.</p>
......@@ -99,8 +99,8 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
* <h3>NEW FORMAT</h3>
*
* <p>Each file has a 110 byte header, a variable length, NUL
* terminated filename, and variable length file data. A header for a
* filename "TRAILER!!!" indicates the end of the archive. All the
* terminated file name, and variable length file data. A header for a
* file name "TRAILER!!!" indicates the end of the archive. All the
* fields in the header are ISO 646 (approximately ASCII) strings of
* hexadecimal numbers, left padded, not NUL terminated.</p>
*
......@@ -765,7 +765,7 @@ public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
*/
public void setSize(final long size) {
if (size < 0 || size > 0xFFFFFFFFL) {
throw new IllegalArgumentException("invalid entry size <" + size
throw new IllegalArgumentException("Invalid entry size <" + size
+ ">");
}
this.filesize = size;
......
......@@ -89,7 +89,7 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
private final int blockSize;
/**
* The encoding to use for filenames and labels.
* The encoding to use for file names and labels.
*/
private final ZipEncoding zipEncoding;
......@@ -148,10 +148,14 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
* @param encoding
* The encoding of file names to expect - use null for
* the platform's default.
* @throws IllegalArgumentException if <code>blockSize</code> is not bigger than 0
* @since 1.6
*/
public CpioArchiveInputStream(final InputStream in, final int blockSize, final String encoding) {
this.in = in;
if (blockSize <= 0) {
throw new IllegalArgumentException("blockSize must be bigger than 0");
}
this.blockSize = blockSize;
this.encoding = encoding;
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
......@@ -203,7 +207,7 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
private void closeEntry() throws IOException {
// the skip implementation of this class will not skip more
// than Integer.MAX_VALUE bytes
while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) { // NOPMD
while (skip((long) Integer.MAX_VALUE) == Integer.MAX_VALUE) { // NOPMD NOSONAR
// do nothing
}
}
......@@ -382,11 +386,17 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
ret.setNumberOfLinks(readAsciiLong(8, 16));
ret.setTime(readAsciiLong(8, 16));
ret.setSize(readAsciiLong(8, 16));
if (ret.getSize() < 0) {
throw new IOException("Found illegal entry with negative length");
}
ret.setDeviceMaj(readAsciiLong(8, 16));
ret.setDeviceMin(readAsciiLong(8, 16));
ret.setRemoteDeviceMaj(readAsciiLong(8, 16));
ret.setRemoteDeviceMin(readAsciiLong(8, 16));
final long namesize = readAsciiLong(8, 16);
if (namesize < 0) {
throw new IOException("Found illegal entry with negative name length");
}
ret.setChksum(readAsciiLong(8, 16));
final String name = readCString((int) namesize);
ret.setName(name);
......@@ -415,7 +425,13 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
ret.setRemoteDevice(readAsciiLong(6, 8));
ret.setTime(readAsciiLong(11, 8));
final long namesize = readAsciiLong(6, 8);
if (namesize < 0) {
throw new IOException("Found illegal entry with negative name length");
}
ret.setSize(readAsciiLong(11, 8));
if (ret.getSize() < 0) {
throw new IOException("Found illegal entry with negative length");
}
final String name = readCString((int) namesize);
ret.setName(name);
if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
......@@ -443,7 +459,13 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
ret.setRemoteDevice(readBinaryLong(2, swapHalfWord));
ret.setTime(readBinaryLong(4, swapHalfWord));
final long namesize = readBinaryLong(2, swapHalfWord);
if (namesize < 0) {
throw new IOException("Found illegal entry with negative name length");
}
ret.setSize(readBinaryLong(4, swapHalfWord));
if (ret.getSize() < 0) {
throw new IOException("Found illegal entry with negative length");
}
final String name = readCString((int) namesize);
ret.setName(name);
if (CpioUtil.fileType(mode) == 0 && !name.equals(CPIO_TRAILER)){
......@@ -460,7 +482,9 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
// don't include trailing NUL in file name to decode
final byte tmpBuffer[] = new byte[length - 1];
readFully(tmpBuffer, 0, tmpBuffer.length);
this.in.read();
if (this.in.read() == -1) {
throw new EOFException();
}
return zipEncoding.decode(tmpBuffer);
}
......@@ -478,7 +502,7 @@ public class CpioArchiveInputStream extends ArchiveInputStream implements
@Override
public long skip(final long n) throws IOException {
if (n < 0) {
throw new IllegalArgumentException("negative skip length");
throw new IllegalArgumentException("Negative skip length");
}
ensureOpen();
final int max = (int) Math.min(n, Integer.MAX_VALUE);
......