diff options
author | Narayan Kamath <narayan@google.com> | 2015-04-03 12:42:15 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-04-03 12:42:17 +0000 |
commit | f640e5b7bc2a08cd48e9bc691d60e76303417778 (patch) | |
tree | 6353891f128a07e8c741063395a13696b66844b2 | |
parent | ff8391a7f7f2181dba58ea6e7dd5928cff81167e (diff) | |
parent | a812a87e69850d1492c45bd88d7ff3dbf21d5075 (diff) | |
download | libcore-f640e5b7bc2a08cd48e9bc691d60e76303417778.zip libcore-f640e5b7bc2a08cd48e9bc691d60e76303417778.tar.gz libcore-f640e5b7bc2a08cd48e9bc691d60e76303417778.tar.bz2 |
Merge "Don't use zip64 by default."
6 files changed, 669 insertions, 579 deletions
diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java index 4995f5b..7748cfd 100644 --- a/luni/src/main/java/java/util/zip/ZipOutputStream.java +++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java @@ -463,7 +463,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // In this particular case, we'll write a zip64 eocd record locator and a zip64 eocd // record but we won't actually need zip64 extended info records for any of the individual // entries (unless they trigger the checks below). - if (entriesWritten == 64*1024-1) { + if (entriesWritten == 64*1024 - 1) { archiveNeedsZip64EocdRecord = true; } @@ -473,13 +473,8 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // // TODO: This is an imprecise check. When method != STORED it's possible that the compressed // size will be (slightly) larger than the actual size. How can we improve this ? - // - // TODO: Will we regret forcing zip64 for archive entries with unknown entry sizes ? This is - // standard "zip" behaviour on linux but i'm not sure if we'll end up breaking somebody as a - // result. if (totalBytesWritten > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || - (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) || - (entry.getSize() == -1)) { + (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE)) { currentEntryNeedsZip64 = true; archiveNeedsZip64EocdRecord = true; } @@ -566,6 +561,13 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant throw new ZipException("No active entry"); } + final long totalBytes = crc.tbytes + byteCount; + if ((totalBytes > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) && !currentEntryNeedsZip64) { + throw new IOException("Zip entry size (" + totalBytes + + " bytes) cannot be represented in the zip format (needs Zip64)." + + " Set the entry length using ZipEntry#setLength to use Zip64 where necessary."); + } + if (currentEntry.getMethod() == STORED) { out.write(buffer, offset, byteCount); } else { diff --git a/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java new file mode 100644 index 0000000..9e049c0 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java @@ -0,0 +1,548 @@ +/* + * 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.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +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; + +import tests.support.resource.Support_Resources; + +public abstract class AbstractZipFileTest 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]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + 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 = createZipOutputStream(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 = createZipOutputStream(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); + zip.close(); + } + + public void testInflatingStreamsRequiringZipRefill() throws IOException { + int originalSize = 1024 * 1024; + byte[] readBuffer = new byte[8192]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + + ZipInputStream in = new ZipInputStream(new FileInputStream(f)); + while (in.getNextEntry() != null) { + while (in.read(readBuffer, 0, readBuffer.length) != -1) {} + } + in.close(); + } + + public void testZipFileWithLotsOfEntries() throws IOException { + int expectedEntryCount = 64*1024 - 1; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), expectedEntryCount, 0, false /* setEntrySize */); + 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! + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1024, 3*1024*1024, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + int entryCount = 0; + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + e.nextElement(); + ++entryCount; + } + assertEquals(1024, entryCount); + zipFile.close(); + } + } + + /** + * Compresses the given number of files, each of the given size, into a .zip archive. + */ + protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize, + boolean setEntrySize) + throws IOException { + byte[] writeBuffer = new byte[8192]; + Random random = new Random(); + try { + for (int entry = 0; entry < entryCount; ++entry) { + ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); + if (setEntrySize) { + ze.setSize(entrySize); + } + out.putNextEntry(ze); + + for (long i = 0; i < entrySize; i += writeBuffer.length) { + random.nextBytes(writeBuffer); + int byteCount = (int) Math.min(writeBuffer.length, entrySize - i); + out.write(writeBuffer, 0, byteCount); + } + + out.closeEntry(); + } + } finally { + out.close(); + } + } + + static File createTemporaryZipFile() throws IOException { + File result = File.createTempFile("ZipFileTest", ".zip"); + result.deleteOnExit(); + return result; + } + + private ZipOutputStream createZipOutputStream(File f) throws IOException { + return createZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); + } + + protected abstract ZipOutputStream createZipOutputStream(OutputStream wrapped); + + 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(); + } + + /** + * RI does not allow reading of an empty zip using a {@link ZipFile}. + */ + public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { + + File resources = Support_Resources.createTempFolder(); + File emptyZip = Support_Resources.copyFile( + resources, "java/util/zip", "EmptyArchive.zip"); + + try { + // The following should fail with an exception but if it doesn't then we need to clean + // up the resource so we need a reference to it. + ZipFile zipFile = new ZipFile(emptyZip); + + // Clean up the resource. + try { + zipFile.close(); + } catch (Exception e) { + // Ignore + } + fail(); + } catch (ZipException expected) { + // expected + } + } + + // Demonstrates http://b/18644314 : Zip entry names are relative to the point of + // extraction and can contain relative paths "../" and "./". + // + // It is left to callers of the API to perform any validation / santization to + // ensure that files are not written outside of the destination directory, where that + // is a concern. + public void testArchivesWithRelativePaths() throws IOException { + String[] entryNames = { + "../", + "../foo.bar", + "foo/../../", + "foo/../../bar.baz" + }; + + File zip = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(zip); + + try { + byte[] entryData = new byte[1024]; + for (String entryName : entryNames) { + ZipEntry ze = new ZipEntry(entryName); + out.putNextEntry(ze); + out.write(entryData); + out.closeEntry(); + } + } finally { + out.close(); + } + + ZipFile zf = new ZipFile(zip, ZipFile.OPEN_READ); + Enumeration<? extends ZipEntry> entries = zf.entries(); + Set<String> entryNamesFromFile = new HashSet<>(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); + entryNamesFromFile.add(ze.getName()); + } + + zf.close(); + + for (String entryName : entryNames) { + assertTrue(entryNamesFromFile.contains(entryName)); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java new file mode 100644 index 0000000..1b733f9 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public final class Zip64FileTest extends AbstractZipFileTest { + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped, true /* forceZip64 */); + } + + public void testZip64Support_largeNumberOfEntries() throws IOException { + final File file = createZipFile(65550, 2, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(65550, zf.size()); + + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(2, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_totalLargerThan4G() throws IOException { + final File file = createZipFile(5, 1073741824L, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(5, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(1073741824L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_hugeEntry() throws IOException { + try { + createZipFile(1, 4294967410L, false /* setEntrySize */); + fail(); + } catch (IOException expected) { + } + + final File file = createZipFile(1, 4294967410L, true /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(1, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(4294967410L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + private File createZipFile(int numEntries, long entrySize, boolean setEntrySize) + throws IOException { + File file = createTemporaryZipFile(); + // Don't force a 64 bit zip file to test that our heuristics work. + ZipOutputStream os = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(file))); + writeEntries(os, numEntries, entrySize, setEntrySize); + return file; + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java index 9c3e870..c7667b9 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java @@ -151,7 +151,8 @@ public class ZipEntryTest extends junit.framework.TestCase { byte[] maxLengthExtra = new byte[65530]; File f = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(f); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)), + true /* forceZip64 */); ZipEntry ze = new ZipEntry("x"); ze.setExtra(maxLengthExtra); @@ -163,7 +164,7 @@ public class ZipEntryTest extends junit.framework.TestCase { } - public void testTooLongComment() throws Exception { + public void testTooLongComment() throws Exception { String tooLongComment = makeString(65536, "z"); ZipEntry ze = new ZipEntry("x"); try { diff --git a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java index bf18978..02210ac 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2015 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. @@ -11,580 +11,18 @@ * 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. + * 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.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; -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.io.OutputStream; import java.util.zip.ZipOutputStream; -import junit.framework.TestCase; -import tests.support.resource.Support_Resources; +public final class ZipFileTest extends AbstractZipFileTest { -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); - zip.close(); - } - - 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(); ) { - e.nextElement(); - ++entryCount; - } - assertEquals(1024, entryCount); - zipFile.close(); - } - } - - public void testZip64Support_largeNumberOfEntries() throws IOException { - File file = createZipFile(65550, 2); - ZipFile zf = null; - try { - zf = new ZipFile(file); - assertEquals(65550, zf.size()); - - Enumeration<? extends ZipEntry> entries = zf.entries(); - assertTrue(entries.hasMoreElements()); - ZipEntry ze = entries.nextElement(); - assertEquals(2, ze.getSize()); - } finally { - if (zf != null) { - zf.close(); - } - } - } - - public void testZip64Support_totalLargerThan4G() throws IOException { - final File file = createZipFile(5, 1073741824L); - ZipFile zf = null; - try { - zf = new ZipFile(file); - assertEquals(5, zf.size()); - Enumeration<? extends ZipEntry> entries = zf.entries(); - assertTrue(entries.hasMoreElements()); - ZipEntry ze = entries.nextElement(); - assertEquals(1073741824L, ze.getSize()); - } finally { - if (zf != null) { - zf.close(); - } - } - } - - public void testZip64Support_hugeEntry() throws IOException { - final File file = createZipFile(1, 4294967410L); - ZipFile zf = null; - try { - zf = new ZipFile(file); - assertEquals(1, zf.size()); - Enumeration<? extends ZipEntry> entries = zf.entries(); - assertTrue(entries.hasMoreElements()); - ZipEntry ze = entries.nextElement(); - assertEquals(4294967410L, ze.getSize()); - } finally { - if (zf != null) { - zf.close(); - } - } - } - - /** - * Compresses the given number of files, each of the given size, into a .zip archive. - */ - private static File createZipFile(int entryCount, long entrySize) throws IOException { - File result = createTemporaryZipFile(); - - byte[] writeBuffer = new byte[8192]; - Random random = new Random(); - - ZipOutputStream out = createZipOutputStream(result); - try { - for (int entry = 0; entry < entryCount; ++entry) { - ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); - out.putNextEntry(ze); - - for (long i = 0; i < entrySize; i += writeBuffer.length) { - random.nextBytes(writeBuffer); - int byteCount = (int) Math.min(writeBuffer.length, entrySize - i); - out.write(writeBuffer, 0, byteCount); - } - - out.closeEntry(); - } - } finally { - out.close(); - } - return result; - } - - private static File createTemporaryZipFile() throws IOException { - File result = File.createTempFile("ZipFileTest", ".zip"); - result.deleteOnExit(); - return result; - } - - private static 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(); - } - - /** - * RI does not allow reading of an empty zip using a {@link ZipFile}. - */ - public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { - - File resources = Support_Resources.createTempFolder(); - File emptyZip = Support_Resources.copyFile( - resources, "java/util/zip", "EmptyArchive.zip"); - - try { - // The following should fail with an exception but if it doesn't then we need to clean - // up the resource so we need a reference to it. - ZipFile zipFile = new ZipFile(emptyZip); - - // Clean up the resource. - try { - zipFile.close(); - } catch (Exception e) { - // Ignore - } - fail(); - } catch (ZipException expected) { - // expected - } - } - - // Demonstrates http://b/18644314 : Zip entry names are relative to the point of - // extraction and can contain relative paths "../" and "./". - // - // It is left to callers of the API to perform any validation / santization to - // ensure that files are not written outside of the destination directory, where that - // is a concern. - public void testArchivesWithRelativePaths() throws IOException { - String[] entryNames = { - "../", - "../foo.bar", - "foo/../../", - "foo/../../bar.baz" - }; - - File zip = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(zip); - - try { - byte[] entryData = new byte[1024]; - for (String entryName : entryNames) { - ZipEntry ze = new ZipEntry(entryName); - out.putNextEntry(ze); - out.write(entryData); - out.closeEntry(); - } - } finally { - out.close(); - } - - ZipFile zf = new ZipFile(zip, ZipFile.OPEN_READ); - Enumeration<? extends ZipEntry> entries = zf.entries(); - Set<String> entryNamesFromFile = new HashSet<>(); - while (entries.hasMoreElements()) { - ZipEntry ze = entries.nextElement(); - entryNamesFromFile.add(ze.getName()); - } - - zf.close(); - - for (String entryName : entryNames) { - assertTrue(entryNamesFromFile.contains(entryName)); - } + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped); } } diff --git a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java index e69f010..15600de 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java @@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; import java.util.zip.ZipEntry; |