summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD2
-rw-r--r--apkzlib.iml1
-rw-r--r--build.gradle2
-rw-r--r--src/main/java/com/android/apkzlib/zip/ExtraField.java10
-rw-r--r--src/main/java/com/android/apkzlib/zip/FileUseMap.java37
-rw-r--r--src/main/java/com/android/apkzlib/zip/StoredEntry.java2
-rw-r--r--src/main/java/com/android/apkzlib/zip/ZFile.java129
-rw-r--r--src/test/java/com/android/apkzlib/zip/ExtraFieldTest.java26
-rw-r--r--src/test/java/com/android/apkzlib/zip/ZFileReadOnlyTest.java240
-rw-r--r--src/test/java/com/android/apkzlib/zip/ZFileTest.java36
-rw-r--r--src/test/resources/testData/packaging/entry-outside-file.zipbin0 -> 364 bytes
-rw-r--r--src/test/resources/testData/packaging/overlapping.zipbin0 -> 502 bytes
-rw-r--r--src/test/resources/testData/packaging/overlapping2.zipbin0 -> 502 bytes
13 files changed, 481 insertions, 4 deletions
diff --git a/BUILD b/BUILD
index f0f0bb8..0094d45 100644
--- a/BUILD
+++ b/BUILD
@@ -7,7 +7,7 @@ java_library(
]),
visibility = ["//tools/base/build-system/builder:__pkg__"],
deps = [
- "//tools/base/build-system:tools.apksig",
+ "//tools/apksig",
"//tools/base/third_party:com.google.code.findbugs_jsr305",
"//tools/base/third_party:com.google.guava_guava",
"//tools/base/third_party:org.bouncycastle_bcpkix-jdk15on",
diff --git a/apkzlib.iml b/apkzlib.iml
index 3c33d1b..999fffa 100644
--- a/apkzlib.iml
+++ b/apkzlib.iml
@@ -16,5 +16,6 @@
<orderEntry type="library" name="bouncy-castle" level="project" />
<orderEntry type="module" module-name="testutils" scope="TEST" />
<orderEntry type="module" module-name="apksig" />
+ <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index f49541a..771d1b8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'java'
+apply from: "$rootDir/buildSrc/base/baseJava.gradle"
dependencies {
compile 'com.google.code.findbugs:jsr305:1.3.9'
diff --git a/src/main/java/com/android/apkzlib/zip/ExtraField.java b/src/main/java/com/android/apkzlib/zip/ExtraField.java
index 90c6fae..d70fa7f 100644
--- a/src/main/java/com/android/apkzlib/zip/ExtraField.java
+++ b/src/main/java/com/android/apkzlib/zip/ExtraField.java
@@ -158,6 +158,16 @@ public class ExtraField {
}
byte[] data = new byte[dataSize];
+ if (buffer.remaining() < dataSize) {
+ throw new IOException(
+ "Invalid data size for extra field segment with header ID "
+ + headerId
+ + ": "
+ + dataSize
+ + " (only "
+ + buffer.remaining()
+ + " bytes are available)");
+ }
buffer.get(data);
SegmentFactory factory = identifySegmentFactory(headerId);
diff --git a/src/main/java/com/android/apkzlib/zip/FileUseMap.java b/src/main/java/com/android/apkzlib/zip/FileUseMap.java
index a72a956..8a76878 100644
--- a/src/main/java/com/android/apkzlib/zip/FileUseMap.java
+++ b/src/main/java/com/android/apkzlib/zip/FileUseMap.java
@@ -538,6 +538,43 @@ class FileUseMap {
return map.lower(entry);
}
+ /**
+ * Obtains the entry that is located after the one provided.
+ *
+ * @param entry the map entry to get the next one for; must belong to the map
+ * @return the entry after the provided one, {@code null} if {@code entry} is the last entry in
+ * the map
+ */
+ @Nullable
+ FileUseMapEntry<?> after(@Nonnull FileUseMapEntry<?> entry) {
+ Preconditions.checkNotNull(entry, "entry == null");
+
+ return map.higher(entry);
+ }
+
+ /**
+ * Obtains the entry at the given offset.
+ *
+ * @param offset the offset to look for
+ * @return the entry found or {@code null} if there is no entry (not even a free one) at the
+ * given offset
+ */
+ @Nullable
+ FileUseMapEntry<?> at(long offset) {
+ Preconditions.checkArgument(offset >= 0, "offset < 0");
+ Preconditions.checkArgument(offset < size, "offset >= size");
+
+ FileUseMapEntry<?> entry = map.floor(FileUseMapEntry.makeFree(offset, offset + 1));
+ if (entry == null) {
+ return null;
+ }
+
+ Verify.verify(entry.getStart() <= offset);
+ Verify.verify(entry.getEnd() > offset);
+
+ return entry;
+ }
+
@Override
public String toString() {
StringJoiner j = new StringJoiner(", ");
diff --git a/src/main/java/com/android/apkzlib/zip/StoredEntry.java b/src/main/java/com/android/apkzlib/zip/StoredEntry.java
index 94aa01a..854bf3a 100644
--- a/src/main/java/com/android/apkzlib/zip/StoredEntry.java
+++ b/src/main/java/com/android/apkzlib/zip/StoredEntry.java
@@ -380,6 +380,7 @@ public class StoredEntry {
* To eventually write updates to disk, {@link ZFile#update()} must be called.
*
* @throws IOException failed to delete the entry
+ * @throws IllegalStateException if the zip file was open in read-only mode
*/
public void delete() throws IOException {
delete(true);
@@ -392,6 +393,7 @@ public class StoredEntry {
* @param notify should listeners be notified of the deletion? This will only be
* {@code false} if the entry is being removed as part of a replacement
* @throws IOException failed to delete the entry
+ * @throws IllegalStateException if the zip file was open in read-only mode
*/
void delete(boolean notify) throws IOException {
Preconditions.checkState(!deleted, "deleted");
diff --git a/src/main/java/com/android/apkzlib/zip/ZFile.java b/src/main/java/com/android/apkzlib/zip/ZFile.java
index 8a88cd0..9034f4c 100644
--- a/src/main/java/com/android/apkzlib/zip/ZFile.java
+++ b/src/main/java/com/android/apkzlib/zip/ZFile.java
@@ -414,6 +414,11 @@ public class ZFile implements Closeable {
@Nullable
private byte[] eocdComment;
+ /**
+ * Is the file in read-only mode? In read-only mode no changes are allowed.
+ */
+ private boolean readOnly;
+
/**
* Creates a new zip file. If the zip file does not exist, then no file is created at this
@@ -439,12 +444,30 @@ public class ZFile implements Closeable {
* @throws IOException some file exists but could not be read
*/
public ZFile(@Nonnull File file, @Nonnull ZFileOptions options) throws IOException {
+ this(file, options, false);
+ }
+
+ /**
+ * Creates a new zip file. If the zip file does not exist, then no file is created at this
+ * point and {@code ZFile} will contain an empty structure. However, an (empty) zip file will
+ * be created if either {@link #update()} or {@link #close()} are used. If a zip file exists,
+ * it will be parsed and read.
+ *
+ * @param file the zip file
+ * @param options configuration options
+ * @param readOnly should the file be open in read-only mode? If {@code true} then the file must
+ * exist and no methods can be invoked that could potentially change the file
+ * @throws IOException some file exists but could not be read
+ */
+ public ZFile(@Nonnull File file, @Nonnull ZFileOptions options, boolean readOnly)
+ throws IOException {
this.file = file;
map = new FileUseMap(
0,
options.getCoverEmptySpaceUsingExtraField()
? MINIMUM_EXTRA_FIELD_SIZE
: 0);
+ this.readOnly = readOnly;
dirty = false;
closedControl = null;
alignmentRule = options.getAlignmentRule();
@@ -466,6 +489,8 @@ public class ZFile implements Closeable {
if (file.exists()) {
openReadOnly();
+ } else if (readOnly) {
+ throw new IOException("File does not exist but read-only mode requested");
} else {
dirty = true;
}
@@ -572,7 +597,7 @@ public class ZFile implements Closeable {
readCentralDirectory();
/*
- * Compute where the last file ends. We will need this to compute thee extra offset.
+ * Go over all files and create the usage map, verifying there is no overlap in the files.
*/
long entryEndOffset;
long directoryStartOffset;
@@ -600,6 +625,51 @@ public class ZFile implements Closeable {
* file.
*/
+ Verify.verify(start >= 0, "start < 0");
+ Verify.verify(end < map.size(), "end >= map.size()");
+
+ FileUseMapEntry<?> found = map.at(start);
+ Verify.verifyNotNull(found);
+
+ // We've got a problem if the found entry is not free or is a free entry but
+ // doesn't cover the whole file.
+ if (!found.isFree() || found.getEnd() < end) {
+ if (found.isFree()) {
+ found = map.after(found);
+ Verify.verify(found != null && !found.isFree());
+ }
+
+ Object foundEntry = found.getStore();
+ Verify.verify(foundEntry != null);
+
+ // Obtains a custom description of an entry.
+ IOExceptionFunction<StoredEntry, String> describe =
+ e ->
+ String.format(
+ "'%s' (offset: %d, size: %d)",
+ e.getCentralDirectoryHeader().getName(),
+ e.getCentralDirectoryHeader().getOffset(),
+ e.getInFileSize());
+
+ String overlappingEntryDescription;
+ if (foundEntry instanceof StoredEntry) {
+ StoredEntry foundStored = (StoredEntry) foundEntry;
+ overlappingEntryDescription = describe.apply((StoredEntry) foundEntry);
+ } else {
+ overlappingEntryDescription =
+ "Central Directory / EOCD: "
+ + found.getStart()
+ + " - "
+ + found.getEnd();
+ }
+
+ throw new IOException(
+ "Cannot read entry "
+ + describe.apply(entry)
+ + " because it overlaps with "
+ + overlappingEntryDescription);
+ }
+
FileUseMapEntry<StoredEntry> mapEntry = map.add(start, end, entry);
entries.put(entry.getCentralDirectoryHeader().getName(), mapEntry);
@@ -873,8 +943,11 @@ public class ZFile implements Closeable {
* @param notify should listeners be notified of the deletion? This will only be
* {@code false} if the entry is being removed as part of a replacement
* @throws IOException failed to delete the entry
+ * @throws IllegalStateException if open in read-only mode
*/
void delete(@Nonnull final StoredEntry entry, boolean notify) throws IOException {
+ checkNotInReadOnlyMode();
+
String path = entry.getCentralDirectoryHeader().getName();
FileUseMapEntry<StoredEntry> mapEntry = entries.get(path);
Preconditions.checkNotNull(mapEntry, "mapEntry == null");
@@ -891,6 +964,17 @@ public class ZFile implements Closeable {
}
/**
+ * Checks that the file is not in read-only mode.
+ *
+ * @throws IllegalStateException if the file is in read-only mode
+ */
+ private void checkNotInReadOnlyMode() {
+ if (readOnly) {
+ throw new IllegalStateException("Illegal operation in read only model");
+ }
+ }
+
+ /**
* Updates the file writing new entries and removing deleted entries. This will force
* reopening the file as read/write if the file wasn't open in read/write mode.
*
@@ -898,6 +982,8 @@ public class ZFile implements Closeable {
* the compressor but only reported here
*/
public void update() throws IOException {
+ checkNotInReadOnlyMode();
+
/*
* Process all background stuff before calling in the extensions.
*/
@@ -1193,7 +1279,9 @@ public class ZFile implements Closeable {
// We need to make sure to release raf, otherwise we end up locking the file on
// Windows. Use try-with-resources to handle exception suppressing.
try (Closeable ignored = this::innerClose) {
- update();
+ if (!readOnly) {
+ update();
+ }
}
notify(ext -> {
@@ -1489,6 +1577,9 @@ public class ZFile implements Closeable {
* has been modified outside the control of this object
*/
private void reopenRw() throws IOException {
+ // We an never open a file RW in read-only mode. We should never get this far, though.
+ Verify.verify(!readOnly);
+
if (state == ZipFileState.OPEN_RW) {
return;
}
@@ -1537,8 +1628,10 @@ public class ZFile implements Closeable {
* and the name should not end in slash
* @param stream the source for the file's data
* @throws IOException failed to read the source data
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void add(@Nonnull String name, @Nonnull InputStream stream) throws IOException {
+ checkNotInReadOnlyMode();
add(name, stream, true);
}
@@ -1656,9 +1749,11 @@ public class ZFile implements Closeable {
* @param mayCompress can the file be compressed? This flag will be ignored if the alignment
* rules force the file to be aligned, in which case the file will not be compressed.
* @throws IOException failed to read the source data
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void add(@Nonnull String name, @Nonnull InputStream stream, boolean mayCompress)
throws IOException {
+ checkNotInReadOnlyMode();
/*
* Clean pending background work, if needed.
@@ -1868,9 +1963,12 @@ public class ZFile implements Closeable {
* @param ignoreFilter predicate that, if {@code true}, identifies files in <em>src</em> that
* should be ignored by merging; merging will behave as if these files were not there
* @throws IOException failed to read from <em>src</em> or write on the output
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void mergeFrom(@Nonnull ZFile src, @Nonnull Predicate<String> ignoreFilter)
throws IOException {
+ checkNotInReadOnlyMode();
+
for (StoredEntry fromEntry : src.entries()) {
if (ignoreFilter.test(fromEntry.getCentralDirectoryHeader().getName())) {
continue;
@@ -1960,8 +2058,11 @@ public class ZFile implements Closeable {
/**
* Forcibly marks this zip file as touched, forcing it to be updated when {@link #update()}
* or {@link #close()} are invoked.
+ *
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void touch() {
+ checkNotInReadOnlyMode();
dirty = true;
}
@@ -1988,8 +2089,11 @@ public class ZFile implements Closeable {
* of {@code ZFile} may refer to {@link StoredEntry}s that are no longer valid
* @throws IOException failed to realign the zip; some entries in the zip may have been lost
* due to the I/O error
+ * @throws IllegalStateException if the file is in read-only mode
*/
public boolean realign() throws IOException {
+ checkNotInReadOnlyMode();
+
boolean anyChanges = false;
for (StoredEntry entry : entries()) {
anyChanges |= entry.realign();
@@ -2103,8 +2207,10 @@ public class ZFile implements Closeable {
* Adds an extension to this zip file.
*
* @param extension the listener to add
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void addZFileExtension(@Nonnull ZFileExtension extension) {
+ checkNotInReadOnlyMode();
extensions.add(extension);
}
@@ -2112,8 +2218,10 @@ public class ZFile implements Closeable {
* Removes an extension from this zip file.
*
* @param extension the listener to remove
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void removeZFileExtension(@Nonnull ZFileExtension extension) {
+ checkNotInReadOnlyMode();
extensions.remove(extension);
}
@@ -2157,9 +2265,12 @@ public class ZFile implements Closeable {
* @param start start offset in {@code data} where data to write is located
* @param count number of bytes of data to write
* @throws IOException failed to write the data
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void directWrite(long offset, @Nonnull byte[] data, int start, int count)
throws IOException {
+ checkNotInReadOnlyMode();
+
Preconditions.checkArgument(offset >= 0, "offset < 0");
Preconditions.checkArgument(start >= 0, "start >= 0");
Preconditions.checkArgument(count >= 0, "count >= 0");
@@ -2184,8 +2295,10 @@ public class ZFile implements Closeable {
* @param offset the offset at which data should be written
* @param data the data to write, may be an empty array
* @throws IOException failed to write the data
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void directWrite(long offset, @Nonnull byte[] data) throws IOException {
+ checkNotInReadOnlyMode();
directWrite(offset, data, 0, data.length);
}
@@ -2322,8 +2435,10 @@ public class ZFile implements Closeable {
* @param file a file or directory; if it is a directory, all files and directories will be
* added recursively
* @throws IOException failed to some (or all ) of the files
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void addAllRecursively(@Nonnull File file) throws IOException {
+ checkNotInReadOnlyMode();
addAllRecursively(file, f -> true);
}
@@ -2334,10 +2449,13 @@ public class ZFile implements Closeable {
* added recursively
* @param mayCompress a function that decides whether files may be compressed
* @throws IOException failed to some (or all ) of the files
+ * @throws IllegalStateException if the file is in read-only mode
*/
public void addAllRecursively(
@Nonnull File file,
@Nonnull Function<? super File, Boolean> mayCompress) throws IOException {
+ checkNotInReadOnlyMode();
+
/*
* The case of file.isFile() is different because if file.isFile() we will add it to the
* zip in the root. However, if file.isDirectory() we won't add it and add its children.
@@ -2469,8 +2587,11 @@ public class ZFile implements Closeable {
*
* @param comment the new comment; no conversion is done, these exact bytes will be placed in
* the EOCD comment
+ * @throws IllegalStateException if file is in read-only mode
*/
public void setEocdComment(@Nonnull byte[] comment) {
+ checkNotInReadOnlyMode();
+
if (comment.length > MAX_EOCD_COMMENT_SIZE) {
throw new IllegalArgumentException(
"EOCD comment size ("
@@ -2514,8 +2635,10 @@ public class ZFile implements Closeable {
* updated.
*
* @param offset the offset or {@code 0} to write the central directory at its current location
+ * @throws IllegalStateException if file is in read-only mode
*/
public void setExtraDirectoryOffset(long offset) {
+ checkNotInReadOnlyMode();
Preconditions.checkArgument(offset >= 0, "offset < 0");
if (extraDirectoryOffset != offset) {
@@ -2551,8 +2674,10 @@ public class ZFile implements Closeable {
* written to disk.
*
* @throws IOException failed to load or move a file in the zip
+ * @throws IllegalStateException if file is in read-only mode
*/
public void sortZipContents() throws IOException {
+ checkNotInReadOnlyMode();
reopenRw();
processAllReadyEntriesWithWait();
diff --git a/src/test/java/com/android/apkzlib/zip/ExtraFieldTest.java b/src/test/java/com/android/apkzlib/zip/ExtraFieldTest.java
index d80ccc4..2371849 100644
--- a/src/test/java/com/android/apkzlib/zip/ExtraFieldTest.java
+++ b/src/test/java/com/android/apkzlib/zip/ExtraFieldTest.java
@@ -19,6 +19,7 @@ package com.android.apkzlib.zip;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
@@ -332,4 +333,29 @@ public class ExtraFieldTest {
assertArrayEquals(new byte[] { 0x54, 0x76, 0x04, 0x00, 2, 4, 2, 4 }, sData);
}
}
+
+ @Test
+ public void parseInvalidExtraFieldWithInvalidHeader() throws Exception {
+ byte[] raw = new byte[1];
+ ExtraField ef = new ExtraField(raw);
+ try {
+ ef.getSegments();
+ fail();
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void parseInvalidExtraFieldWithInsufficientData() throws Exception {
+ // Remember: 0x05, 0x00 = 5 in little endian!
+ byte[] raw = new byte[] { /* Header */ 0x01, 0x02, /* Size */ 0x05, 0x00, /* Data */ 0x01 };
+ ExtraField ef = new ExtraField(raw);
+ try {
+ ef.getSegments();
+ fail();
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
}
diff --git a/src/test/java/com/android/apkzlib/zip/ZFileReadOnlyTest.java b/src/test/java/com/android/apkzlib/zip/ZFileReadOnlyTest.java
new file mode 100644
index 0000000..a030a83
--- /dev/null
+++ b/src/test/java/com/android/apkzlib/zip/ZFileReadOnlyTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkzlib.zip;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ZFileReadOnlyTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void cannotCreateRoFileOnNonExistingFile() throws Exception {
+ try {
+ new ZFile(new File(temporaryFolder.getRoot(), "foo.zip"), new ZFileOptions(), true);
+ fail();
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
+
+ @Nonnull
+ private File makeTestZip() throws IOException {
+ File zip = new File(temporaryFolder.getRoot(), "foo.zip");
+ try (ZFile zf = new ZFile(zip)) {
+ zf.add("bar", new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4, 5 }));
+ }
+
+ return zip;
+ }
+
+ @Test
+ public void cannotUpdateInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.update();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotAddFilesInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.add("bar2", new ByteArrayInputStream(new byte[] { 6, 7, }));
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotAddRecursivelyInRoMode() throws Exception {
+ File folder = temporaryFolder.newFolder();
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.addAllRecursively(folder);
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotReplaceFilesInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.add("bar", new ByteArrayInputStream(new byte[] { 6, 7 }));
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotDeleteFilesInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ StoredEntry bar = zf.get("bar");
+ assertNotNull(bar);
+ try {
+ bar.delete();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotMergeInRoMode() throws Exception {
+ try (ZFile toMerge = new ZFile(new File(temporaryFolder.getRoot(), "a.zip"))) {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.mergeFrom(toMerge, s -> false);
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+ }
+
+ @Test
+ public void cannotTouchInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.touch();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotRealignInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.realign();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotAddExtensionInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.addZFileExtension(new ZFileExtension() {});
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotDirectWriteInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.directWrite(0, new byte[1]);
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotSetEocdCommentInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.setEocdComment(new byte[2]);
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotSetCentralDirectoryOffsetInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.setExtraDirectoryOffset(4);
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void cannotSortZipContentsInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ try {
+ zf.sortZipContents();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expeted.
+ }
+ }
+ }
+
+ @Test
+ public void canOpenAndReadFilesInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ StoredEntry bar = zf.get("bar");
+ assertNotNull(bar);
+ assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5 }, bar.read());
+ }
+ }
+
+ @Test
+ public void canGetDirectoryAndEocdBytesInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ zf.getCentralDirectoryBytes();
+ zf.getEocdBytes();
+ zf.getEocdComment();
+ }
+ }
+
+ @Test
+ public void canDirectReadInRoMode() throws Exception {
+ try (ZFile zf = new ZFile(makeTestZip(), new ZFileOptions(), true)) {
+ zf.directRead(0, new byte[2]);
+ }
+ }
+}
diff --git a/src/test/java/com/android/apkzlib/zip/ZFileTest.java b/src/test/java/com/android/apkzlib/zip/ZFileTest.java
index bce0286..b7f2979 100644
--- a/src/test/java/com/android/apkzlib/zip/ZFileTest.java
+++ b/src/test/java/com/android/apkzlib/zip/ZFileTest.java
@@ -33,6 +33,7 @@ import com.android.apkzlib.zip.utils.CloseableByteSource;
import com.android.apkzlib.zip.utils.RandomAccessFileUtils;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
+import com.google.common.base.Throwables;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
@@ -1782,4 +1783,39 @@ public class ZFileTest {
assertArrayEquals(comment, zf.getEocdComment());
}
}
+
+ @Test
+ public void overlappingZipEntries() throws Exception {
+ File myZip = ZipTestUtils.cloneRsrc("overlapping.zip", mTemporaryFolder);
+ try (ZFile zf = new ZFile(myZip)) {
+ fail();
+ } catch (IOException e) {
+ assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/bbb"));
+ assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd"));
+ assertFalse(Throwables.getStackTraceAsString(e).contains("Central Directory"));
+ }
+ }
+
+ @Test
+ public void overlappingZipEntryWithCentralDirectory() throws Exception {
+ File myZip = ZipTestUtils.cloneRsrc("overlapping2.zip", mTemporaryFolder);
+ try (ZFile zf = new ZFile(myZip)) {
+ fail();
+ } catch (IOException e) {
+ assertFalse(Throwables.getStackTraceAsString(e).contains("overlapping/bbb"));
+ assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd"));
+ assertTrue(Throwables.getStackTraceAsString(e).contains("Central Directory"));
+ }
+ }
+
+ @Test
+ public void readFileWithOffsetBeyondFileEnd() throws Exception {
+ File myZip = ZipTestUtils.cloneRsrc("entry-outside-file.zip", mTemporaryFolder);
+ try (ZFile zf = new ZFile(myZip)) {
+ fail();
+ } catch (IOException e) {
+ assertTrue(Throwables.getStackTraceAsString(e).contains("entry-outside-file/foo"));
+ assertTrue(Throwables.getStackTraceAsString(e).contains("EOF"));
+ }
+ }
}
diff --git a/src/test/resources/testData/packaging/entry-outside-file.zip b/src/test/resources/testData/packaging/entry-outside-file.zip
new file mode 100644
index 0000000..ffd6be9
--- /dev/null
+++ b/src/test/resources/testData/packaging/entry-outside-file.zip
Binary files differ
diff --git a/src/test/resources/testData/packaging/overlapping.zip b/src/test/resources/testData/packaging/overlapping.zip
new file mode 100644
index 0000000..7f6144c
--- /dev/null
+++ b/src/test/resources/testData/packaging/overlapping.zip
Binary files differ
diff --git a/src/test/resources/testData/packaging/overlapping2.zip b/src/test/resources/testData/packaging/overlapping2.zip
new file mode 100644
index 0000000..eecefa9
--- /dev/null
+++ b/src/test/resources/testData/packaging/overlapping2.zip
Binary files differ