Support zip file comments
OpenJDK on Windows has a "PACK200" zip file comment in rt.jar.
MOE_MIGRATED_REVID=166759545
diff --git a/java/com/google/turbine/zip/Zip.java b/java/com/google/turbine/zip/Zip.java
index 3d75ae1..48d4697 100644
--- a/java/com/google/turbine/zip/Zip.java
+++ b/java/com/google/turbine/zip/Zip.java
@@ -65,7 +65,7 @@
* supported.
* <li>UTF-8 is the only supported encoding.
* <li>STORED and DEFLATE are the only supported compression methods.
- * <li>Jar file comments and zip64 extensible data sectors are not supported.
+ * <li>zip64 extensible data sectors are not supported.
* <li>Zip files larger than Integer.MAX_VALUE bytes are not supported.
* <li>The only supported ZIP64 field is ENDTOT. This implementation assumes that the ZIP64 end
* header is present only if ENDTOT in EOCD header is 0xFFFF.
@@ -73,8 +73,6 @@
*/
public class Zip {
- static final int CENSIG = 0x02014b50;
- static final int ENDSIG = 0x06054b50;
static final int ZIP64_ENDSIG = 0x06064b50;
static final int LOCHDR = 30; // LOC header size
@@ -85,6 +83,7 @@
static final int ENDTOT = 10; // total number of entries
static final int ENDSIZ = 12; // central directory size in bytes
+ static final int ENDCOM = 20; // zip file comment length
static final int CENHOW = 10; // compression method
static final int CENLEN = 24; // uncompressed size
@@ -126,7 +125,7 @@
@Override
public Entry next() {
// TODO(cushon): technically we're supposed to throw NSEE
- checkSignature(path, cd, cdindex, 2, 1, "CENSIG");
+ checkSignature(path, cd, cdindex, 1, 2, "CENSIG");
int nameLength = cd.getChar(cdindex + CENNAM);
int extLength = cd.getChar(cdindex + CENEXT);
int commentLength = cd.getChar(cdindex + CENCOM);
@@ -158,8 +157,7 @@
public ZipIterable(Path path) throws IOException {
this.path = path;
this.chan = FileChannel.open(path, StandardOpenOption.READ);
- // Locate the EOCD, assuming that the archive does not contain trailing garbage or a zip file
- // comment.
+ // Locate the EOCD
long size = chan.size();
if (size < ENDHDR) {
throw new ZipException("invalid zip archive");
@@ -167,9 +165,33 @@
long eocdOffset = size - ENDHDR;
MappedByteBuffer eocd = chan.map(MapMode.READ_ONLY, eocdOffset, ENDHDR);
eocd.order(ByteOrder.LITTLE_ENDIAN);
- checkSignature(path, eocd, 0, 6, 5, "ENDSIG");
- int totalEntries = eocd.getChar(ENDTOT);
- long cdsize = UnsignedInts.toLong(eocd.getInt(ENDSIZ));
+ int index = 0;
+ int commentSize = 0;
+ if (!isSignature(eocd, 0, 5, 6)) {
+ // The archive may contain a zip file comment; keep looking for the EOCD.
+ long start = Math.max(0, size - ENDHDR - 0xFFFF);
+ eocd = chan.map(MapMode.READ_ONLY, start, (size - start));
+ eocd.order(ByteOrder.LITTLE_ENDIAN);
+ index = (int) ((size - start) - ENDHDR);
+ while (index > 0) {
+ index--;
+ eocd.position(index);
+ if (isSignature(eocd, index, 5, 6)) {
+ commentSize = (int) ((size - start) - ENDHDR) - index;
+ eocdOffset = start + index;
+ break;
+ }
+ }
+ }
+ checkSignature(path, eocd, index, 5, 6, "ENDSIG");
+ int totalEntries = eocd.getChar(index + ENDTOT);
+ long cdsize = UnsignedInts.toLong(eocd.getInt(index + ENDSIZ));
+ int actualCommentSize = eocd.getChar(index + ENDCOM);
+ if (commentSize != actualCommentSize) {
+ throw new ZipException(
+ String.format(
+ "zip file comment length was %d, expected %d", commentSize, actualCommentSize));
+ }
// If the number of entries is 0xffff, check if the archive has a zip64 EOCD locator.
if (totalEntries == ZIP64_MAGICCOUNT) {
// Assume the zip64 EOCD has the usual size; we don't support zip64 extensible data sectors.
@@ -270,7 +292,7 @@
LOCHDR + nameLength + cenExtLength + size + EXTRA_FIELD_SLACK,
chan.size() - offset));
fc.order(ByteOrder.LITTLE_ENDIAN);
- checkSignature(path, fc, /* offset= */ 0, 4, 3, "LOCSIG");
+ checkSignature(path, fc, /* index= */ 0, 3, 4, "LOCSIG");
int locExtLength = fc.getChar(LOCEXT);
if (locExtLength > cenExtLength + EXTRA_FIELD_SLACK) {
// If the local header's extra fields don't match the central directory and we didn't
@@ -299,14 +321,18 @@
static void checkSignature(
Path path, MappedByteBuffer buf, int index, int i, int j, String name) {
- if (!((buf.get(index) == 'P')
- && (buf.get(index + 1) == 'K')
- && (buf.get(index + 2) == j)
- && (buf.get(index + 3) == i))) {
+ if (!isSignature(buf, index, i, j)) {
throw new AssertionError(
String.format(
"%s: bad %s (expected: 0x%02x%02x%02x%02x, actual: 0x%08x)",
path, name, i, j, (int) 'K', (int) 'P', buf.getInt(index)));
}
}
+
+ static boolean isSignature(MappedByteBuffer buf, int index, int i, int j) {
+ return (buf.get(index) == 'P')
+ && (buf.get(index + 1) == 'K')
+ && (buf.get(index + 2) == i)
+ && (buf.get(index + 3) == j);
+ }
}
diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java
index cebbebb..90b2315 100644
--- a/javatests/com/google/turbine/zip/ZipTest.java
+++ b/javatests/com/google/turbine/zip/ZipTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
@@ -28,6 +29,7 @@
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
import java.util.Enumeration;
import java.util.LinkedHashMap;
@@ -35,6 +37,8 @@
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -83,7 +87,7 @@
assertThat(actual(path)).isEqualTo(expected(path));
}
- private static void createEntry(JarOutputStream jos, String name, byte[] bytes)
+ private static void createEntry(ZipOutputStream jos, String name, byte[] bytes)
throws IOException {
JarEntry je = new JarEntry(name);
je.setMethod(JarEntry.STORED);
@@ -134,4 +138,34 @@
}
assertThat(actual(path)).isEqualTo(expected(path));
}
+
+ @Test
+ public void zipFileCommentsAreSupported() throws Exception {
+ Path path = temporaryFolder.newFile("test.jar").toPath();
+ Files.delete(path);
+ try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
+ createEntry(zos, "hello", "world".getBytes(UTF_8));
+ zos.setComment("this is a comment");
+ }
+ assertThat(actual(path)).isEqualTo(expected(path));
+ }
+
+ @Test
+ public void malformedComment() throws Exception {
+ Path path = temporaryFolder.newFile("test.jar").toPath();
+ Files.delete(path);
+
+ try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
+ createEntry(zos, "hello", "world".getBytes(UTF_8));
+ zos.setComment("this is a comment");
+ }
+ Files.write(path, "trailing garbage".getBytes(UTF_8), StandardOpenOption.APPEND);
+
+ try {
+ actual(path);
+ fail();
+ } catch (ZipException e) {
+ assertThat(e).hasMessage("zip file comment length was 33, expected 17");
+ }
+ }
}