diff options
author | Liam Miller-Cushon <cushon@google.com> | 2022-04-07 13:31:48 -0700 |
---|---|---|
committer | Javac Team <javac-team+copybara@google.com> | 2022-04-07 13:32:41 -0700 |
commit | 9bf393cbcfc281bbac6e3d3f042b4444156567ac (patch) | |
tree | 40990cc376848076decce3925aa0cdc72176a667 | |
parent | c527031ef8d9112b12d768f308e768af18fa12f0 (diff) | |
download | turbine-9bf393cbcfc281bbac6e3d3f042b4444156567ac.tar.gz |
Support zip64 extensible data sectors
PiperOrigin-RevId: 440187167
-rw-r--r-- | java/com/google/turbine/zip/Zip.java | 33 | ||||
-rw-r--r-- | javatests/com/google/turbine/zip/ZipTest.java | 163 |
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)); + } } |