blob: 1d92fbd87caa304c32f24e269e1310dedaf4884a [file] [log] [blame]
/*
* 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.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.archivers.zip.ZipSplitReadOnlySeekableByteChannel;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
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");
try (final OutputStream out = Files.newOutputStream(output.toPath())) {
try (ArchiveOutputStream os = ArchiveStreamFactory.DEFAULT.createArchiveOutputStream("zip", out)) {
os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml"));
try (final InputStream input = Files.newInputStream(file1.toPath())) {
IOUtils.copy(input, os);
}
os.closeArchiveEntry();
os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml"));
try (final InputStream input = Files.newInputStream(file2.toPath())) {
IOUtils.copy(input, os);
}
os.closeArchiveEntry();
}
}
// Unarchive the same
final List<File> results = new ArrayList<>();
try (final InputStream fileInputStream = Files.newInputStream(output.toPath())) {
try (ArchiveInputStream archiveInputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip",
fileInputStream)) {
ZipArchiveEntry entry = null;
while ((entry = (ZipArchiveEntry) archiveInputStream.getNextEntry()) != null) {
final File outfile = new File(resultDir.getCanonicalPath() + "/result/" + entry.getName());
outfile.getParentFile().mkdirs();
try (OutputStream o = Files.newOutputStream(outfile.toPath())) {
IOUtils.copy(archiveInputStream, o);
}
results.add(outfile);
}
}
}
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.read(file1, file1Contents);
IOUtils.read(file2, file2Contents);
final List<byte[]> results = new ArrayList<>();
try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel()) {
try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(channel)) {
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
try (ArchiveInputStream inputStream = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip",
new ByteArrayInputStream(channel.array()))) {
ZipArchiveEntry entry;
while ((entry = (ZipArchiveEntry) inputStream.getNextEntry()) != null) {
final byte[] result = new byte[(int) entry.getSize()];
IOUtils.readFully(inputStream, 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");
try (final InputStream is = Files.newInputStream(input.toPath());
final ArchiveInputStream in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", is)) {
final ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry();
try (final OutputStream out = Files.newOutputStream(new File(dir, entry.getName()).toPath())) {
IOUtils.copy(in, out);
}
}
}
/**
* 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 fis = Files.newInputStream(input.toPath())) {
checkArchiveContent(new ZipArchiveInputStream(fis), al);
}
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/COMPRESS-93"
* >COMPRESS-93</a>.
*/
@Test
public void testTokenizationCompressionMethod() throws IOException {
try (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));
}
}
/**
* 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(Files.newInputStream(getFile("moby.zip").toPath()))) {
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<>();
try (final InputStream fis = Files.newInputStream(input.toPath());
ArchiveInputStream in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", fis)) {
ZipArchiveEntry entry = null;
while ((entry = (ZipArchiveEntry) in.getNextEntry()) != null) {
results.add(entry.getName());
final ArchiveInputStream nestedIn = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", in);
try {
ZipArchiveEntry nestedEntry = null;
while ((nestedEntry = (ZipArchiveEntry) nestedIn.getNextEntry()) != null) {
results.add(nestedEntry.getName());
}
} catch (final ZipException ex) {
// expected since you cannot create a final ArchiveInputStream from test3.xml
expectedExceptions.add(ex);
}
// nested stream must not be closed here
}
}
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]);
}
}
final String first_payload = "ABBA";
final String second_payload = "AAAAAAAAAAAA";
final ZipArchiveEntryPredicate allFilesPredicate = zipArchiveEntry -> true;
@Test
public void testCopyRawEntriesFromFile()
throws IOException {
final File[] tmp = createTempDirAndFile();
final File reference = createReferenceFile(tmp[0], Zip64Mode.Never, "expected.");
final File file1 = File.createTempFile("src1.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
zos.setUseZip64(Zip64Mode.Never);
createFirstEntry(zos).close();
}
final File file2 = File.createTempFile("src2.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(file2)) {
zos1.setUseZip64(Zip64Mode.Never);
createSecondEntry(zos1).close();
}
try (final ZipFile zipFile1 = new ZipFile(file1); final ZipFile zipFile2 = new ZipFile(file2)) {
final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
zipFile1.copyRawEntries(zos2, allFilesPredicate);
zipFile2.copyRawEntries(zos2, allFilesPredicate);
}
// 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);
}
}
@Test
public void testCopyRawZip64EntryFromFile()
throws IOException {
final File[] tmp = createTempDirAndFile();
final File reference = File.createTempFile("z64reference.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos1 = new ZipArchiveOutputStream(reference)) {
zos1.setUseZip64(Zip64Mode.Always);
createFirstEntry(zos1);
}
final File file1 = File.createTempFile("zip64src.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
zos.setUseZip64(Zip64Mode.Always);
createFirstEntry(zos).close();
}
final File fileResult = File.createTempFile("file-actual.", ".zip", tmp[0]);
try (final ZipFile zipFile1 = new ZipFile(file1)) {
try (final ZipArchiveOutputStream zos2 = new ZipArchiveOutputStream(fileResult)) {
zos2.setUseZip64(Zip64Mode.Always);
zipFile1.copyRawEntries(zos2, allFilesPredicate);
}
assertSameFileContents(reference, fileResult);
}
}
@Test
public void testUnixModeInAddRaw() throws IOException {
final File[] tmp = createTempDirAndFile();
final File file1 = File.createTempFile("unixModeBits.", ".zip", tmp[0]);
try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(file1)) {
final ZipArchiveEntry archiveEntry = new ZipArchiveEntry("fred");
archiveEntry.setUnixMode(0664);
archiveEntry.setMethod(ZipEntry.DEFLATED);
zos.addRawArchiveEntry(archiveEntry, new ByteArrayInputStream("fud".getBytes()));
}
try (final ZipFile zf1 = new ZipFile(file1)) {
final ZipArchiveEntry fred = zf1.getEntry("fred");
assertEquals(0664, fred.getUnixMode());
}
}
private File createReferenceFile(final File directory, final Zip64Mode zipMode, final String prefix)
throws IOException {
final File reference = File.createTempFile(prefix, ".zip", directory);
try (final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(reference)) {
zos.setUseZip64(zipMode);
createFirstEntry(zos);
createSecondEntry(zos);
}
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());
try (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());
try (final InputStream actualIs = actual.getInputStream(actualElement);
final InputStream expectedIs = expected.getInputStream(expectedElement)) {
IOUtils.readFully(expectedIs, expectedBuf);
IOUtils.readFully(actualIs, actualBuf);
}
Assert.assertArrayEquals(expectedBuf, actualBuf); // Buffers are larger than payload. dont care
}
}
}
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;
InputStream fis = null;
final File tmpDir = tmp[0];
final File tmpFile = tmp[1];
try {
archive = File.createTempFile("test.", ".zip", tmpDir);
archive.deleteOnExit();
zos = new ZipArchiveOutputStream(archive);
final ZipArchiveEntry in = new ZipArchiveEntry(tmpFile, "foo");
zos.putArchiveEntry(in);
final byte[] b = new byte[(int) tmpFile.length()];
fis = Files.newInputStream(tmpFile.toPath());
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(tmpFile.length(), out.getSize());
assertEquals(tmpFile.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(tmpFile);
rmdir(tmpDir);
}
}
@Test
public void testZipArchiveEntryNewFromPath() throws Exception {
final File[] tmp = createTempDirAndFile();
File archiveFile = null;
Path archivePath = null;
ZipArchiveOutputStream zos = null;
ZipFile zf = null;
InputStream fis = null;
final File tmpDir = tmp[0];
final File tmpFile = tmp[1];
final Path tmpFilePath = tmpFile.toPath();
try {
archiveFile = File.createTempFile("test.", ".zip", tmpDir);
archivePath = archiveFile.toPath();
archiveFile.deleteOnExit();
zos = new ZipArchiveOutputStream(archivePath);
final ZipArchiveEntry in = (ZipArchiveEntry) zos.createArchiveEntry(tmpFilePath, "foo");
zos.putArchiveEntry(in);
final byte[] b = new byte[(int) tmpFile.length()];
fis = Files.newInputStream(tmpFile.toPath());
while (fis.read(b) > 0) {
zos.write(b);
}
fis.close();
fis = null;
zos.closeArchiveEntry();
zos.close();
zos = null;
zf = new ZipFile(archiveFile);
final ZipArchiveEntry out = zf.getEntry("foo");
assertNotNull(out);
assertEquals("foo", out.getName());
assertEquals(tmpFile.length(), out.getSize());
assertEquals(tmpFile.lastModified() / 2000,
out.getLastModifiedDate().getTime() / 2000);
assertFalse(out.isDirectory());
} finally {
ZipFile.closeQuietly(zf);
if (zos != null) {
zos.close();
}
tryHardToDelete(archiveFile);
if (fis != null) {
fis.close();
}
tryHardToDelete(tmpFile);
rmdir(tmpDir);
}
}
@Test
public void testExplicitFileEntry() throws Exception {
final File[] tmp = createTempDirAndFile();
File archive = null;
ZipArchiveOutputStream zos = null;
ZipFile zf = null;
InputStream 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 = Files.newInputStream(tmp[1].toPath());
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 inputStreamStatisticsOfZipBombExcel() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("[Content_Types].xml", Arrays.asList(8390036L, 8600L));
put("xl/worksheets/sheet1.xml", Arrays.asList(1348L, 508L));
}};
testInputStreamStatistics("zipbomb.xlsx", expected);
}
@Test
public void inputStreamStatisticsForImplodedEntry() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("LICENSE.TXT", Arrays.asList(11560L, 4131L));
}};
testInputStreamStatistics("imploding-8Kdict-3trees.zip", expected);
}
@Test
public void inputStreamStatisticsForShrunkEntry() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("TEST1.XML", Arrays.asList(76L, 66L));
put("TEST2.XML", Arrays.asList(81L, 76L));
}};
testInputStreamStatistics("SHRUNK.ZIP", expected);
}
@Test
public void inputStreamStatisticsForStoredEntry() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("test.txt", Arrays.asList(5L, 5L));
}};
testInputStreamStatistics("COMPRESS-264.zip", expected);
}
@Test
public void inputStreamStatisticsForBzip2Entry() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("lots-of-as", Arrays.asList(42L, 39L));
}};
testInputStreamStatistics("bzip2-zip.zip", expected);
}
@Test
public void inputStreamStatisticsForDeflate64Entry() throws IOException, ArchiveException {
final Map<String, List<Long>> expected = new HashMap<String, List<Long>>() {{
put("input2", Arrays.asList(3072L, 2111L));
}};
testInputStreamStatistics("COMPRESS-380/COMPRESS-380.zip", expected);
}
@Test(expected = IllegalArgumentException.class)
public void buildSplitZipWithTooSmallSizeThrowsException() throws IOException {
new ZipArchiveOutputStream(File.createTempFile("temp", "zip"), 64 * 1024 - 1);
}
@Test(expected = IllegalArgumentException.class)
public void buildSplitZipWithTooLargeSizeThrowsException() throws IOException {
new ZipArchiveOutputStream(File.createTempFile("temp", "zip"), 4294967295L + 1);
}
@Test(expected = IOException.class)
public void buildSplitZipWithSegmentAlreadyExistThrowsException() throws IOException {
final File directoryToZip = getFilesToZip();
final File outputZipFile = new File(dir, "splitZip.zip");
final long splitSize = 100 * 1024L; /* 100 KB */
try (final ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile,
splitSize)) {
// create a file that has the same name of one of the created split segments
final File sameNameFile = new File(dir, "splitZip.z01");
sameNameFile.createNewFile();
addFilesToZip(zipArchiveOutputStream, directoryToZip);
}
}
@Test
public void buildSplitZipTest() throws IOException {
final File directoryToZip = getFilesToZip();
createTestSplitZipSegments();
final File lastFile = new File(dir, "splitZip.zip");
try (SeekableByteChannel channel = ZipSplitReadOnlySeekableByteChannel.buildFromLastSplitSegment(lastFile);
InputStream inputStream = Channels.newInputStream(channel);
ZipArchiveInputStream splitInputStream = new ZipArchiveInputStream(inputStream,
StandardCharsets.UTF_8.toString(), true, false, true)) {
ArchiveEntry entry;
final int filesNum = countNonDirectories(directoryToZip);
int filesCount = 0;
while ((entry = splitInputStream.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
// compare all files one by one
final File fileToCompare = new File(entry.getName());
try (InputStream inputStreamToCompare = Files.newInputStream(fileToCompare.toPath())) {
assertArrayEquals(IOUtils.toByteArray(splitInputStream),
IOUtils.toByteArray(inputStreamToCompare));
}
filesCount++;
}
// and the number of files should equal
assertEquals(filesCount, filesNum);
}
}
private void testInputStreamStatistics(final String fileName, final Map<String, List<Long>> expectedStatistics)
throws IOException, ArchiveException {
final File input = getFile(fileName);
final Map<String,List<List<Long>>> actualStatistics = new HashMap<>();
// stream access
try (final InputStream fis = Files.newInputStream(input.toPath());
final ArchiveInputStream in = ArchiveStreamFactory.DEFAULT.createArchiveInputStream("zip", fis)) {
for (ArchiveEntry entry; (entry = in.getNextEntry()) != null; ) {
readStream(in, entry, actualStatistics);
}
}
// file access
try (final ZipFile zf = new ZipFile(input)) {
final Enumeration<ZipArchiveEntry> entries = zf.getEntries();
while (entries.hasMoreElements()) {
final ZipArchiveEntry zae = entries.nextElement();
try (InputStream in = zf.getInputStream(zae)) {
readStream(in, zae, actualStatistics);
}
}
}
// compare statistics of stream / file access
for (final Map.Entry<String,List<List<Long>>> me : actualStatistics.entrySet()) {
assertEquals("Mismatch of stats for: " + me.getKey(),
me.getValue().get(0), me.getValue().get(1));
}
for (final Map.Entry<String, List<Long>> me : expectedStatistics.entrySet()) {
assertEquals("Mismatch of stats with expected value for: " + me.getKey(),
me.getValue(), actualStatistics.get(me.getKey()).get(0));
}
}
private void readStream(final InputStream in, final ArchiveEntry entry, final Map<String,List<List<Long>>> map) throws IOException {
final byte[] buf = new byte[4096];
final InputStreamStatistics stats = (InputStreamStatistics) in;
while (in.read(buf) != -1) {
// consume all.
}
final String name = entry.getName();
final List<List<Long>> l;
if (map.containsKey(name)) {
l = map.get(name);
} else {
map.put(name, l = new ArrayList<>());
}
final long t = stats.getUncompressedCount();
final long b = stats.getCompressedCount();
l.add(Arrays.asList(t, b));
}
private File getFilesToZip() throws IOException {
final File originalZipFile = getFile("COMPRESS-477/split_zip_created_by_zip/zip_to_compare_created_by_zip.zip");
try (ZipFile zipFile = new ZipFile(originalZipFile)) {
final Enumeration<ZipArchiveEntry> zipEntries = zipFile.getEntries();
ZipArchiveEntry zipEntry;
File outputFile;
byte[] buffer;
int readLen;
while (zipEntries.hasMoreElements()) {
zipEntry = zipEntries.nextElement();
if (zipEntry.isDirectory()) {
continue;
}
outputFile = new File(dir, zipEntry.getName());
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
outputFile = new File(dir, zipEntry.getName());
try (InputStream inputStream = zipFile.getInputStream(zipEntry);
OutputStream outputStream = Files.newOutputStream(outputFile.toPath())) {
buffer = new byte[(int) zipEntry.getSize()];
while ((readLen = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, readLen);
}
}
}
}
return dir.listFiles()[0];
}
private void createTestSplitZipSegments() throws IOException {
final File directoryToZip = getFilesToZip();
final File outputZipFile = new File(dir, "splitZip.zip");
final long splitSize = 100 * 1024L; /* 100 KB */
try (final ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputZipFile,
splitSize)) {
addFilesToZip(zipArchiveOutputStream, directoryToZip);
}
}
private void addFilesToZip(final ZipArchiveOutputStream zipArchiveOutputStream, final File fileToAdd) throws IOException {
if (fileToAdd.isDirectory()) {
for (final File file : fileToAdd.listFiles()) {
addFilesToZip(zipArchiveOutputStream, file);
}
} else {
final ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(fileToAdd.getPath());
zipArchiveEntry.setMethod(ZipEntry.DEFLATED);
zipArchiveOutputStream.putArchiveEntry(zipArchiveEntry);
try (final InputStream input = Files.newInputStream(fileToAdd.toPath())) {
IOUtils.copy(input, zipArchiveOutputStream);
}
zipArchiveOutputStream.closeArchiveEntry();
}
}
private int countNonDirectories(final File file) {
if(!file.isDirectory()) {
return 1;
}
int result = 0;
for (final File fileInDirectory : file.listFiles()) {
result += countNonDirectories(fileInDirectory);
}
return result;
}
}