diff options
author | Paulo Casanova <pasc@google.com> | 2018-01-25 22:33:00 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-01-25 22:33:00 +0000 |
commit | 3a62a1f0b85784a7183ecdef2c559a4ea1fcbac4 (patch) | |
tree | 3dcf56e8eb58be9bd52255111d02fd20126fc2a8 | |
parent | 111c51b721880667382e68ed998593bdc4153f9b (diff) | |
parent | 29ad7967edb2047c8e9eddc952002db9aaaf4a81 (diff) | |
download | apkzlib-3a62a1f0b85784a7183ecdef2c559a4ea1fcbac4.tar.gz |
Added support for zip file comments in apkzlib.
am: 29ad7967ed
Change-Id: Ie3a2932c60b44e2704d8c831a465f5aee0fd1bc0
-rw-r--r-- | src/main/java/com/android/apkzlib/zip/Eocd.java | 20 | ||||
-rw-r--r-- | src/main/java/com/android/apkzlib/zip/ZFile.java | 118 | ||||
-rw-r--r-- | src/test/java/com/android/apkzlib/zip/ZFileTest.java | 113 |
3 files changed, 241 insertions, 10 deletions
diff --git a/src/main/java/com/android/apkzlib/zip/Eocd.java b/src/main/java/com/android/apkzlib/zip/Eocd.java index 36a0a6e..665f0a8 100644 --- a/src/main/java/com/android/apkzlib/zip/Eocd.java +++ b/src/main/java/com/android/apkzlib/zip/Eocd.java @@ -167,8 +167,9 @@ class Eocd { * @param directoryOffset offset, since beginning of archive, where the Central Directory is * located * @param directorySize number of bytes of the Central Directory + * @param comment the EOCD comment */ - Eocd(int totalRecords, long directoryOffset, long directorySize) { + Eocd(int totalRecords, long directoryOffset, long directorySize, @Nonnull byte[] comment) { Preconditions.checkArgument(totalRecords >= 0, "totalRecords < 0"); Preconditions.checkArgument(directoryOffset >= 0, "directoryOffset < 0"); Preconditions.checkArgument(directorySize >= 0, "directorySize < 0"); @@ -176,8 +177,8 @@ class Eocd { this.totalRecords = totalRecords; this.directoryOffset = directoryOffset; this.directorySize = directorySize; - comment = new byte[0]; - byteSupplier = new CachedSupplier<byte[]>(this::computeByteRepresentation); + this.comment = comment; + byteSupplier = new CachedSupplier<>(this::computeByteRepresentation); } /** @@ -228,6 +229,19 @@ class Eocd { return byteSupplier.get(); } + /* + * Obtains the comment in the EOCD. + * + * @return the comment exactly as it is represented in the file (no encoding conversion is + * done) + */ + @Nonnull + byte[] getComment() { + byte[] commentCopy = new byte[comment.length]; + System.arraycopy(comment, 0, commentCopy, 0, comment.length); + return commentCopy; + } + /** * Computes the byte representation of the EOCD. * diff --git a/src/main/java/com/android/apkzlib/zip/ZFile.java b/src/main/java/com/android/apkzlib/zip/ZFile.java index d4d730e..1e22d9b 100644 --- a/src/main/java/com/android/apkzlib/zip/ZFile.java +++ b/src/main/java/com/android/apkzlib/zip/ZFile.java @@ -201,9 +201,14 @@ public class ZFile implements Closeable { private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; /** + * Maximum size for the EOCD. + */ + private static final int MAX_EOCD_COMMENT_SIZE = 65535; + + /** * How many bytes to look back from the end of the file to look for the EOCD signature. */ - private static final int LAST_BYTES_TO_READ = 65535 + MIN_EOCD_SIZE; + private static final int LAST_BYTES_TO_READ = MIN_EOCD_SIZE + MAX_EOCD_COMMENT_SIZE; /** * Signature of the Zip64 EOCD locator record. @@ -211,6 +216,11 @@ public class ZFile implements Closeable { private static final int ZIP64_EOCD_LOCATOR_SIGNATURE = 0x07064b50; /** + * Signature of the EOCD record. + */ + private static final byte[] EOCD_SIGNATURE = new byte[] { 0x06, 0x05, 0x4b, 0x50 }; + + /** * Size of buffer for I/O operations. */ private static final int IO_BUFFER_SIZE = 1024 * 1024; @@ -257,6 +267,9 @@ public class ZFile implements Closeable { /** * The EOCD entry. Will be {@code null} if there is no EOCD (because the zip is new) or the * one that exists on disk is no longer valid (because the zip has been changed). + * + * <p>If the EOCD is deleted because the zip has been changed and the old EOCD was no longer + * valid, then {@link #eocdComment} will contain the comment saved from the EOCD. */ @Nullable private FileUseMapEntry<Eocd> eocdEntry; @@ -386,6 +399,19 @@ public class ZFile implements Closeable { @Nonnull private final VerifyLog verifyLog; + /** + * This field contains the comment in the zip's EOCD if there is no in-memory EOCD structure. + * This may happen, for example, if the zip has been changed and the Central Directory and + * EOCD have been deleted (in-memory). In that case, this field will save the comment to place + * on the EOCD once it is created. + * + * <p>This field will only be non-{@code null} if there is no in-memory EOCD structure + * (<i>i.e.</i>, {@link #eocdEntry} is {@code null}). If there is an {@link #eocdEntry}, then + * the comment will be there instead of being in this field. + */ + @Nullable + private byte[] eocdComment; + /** * Creates a new zip file. If the zip file does not exist, then no file is created at this @@ -455,7 +481,15 @@ public class ZFile implements Closeable { map.extend(Ints.checkedCast(rafSize)); readData(); + } + + // If we don't have an EOCD entry, set the comment to empty. + if (eocdEntry == null) { + eocdComment = new byte[0]; + } + // Notify the extensions if the zip file has been open. + if (state != ZipFileState.CLOSED) { notify(ZFileExtension::open); } } catch (Zip64NotSupportedException e) { @@ -614,7 +648,6 @@ public class ZFile implements Closeable { byte[] last = new byte[lastToRead]; directFullyRead(raf.length() - lastToRead, last); - byte[] eocdSignature = new byte[] { 0x06, 0x05, 0x4b, 0x50 }; /* * Start endIdx at the first possible location where the signature can be located and then @@ -635,10 +668,10 @@ public class ZFile implements Closeable { /* * Remember: little endian... */ - if (last[endIdx] == eocdSignature[3] - && last[endIdx + 1] == eocdSignature[2] - && last[endIdx + 2] == eocdSignature[1] - && last[endIdx + 3] == eocdSignature[0]) { + if (last[endIdx] == EOCD_SIGNATURE[3] + && last[endIdx + 1] == EOCD_SIGNATURE[2] + && last[endIdx + 2] == EOCD_SIGNATURE[1] + && last[endIdx + 3] == EOCD_SIGNATURE[0]) { /* * We found a signature. Try to read the EOCD record. @@ -1170,6 +1203,8 @@ public class ZFile implements Closeable { /** * Removes the Central Directory and EOCD from the file. This will free space for new entries * as well as allowing the zip file to be truncated if files have been removed. + * + * <p>This method does not mark the zip as dirty. */ private void deleteDirectoryAndEocd() { if (directoryEntry != null) { @@ -1179,6 +1214,10 @@ public class ZFile implements Closeable { if (eocdEntry != null) { map.remove(eocdEntry); + + Eocd eocd = eocdEntry.getStore(); + Verify.verify(eocd != null); + eocdComment = eocd.getComment(); eocdEntry = null; } } @@ -1353,7 +1392,9 @@ public class ZFile implements Closeable { dirStart = extraDirectoryOffset; } - Eocd eocd = new Eocd(entries.size(), dirStart, dirSize); + Verify.verify(eocdComment != null); + Eocd eocd = new Eocd(entries.size(), dirStart, dirSize, eocdComment); + eocdComment = null; byte[] eocdBytes = eocd.toBytes(); long eocdOffset = map.size(); @@ -2403,6 +2444,69 @@ public class ZFile implements Closeable { } /** + * Obtains the comment in the EOCD. + * + * @return the comment exactly as it was encoded in the EOCD, no encoding conversion is done + */ + @Nonnull + public byte[] getEocdComment() { + if (eocdEntry == null) { + Verify.verify(eocdComment != null); + byte[] eocdCommentCopy = new byte[eocdComment.length]; + System.arraycopy(eocdComment, 0, eocdCommentCopy, 0, eocdComment.length); + return eocdCommentCopy; + } + + Eocd eocd = eocdEntry.getStore(); + Verify.verify(eocd != null); + return eocd.getComment(); + } + + /** + * Sets the comment in the EOCD. + * + * @param comment the new comment; no conversion is done, these exact bytes will be placed in + * the EOCD comment + */ + public void setEocdComment(@Nonnull byte[] comment) { + if (comment.length > MAX_EOCD_COMMENT_SIZE) { + throw new IllegalArgumentException( + "EOCD comment size (" + + comment.length + + ") is larger than the maximum allowed (" + + MAX_EOCD_COMMENT_SIZE + + ")"); + } + + // Check if the EOCD signature appears anywhere in the comment we need to check if it + // is valid. + for (int i = 0; i < comment.length - MIN_EOCD_SIZE; i++) { + // Remember: little endian... + if (comment[i] == EOCD_SIGNATURE[3] + && comment[i + 1] == EOCD_SIGNATURE[2] + && comment[i + 2] == EOCD_SIGNATURE[1] + && comment[i + 3] == EOCD_SIGNATURE[0]) { + // We found a possible EOCD signature at position i. Try to read it. + ByteBuffer bytes = ByteBuffer.wrap(comment, i, comment.length - i); + try { + Eocd eocdInComment = new Eocd(bytes); + throw new IllegalArgumentException( + "Position " + + i + + " of the comment contains a valid EOCD record."); + } catch (IOException e) { + // Fine, this is an invalid record. Move along... + } + } + } + + deleteDirectoryAndEocd(); + eocdComment = new byte[comment.length]; + System.arraycopy(comment, 0, eocdComment, 0, comment.length); + dirty = true; + } + + /** * Sets an extra offset for the central directory. See class description for details. Changing * this value will mark the file as dirty and force a rewrite of the central directory when * updated. diff --git a/src/test/java/com/android/apkzlib/zip/ZFileTest.java b/src/test/java/com/android/apkzlib/zip/ZFileTest.java index f6062e7..bce0286 100644 --- a/src/test/java/com/android/apkzlib/zip/ZFileTest.java +++ b/src/test/java/com/android/apkzlib/zip/ZFileTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.android.apkzlib.zip.compress.DeflateExecutionCompressor; import com.android.apkzlib.zip.utils.CloseableByteSource; @@ -1669,4 +1670,116 @@ public class ZFileTest { assertNotEquals(DataDescriptorType.NO_DATA_DESCRIPTOR, se.getDataDescriptorType()); } } + + @Test + public void zipCommentsAreSaved() throws Exception { + File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileWithComments))) { + zos.setComment("foo"); + } + + /* + * Open the zip file and check the comment is there. + */ + try (ZFile zf = new ZFile(zipFileWithComments)) { + byte[] comment = zf.getEocdComment(); + assertArrayEquals(new byte[] { 'f', 'o', 'o' }, comment); + + /* + * Modify the comment and write the file. + */ + zf.setEocdComment(new byte[] { 'b', 'a', 'r', 'r' }); + } + + /* + * Open the file and see that the comment is there (both with java and zfile). + */ + try (ZipFile zf2 = new ZipFile(zipFileWithComments)) { + assertEquals("barr", zf2.getComment()); + } + + try (ZFile zf3 = new ZFile(zipFileWithComments)) { + assertArrayEquals(new byte[] { 'b', 'a', 'r', 'r' }, zf3.getEocdComment()); + } + } + + @Test + public void eocdCommentsWithMoreThan64kNotAllowed() throws Exception { + File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); + try (ZFile zf = new ZFile(zipFileWithComments)) { + try { + zf.setEocdComment(new byte[65536]); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } + + zf.setEocdComment(new byte[65535]); + } + } + + @Test + public void eocdCommentsWithTheEocdMarkerAreAllowed() throws Exception { + File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); + byte[] data = new byte[100]; + data[50] = 0x50; // Signature + data[51] = 0x4b; + data[52] = 0x05; + data[53] = 0x06; + data[54] = 0x00; // Number of disk + data[55] = 0x00; + data[56] = 0x00; // Disk CD start + data[57] = 0x00; + data[54] = 0x01; // Total records 1 + data[55] = 0x00; + data[56] = 0x02; // Total records 2, must be = to total records 1 + data[57] = 0x00; + + try (ZFile zf = new ZFile(zipFileWithComments)) { + zf.setEocdComment(data); + } + + try (ZFile zf = new ZFile(zipFileWithComments)) { + assertArrayEquals(data, zf.getEocdComment()); + } + } + + @Test + public void eocdCommentsWithTheEocdMarkerThatAreInvalidAreNotAllowed() throws Exception { + File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); + byte[] data = new byte[100]; + data[50] = 0x50; + data[51] = 0x4b; + data[52] = 0x05; + data[53] = 0x06; + data[67] = 0x00; + + try (ZFile zf = new ZFile(zipFileWithComments)) { + try { + zf.setEocdComment(data); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } + } + } + + @Test + public void zipCommentsArePreservedWithFileChanges() throws Exception { + File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); + byte[] comment = new byte[] { 1, 3, 4 }; + try (ZFile zf = new ZFile(zipFileWithComments)) { + zf.add("foo", new ByteArrayInputStream(new byte[50])); + zf.setEocdComment(comment); + } + + try (ZFile zf = new ZFile(zipFileWithComments)) { + assertArrayEquals(comment, zf.getEocdComment()); + zf.add("bar", new ByteArrayInputStream(new byte[100])); + } + + try (ZFile zf = new ZFile(zipFileWithComments)) { + assertArrayEquals(comment, zf.getEocdComment()); + } + } } |