/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.commons.compress.archivers; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; 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.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import org.apache.commons.compress.AbstractTestCase; import org.apache.commons.compress.archivers.zip.Zip64Mode; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveEntryPredicate; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.compress.archivers.zip.ZipMethod; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.junit.Assert; import org.junit.Test; public final class ZipTestCase extends AbstractTestCase { /** * Archives 2 files and unarchives it again. If the file length of result * and source is the same, it looks like the operations have worked * @throws Exception */ @Test public void testZipArchiveCreation() throws Exception { // Archive final File output = new File(dir, "bla.zip"); final File file1 = getFile("test1.xml"); final File file2 = getFile("test2.xml"); final OutputStream out = new FileOutputStream(output); ArchiveOutputStream os = null; try { os = new ArchiveStreamFactory() .createArchiveOutputStream("zip", out); os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml")); IOUtils.copy(new FileInputStream(file1), os); os.closeArchiveEntry(); os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml")); IOUtils.copy(new FileInputStream(file2), os); os.closeArchiveEntry(); } finally { if (os != null) { os.close(); } } out.close(); // Unarchive the same final List<File> results = new ArrayList<>(); final InputStream is = new FileInputStream(output); ArchiveInputStream in = null; try { in = new ArchiveStreamFactory() .createArchiveInputStream("zip", is); ZipArchiveEntry entry = null; while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) { final File outfile = new File(resultDir.getCanonicalPath() + "/result/" + entry.getName()); outfile.getParentFile().mkdirs(); try (OutputStream o = new FileOutputStream(outfile)) { IOUtils.copy(in, o); } results.add(outfile); } } finally { if (in != null) { in.close(); } } is.close(); assertEquals(results.size(), 2); File result = results.get(0); assertEquals(file1.length(), result.length()); result = results.get(1); assertEquals(file2.length(), result.length()); } /** * Archives 2 files and unarchives it again. If the file contents of result * and source is the same, it looks like the operations have worked * @throws Exception */ @Test public void testZipArchiveCreationInMemory() throws Exception { final File file1 = getFile("test1.xml"); final File file2 = getFile("test2.xml"); final byte[] file1Contents = new byte[(int) file1.length()]; final byte[] file2Contents = new byte[(int) file2.length()]; IOUtils.readFully(new FileInputStream(file1), file1Contents); IOUtils.readFully(new FileInputStream(file2), file2Contents); SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(); try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(c)) { os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml")); os.write(file1Contents); os.closeArchiveEntry(); os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml")); os.write(file2Contents); os.closeArchiveEntry(); } // Unarchive the same final List<byte[]> results = new ArrayList<>(); try (ArchiveInputStream in = new ArchiveStreamFactory() .createArchiveInputStream("zip", new ByteArrayInputStream(c.array()))) { ZipArchiveEntry entry; while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) { byte[] result = new byte[(int) entry.getSize()]; IOUtils.readFully(in, result); results.add(result); } } assertArrayEquals(results.get(0), file1Contents); assertArrayEquals(results.get(1), file2Contents); } /** * Simple unarchive test. Asserts nothing. * @throws Exception */ @Test public void testZipUnarchive() throws Exception { final File input = getFile("bla.zip"); final InputStream is = new FileInputStream(input); final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream("zip", is); final ZipArchiveEntry entry = (ZipArchiveEntry)in.getNextEntry(); final OutputStream out = new FileOutputStream(new File(dir, entry.getName())); IOUtils.copy(in, out); out.close(); in.close(); } /** * Test case for * <a href="https://issues.apache.org/jira/browse/COMPRESS-208" * >COMPRESS-208</a>. */ @Test public void testSkipsPK00Prefix() throws Exception { final File input = getFile("COMPRESS-208.zip"); final ArrayList<String> al = new ArrayList<>(); al.add("test1.xml"); al.add("test2.xml"); try (InputStream is = new FileInputStream(input)) { checkArchiveContent(new ZipArchiveInputStream(is), al); } } /** * Test case for * <a href="https://issues.apache.org/jira/browse/COMPRESS-93" * >COMPRESS-93</a>. */ @Test public void testSupportedCompressionMethod() throws IOException { /* ZipFile bla = new ZipFile(getFile("bla.zip")); assertTrue(bla.canReadEntryData(bla.getEntry("test1.xml"))); bla.close(); */ final ZipFile moby = new ZipFile(getFile("moby.zip")); final ZipArchiveEntry entry = moby.getEntry("README"); assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod()); assertFalse(moby.canReadEntryData(entry)); moby.close(); } /** * Test case for being able to skip an entry in an * {@link ZipArchiveInputStream} even if the compression method of that * entry is unsupported. * * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93" * >COMPRESS-93</a> */ @Test public void testSkipEntryWithUnsupportedCompressionMethod() throws IOException { try (ZipArchiveInputStream zip = new ZipArchiveInputStream(new FileInputStream(getFile("moby.zip")))) { final ZipArchiveEntry entry = zip.getNextZipEntry(); assertEquals("method", ZipMethod.TOKENIZATION.getCode(), entry.getMethod()); assertEquals("README", entry.getName()); assertFalse(zip.canReadEntryData(entry)); try { assertNull(zip.getNextZipEntry()); } catch (final IOException e) { e.printStackTrace(); fail("COMPRESS-93: Unable to skip an unsupported zip entry"); } } } /** * Checks if all entries from a nested archive can be read. * The archive: OSX_ArchiveWithNestedArchive.zip contains: * NestedArchiv.zip and test.xml3. * * The nested archive: NestedArchive.zip contains test1.xml and test2.xml * * @throws Exception */ @Test public void testListAllFilesWithNestedArchive() throws Exception { final File input = getFile("OSX_ArchiveWithNestedArchive.zip"); final List<String> results = new ArrayList<>(); final List<ZipException> expectedExceptions = new ArrayList<>(); final InputStream is = new FileInputStream(input); ArchiveInputStream in = null; try { in = new ArchiveStreamFactory().createArchiveInputStream("zip", is); ZipArchiveEntry entry = null; while ((entry = (ZipArchiveEntry) in.getNextEntry()) != null) { results.add(entry.getName()); final ArchiveInputStream nestedIn = new ArchiveStreamFactory().createArchiveInputStream("zip", in); try { ZipArchiveEntry nestedEntry = null; while ((nestedEntry = (ZipArchiveEntry) nestedIn.getNextEntry()) != null) { results.add(nestedEntry.getName()); } } catch (ZipException ex) { // expected since you cannot create a final ArchiveInputStream from test3.xml expectedExceptions.add(ex); } // nested stream must not be closed here } } finally { if (in != null) { in.close(); } } is.close(); assertTrue(results.contains("NestedArchiv.zip")); assertTrue(results.contains("test1.xml")); assertTrue(results.contains("test2.xml")); assertTrue(results.contains("test3.xml")); assertEquals(1, expectedExceptions.size()); } @Test public void testDirectoryEntryFromFile() throws Exception { final File[] tmp = createTempDirAndFile(); File archive = null; ZipArchiveOutputStream zos = null; ZipFile zf = null; try { archive = File.createTempFile("test.", ".zip", tmp[0]); archive.deleteOnExit(); zos = new ZipArchiveOutputStream(archive); final long beforeArchiveWrite = tmp[0].lastModified(); final ZipArchiveEntry in = new ZipArchiveEntry(tmp[0], "foo"); zos.putArchiveEntry(in); zos.closeArchiveEntry(); zos.close(); zos = null; zf = new ZipFile(archive); final ZipArchiveEntry out = zf.getEntry("foo/"); assertNotNull(out); assertEquals("foo/", out.getName()); assertEquals(0, out.getSize()); // ZIP stores time with a granularity of 2 seconds assertEquals(beforeArchiveWrite / 2000, out.getLastModifiedDate().getTime() / 2000); assertTrue(out.isDirectory()); } finally { ZipFile.closeQuietly(zf); if (zos != null) { zos.close(); } tryHardToDelete(archive); tryHardToDelete(tmp[1]); rmdir(tmp[0]); } } @Test public void testExplicitDirectoryEntry() throws Exception { final File[] tmp = createTempDirAndFile(); File archive = null; ZipArchiveOutputStream zos = null; ZipFile zf = null; try { archive = File.createTempFile("test.", ".zip", tmp[0]); archive.deleteOnExit(); zos = new ZipArchiveOutputStream(archive); final long beforeArchiveWrite = tmp[0].lastModified(); final ZipArchiveEntry in = new ZipArchiveEntry("foo/"); in.setTime(beforeArchiveWrite); zos.putArchiveEntry(in); zos.closeArchiveEntry(); zos.close(); zos = null; zf = new ZipFile(archive); final ZipArchiveEntry out = zf.getEntry("foo/"); assertNotNull(out); assertEquals("foo/", out.getName()); assertEquals(0, out.getSize()); assertEquals(beforeArchiveWrite / 2000, out.getLastModifiedDate().getTime() / 2000); assertTrue(out.isDirectory()); } finally { ZipFile.closeQuietly(zf); if (zos != null) { zos.close(); } tryHardToDelete(archive); tryHardToDelete(tmp[1]); rmdir(tmp[0]); } } String first_payload = "ABBA"; String second_payload = "AAAAAAAAAAAA"; ZipArchiveEntryPredicate allFilesPredicate = new ZipArchiveEntryPredicate() { @Override public boolean test(final ZipArchiveEntry zipArchiveEntry) { return true; } }; @Test public void testCopyRawEntriesFromFile() throws IOException { final File[] tmp = createTempDirAndFile(); final File reference = createReferenceFile(tmp[0], Zip64Mode.Never, "expected."); final File a1 = File.createTempFile("src1.", ".zip", tmp[0]); final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1); zos.setUseZip64(Zip64Mode.Never); createFirstEntry(zos).close(); final File a2 = File.createTempFile("src2.", ".zip", tmp[0]); final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(a2); zos1.setUseZip64(Zip64Mode.Never); createSecondEntry(zos1).close(); final ZipFile zf1 = new ZipFile(a1); final ZipFile zf2 = new ZipFile(a2); final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]); final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult); zf1.copyRawEntries(zos2, allFilesPredicate); zf2.copyRawEntries(zos2, allFilesPredicate); zos2.close(); // copyRawEntries does not add superfluous zip64 header like regular zip output stream // does when using Zip64Mode.AsNeeded so all the source material has to be Zip64Mode.Never, // if exact binary equality is to be achieved assertSameFileContents(reference, fileResult); zf1.close(); zf2.close(); } @Test public void testCopyRawZip64EntryFromFile() throws IOException { final File[] tmp = createTempDirAndFile(); final File reference = File.createTempFile("z64reference.", ".zip", tmp[0]); final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(reference); zos1.setUseZip64(Zip64Mode.Always); createFirstEntry(zos1); zos1.close(); final File a1 = File.createTempFile("zip64src.", ".zip", tmp[0]); final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1); zos.setUseZip64(Zip64Mode.Always); createFirstEntry(zos).close(); final ZipFile zf1 = new ZipFile(a1); final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]); final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult); zos2.setUseZip64(Zip64Mode.Always); zf1.copyRawEntries(zos2, allFilesPredicate); zos2.close(); assertSameFileContents(reference, fileResult); zf1.close(); } @Test public void testUnixModeInAddRaw() throws IOException { final File[] tmp = createTempDirAndFile(); final File a1 = File.createTempFile("unixModeBits.", ".zip", tmp[0]); final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(a1); final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred"); archiveEntry.setUnixMode(0664); archiveEntry.setMethod(ZipEntry.DEFLATED); zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes())); zos.close(); final ZipFile zf1 = new ZipFile(a1); final ZipArchiveEntry fred = zf1.getEntry("fred"); assertEquals(0664, fred.getUnixMode()); zf1.close(); } private File createReferenceFile(final File directory, final Zip64Mode zipMode, final String prefix) throws IOException { final File reference = File.createTempFile(prefix, ".zip", directory); final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference); zos.setUseZip64(zipMode); createFirstEntry(zos); createSecondEntry(zos); zos.close(); return reference; } private ZipArchiveOutputStream createFirstEntry(final ZipArchiveOutputStream zos) throws IOException { createArchiveEntry(first_payload, zos, "file1.txt"); return zos; } private ZipArchiveOutputStream createSecondEntry(final ZipArchiveOutputStream zos) throws IOException { createArchiveEntry(second_payload, zos, "file2.txt"); return zos; } private void assertSameFileContents(final File expectedFile, final File actualFile) throws IOException { final int size = (int) Math.max(expectedFile.length(), actualFile.length()); final ZipFile expected = new ZipFile(expectedFile); final ZipFile actual = new ZipFile(actualFile); final byte[] expectedBuf = new byte[size]; final byte[] actualBuf = new byte[size]; final Enumeration<ZipArchiveEntry> actualInOrder = actual.getEntriesInPhysicalOrder(); final Enumeration<ZipArchiveEntry> expectedInOrder = expected.getEntriesInPhysicalOrder(); while (actualInOrder.hasMoreElements()){ final ZipArchiveEntry actualElement = actualInOrder.nextElement(); final ZipArchiveEntry expectedElement = expectedInOrder.nextElement(); assertEquals( expectedElement.getName(), actualElement.getName()); // Don't compare timestamps since they may vary; // there's no support for stubbed out clock (TimeSource) in ZipArchiveOutputStream assertEquals( expectedElement.getMethod(), actualElement.getMethod()); assertEquals( expectedElement.getGeneralPurposeBit(), actualElement.getGeneralPurposeBit()); assertEquals( expectedElement.getCrc(), actualElement.getCrc()); assertEquals( expectedElement.getCompressedSize(), actualElement.getCompressedSize()); assertEquals( expectedElement.getSize(), actualElement.getSize()); assertEquals( expectedElement.getExternalAttributes(), actualElement.getExternalAttributes()); assertEquals( expectedElement.getInternalAttributes(), actualElement.getInternalAttributes()); final InputStream actualIs = actual.getInputStream(actualElement); final InputStream expectedIs = expected.getInputStream(expectedElement); IOUtils.readFully(expectedIs, expectedBuf); IOUtils.readFully(actualIs, actualBuf); expectedIs.close(); actualIs.close(); Assert.assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. dont care } expected.close(); actual.close(); } private void createArchiveEntry(final String payload, final ZipArchiveOutputStream zos, final String name) throws IOException { final ZipArchiveEntry in = new ZipArchiveEntry(name); zos.putArchiveEntry(in); zos.write(payload.getBytes()); zos.closeArchiveEntry(); } @Test public void testFileEntryFromFile() throws Exception { final File[] tmp = createTempDirAndFile(); File archive = null; ZipArchiveOutputStream zos = null; ZipFile zf = null; FileInputStream fis = null; try { archive = File.createTempFile("test.", ".zip", tmp[0]); archive.deleteOnExit(); zos = new ZipArchiveOutputStream(archive); final ZipArchiveEntry in = new ZipArchiveEntry(tmp[1], "foo"); zos.putArchiveEntry(in); final byte[] b = new byte[(int) tmp[1].length()]; fis = new FileInputStream(tmp[1]); while (fis.read(b) > 0) { zos.write(b); } fis.close(); fis = null; zos.closeArchiveEntry(); zos.close(); zos = null; zf = new ZipFile(archive); final ZipArchiveEntry out = zf.getEntry("foo"); assertNotNull(out); assertEquals("foo", out.getName()); assertEquals(tmp[1].length(), out.getSize()); assertEquals(tmp[1].lastModified() / 2000, out.getLastModifiedDate().getTime() / 2000); assertFalse(out.isDirectory()); } finally { ZipFile.closeQuietly(zf); if (zos != null) { zos.close(); } tryHardToDelete(archive); if (fis != null) { fis.close(); } tryHardToDelete(tmp[1]); rmdir(tmp[0]); } } @Test public void testExplicitFileEntry() throws Exception { final File[] tmp = createTempDirAndFile(); File archive = null; ZipArchiveOutputStream zos = null; ZipFile zf = null; FileInputStream fis = null; try { archive = File.createTempFile("test.", ".zip", tmp[0]); archive.deleteOnExit(); zos = new ZipArchiveOutputStream(archive); final ZipArchiveEntry in = new ZipArchiveEntry("foo"); in.setTime(tmp[1].lastModified()); in.setSize(tmp[1].length()); zos.putArchiveEntry(in); final byte[] b = new byte[(int) tmp[1].length()]; fis = new FileInputStream(tmp[1]); while (fis.read(b) > 0) { zos.write(b); } fis.close(); fis = null; zos.closeArchiveEntry(); zos.close(); zos = null; zf = new ZipFile(archive); final ZipArchiveEntry out = zf.getEntry("foo"); assertNotNull(out); assertEquals("foo", out.getName()); assertEquals(tmp[1].length(), out.getSize()); assertEquals(tmp[1].lastModified() / 2000, out.getLastModifiedDate().getTime() / 2000); assertFalse(out.isDirectory()); } finally { ZipFile.closeQuietly(zf); if (zos != null) { zos.close(); } tryHardToDelete(archive); if (fis != null) { fis.close(); } tryHardToDelete(tmp[1]); rmdir(tmp[0]); } } }