This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git
The following commit(s) were added to refs/heads/master by this push:
new e72c032d6 [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected
record signature: 0x0" (#753)
e72c032d6 is described below
commit e72c032d6a1e142d0b0d9dde9b61d473f170755b
Author: Gary Gregory <[email protected]>
AuthorDate: Tue Jan 13 08:59:27 2026 -0500
[COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0" (#753)
* [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0"
* [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0"
* Checkstyle
* [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0"
Refactor test
* [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0"
ZipCompress715Test now asserts checksum values
* [COMPRESS-715] ZipArchiveInputStream fails with "Unexpected record
signature: 0x0"
Apply Piotr's suggestion: I couldn't find any documentation that
describes the process of padding a ZIP file. Therefore, we must assume
that the “padding” is just arbitrary data.
---
.gitignore | 1 +
src/conf/checkstyle/checkstyle.xml | 2 +-
.../archivers/zip/ZipArchiveInputStream.java | 59 +-----------
.../commons/compress/archivers/zip/ZipLong.java | 2 +
.../compress/archivers/zip/ZipCompress715Test.java | 103 +++++++++++++++++++++
.../commons/compress/COMPRESS-715/compress715.zip | Bin 0 -> 1287745 bytes
6 files changed, 110 insertions(+), 57 deletions(-)
diff --git a/.gitignore b/.gitignore
index 92a0cd4ef..560a200e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ target
/.externalToolBuilders/
/maven-eclipse.xml
/.java-version
+/.checkstyle
# NetBeans files
nb-configuration.xml
diff --git a/src/conf/checkstyle/checkstyle.xml
b/src/conf/checkstyle/checkstyle.xml
index 83dc97197..7e8923a44 100644
--- a/src/conf/checkstyle/checkstyle.xml
+++ b/src/conf/checkstyle/checkstyle.xml
@@ -26,7 +26,7 @@ limitations under the License.
</module>
<module name="JavadocPackage" />
<module name="LineLength">
- <property name="max" value="160" />
+ <property name="max" value="170" />
</module>
<module name="NewlineAtEndOfFile">
<!-- Files are saved in the repository with LF line endings; on Windows,
use git config core.autocrlf input -->
diff --git
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
index bceaed7db..8cc75880c 100644
---
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
+++
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveInputStream.java
@@ -30,10 +30,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
-import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
import java.util.function.Function;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
@@ -333,8 +331,6 @@ private <T extends InputStream> T checkInputStream() throws
ZipException {
private static final byte[] LFH = ZipLong.LFH_SIG.getBytes();
private static final byte[] CFH = ZipLong.CFH_SIG.getBytes();
private static final byte[] DD = ZipLong.DD_SIG.getBytes();
- private static final byte[] APK_SIGNING_BLOCK_MAGIC = { 'A', 'P', 'K', '
', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2', };
- private static final BigInteger LONG_MAX =
BigInteger.valueOf(Long.MAX_VALUE);
/**
* Creates a new builder.
@@ -856,12 +852,9 @@ public ZipArchiveEntry getNextZipEntry() throws
IOException {
}
final ZipLong sig = new ZipLong(lfhBuf);
if (!sig.equals(ZipLong.LFH_SIG)) {
- if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) ||
isApkSigningBlock(lfhBuf)) {
- hitCentralDirectory = true;
- skipRemainderOfArchive();
- return null;
- }
- throw new ZipException(String.format("Unexpected record signature:
0x%x", sig.getValue()));
+ hitCentralDirectory = true;
+ skipRemainderOfArchive();
+ return null;
}
// off: go past the signature
int off = WORD;
@@ -964,52 +957,6 @@ public long getUncompressedCount() {
return uncompressedCount;
}
- /**
- * Checks whether this might be an APK Signing Block.
- * <p>
- * Unfortunately the APK signing block does not start with some kind of
signature, it rather ends with one. It starts with a length, so what we do is
parse
- * the suspect length, skip ahead far enough, look for the signature and
if we've found it, return true.
- * </p>
- *
- * @param suspectLocalFileHeader the bytes read from the underlying stream
in the expectation that they would hold the local file header of the next entry.
- * @return true if this looks like an APK signing block.
- * @see <a
href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a>
- */
- private boolean isApkSigningBlock(final byte[] suspectLocalFileHeader)
throws IOException {
- // length of block excluding the size field itself
- final BigInteger len =
ZipEightByteInteger.getValue(suspectLocalFileHeader);
- // LFH has already been read and all but the first eight bytes contain
(part of) the APK signing block,
- // also subtract 16 bytes in order to position us at the magic string
- BigInteger toSkip = len.add(BigInteger.valueOf(DWORD -
suspectLocalFileHeader.length - (long) APK_SIGNING_BLOCK_MAGIC.length));
- final byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length];
- try {
- if (toSkip.signum() < 0) {
- // suspectLocalFileHeader contains the start of suspect magic
string
- final int off = suspectLocalFileHeader.length +
toSkip.intValue();
- // length was shorter than magic length
- if (off < DWORD) {
- return false;
- }
- final int bytesInBuffer = Math.abs(toSkip.intValue());
- System.arraycopy(suspectLocalFileHeader, off, magic, 0,
Math.min(bytesInBuffer, magic.length));
- if (bytesInBuffer < magic.length) {
- readFully(magic, bytesInBuffer);
- }
- } else {
- while (toSkip.compareTo(LONG_MAX) > 0) {
- realSkip(Long.MAX_VALUE);
- toSkip = toSkip.add(LONG_MAX.negate());
- }
- realSkip(toSkip.longValue());
- readFully(magic);
- }
- } catch (final EOFException ex) { // NOSONAR
- // length was invalid
- return false;
- }
- return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC);
- }
-
private boolean isFirstByteOfEocdSig(final int b) {
return b == ZipArchiveOutputStream.EOCD_SIG[0];
}
diff --git
a/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
b/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
index 01b43391a..398b4bc4c 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java
@@ -50,6 +50,8 @@ public final class ZipLong implements Cloneable, Serializable
{
*/
public static final ZipLong DD_SIG = new ZipLong(0X08074B50L);
+ static final ZipLong ZERO = new ZipLong(0);
+
/**
* Value stored in size and similar fields if ZIP64 extensions are used.
*
diff --git
a/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress715Test.java
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress715Test.java
new file mode 100644
index 000000000..2439f9ba1
--- /dev/null
+++
b/src/test/java/org/apache/commons/compress/archivers/zip/ZipCompress715Test.java
@@ -0,0 +1,103 @@
+/*
+ * 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
+ *
+ * https://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 static org.junit.Assert.assertNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.zip.CRC32;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.IOConsumer;
+import org.apache.commons.io.input.ChecksumInputStream;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests https://issues.apache.org/jira/browse/COMPRESS-715
+ */
+class ZipCompress715Test {
+
+ private void assertNextEntry(final ZipArchiveInputStream inputStream,
final String expectedName, final int expectedSize, final String
expectedCrcRadix16)
+ throws IOException {
+ final ZipArchiveEntry nextEntry = inputStream.getNextEntry();
+ assertEquals(expectedName, nextEntry.getName());
+ assertEquals(expectedSize, nextEntry.getSize());
+ final long crc = nextEntry.getCrc();
+ final long expectedCrc = Long.parseUnsignedLong(expectedCrcRadix16,
16);
+ assertEquals(expectedCrc, crc, () -> String.format("0x%x", crc));
+ // @formatter:off
+ @SuppressWarnings("resource")
+ final ChecksumInputStream checksum = ChecksumInputStream.builder()
+ .setInputStream(inputStream)
+ .setChecksum(new CRC32())
+ .setExpectedChecksumValue(expectedCrc)
+ .setCountThreshold(expectedSize)
+ .get();
+ // @formatter:on
+ IOUtils.consume(checksum);
+ }
+
+ /**
+ * The test fixture unzips OK using {@code UnZip 6.00 of 20 April 2009, by
Info-ZIP, with modifications by Apple Inc.}
+ *
+ * <p>
+ * Running:
+ * </p>
+ * <pre>
+ * unzip -lv compress715.zip
+ * </pre>
+ * <p>
+ * returns:
+ * </p>
+ *
+ * <pre>
+ * Archive: compress715.zip
+ * Length Method Size Cmpr Date Time CRC-32 Name
+ * -------- ------ ------- ---- ---------- ----- -------- ----
+ * 341552 Defl:N 14034 96% 03-18-2024 21:36 88cbd694
50700006_82901717_20240318�?162450_Speichergruppe-14_Monitoring_AMT_M5190M_12394_986197575850.mf4
+ * 641560 Defl:N 49549 92% 03-18-2024 21:36 ef0dba37
50700006_82901717_20240318�?162450_Speichergruppe-11_Monitoring_CAN_M5190M_7651_986197575850.mf4
+ * 37280 Defl:X 36887 1% 03-18-2024 21:36 ba89ba80
50700006_82901717_20240318�?162450_MEA_5190.dmp
+ * 405692 Defl:X 371118 9% 03-18-2024 21:24 1e1402e5
50700006_82901717_20240318�?162450_CFG_5190.ilf
+ * 847104 Defl:X 812041 4% 03-18-2024 21:24 d0b8c08d
50700006_82901717_20240318�?162450_CFG_5190.zip
+ * 11107 Defl:X 2764 75% 03-18-2024 21:36 bb2f0f57
50700006_82901717_20240318�?162450_MEA_5190.LOG
+ * -------- ------- --- -------
+ * 2284295 1286393 44% 6 files
+ * </pre>
+ *
+ * @throws IOException Thrown if the test fails.
+ */
+ @Test
+ void testCompress715() throws IOException {
+ final String fixture =
"src/test/resources/org/apache/commons/compress/COMPRESS-715/compress715.zip";
+ try (ZipArchiveInputStream inputStream =
ZipArchiveInputStream.builder().setPath(fixture).get()) {
+ inputStream.forEach(IOConsumer.noop());
+ }
+ try (ZipArchiveInputStream inputStream =
ZipArchiveInputStream.builder().setPath(fixture).get()) {
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_Speichergruppe-14_Monitoring_AMT_M5190M_12394_986197575850.mf4",
341_552, "88cbd694");
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_Speichergruppe-11_Monitoring_CAN_M5190M_7651_986197575850.mf4",
641_560, "ef0dba37");
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_MEA_5190.dmp", 37_280, "ba89ba80");
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_CFG_5190.ilf", 405_692, "1e1402e5");
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_CFG_5190.zip", 847_104, "d0b8c08d");
+ assertNextEntry(inputStream,
"50700006_82901717_20240318–162450_MEA_5190.LOG", 11_107, "bb2f0f57");
+ assertNull(inputStream.getNextEntry());
+ }
+ }
+}
diff --git
a/src/test/resources/org/apache/commons/compress/COMPRESS-715/compress715.zip
b/src/test/resources/org/apache/commons/compress/COMPRESS-715/compress715.zip
new file mode 100644
index 000000000..ef55fe455
Binary files /dev/null and
b/src/test/resources/org/apache/commons/compress/COMPRESS-715/compress715.zip
differ