diff options
author | Vladimir Shushlin <vshushlin@gitlab.com> | 2020-08-11 14:48:39 +0300 |
---|---|---|
committer | Vladimir Shushlin <vshushlin@gitlab.com> | 2020-08-11 14:48:39 +0300 |
commit | 8617aaebb1e45b9c222f3c67f93112d7dc37ad61 (patch) | |
tree | 45327f84ab9848f19564f0180a1f8fbe3f97a161 | |
parent | 04e50760b07febf8174143ae7c110702e26a07b9 (diff) | |
parent | 334bda2bd7405e10705debc577b7b737e9ce8f4d (diff) |
Merge branch 'jv-vendor-symlink-code' into 'master'
Vendor Go 1.14 filepath.EvalSymlinks code
See merge request gitlab-org/gitlab-pages!321
-rw-r--r-- | .golangci.yml | 1 | ||||
-rw-r--r-- | internal/serving/disk/symlink/LICENSE | 27 | ||||
-rw-r--r-- | internal/serving/disk/symlink/PATENTS | 22 | ||||
-rw-r--r-- | internal/serving/disk/symlink/README.md | 7 | ||||
-rw-r--r-- | internal/serving/disk/symlink/path_test.go | 262 | ||||
-rw-r--r-- | internal/serving/disk/symlink/shims.go | 13 | ||||
-rw-r--r-- | internal/serving/disk/symlink/symlink.go | 146 |
7 files changed, 478 insertions, 0 deletions
diff --git a/.golangci.yml b/.golangci.yml index 5c60dcfb..2b97de09 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,7 @@ run: skip-dirs: - vendor - internal/httputil # from github.com/golang/gddo + - internal/serving/disk/symlink skip-files: - mock_*.go diff --git a/internal/serving/disk/symlink/LICENSE b/internal/serving/disk/symlink/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/internal/serving/disk/symlink/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/internal/serving/disk/symlink/PATENTS b/internal/serving/disk/symlink/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/internal/serving/disk/symlink/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/internal/serving/disk/symlink/README.md b/internal/serving/disk/symlink/README.md new file mode 100644 index 00000000..2b3678c2 --- /dev/null +++ b/internal/serving/disk/symlink/README.md @@ -0,0 +1,7 @@ +# Symlink code extracted from Go 1.14 stdlib + +This directory contains part of the Go standard library +`filepath.EvalSymlinks` code. It was vendored from Go 1.14.6. + +- `symlink.go` is based on https://github.com/golang/go/blob/go1.14.6/src/path/filepath/symlink.go +- `path_test.go` is based on https://github.com/golang/go/blob/go1.14.6/src/path/filepath/path_test.go#L768-L1000 diff --git a/internal/serving/disk/symlink/path_test.go b/internal/serving/disk/symlink/path_test.go new file mode 100644 index 00000000..077b0ad2 --- /dev/null +++ b/internal/serving/disk/symlink/path_test.go @@ -0,0 +1,262 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath_test + +import ( + "io/ioutil" + "os" + "runtime" + "testing" + + filepath "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk/symlink" +) + +func chtmpdir(t *testing.T) (restore func()) { + oldwd, err := os.Getwd() + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + d, err := ioutil.TempDir("", "test") + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + if err := os.Chdir(d); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + return func() { + if err := os.Chdir(oldwd); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + os.RemoveAll(d) + } +} + +type EvalSymlinksTest struct { + // If dest is empty, the path is created; otherwise the dest is symlinked to the path. + path, dest string +} + +var EvalSymlinksTestDirs = []EvalSymlinksTest{ + {"test", ""}, + {"test/dir", ""}, + {"test/dir/link3", "../../"}, + {"test/link1", "../test"}, + {"test/link2", "dir"}, + {"test/linkabs", "/"}, + {"test/link4", "../test2"}, + {"test2", "test/dir"}, + // Issue 23444. + {"src", ""}, + {"src/pool", ""}, + {"src/pool/test", ""}, + {"src/versions", ""}, + {"src/versions/current", "../../version"}, + {"src/versions/v1", ""}, + {"src/versions/v1/modules", ""}, + {"src/versions/v1/modules/test", "../../../pool/test"}, + {"version", "src/versions/v1"}, +} + +var EvalSymlinksTests = []EvalSymlinksTest{ + {"test", "test"}, + {"test/dir", "test/dir"}, + {"test/dir/../..", "."}, + {"test/link1", "test"}, + {"test/link2", "test/dir"}, + {"test/link1/dir", "test/dir"}, + {"test/link2/..", "test"}, + {"test/dir/link3", "."}, + {"test/link2/link3/test", "test"}, + {"test/linkabs", "/"}, + {"test/link4/..", "test"}, + {"src/versions/current/modules/test", "src/pool/test"}, +} + +// simpleJoin builds a file name from the directory and path. +// It does not use Join because we don't want ".." to be evaluated. +func simpleJoin(dir, path string) string { + return dir + string(filepath.Separator) + path +} + +func testEvalSymlinks(t *testing.T, path, want string) { + have, err := filepath.EvalSymlinks(path) + if err != nil { + t.Errorf("EvalSymlinks(%q) error: %v", path, err) + return + } + if filepath.Clean(have) != filepath.Clean(want) { + t.Errorf("EvalSymlinks(%q) returns %q, want %q", path, have, want) + } +} + +func testEvalSymlinksAfterChdir(t *testing.T, wd, path, want string) { + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer func() { + err := os.Chdir(cwd) + if err != nil { + t.Fatal(err) + } + }() + + err = os.Chdir(wd) + if err != nil { + t.Fatal(err) + } + + have, err := filepath.EvalSymlinks(path) + if err != nil { + t.Errorf("EvalSymlinks(%q) in %q directory error: %v", path, wd, err) + return + } + if filepath.Clean(have) != filepath.Clean(want) { + t.Errorf("EvalSymlinks(%q) in %q directory returns %q, want %q", path, wd, have, want) + } +} + +func TestEvalSymlinks(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "evalsymlink") + if err != nil { + t.Fatal("creating temp dir:", err) + } + defer os.RemoveAll(tmpDir) + + // /tmp may itself be a symlink! Avoid the confusion, although + // it means trusting the thing we're testing. + tmpDir, err = filepath.EvalSymlinks(tmpDir) + if err != nil { + t.Fatal("eval symlink for tmp dir:", err) + } + + // Create the symlink farm using relative paths. + for _, d := range EvalSymlinksTestDirs { + var err error + path := simpleJoin(tmpDir, d.path) + if d.dest == "" { + err = os.Mkdir(path, 0755) + } else { + err = os.Symlink(d.dest, path) + } + if err != nil { + t.Fatal(err) + } + } + + // Evaluate the symlink farm. + for _, test := range EvalSymlinksTests { + path := simpleJoin(tmpDir, test.path) + + dest := simpleJoin(tmpDir, test.dest) + if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) { + dest = test.dest + } + testEvalSymlinks(t, path, dest) + + // test EvalSymlinks(".") + testEvalSymlinksAfterChdir(t, path, ".", ".") + + // test EvalSymlinks("C:.") on Windows + if runtime.GOOS == "windows" { + volDot := filepath.VolumeName(tmpDir) + "." + testEvalSymlinksAfterChdir(t, path, volDot, volDot) + } + + // test EvalSymlinks(".."+path) + dotdotPath := simpleJoin("..", test.dest) + if filepath.IsAbs(test.dest) || os.IsPathSeparator(test.dest[0]) { + dotdotPath = test.dest + } + testEvalSymlinksAfterChdir(t, + simpleJoin(tmpDir, "test"), + simpleJoin("..", test.path), + dotdotPath) + + // test EvalSymlinks(p) where p is relative path + testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest) + } +} + +func TestEvalSymlinksIsNotExist(t *testing.T) { + defer chtmpdir(t)() + + _, err := filepath.EvalSymlinks("notexist") + if !os.IsNotExist(err) { + t.Errorf("expected the file is not found, got %v\n", err) + } + + err = os.Symlink("notexist", "link") + if err != nil { + t.Fatal(err) + } + defer os.Remove("link") + + _, err = filepath.EvalSymlinks("link") + if !os.IsNotExist(err) { + t.Errorf("expected the file is not found, got %v\n", err) + } +} + +func TestIssue13582(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "issue13582") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + dir := filepath.Join(tmpDir, "dir") + err = os.Mkdir(dir, 0755) + if err != nil { + t.Fatal(err) + } + linkToDir := filepath.Join(tmpDir, "link_to_dir") + err = os.Symlink(dir, linkToDir) + if err != nil { + t.Fatal(err) + } + file := filepath.Join(linkToDir, "file") + err = ioutil.WriteFile(file, nil, 0644) + if err != nil { + t.Fatal(err) + } + link1 := filepath.Join(linkToDir, "link1") + err = os.Symlink(file, link1) + if err != nil { + t.Fatal(err) + } + link2 := filepath.Join(linkToDir, "link2") + err = os.Symlink(link1, link2) + if err != nil { + t.Fatal(err) + } + + // /tmp may itself be a symlink! + realTmpDir, err := filepath.EvalSymlinks(tmpDir) + if err != nil { + t.Fatal(err) + } + realDir := filepath.Join(realTmpDir, "dir") + realFile := filepath.Join(realDir, "file") + + tests := []struct { + path, want string + }{ + {dir, realDir}, + {linkToDir, realDir}, + {file, realFile}, + {link1, realFile}, + {link2, realFile}, + } + for i, test := range tests { + have, err := filepath.EvalSymlinks(test.path) + if err != nil { + t.Fatal(err) + } + if have != test.want { + t.Errorf("test#%d: EvalSymlinks(%q) returns %q, want %q", i, test.path, have, test.want) + } + } +} diff --git a/internal/serving/disk/symlink/shims.go b/internal/serving/disk/symlink/shims.go new file mode 100644 index 00000000..4b344180 --- /dev/null +++ b/internal/serving/disk/symlink/shims.go @@ -0,0 +1,13 @@ +package filepath + +import stdlib "path/filepath" + +func volumeNameLen(s string) int { return 0 } + +func IsAbs(path string) bool { return stdlib.IsAbs(path) } +func Clean(path string) string { return stdlib.Clean(path) } +func Join(elem ...string) string { return stdlib.Join(elem...) } +func VolumeName(path string) string { return stdlib.VolumeName(path) } +func EvalSymlinks(path string) (string, error) { return walkSymlinks(path) } + +const Separator = stdlib.Separator diff --git a/internal/serving/disk/symlink/symlink.go b/internal/serving/disk/symlink/symlink.go new file mode 100644 index 00000000..335b315a --- /dev/null +++ b/internal/serving/disk/symlink/symlink.go @@ -0,0 +1,146 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath + +import ( + "errors" + "os" + "runtime" + "syscall" +) + +func walkSymlinks(path string) (string, error) { + volLen := volumeNameLen(path) + pathSeparator := string(os.PathSeparator) + + if volLen < len(path) && os.IsPathSeparator(path[volLen]) { + volLen++ + } + vol := path[:volLen] + dest := vol + linksWalked := 0 + for start, end := volLen, volLen; start < len(path); start = end { + for start < len(path) && os.IsPathSeparator(path[start]) { + start++ + } + end = start + for end < len(path) && !os.IsPathSeparator(path[end]) { + end++ + } + + // On Windows, "." can be a symlink. + // We look it up, and use the value if it is absolute. + // If not, we just return ".". + isWindowsDot := runtime.GOOS == "windows" && path[volumeNameLen(path):] == "." + + // The next path component is in path[start:end]. + if end == start { + // No more path components. + break + } else if path[start:end] == "." && !isWindowsDot { + // Ignore path component ".". + continue + } else if path[start:end] == ".." { + // Back up to previous component if possible. + // Note that volLen includes any leading slash. + + // Set r to the index of the last slash in dest, + // after the volume. + var r int + for r = len(dest) - 1; r >= volLen; r-- { + if os.IsPathSeparator(dest[r]) { + break + } + } + if r < volLen || dest[r+1:] == ".." { + // Either path has no slashes + // (it's empty or just "C:") + // or it ends in a ".." we had to keep. + // Either way, keep this "..". + if len(dest) > volLen { + dest += pathSeparator + } + dest += ".." + } else { + // Discard everything since the last slash. + dest = dest[:r] + } + continue + } + + // Ordinary path component. Add it to result. + + if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) { + dest += pathSeparator + } + + dest += path[start:end] + + // Resolve symlink. + + fi, err := os.Lstat(dest) + if err != nil { + return "", err + } + + if fi.Mode()&os.ModeSymlink == 0 { + if !fi.Mode().IsDir() && end < len(path) { + return "", syscall.ENOTDIR + } + continue + } + + // Found symlink. + + linksWalked++ + if linksWalked > 255 { + return "", errors.New("EvalSymlinks: too many links") + } + + link, err := os.Readlink(dest) + if err != nil { + return "", err + } + + if isWindowsDot && !IsAbs(link) { + // On Windows, if "." is a relative symlink, + // just return ".". + break + } + + path = link + path[end:] + + v := volumeNameLen(link) + if v > 0 { + // Symlink to drive name is an absolute path. + if v < len(link) && os.IsPathSeparator(link[v]) { + v++ + } + vol = link[:v] + dest = vol + end = len(vol) + } else if len(link) > 0 && os.IsPathSeparator(link[0]) { + // Symlink to absolute path. + dest = link[:1] + end = 1 + } else { + // Symlink to relative path; replace last + // path component in dest. + var r int + for r = len(dest) - 1; r >= volLen; r-- { + if os.IsPathSeparator(dest[r]) { + break + } + } + if r < volLen { + dest = vol + } else { + dest = dest[:r] + } + end = 0 + } + } + return Clean(dest), nil +} |