diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java b/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java new file mode 100644 index 0000000000..462e005b20 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Google LLC. + * + * 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.google.cloud.tools.jib.tar; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; + +/** Extracts a tarball. */ +public class TarExtractor { + + /** + * Extracts a tarball to the specified destination. + * + * @param source the tarball to extract + * @param destination the output directory + * @throws IOException if extraction fails + */ + public static void extract(Path source, Path destination) throws IOException { + String canonicalDestination = destination.toFile().getCanonicalPath(); + + try (InputStream in = new BufferedInputStream(Files.newInputStream(source)); + TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(in)) { + byte[] buffer = new byte[1024]; + TarArchiveEntry entry = tarArchiveInputStream.getNextTarEntry(); + while (entry != null) { + Path entryPath = destination.resolve(entry.getName()); + + String canonicalTarget = entryPath.toFile().getCanonicalPath(); + if (!canonicalTarget.startsWith(canonicalDestination + File.separator)) { + String offender = entry.getName() + " from " + source; + throw new IOException("Blocked unzipping files outside destination: " + offender); + } + + if (entry.isDirectory()) { + Files.createDirectories(entryPath); + } else { + try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(entryPath))) { + int read = tarArchiveInputStream.read(buffer); + while (read != -1) { + out.write(buffer, 0, read); + read = tarArchiveInputStream.read(buffer); + } + } + } + entry = tarArchiveInputStream.getNextTarEntry(); + } + } + } +} diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java new file mode 100644 index 0000000000..cfa67a3fbc --- /dev/null +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google LLC. + * + * 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.google.cloud.tools.jib.tar; + +import com.google.common.io.Resources; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** Tests for {@link TarExtractor}. */ +public class TarExtractorTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testExtract() throws URISyntaxException, IOException { + Path source = Paths.get(Resources.getResource("core/extract.tar").toURI()); + Path destination = temporaryFolder.getRoot().toPath(); + TarExtractor.extract(source, destination); + + Assert.assertTrue(Files.exists(destination.resolve("file A"))); + Assert.assertTrue(Files.exists(destination.resolve("file B"))); + Assert.assertTrue( + Files.exists(destination.resolve("folder").resolve("nested folder").resolve("file C"))); + + try (Stream lines = Files.lines(destination.resolve("file A"))) { + String contents = lines.collect(Collectors.joining()); + Assert.assertEquals("Hello", contents); + } + } +} diff --git a/jib-core/src/test/resources/core/extract.tar b/jib-core/src/test/resources/core/extract.tar new file mode 100644 index 0000000000..fdc05b1427 Binary files /dev/null and b/jib-core/src/test/resources/core/extract.tar differ