Skip to content

Commit

Permalink
Support zip64 extensible data sectors
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 440187167
  • Loading branch information
cushon authored and Javac Team committed Apr 7, 2022
1 parent c527031 commit 9bf393c
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 4 deletions.
33 changes: 29 additions & 4 deletions java/com/google/turbine/zip/Zip.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
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
Expand Down Expand Up @@ -196,20 +197,44 @@ public ZipIterable(Path path) throws IOException {
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);
Expand Down
163 changes: 163 additions & 0 deletions javatests/com/google/turbine/zip/ZipTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
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;
Expand All @@ -38,6 +40,7 @@
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;
Expand Down Expand Up @@ -164,4 +167,164 @@ public void malformedComment() throws Exception {
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));
}
}

0 comments on commit 9bf393c

Please sign in to comment.