/* * Copyright 2013-present Facebook, 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. */ package com.facebook.buck.zip; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeThat; import com.facebook.buck.io.MoreFiles; import com.facebook.buck.io.MorePosixFilePermissions; import com.facebook.buck.testutil.Zip; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.util.environment.Platform; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermissions; import java.util.zip.ZipEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; public class UnzipTest { private static final byte[] DUMMY_FILE_CONTENTS = "BUCK Unzip Test String!\nNihao\n".getBytes(); @Rule public TemporaryPaths tmpFolder = new TemporaryPaths(); private Path zipFile; @Before public void setUp() { zipFile = tmpFolder.getRoot().resolve("tmp.zip"); } @Test public void testExtractZipFile() throws InterruptedException, IOException { try (Zip zip = new Zip(zipFile, true)) { zip.add("1.bin", DUMMY_FILE_CONTENTS); zip.add("subdir/2.bin", DUMMY_FILE_CONTENTS); zip.addDir("emptydir"); } Path extractFolder = tmpFolder.newFolder(); ImmutableList<Path> result = Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE); assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve("1.bin"))); Path bin2 = extractFolder.toAbsolutePath().resolve("subdir/2.bin"); assertTrue(Files.exists(bin2)); assertTrue(Files.isDirectory(extractFolder.toAbsolutePath().resolve("emptydir"))); try (InputStream input = Files.newInputStream(bin2)) { byte[] buffer = new byte[DUMMY_FILE_CONTENTS.length]; int bytesRead = input.read(buffer, 0, DUMMY_FILE_CONTENTS.length); assertEquals(DUMMY_FILE_CONTENTS.length, bytesRead); for (int i = 0; i < DUMMY_FILE_CONTENTS.length; i++) { assertEquals(DUMMY_FILE_CONTENTS[i], buffer[i]); } } assertEquals( ImmutableList.of(extractFolder.resolve("1.bin"), extractFolder.resolve("subdir/2.bin")), result); } @Test public void testExtractZipFilePreservesExecutePermissionsAndModificationTime() throws InterruptedException, IOException { // getFakeTime returs time with some non-zero millis. By doing division and multiplication by // 1000 we get rid of that. final long time = ZipConstants.getFakeTime() / 1000 * 1000; // Create a simple zip archive using apache's commons-compress to store executable info. try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(zipFile.toFile())) { ZipArchiveEntry entry = new ZipArchiveEntry("test.exe"); entry.setUnixMode( (int) MorePosixFilePermissions.toMode(PosixFilePermissions.fromString("r-x------"))); entry.setSize(DUMMY_FILE_CONTENTS.length); entry.setMethod(ZipEntry.STORED); entry.setTime(time); zip.putArchiveEntry(entry); zip.write(DUMMY_FILE_CONTENTS); zip.closeArchiveEntry(); } // Now run `Unzip.extractZipFile` on our test zip and verify that the file is executable. Path extractFolder = tmpFolder.newFolder(); ImmutableList<Path> result = Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE); Path exe = extractFolder.toAbsolutePath().resolve("test.exe"); assertTrue(Files.exists(exe)); assertThat(Files.getLastModifiedTime(exe).toMillis(), Matchers.equalTo(time)); assertTrue(Files.isExecutable(exe)); assertEquals(ImmutableList.of(extractFolder.resolve("test.exe")), result); } @Test public void testExtractSymlink() throws InterruptedException, IOException { assumeThat(Platform.detect(), Matchers.is(Matchers.not(Platform.WINDOWS))); // Create a simple zip archive using apache's commons-compress to store executable info. try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(zipFile.toFile())) { ZipArchiveEntry entry = new ZipArchiveEntry("link.txt"); entry.setUnixMode((int) MoreFiles.S_IFLNK); String target = "target.txt"; entry.setSize(target.getBytes(Charsets.UTF_8).length); entry.setMethod(ZipEntry.STORED); zip.putArchiveEntry(entry); zip.write(target.getBytes(Charsets.UTF_8)); zip.closeArchiveEntry(); } // Now run `Unzip.extractZipFile` on our test zip and verify that the file is executable. Path extractFolder = tmpFolder.newFolder(); Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE); Path link = extractFolder.toAbsolutePath().resolve("link.txt"); assertTrue(Files.isSymbolicLink(link)); assertThat(Files.readSymbolicLink(link).toString(), Matchers.equalTo("target.txt")); } @Test public void testExtractWeirdIndex() throws InterruptedException, IOException { try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(zipFile.toFile())) { zip.putArchiveEntry(new ZipArchiveEntry("foo/bar/baz")); zip.write(DUMMY_FILE_CONTENTS, 0, DUMMY_FILE_CONTENTS.length); zip.closeArchiveEntry(); zip.putArchiveEntry(new ZipArchiveEntry("foo/")); zip.closeArchiveEntry(); zip.putArchiveEntry(new ZipArchiveEntry("qux/")); zip.closeArchiveEntry(); } Path extractFolder = tmpFolder.newFolder(); ImmutableList<Path> result = Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve("foo"))); assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve("foo/bar/baz"))); assertEquals(ImmutableList.of(extractFolder.resolve("foo/bar/baz")), result); } @Test public void testNonCanonicalPaths() throws InterruptedException, IOException { String names[] = { "foo/./", "foo/./bar/", "foo/./bar/baz.cpp", "foo/./bar/baz.h", }; try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(zipFile.toFile())) { for (String name : names) { zip.putArchiveEntry(new ZipArchiveEntry(name)); zip.closeArchiveEntry(); } } Path extractFolder = tmpFolder.newFolder(); Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); for (String name : names) { assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve(name))); } Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); for (String name : names) { assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve(name))); } } @Test public void testParentDirPaths() throws InterruptedException, IOException { try (ZipArchiveOutputStream zip = new ZipArchiveOutputStream(zipFile.toFile())) { // It seems very unlikely that a zip file would contain ".." paths, but handle it anyways. zip.putArchiveEntry(new ZipArchiveEntry("foo/bar/")); zip.closeArchiveEntry(); zip.putArchiveEntry(new ZipArchiveEntry("foo/bar/../")); zip.closeArchiveEntry(); } Path extractFolder = tmpFolder.newFolder(); Unzip.extractZipFile( zipFile.toAbsolutePath(), extractFolder.toAbsolutePath(), Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve("foo"))); assertTrue(Files.exists(extractFolder.toAbsolutePath().resolve("foo/bar"))); } }