/*************************GO-LICENSE-START********************************* * Copyright 2014 ThoughtWorks, Inc. * * 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. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.util; import com.googlecode.junit.ext.JunitExtRunner; import com.googlecode.junit.ext.RunIf; import com.googlecode.junit.ext.checkers.OSChecker; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.hamcrest.core.Is; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.zip.Deflater; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @RunWith(JunitExtRunner.class) public class ZipUtilTest { private File srcDir; private File destDir; private ZipUtil zipUtil; private File childDir1; private File file1; private File file2; private File zipFile; private File emptyDir; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); private static String fileContent(File file) throws IOException { return IOUtils.toString(new FileInputStream(file)); } @Before public void setUp() throws Exception { temporaryFolder.create(); srcDir = temporaryFolder.newFolder("_test1"); destDir = temporaryFolder.newFolder("_test2"); emptyDir = new File(srcDir, "_emptyDir"); emptyDir.mkdir(); childDir1 = new File(srcDir, "_child1"); childDir1.mkdir(); file1 = new File(srcDir, "_file1"); FileUtils.writeStringToFile(file1, "_file1"); file2 = new File(childDir1, "_file2"); FileUtils.writeStringToFile(file2, "_file2"); zipUtil = new ZipUtil(); } @After public void tearDown() { temporaryFolder.delete(); } @Test public void shouldZipFileAndUnzipIt() throws IOException { zipFile = zipUtil.zip(srcDir, temporaryFolder.newFile(), Deflater.NO_COMPRESSION); assertThat(zipFile.isFile(), is(true)); zipUtil.unzip(zipFile, destDir); File baseDir = new File(destDir, srcDir.getName()); assertIsDirectory(new File(baseDir, emptyDir.getName())); assertIsDirectory(new File(baseDir, childDir1.getName())); File actual1 = new File(baseDir, file1.getName()); assertThat(actual1.isFile(), is(true)); assertThat(fileContent(actual1), is(fileContent(file1))); File actual2 = new File(baseDir, childDir1.getName() + File.separator + file2.getName()); assertThat(actual2.isFile(), is(true)); assertThat(fileContent(actual2), is(fileContent(file2))); } @Test public void shouldZipFileContentsAndUnzipIt() throws IOException { zipFile = zipUtil.zip(srcDir, temporaryFolder.newFile(), Deflater.NO_COMPRESSION); assertThat(zipFile.isFile(), is(true)); zipUtil.unzip(zipFile, destDir); File baseDir = new File(destDir, srcDir.getName()); assertIsDirectory(new File(baseDir, emptyDir.getName())); assertIsDirectory(new File(baseDir, childDir1.getName())); File actual1 = new File(baseDir, file1.getName()); assertThat(actual1.isFile(), is(true)); assertThat(fileContent(actual1), is(fileContent(file1))); File actual2 = new File(baseDir, childDir1.getName() + File.separator + file2.getName()); assertThat(actual2.isFile(), is(true)); assertThat(fileContent(actual2), is(fileContent(file2))); } @Test public void shouldZipFileContentsOnly() throws IOException { zipFile = zipUtil.zipFolderContents(srcDir, temporaryFolder.newFile(), Deflater.NO_COMPRESSION); assertThat(zipFile.isFile(), is(true)); zipUtil.unzip(zipFile, destDir); assertIsDirectory(new File(destDir, emptyDir.getName())); assertIsDirectory(new File(destDir, childDir1.getName())); File actual1 = new File(destDir, file1.getName()); assertThat(actual1.isFile(), is(true)); assertThat(fileContent(actual1), is(fileContent(file1))); File actual2 = new File(destDir, childDir1.getName() + File.separator + file2.getName()); assertThat(actual2.isFile(), is(true)); assertThat(fileContent(actual2), is(fileContent(file2))); } @Test @RunIf(value = OSChecker.class, arguments = OSChecker.LINUX) public void shouldZipFileWhoseNameHasSpecialCharactersOnLinux() throws IOException { File specialFile = new File(srcDir, "$`#?@!()?-_{}^'~.+=[];,a.txt"); FileUtils.writeStringToFile(specialFile, "specialFile"); zipFile = zipUtil.zip(srcDir, temporaryFolder.newFile(), Deflater.NO_COMPRESSION); zipUtil.unzip(zipFile, destDir); File baseDir = new File(destDir, srcDir.getName()); File actualSpecialFile = new File(baseDir, specialFile.getName()); assertThat(actualSpecialFile.isFile(), is(true)); assertThat(fileContent(actualSpecialFile), is(fileContent(specialFile))); } @Test public void shouldReadContentsOfAFileWhichIsInsideAZip() throws Exception { FileUtils.writeStringToFile(new File(srcDir, "some-file.txt"), "some-text-here"); zipFile = zipUtil.zip(srcDir, temporaryFolder.newFile(), Deflater.NO_COMPRESSION); String someStuff = zipUtil.getFileContentInsideZip(new ZipInputStream(new FileInputStream(zipFile)), "some-file.txt"); assertThat(someStuff, Is.is("some-text-here")); } @Test public void shouldZipMultipleFolderContentsAndExcludeRootDirectory() throws IOException { File folderOne = temporaryFolder.newFolder("a-folder1"); FileUtils.writeStringToFile(new File(folderOne, "folder1-file1.txt"), "folder1-file1"); FileUtils.writeStringToFile(new File(folderOne, "folder1-file2.txt"), "folder1-file2"); File folderTwo = temporaryFolder.newFolder("a-folder2"); FileUtils.writeStringToFile(new File(folderTwo, "folder2-file1.txt"), "folder2-file1"); FileUtils.writeStringToFile(new File(folderTwo, "folder2-file2.txt"), "folder2-file2"); File targetZipFile = temporaryFolder.newFile("final1.zip"); ZipBuilder zipBuilder = zipUtil.zipContentsOfMultipleFolders(targetZipFile, true); zipBuilder.add("folder-one", folderOne); zipBuilder.add("folder-two", folderTwo); zipBuilder.done(); assertContent(targetZipFile, "folder-one/folder1-file1.txt", "folder1-file1"); assertContent(targetZipFile, "folder-one/folder1-file2.txt", "folder1-file2"); assertContent(targetZipFile, "folder-two/folder2-file1.txt", "folder2-file1"); assertContent(targetZipFile, "folder-two/folder2-file2.txt", "folder2-file2"); } @Test public void shouldZipMultipleFolderContentsWhenNotExcludingRootDirectory() throws IOException { File folderOne = temporaryFolder.newFolder("folder1"); FileUtils.writeStringToFile(new File(folderOne, "folder1-file1.txt"), "folder1-file1"); FileUtils.writeStringToFile(new File(folderOne, "folder1-file2.txt"), "folder1-file2"); File folderTwo = temporaryFolder.newFolder("folder2"); FileUtils.writeStringToFile(new File(folderTwo, "folder2-file1.txt"), "folder2-file1"); FileUtils.writeStringToFile(new File(folderTwo, "folder2-file2.txt"), "folder2-file2"); File targetZipFile = temporaryFolder.newFile("final2.zip"); ZipBuilder zipBuilder = zipUtil.zipContentsOfMultipleFolders(targetZipFile, false); zipBuilder.add("folder-one", folderOne); zipBuilder.add("folder-two", folderTwo); zipBuilder.done(); assertContent(targetZipFile, "folder-one/folder1/folder1-file1.txt", "folder1-file1"); assertContent(targetZipFile, "folder-one/folder1/folder1-file2.txt", "folder1-file2"); assertContent(targetZipFile, "folder-two/folder2/folder2-file1.txt", "folder2-file1"); assertContent(targetZipFile, "folder-two/folder2/folder2-file2.txt", "folder2-file2"); } @Test public void shouldPreserveFileTimestampWhileGeneratingTheZipFile() throws Exception { File file = temporaryFolder.newFile("foo.txt"); file.setLastModified(1297989100000L); // Set this to any date in the past which is greater than the epoch File zip = zipUtil.zip(file, temporaryFolder.newFile("foo.zip"), Deflater.DEFAULT_COMPRESSION); ZipFile actualZip = new ZipFile(zip.getAbsolutePath()); ZipEntry entry = actualZip.getEntry(file.getName()); assertThat(entry.getTime(), is(file.lastModified())); } @Test public void shouldThrowUpWhileTryingToUnzipIfAnyOfTheFilePathsInArchiveHasAPathContainingDotDotSlashPath() throws URISyntaxException, IOException { try { zipUtil.unzip(new File(getClass().getResource("/archive_traversal_attack.zip").toURI()), destDir); fail("squash.zip is capable of causing archive traversal attack and hence should not be allowed."); } catch (IllegalPathException e) { assertThat(e.getMessage(), is("File ../2.txt is outside extraction target directory")); } } private void assertContent(File targetZipFile, String file, String expectedContent) throws IOException { ZipFile actualZip = new ZipFile(targetZipFile); ZipEntry entry = actualZip.getEntry(file); assertThat(entry, is(notNullValue())); assertThat(IOUtils.toString(actualZip.getInputStream(entry)), is(expectedContent)); } private void assertIsDirectory(File file) { assertThat("File " + file.getPath() + " should exist", file.exists(), is(true)); assertThat("File " + file.getPath() + " should be a directory", file.isDirectory(), is(true)); } }