aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiam Miller-Cushon <cushon@google.com>2022-04-07 13:31:48 -0700
committerJavac Team <javac-team+copybara@google.com>2022-04-07 13:32:41 -0700
commit9bf393cbcfc281bbac6e3d3f042b4444156567ac (patch)
tree40990cc376848076decce3925aa0cdc72176a667
parentc527031ef8d9112b12d768f308e768af18fa12f0 (diff)
downloadturbine-9bf393cbcfc281bbac6e3d3f042b4444156567ac.tar.gz
Support zip64 extensible data sectors
PiperOrigin-RevId: 440187167
-rw-r--r--java/com/google/turbine/zip/Zip.java33
-rw-r--r--javatests/com/google/turbine/zip/ZipTest.java163
2 files changed, 192 insertions, 4 deletions
diff --git a/java/com/google/turbine/zip/Zip.java b/java/com/google/turbine/zip/Zip.java
index fa0f0e0..366cd6b 100644
--- a/java/com/google/turbine/zip/Zip.java
+++ b/java/com/google/turbine/zip/Zip.java
@@ -74,6 +74,7 @@ import java.util.zip.ZipException;
public final class Zip {
static final int ZIP64_ENDSIG = 0x06064b50;
+ static final int ZIP64_LOCSIG = 0x07064b50;
static final int LOCHDR = 30; // LOC header size
static final int CENHDR = 46; // CEN header size
@@ -196,20 +197,44 @@ public final class Zip {
if (totalEntries == ZIP64_MAGICCOUNT) {
// Assume the zip64 EOCD has the usual size; we don't support zip64 extensible data sectors.
long zip64eocdOffset = size - ENDHDR - ZIP64_LOCHDR - ZIP64_ENDHDR;
- MappedByteBuffer zip64eocd = chan.map(MapMode.READ_ONLY, zip64eocdOffset, ZIP64_ENDHDR);
- zip64eocd.order(ByteOrder.LITTLE_ENDIAN);
// Note that zip reading is necessarily best-effort, since an archive could contain 0xFFFF
// entries and the last entry's data could contain a ZIP64_ENDSIG. Some implementations
// read the full EOCD records and compare them.
- if (zip64eocd.getInt(0) == ZIP64_ENDSIG) {
- cdsize = zip64eocd.getLong(ZIP64_ENDSIZ);
+ long zip64cdsize = zip64cdsize(chan, zip64eocdOffset);
+ if (zip64cdsize != -1) {
eocdOffset = zip64eocdOffset;
+ cdsize = zip64cdsize;
+ } else {
+ // If we couldn't find a zip64 EOCD at a fixed offset, either it doesn't exist
+ // or there was a zip64 extensible data sector, so try going through the
+ // locator. This approach doesn't work if data was prepended to the archive
+ // without updating the offset in the locator.
+ MappedByteBuffer zip64loc =
+ chan.map(MapMode.READ_ONLY, size - ENDHDR - ZIP64_LOCHDR, ZIP64_LOCHDR);
+ zip64loc.order(ByteOrder.LITTLE_ENDIAN);
+ if (zip64loc.getInt(0) == ZIP64_LOCSIG) {
+ zip64eocdOffset = zip64loc.getLong(8);
+ zip64cdsize = zip64cdsize(chan, zip64eocdOffset);
+ if (zip64cdsize != -1) {
+ eocdOffset = zip64eocdOffset;
+ cdsize = zip64cdsize;
+ }
+ }
}
}
this.cd = chan.map(MapMode.READ_ONLY, eocdOffset - cdsize, cdsize);
cd.order(ByteOrder.LITTLE_ENDIAN);
}
+ static long zip64cdsize(FileChannel chan, long eocdOffset) throws IOException {
+ MappedByteBuffer zip64eocd = chan.map(MapMode.READ_ONLY, eocdOffset, ZIP64_ENDHDR);
+ zip64eocd.order(ByteOrder.LITTLE_ENDIAN);
+ if (zip64eocd.getInt(0) == ZIP64_ENDSIG) {
+ return zip64eocd.getLong(ZIP64_ENDSIZ);
+ }
+ return -1;
+ }
+
@Override
public Iterator<Entry> iterator() {
return new ZipIterator(path, chan, cd);
diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java
index e9dfc44..2b6636d 100644
--- a/javatests/com/google/turbine/zip/ZipTest.java
+++ b/javatests/com/google/turbine/zip/ZipTest.java
@@ -25,6 +25,8 @@ import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -38,6 +40,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.junit.Rule;
import org.junit.Test;
@@ -164,4 +167,164 @@ public class ZipTest {
ZipException e = assertThrows(ZipException.class, () -> actual(path));
assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17");
}
+
+ // Create a zip64 archive with an extensible data sector
+ @Test
+ public void zip64extension() throws IOException {
+
+ ByteBuffer buf = ByteBuffer.allocate(1000);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ // The jar has a single entry named 'hello', with the value 'world'
+ byte[] name = "hello".getBytes(UTF_8);
+ byte[] value = "world".getBytes(UTF_8);
+ int crc = Hashing.crc32().hashBytes(value).asInt();
+
+ int localHeaderPosition = buf.position();
+
+ // local file header signature 4 bytes (0x04034b50)
+ buf.putInt((int) ZipFile.LOCSIG);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // general purpose bit flag 2 bytes
+ buf.putShort((short) 0);
+ // compression method 2 bytes
+ buf.putShort((short) 0);
+ // last mod file time 2 bytes
+ buf.putShort((short) 0);
+ // last mod file date 2 bytes
+ buf.putShort((short) 0);
+ // crc-32 4 bytes
+ buf.putInt(crc);
+ // compressed size 4 bytes
+ buf.putInt(value.length);
+ // uncompressed size 4 bytes
+ buf.putInt(value.length);
+ // file name length 2 bytes
+ buf.putShort((short) name.length);
+ // extra field length 2 bytes
+ buf.putShort((short) 0);
+ // file name (variable size)
+ buf.put(name);
+ // extra field (variable size)
+ // file data
+ buf.put(value);
+
+ int centralDirectoryPosition = buf.position();
+
+ // central file header signature 4 bytes (0x02014b50)
+ buf.putInt((int) ZipFile.CENSIG);
+ // version made by 2 bytes
+ buf.putShort((short) 0);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // general purpose bit flag 2 bytes
+ buf.putShort((short) 0);
+ // compression method 2 bytes
+ buf.putShort((short) 0);
+ // last mod file time 2 bytes
+ buf.putShort((short) 0);
+ // last mod file date 2 bytes
+ buf.putShort((short) 0);
+ // crc-32 4 bytes
+ buf.putInt(crc);
+ // compressed size 4 bytes
+ buf.putInt(value.length);
+ // uncompressed size 4 bytes
+ buf.putInt(value.length);
+ // file name length 2 bytes
+ buf.putShort((short) name.length);
+ // extra field length 2 bytes
+ buf.putShort((short) 0);
+ // file comment length 2 bytes
+ buf.putShort((short) 0);
+ // disk number start 2 bytes
+ buf.putShort((short) 0);
+ // internal file attributes 2 bytes
+ buf.putShort((short) 0);
+ // external file attributes 4 bytes
+ buf.putInt(0);
+ // relative offset of local header 4 bytes
+ buf.putInt(localHeaderPosition);
+ // file name (variable size)
+ buf.put(name);
+
+ int centralDirectorySize = buf.position() - centralDirectoryPosition;
+ int zip64eocdPosition = buf.position();
+
+ // zip64 end of central dir
+ // signature 4 bytes (0x06064b50)
+ buf.putInt(Zip.ZIP64_ENDSIG);
+ // size of zip64 end of central
+ // directory record 8 bytes
+ buf.putLong(Zip.ZIP64_ENDSIZ + 5);
+ // version made by 2 bytes
+ buf.putShort((short) 0);
+ // version needed to extract 2 bytes
+ buf.putShort((short) 0);
+ // number of this disk 4 bytes
+ buf.putInt(0);
+ // number of the disk with the
+ // start of the central directory 4 bytes
+ buf.putInt(0);
+ // total number of entries in the
+ // central directory on this disk 8 bytes
+ buf.putLong(1);
+ // total number of entries in the
+ // central directory 8 bytes
+ buf.putLong(1);
+ // size of the central directory 8 bytes
+ buf.putLong(centralDirectorySize);
+ // offset of start of central
+ // directory with respect to
+ // offset of start of central
+ // the starting disk number 8 bytes
+ buf.putLong(centralDirectoryPosition);
+ // zip64 extensible data sector (variable size)
+ buf.put((byte) 3);
+ buf.putInt(42);
+
+ // zip64 end of central dir locator
+ // signature 4 bytes (0x07064b50)
+ buf.putInt(Zip.ZIP64_LOCSIG);
+ // number of the disk with the
+ // start of the zip64 end of
+ // central directory 4 bytes
+ buf.putInt(0);
+ // relative offset of the zip64
+ // end of central directory record 8 bytes
+ buf.putLong(zip64eocdPosition);
+ // total number of disks 4 bytes
+ buf.putInt(0);
+
+ // end of central dir signature 4 bytes (0x06054b50)
+ buf.putInt((int) ZipFile.ENDSIG);
+ // number of this disk 2 bytes
+ buf.putShort((short) 0);
+ // number of the disk with the
+ // start of the central directory 2 bytes
+ buf.putShort((short) 0);
+ // total number of entries in the
+ // central directory on this disk 2 bytes
+ buf.putShort((short) 1);
+ // total number of entries in
+ // the central directory 2 bytes
+ buf.putShort((short) Zip.ZIP64_MAGICCOUNT);
+ // size of the central directory 4 bytes
+ buf.putInt(centralDirectorySize);
+ // offset of start of central
+ // directory with respect to
+ // the starting disk number 4 bytes
+ buf.putInt(centralDirectoryPosition);
+ // .ZIP file comment length 2 bytes
+ buf.putShort((short) 0);
+ // .ZIP file comment (variable size)
+
+ byte[] bytes = new byte[buf.position()];
+ buf.rewind();
+ buf.get(bytes);
+ Path path = temporaryFolder.newFile("test.jar").toPath();
+ Files.write(path, bytes);
+ assertThat(actual(path)).isEqualTo(expected(path));
+ }
}