summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaulo Casanova <pasc@google.com>2018-01-25 23:06:05 +0000
committerandroid-build-merger <android-build-merger@google.com>2018-01-25 23:06:05 +0000
commit4e772cdd38f91f7e46b81c62940b6f80089446de (patch)
tree3dcf56e8eb58be9bd52255111d02fd20126fc2a8
parent00ca2aff51f45d6dba9f5713a5aba45e6e321051 (diff)
parent3a62a1f0b85784a7183ecdef2c559a4ea1fcbac4 (diff)
downloadapkzlib-4e772cdd38f91f7e46b81c62940b6f80089446de.tar.gz
Added support for zip file comments in apkzlib. am: 29ad7967ed
am: 3a62a1f0b8 Change-Id: I79de76a8831f0c4db44ca180b8616a05f85b1165
-rw-r--r--src/main/java/com/android/apkzlib/zip/Eocd.java20
-rw-r--r--src/main/java/com/android/apkzlib/zip/ZFile.java118
-rw-r--r--src/test/java/com/android/apkzlib/zip/ZFileTest.java113
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());
+ }
+ }
}