blob: 60af4d078fccd8e0837fb4328a9160a512246647 [file] [log] [blame]
/*
* Copyright (C) 2010 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 libcore.java.util.zip;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Random;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import junit.framework.TestCase;
public final class ZipFileTest extends TestCase {
/**
* Exercise Inflater's ability to refill the zlib's input buffer. As of this
* writing, this buffer's max size is 64KiB compressed bytes. We'll write a
* full megabyte of uncompressed data, which should be sufficient to exhaust
* the buffer. http://b/issue?id=2734751
*/
public void testInflatingFilesRequiringZipRefill() throws IOException {
int originalSize = 1024 * 1024;
byte[] readBuffer = new byte[8192];
ZipFile zipFile = new ZipFile(createZipFile(1, originalSize));
for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
ZipEntry zipEntry = e.nextElement();
assertTrue("This test needs >64 KiB of compressed data to exercise Inflater",
zipEntry.getCompressedSize() > (64 * 1024));
InputStream is = zipFile.getInputStream(zipEntry);
while (is.read(readBuffer, 0, readBuffer.length) != -1) {}
is.close();
}
zipFile.close();
}
private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) {
// Gotcha here: original and replacement must be the same length
assertEquals(original.length, replacement.length);
boolean found;
for(int i=0; i < buffer.length - original.length; i++) {
found = false;
if (buffer[i] == original[0]) {
found = true;
for (int j=0; j < original.length; j++) {
if (buffer[i+j] != original[j]) {
found = false;
break;
}
}
}
if (found) {
for (int j=0; j < original.length; j++) {
buffer[i+j] = replacement[j];
}
}
}
}
private static void writeBytes(File f, byte[] bytes) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(bytes);
out.close();
}
/**
* Make sure we don't fail silently for duplicate entries.
* b/8219321
*/
public void testDuplicateEntries() throws Exception {
String name1 = "test_file_name1";
String name2 = "test_file_name2";
// Create the good zip file.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(baos);
out.putNextEntry(new ZipEntry(name2));
out.closeEntry();
out.putNextEntry(new ZipEntry(name1));
out.closeEntry();
out.close();
// Rewrite one of the filenames.
byte[] buffer = baos.toByteArray();
replaceBytes(buffer, name2.getBytes(), name1.getBytes());
// Write the result to a file.
File badZip = createTemporaryZipFile();
writeBytes(badZip, buffer);
// Check that we refuse to load the modified file.
try {
ZipFile bad = new ZipFile(badZip);
fail();
} catch (ZipException expected) {
}
}
/**
* Make sure the size used for stored zip entires is the uncompressed size.
* b/10227498
*/
public void testStoredEntrySize() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(baos);
// Set up a single stored entry.
String name = "test_file";
int expectedLength = 5;
ZipEntry outEntry = new ZipEntry(name);
byte[] buffer = new byte[expectedLength];
outEntry.setMethod(ZipEntry.STORED);
CRC32 crc = new CRC32();
crc.update(buffer);
outEntry.setCrc(crc.getValue());
outEntry.setSize(buffer.length);
out.putNextEntry(outEntry);
out.write(buffer);
out.closeEntry();
out.close();
// Write the result to a file.
byte[] outBuffer = baos.toByteArray();
File zipFile = createTemporaryZipFile();
writeBytes(zipFile, outBuffer);
ZipFile zip = new ZipFile(zipFile);
// Set up the zip entry to have different compressed/uncompressed sizes.
ZipEntry ze = zip.getEntry(name);
ze.setCompressedSize(expectedLength - 1);
// Read the contents of the stream and verify uncompressed size was used.
InputStream stream = zip.getInputStream(ze);
int count = 0;
int read;
while ((read = stream.read(buffer)) != -1) {
count += read;
}
assertEquals(expectedLength, count);
}
public void testInflatingStreamsRequiringZipRefill() throws IOException {
int originalSize = 1024 * 1024;
byte[] readBuffer = new byte[8192];
ZipInputStream in = new ZipInputStream(new FileInputStream(createZipFile(1, originalSize)));
while (in.getNextEntry() != null) {
while (in.read(readBuffer, 0, readBuffer.length) != -1) {}
}
in.close();
}
public void testZipFileWithLotsOfEntries() throws IOException {
int expectedEntryCount = 64*1024 - 1;
File f = createZipFile(expectedEntryCount, 0);
ZipFile zipFile = new ZipFile(f);
int entryCount = 0;
for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
ZipEntry zipEntry = e.nextElement();
++entryCount;
}
assertEquals(expectedEntryCount, entryCount);
zipFile.close();
}
// http://code.google.com/p/android/issues/detail?id=36187
public void testZipFileLargerThan2GiB() throws IOException {
if (false) { // TODO: this test requires too much time and too much disk space!
File f = createZipFile(1024, 3*1024*1024);
ZipFile zipFile = new ZipFile(f);
int entryCount = 0;
for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
ZipEntry zipEntry = e.nextElement();
++entryCount;
}
assertEquals(1024, entryCount);
zipFile.close();
}
}
public void testZip64Support() throws IOException {
try {
createZipFile(64*1024, 0);
fail(); // Make this test more like testHugeZipFile when we have Zip64 support.
} catch (ZipException expected) {
}
}
/**
* Compresses the given number of files, each of the given size, into a .zip archive.
*/
private File createZipFile(int entryCount, int entrySize) throws IOException {
File result = createTemporaryZipFile();
byte[] writeBuffer = new byte[8192];
Random random = new Random();
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(result)));
for (int entry = 0; entry < entryCount; ++entry) {
ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
out.putNextEntry(ze);
for (int i = 0; i < entrySize; i += writeBuffer.length) {
random.nextBytes(writeBuffer);
int byteCount = Math.min(writeBuffer.length, entrySize - i);
out.write(writeBuffer, 0, byteCount);
}
out.closeEntry();
}
out.close();
return result;
}
private File createTemporaryZipFile() throws IOException {
File result = File.createTempFile("ZipFileTest", "zip");
result.deleteOnExit();
return result;
}
private ZipOutputStream createZipOutputStream(File f) throws IOException {
return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
}
public void testSTORED() throws IOException {
ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
CRC32 crc = new CRC32();
// Missing CRC, size, and compressed size => failure.
try {
ZipEntry ze = new ZipEntry("a");
ze.setMethod(ZipEntry.STORED);
out.putNextEntry(ze);
fail();
} catch (ZipException expected) {
}
// Missing CRC and compressed size => failure.
try {
ZipEntry ze = new ZipEntry("a");
ze.setMethod(ZipEntry.STORED);
ze.setSize(0);
out.putNextEntry(ze);
fail();
} catch (ZipException expected) {
}
// Missing CRC and size => failure.
try {
ZipEntry ze = new ZipEntry("a");
ze.setMethod(ZipEntry.STORED);
ze.setSize(0);
ze.setCompressedSize(0);
out.putNextEntry(ze);
fail();
} catch (ZipException expected) {
}
// Missing size and compressed size => failure.
try {
ZipEntry ze = new ZipEntry("a");
ze.setMethod(ZipEntry.STORED);
ze.setCrc(crc.getValue());
out.putNextEntry(ze);
fail();
} catch (ZipException expected) {
}
// Missing size is copied from compressed size.
{
ZipEntry ze = new ZipEntry("okay1");
ze.setMethod(ZipEntry.STORED);
ze.setCrc(crc.getValue());
assertEquals(-1, ze.getSize());
assertEquals(-1, ze.getCompressedSize());
ze.setCompressedSize(0);
assertEquals(-1, ze.getSize());
assertEquals(0, ze.getCompressedSize());
out.putNextEntry(ze);
assertEquals(0, ze.getSize());
assertEquals(0, ze.getCompressedSize());
}
// Missing compressed size is copied from size.
{
ZipEntry ze = new ZipEntry("okay2");
ze.setMethod(ZipEntry.STORED);
ze.setCrc(crc.getValue());
assertEquals(-1, ze.getSize());
assertEquals(-1, ze.getCompressedSize());
ze.setSize(0);
assertEquals(0, ze.getSize());
assertEquals(-1, ze.getCompressedSize());
out.putNextEntry(ze);
assertEquals(0, ze.getSize());
assertEquals(0, ze.getCompressedSize());
}
// Mismatched size and compressed size => failure.
try {
ZipEntry ze = new ZipEntry("a");
ze.setMethod(ZipEntry.STORED);
ze.setCrc(crc.getValue());
ze.setCompressedSize(1);
ze.setSize(0);
out.putNextEntry(ze);
fail();
} catch (ZipException expected) {
}
// Everything present => success.
ZipEntry ze = new ZipEntry("okay");
ze.setMethod(ZipEntry.STORED);
ze.setCrc(crc.getValue());
ze.setSize(0);
ze.setCompressedSize(0);
out.putNextEntry(ze);
out.close();
}
private String makeString(int count, String ch) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; ++i) {
sb.append(ch);
}
return sb.toString();
}
public void testComments() throws Exception {
String expectedFileComment = "1 \u0666 2";
String expectedEntryComment = "a \u0666 b";
File file = createTemporaryZipFile();
ZipOutputStream out = createZipOutputStream(file);
// Is file comment length checking done on bytes or characters? (Should be bytes.)
out.setComment(null);
out.setComment(makeString(0xffff, "a"));
try {
out.setComment(makeString(0xffff + 1, "a"));
fail();
} catch (IllegalArgumentException expected) {
}
try {
out.setComment(makeString(0xffff, "\u0666"));
fail();
} catch (IllegalArgumentException expected) {
}
ZipEntry ze = new ZipEntry("a");
// Is entry comment length checking done on bytes or characters? (Should be bytes.)
ze.setComment(null);
ze.setComment(makeString(0xffff, "a"));
try {
ze.setComment(makeString(0xffff + 1, "a"));
fail();
} catch (IllegalArgumentException expected) {
}
try {
ze.setComment(makeString(0xffff, "\u0666"));
fail();
} catch (IllegalArgumentException expected) {
}
ze.setComment(expectedEntryComment);
out.putNextEntry(ze);
out.closeEntry();
out.setComment(expectedFileComment);
out.close();
ZipFile zipFile = new ZipFile(file);
assertEquals(expectedFileComment, zipFile.getComment());
assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment());
zipFile.close();
}
public void test_getComment_unset() throws Exception {
File file = createTemporaryZipFile();
ZipOutputStream out = createZipOutputStream(file);
ZipEntry ze = new ZipEntry("test entry");
ze.setComment("per-entry comment");
out.putNextEntry(ze);
out.close();
ZipFile zipFile = new ZipFile(file);
assertEquals(null, zipFile.getComment());
}
// https://code.google.com/p/android/issues/detail?id=58465
public void test_NUL_in_filename() throws Exception {
File file = createTemporaryZipFile();
// We allow creation of a ZipEntry whose name contains a NUL byte,
// mainly because it's not likely to happen by accident and it's useful for testing.
ZipOutputStream out = createZipOutputStream(file);
out.putNextEntry(new ZipEntry("hello"));
out.putNextEntry(new ZipEntry("hello\u0000"));
out.close();
// But you can't open a ZIP file containing such an entry, because we reject it
// when we find it in the central directory.
try {
ZipFile zipFile = new ZipFile(file);
fail();
} catch (ZipException expected) {
}
}
public void testCrc() throws IOException {
ZipEntry ze = new ZipEntry("test");
ze.setMethod(ZipEntry.STORED);
ze.setSize(4);
// setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits).
try {
ze.setCrc(-1);
} catch (IllegalArgumentException expected) {
}
// You can set the CRC32 to 0xffffffff if you're slightly more careful though...
ze.setCrc(0xffffffffL);
assertEquals(0xffffffffL, ze.getCrc());
// And it actually works, even though we use -1L to mean "no CRC set"...
ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
out.putNextEntry(ze);
out.write(-1);
out.write(-1);
out.write(-1);
out.write(-1);
out.closeEntry();
out.close();
}
}