/* * Copyright (C) 2016 Google 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.google.copybara.util; import static com.google.common.truth.Truth.assertThat; import static com.google.copybara.testing.FileSubjects.assertThatPath; import static com.google.copybara.util.FileUtil.CopySymlinkStrategy.FAIL_OUTSIDE_SYMLINKS; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.copybara.util.FileUtil.CopySymlinkStrategy; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Matchers; @RunWith(JUnit4.class) public class FileUtilTest { private Path temp; @Before public void setup() throws Exception { temp = newTempDirectory("temp"); } private Path newTempDirectory(String prefix) throws IOException { // TODO(copybara-team): Explore running the tests against different filesystems using: // Jimfs.newFileSystem(Configuration.windows()); // Right now it's not possible because we rely on {@link Path#toFile}, which is not supported // by Jimfs. return Files.createTempDirectory(prefix); } @Test public void checkRelativism_string_absolute() { assertThrows(IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative("/foo")); } @Test public void checkRelativism_path_absolute() { assertThrows( IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative(Paths.get("/foo"))); } @Test public void checkRelativism_oneDot() { assertThrows( IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative("foo/./bar")); } @Test public void checkRelativism_twoDots() { assertThrows( IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative("foo/../bar")); } @Test public void checkRelativism_path_twoDotsAtStart() { assertThrows( IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative(Paths.get("../bar"))); } @Test public void checkRelativism_twoDotsAtEnd() { assertThrows( IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative(Paths.get("bar/.."))); } @Test public void checkRelativism_oneDotAtStart() { assertThrows(IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative("./foo")); } @Test public void checkRelativism_oneDotAtEnd() { assertThrows(IllegalArgumentException.class, () -> FileUtil.checkNormalizedRelative("foo/.")); } @Test public void checkRelativism_succeedsForDotInValidComponent() { FileUtil.checkNormalizedRelative("foo/.emacs.d"); FileUtil.checkNormalizedRelative("foo/bar.baz"); } @Test public void testCopyFilesRecursively_symlink_to_other_root() throws Exception{ Path orig = Files.createDirectory(temp.resolve("orig")); Path dest = Files.createDirectory(temp.resolve("dest")); Files.createDirectory(orig.resolve("foo")); Files.createDirectory(orig.resolve("bar")); Files.write(orig.resolve("bar/bar.txt"), new byte[]{}); Files.createSymbolicLink(orig.resolve("foo/foo.txt"), orig.getFileSystem().getPath("../bar/bar.txt")); FileUtil.copyFilesRecursively(orig, dest, FAIL_OUTSIDE_SYMLINKS, Glob.createGlob(ImmutableList.of("foo/**", "bar/**"))); } /** * Regression test for folder.origin refs that contain '..' and globs that wouldn't match * globs like foo/../foo/file if we didn't normalize symlinks before. */ @Test public void testCopyFilesRecursively_symlink_with_dot_dot() throws Exception { Path orig = Files.createDirectory(temp.resolve("orig")); Path dest = Files.createDirectory(temp.resolve("dest")); Files.createDirectory(orig.resolve("foo")); Files.createDirectory(orig.resolve("bar")); Files.write(orig.resolve("bar/bar.txt"), new byte[]{}); Files.createSymbolicLink(orig.resolve("foo/foo.txt"), orig.getFileSystem().getPath("../bar/bar.txt")); FileUtil.copyFilesRecursively(orig.resolve("../" + orig.getFileName()), dest, FAIL_OUTSIDE_SYMLINKS, Glob.createGlob(ImmutableList.of("foo/*", "bar/*"))); } @Test public void testCopyMaterializeAbsolutePaths() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); Path absolute = touch(Files.createDirectory(temp.resolve("absolute")).resolve("absolute")); Path absoluteSymlink = Files.createSymbolicLink( temp.resolve("absolute").resolve("symlink"), absolute); Path absoluteDir = newTempDirectory("absoluteDir"); Files.createDirectories(absoluteDir.resolve("absoluteDirDir")); Files.write(absoluteDir.resolve("absoluteDirElement"), "abc".getBytes(UTF_8)); Files.write(absoluteDir.resolve("absoluteDirDir/element"), "abc".getBytes(UTF_8)); FileUtil.addPermissions(touch(one.resolve("foo")), ImmutableSet.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ)); touch(one.resolve("some/folder/bar")); Files.createSymbolicLink(one.resolve("some/folder/baz"), one.getFileSystem().getPath("../../foo")); // Symlink to the root: Files.createSymbolicLink(one.resolve("dot"), one.getFileSystem().getPath(".")); // Test multiple jumps inside the root: some/multiple -> folder/baz -> ../../foo Files.createSymbolicLink(one.resolve("some/multiple"), one.resolve("some").relativize(one.resolve("some/folder/baz"))); Path folder = one.resolve("some/folder"); Path absoluteTarget = folder.relativize(absolute); Files.createSymbolicLink(folder.resolve("absolute"), absoluteTarget); Path absoluteSymlinkTarget = folder.relativize(absoluteSymlink); Files.createSymbolicLink(folder.resolve("absoluteSymlink"), absoluteSymlinkTarget); // Multiple jumps of symlinks that ends out of the root Files.createSymbolicLink(folder.resolve("absolute2"), folder.relativize(folder.resolve("absolute"))); // Symlink to a directory outside root Files.createSymbolicLink(folder.resolve("absolute3"), absoluteDir); FileUtil.copyFilesRecursively(one, two, CopySymlinkStrategy.MATERIALIZE_OUTSIDE_SYMLINKS); assertThatPath(two) .containsFile("foo", "abc") .containsFile("dot/foo", "abc") .containsFile("some/folder/bar", "abc") .containsFile("some/multiple", "abc") .containsFile("some/folder/absolute", "abc") .containsFile("some/folder/absoluteSymlink", "abc") .containsFile("some/folder/absolute2", "abc") .containsFile("some/folder/absolute3/absoluteDirElement", "abc") .containsFile("some/folder/absolute3/absoluteDirDir/element", "abc") .containsFile("some/folder/baz", "abc") .containsNoMoreFiles(); assertThat(Files.isExecutable(two.resolve("foo"))).isTrue(); assertThat(Files.isExecutable(two.resolve("foo"))).isTrue(); assertThat(Files.isExecutable(two.resolve("some/folder/bar"))).isFalse(); assertThat(Files.readSymbolicLink(two.resolve("some/folder/baz")).toString()) .isEqualTo(two.getFileSystem().getPath("../../foo").toString()); // Symlink to a directory inside the root are symlinked assertThat(Files.isSymbolicLink(two.resolve("dot"))).isTrue(); assertThat(Files.isSymbolicLink(two.resolve("some/multiple"))).isTrue(); // Anything outside of one/... is copied as a regular file assertThat(Files.isSymbolicLink(two.resolve("some/folder/absolute"))).isFalse(); assertThat(Files.isSymbolicLink(two.resolve("some/folder/absoluteSymlink"))).isFalse(); assertThat(Files.isSymbolicLink(two.resolve("some/folder/absolute2"))).isFalse(); assertThat(Files.isSymbolicLink(two.resolve("some/folder/absolute3"))).isFalse(); } @Test public void testCopyFailAbsoluteSymlinks() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); Path absolute = touch(Files.createDirectory(temp.resolve("absolute")).resolve("absolute")); Path folder = Files.createDirectories(one.resolve("some/folder")); Path absoluteTarget = folder.relativize(absolute); Files.createSymbolicLink(folder.resolve("absolute"), absoluteTarget); AbsoluteSymlinksNotAllowed expected = assertThrows( AbsoluteSymlinksNotAllowed.class, () -> FileUtil.copyFilesRecursively(one, two, FAIL_OUTSIDE_SYMLINKS)); assertThat(expected).hasMessageThat().isNotNull(); assertThat(expected.toString()).contains("is absolute or escaped the root:"); assertThat(expected.toString()).contains("folder/absolute"); assertThat(expected.toString()).contains("absolute/absolute"); } private Path touch(Path path) throws IOException { Files.createDirectories(path.getParent()); Files.write(path, "abc".getBytes(UTF_8)); return path; } @Test public void testMaterializedSymlinksAreWriteable() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); Path absolute = touch(Files.createDirectory(temp.resolve("absolute")).resolve("absolute")); FileUtil.addPermissions(absolute, ImmutableSet.of(PosixFilePermission.OWNER_READ)); Path absoluteTarget = one.relativize(absolute); Files.createSymbolicLink(one.resolve("absolute"), absoluteTarget); FileUtil.copyFilesRecursively(one, two, CopySymlinkStrategy.MATERIALIZE_OUTSIDE_SYMLINKS); assertThat(Files.isSymbolicLink(two.resolve("absolute"))).isFalse(); assertThat(Files.isWritable(two.resolve("absolute"))).isTrue(); } @Test public void testCopyWithGlob() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); Files.createDirectories(one.resolve("foo")); Files.createDirectories(one.resolve("bar")); touch(one.resolve("foo/include.txt")); touch(one.resolve("foo/exclude.txt")); touch(one.resolve("bar/nonono.txt")); FileUtil.copyFilesRecursively(one, two, FAIL_OUTSIDE_SYMLINKS, Glob.createGlob( ImmutableList.of("foo/**"), ImmutableList.of("foo/exclude.txt"))); assertThatPath(two).containsFiles("foo/include.txt") .containsNoMoreFiles(); } @Test public void testNoGlobs() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); Files.createDirectories(one.resolve("foo")); Files.createDirectories(one.resolve("bar")); touch(one.resolve("foo/include.txt")); touch(one.resolve("foo/exclude.txt")); touch(one.resolve("bar/nonono.txt")); FileUtil.copyFilesRecursively(one, two, FAIL_OUTSIDE_SYMLINKS); assertThatPath(two) .containsFiles("foo/include.txt", "foo/exclude.txt", "bar/nonono.txt") .containsNoMoreFiles(); } @Test public void testCopyWithGlob_oneRootNotPresent() throws Exception { Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); // We don't create 'bar' directory. Files.createDirectories(one.resolve("foo")); touch(one.resolve("foo/include.txt")); FileUtil.copyFilesRecursively(one, two, FAIL_OUTSIDE_SYMLINKS, Glob.createGlob(ImmutableList.of("foo/**", "bar/**"))); assertThatPath(two).containsFiles("foo/include.txt") .containsNoMoreFiles(); } @Test public void testCopyWithGlob_validatorCalled() throws Exception { FileUtil.CopyVisitorValidator validator = mock(FileUtil.CopyVisitorValidator.class); Path one = Files.createDirectory(temp.resolve("one")); Path two = Files.createDirectory(temp.resolve("two")); // We don't create 'bar' directory. Files.createDirectories(one.resolve("foo")); touch(one.resolve("foo/include.txt")); FileUtil.copyFilesRecursively(one, two, FAIL_OUTSIDE_SYMLINKS, Glob.createGlob(ImmutableList.of("foo/**", "bar/**")), Optional.of(validator)); verify(validator).validate(Matchers.eq(one.resolve("foo/include.txt"))); verifyNoMoreInteractions(validator); } }