diff options
Diffstat (limited to 'libgo/go/path')
-rw-r--r-- | libgo/go/path/filepath/example_unix_test.go | 15 | ||||
-rw-r--r-- | libgo/go/path/filepath/match_test.go | 2 | ||||
-rw-r--r-- | libgo/go/path/filepath/path.go | 15 | ||||
-rw-r--r-- | libgo/go/path/filepath/path_test.go | 56 | ||||
-rw-r--r-- | libgo/go/path/filepath/path_windows.go | 37 | ||||
-rw-r--r-- | libgo/go/path/filepath/symlink.go | 191 | ||||
-rw-r--r-- | libgo/go/path/filepath/symlink_unix.go | 9 | ||||
-rw-r--r-- | libgo/go/path/filepath/symlink_windows.go | 10 |
8 files changed, 239 insertions, 96 deletions
diff --git a/libgo/go/path/filepath/example_unix_test.go b/libgo/go/path/filepath/example_unix_test.go index cd8233c..20ec892 100644 --- a/libgo/go/path/filepath/example_unix_test.go +++ b/libgo/go/path/filepath/example_unix_test.go @@ -79,3 +79,18 @@ func ExampleJoin() { // a/b/c // a/b/c } + +func ExampleMatch() { + fmt.Println("On Unix:") + fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo")) + fmt.Println(filepath.Match("/home/catch/*", "/home/catch/foo/bar")) + fmt.Println(filepath.Match("/home/?opher", "/home/gopher")) + fmt.Println(filepath.Match("/home/\\*", "/home/*")) + + // Output: + // On Unix: + // true <nil> + // false <nil> + // true <nil> + // true <nil> +} diff --git a/libgo/go/path/filepath/match_test.go b/libgo/go/path/filepath/match_test.go index b73d6d2..70db359 100644 --- a/libgo/go/path/filepath/match_test.go +++ b/libgo/go/path/filepath/match_test.go @@ -106,7 +106,7 @@ func TestMatch(t *testing.T) { } } -// contains returns true if vector contains the string s. +// contains reports whether vector contains the string s. func contains(vector []string, s string) bool { for _, elem := range vector { if elem == s { diff --git a/libgo/go/path/filepath/path.go b/libgo/go/path/filepath/path.go index 1508137..bbb9030 100644 --- a/libgo/go/path/filepath/path.go +++ b/libgo/go/path/filepath/path.go @@ -96,14 +96,19 @@ func Clean(path string) string { } return originalPath + "." } + + n := len(path) + if volLen > 2 && n == 1 && os.IsPathSeparator(path[0]) { + // UNC volume name with trailing slash. + return FromSlash(originalPath[:volLen]) + } rooted := os.IsPathSeparator(path[0]) // Invariants: // reading from path; r is index of next byte to process. - // writing to buf; w is index of next byte to write. - // dotdot is index in buf where .. must stop, either because + // writing to out; w is index of next byte to write. + // dotdot is index in out where .. must stop, either because // it is the leading slash or it is a leading ../../.. prefix. - n := len(path) out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} r, dotdot := 0, 0 if rooted { @@ -166,7 +171,7 @@ func ToSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, string(Separator), "/", -1) + return strings.ReplaceAll(path, string(Separator), "/") } // FromSlash returns the result of replacing each slash ('/') character @@ -176,7 +181,7 @@ func FromSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, "/", string(Separator), -1) + return strings.ReplaceAll(path, "/", string(Separator)) } // SplitList splits a list of paths joined by the OS-specific ListSeparator, diff --git a/libgo/go/path/filepath/path_test.go b/libgo/go/path/filepath/path_test.go index 5983a94..4840f9d 100644 --- a/libgo/go/path/filepath/path_test.go +++ b/libgo/go/path/filepath/path_test.go @@ -15,6 +15,7 @@ import ( "runtime" "sort" "strings" + "syscall" "testing" ) @@ -92,6 +93,9 @@ var wincleantests = []PathTest{ {`//host/share/foo/../baz`, `\\host\share\baz`}, {`\\a\b\..\c`, `\\a\b\c`}, {`\\a\b`, `\\a\b`}, + {`\\a\b\`, `\\a\b`}, + {`\\folder\share\foo`, `\\folder\share\foo`}, + {`\\folder\share\foo\`, `\\folder\share\foo`}, } func TestClean(t *testing.T) { @@ -274,6 +278,10 @@ var winjointests = []JoinTest{ {[]string{`C:`, `a`}, `C:a`}, {[]string{`C:`, `a\b`}, `C:a\b`}, {[]string{`C:`, `a`, `b`}, `C:a\b`}, + {[]string{`C:`, ``, `b`}, `C:b`}, + {[]string{`C:`, ``, ``, `b`}, `C:b`}, + {[]string{`C:`, ``}, `C:.`}, + {[]string{`C:`, ``, ``}, `C:.`}, {[]string{`C:.`, `a`}, `C:a`}, {[]string{`C:a`, `b`}, `C:a\b`}, {[]string{`C:a`, `b`, `d`}, `C:a\b\d`}, @@ -747,6 +755,11 @@ func TestIsAbs(t *testing.T) { for _, test := range isabstests { tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) } + // Test reserved names. + tests = append(tests, IsAbsTest{os.DevNull, true}) + tests = append(tests, IsAbsTest{"NUL", true}) + tests = append(tests, IsAbsTest{"nul", true}) + tests = append(tests, IsAbsTest{"CON", true}) } else { tests = isabstests } @@ -770,6 +783,18 @@ var EvalSymlinksTestDirs = []EvalSymlinksTest{ {"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{ @@ -783,6 +808,8 @@ var EvalSymlinksTests = []EvalSymlinksTest{ {"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. @@ -1047,7 +1074,7 @@ func TestAbs(t *testing.T) { } for _, path := range absTests { - path = strings.Replace(path, "$", root, -1) + path = strings.ReplaceAll(path, "$", root) info, err := os.Stat(path) if err != nil { t.Errorf("%s: %s", path, err) @@ -1349,3 +1376,30 @@ func TestWalkSymlink(t *testing.T) { testenv.MustHaveSymlink(t) testWalkSymlink(t, os.Symlink) } + +func TestIssue29372(t *testing.T) { + f, err := ioutil.TempFile("", "issue29372") + if err != nil { + t.Fatal(err) + } + f.Close() + path := f.Name() + defer os.Remove(path) + + pathSeparator := string(filepath.Separator) + tests := []string{ + path + strings.Repeat(pathSeparator, 1), + path + strings.Repeat(pathSeparator, 2), + path + strings.Repeat(pathSeparator, 1) + ".", + path + strings.Repeat(pathSeparator, 2) + ".", + path + strings.Repeat(pathSeparator, 1) + "..", + path + strings.Repeat(pathSeparator, 2) + "..", + } + + for i, test := range tests { + _, err = filepath.EvalSymlinks(test) + if err != syscall.ENOTDIR { + t.Fatalf("test#%d: want %q, got %q", i, syscall.ENOTDIR, err) + } + } +} diff --git a/libgo/go/path/filepath/path_windows.go b/libgo/go/path/filepath/path_windows.go index 409e8d6..445c868 100644 --- a/libgo/go/path/filepath/path_windows.go +++ b/libgo/go/path/filepath/path_windows.go @@ -13,8 +13,34 @@ func isSlash(c uint8) bool { return c == '\\' || c == '/' } +// reservedNames lists reserved Windows names. Search for PRN in +// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file +// for details. +var reservedNames = []string{ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +} + +// isReservedName returns true, if path is Windows reserved name. +// See reservedNames for the full list. +func isReservedName(path string) bool { + if len(path) == 0 { + return false + } + for _, reserved := range reservedNames { + if strings.EqualFold(path, reserved) { + return true + } + } + return false +} + // IsAbs reports whether the path is absolute. func IsAbs(path string) (b bool) { + if isReservedName(path) { + return true + } l := volumeNameLen(path) if l == 0 { return false @@ -100,7 +126,7 @@ func splitList(path string) []string { // Remove quotes. for i, s := range list { - list[i] = strings.Replace(s, `"`, ``, -1) + list[i] = strings.ReplaceAll(s, `"`, ``) } return list @@ -134,7 +160,14 @@ func joinNonEmpty(elem []string) string { if len(elem[0]) == 2 && elem[0][1] == ':' { // First element is drive letter without terminating slash. // Keep path relative to current directory on that drive. - return Clean(elem[0] + strings.Join(elem[1:], string(Separator))) + // Skip empty elements. + i := 1 + for ; i < len(elem); i++ { + if elem[i] != "" { + break + } + } + return Clean(elem[0] + strings.Join(elem[i:], string(Separator))) } // The following logic prevents Join from inadvertently creating a // UNC path on Windows. Unless the first element is a UNC path, Join diff --git a/libgo/go/path/filepath/symlink.go b/libgo/go/path/filepath/symlink.go index 824aee4..4b41039 100644 --- a/libgo/go/path/filepath/symlink.go +++ b/libgo/go/path/filepath/symlink.go @@ -10,109 +10,128 @@ import ( "runtime" ) -// isRoot returns true if path is root of file system -// (`/` on unix and `/`, `\`, `c:\` or `c:/` on windows). -func isRoot(path string) bool { - if runtime.GOOS != "windows" { - return path == "/" - } - switch len(path) { - case 1: - return os.IsPathSeparator(path[0]) - case 3: - return path[1] == ':' && os.IsPathSeparator(path[2]) - } - return false -} +func walkSymlinks(path string) (string, error) { + volLen := volumeNameLen(path) + pathSeparator := string(os.PathSeparator) -// isDriveLetter returns true if path is Windows drive letter (like "c:"). -func isDriveLetter(path string) bool { - if runtime.GOOS != "windows" { - return false + if volLen < len(path) && os.IsPathSeparator(path[volLen]) { + volLen++ } - return len(path) == 2 && path[1] == ':' -} + 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++ + } -func walkLink(path string, linksWalked *int) (newpath string, islink bool, err error) { - if *linksWalked > 255 { - return "", false, errors.New("EvalSymlinks: too many links") - } - fi, err := os.Lstat(path) - if err != nil { - return "", false, err - } - if fi.Mode()&os.ModeSymlink == 0 { - return path, false, nil - } - newpath, err = os.Readlink(path) - if err != nil { - return "", false, err - } - *linksWalked++ - return newpath, true, nil -} + // 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):] == "." -func walkLinks(path string, linksWalked *int) (string, error) { - switch dir, file := Split(path); { - case dir == "": - newpath, _, err := walkLink(file, linksWalked) - return newpath, err - case file == "": - if isDriveLetter(dir) { - return dir, nil - } - if os.IsPathSeparator(dir[len(dir)-1]) { - if isRoot(dir) { - return dir, nil + // 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. + var r int + for r = len(dest) - 1; r >= volLen; r-- { + if os.IsPathSeparator(dest[r]) { + break + } + } + if r < volLen { + if len(dest) > volLen { + dest += pathSeparator + } + dest += ".." + } else { + dest = dest[:r] } - return walkLinks(dir[:len(dir)-1], linksWalked) + continue } - newpath, _, err := walkLink(dir, linksWalked) - return newpath, err - default: - newdir, err := walkLinks(dir, linksWalked) - if err != nil { - return "", err + + // Ordinary path component. Add it to result. + + if len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) { + dest += pathSeparator } - newpath, islink, err := walkLink(Join(newdir, file), linksWalked) + + dest += path[start:end] + + // Resolve symlink. + + fi, err := os.Lstat(dest) if err != nil { return "", err } - if !islink { - return newpath, nil + + if fi.Mode()&os.ModeSymlink == 0 { + if !fi.Mode().IsDir() && end < len(path) { + return "", slashAfterFilePathError + } + continue } - if IsAbs(newpath) || os.IsPathSeparator(newpath[0]) { - return newpath, nil + + // Found symlink. + + linksWalked++ + if linksWalked > 255 { + return "", errors.New("EvalSymlinks: too many links") } - return Join(newdir, newpath), nil - } -} -func walkSymlinks(path string) (string, error) { - if path == "" { - return path, nil - } - var linksWalked int // to protect against cycles - for { - i := linksWalked - newpath, err := walkLinks(path, &linksWalked) + link, err := os.Readlink(dest) if err != nil { return "", err } - if runtime.GOOS == "windows" { - // walkLinks(".", ...) always returns "." on unix. - // But on windows it returns symlink target, if current - // directory is a symlink. Stop the walk, if symlink - // target is not absolute path, and return "." - // to the caller (just like unix does). - // Same for "C:.". - if path[volumeNameLen(path):] == "." && !IsAbs(newpath) { - return path, nil - } + + if isWindowsDot && !IsAbs(link) { + // On Windows, if "." is a relative symlink, + // just return ".". + break } - if i == linksWalked { - return Clean(newpath), nil + + 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 } - path = newpath } + return Clean(dest), nil } diff --git a/libgo/go/path/filepath/symlink_unix.go b/libgo/go/path/filepath/symlink_unix.go index d20e63a..b57e7f2 100644 --- a/libgo/go/path/filepath/symlink_unix.go +++ b/libgo/go/path/filepath/symlink_unix.go @@ -2,6 +2,15 @@ package filepath +import ( + "syscall" +) + +// walkSymlinks returns slashAfterFilePathError error for paths like +// //path/to/existing_file/ and /path/to/existing_file/. and /path/to/existing_file/.. + +var slashAfterFilePathError = syscall.ENOTDIR + func evalSymlinks(path string) (string, error) { return walkSymlinks(path) } diff --git a/libgo/go/path/filepath/symlink_windows.go b/libgo/go/path/filepath/symlink_windows.go index 78cde4a..531dc26 100644 --- a/libgo/go/path/filepath/symlink_windows.go +++ b/libgo/go/path/filepath/symlink_windows.go @@ -43,7 +43,7 @@ func normBase(path string) (string, error) { return syscall.UTF16ToString(data.FileName[:]), nil } -// baseIsDotDot returns whether the last element of path is "..". +// baseIsDotDot reports whether the last element of path is "..". // The given path should be 'Clean'-ed in advance. func baseIsDotDot(path string) bool { i := strings.LastIndexByte(path, Separator) @@ -171,8 +171,16 @@ func samefile(path1, path2 string) bool { return os.SameFile(fi1, fi2) } +// walkSymlinks returns slashAfterFilePathError error for paths like +// //path/to/existing_file/ and /path/to/existing_file/. and /path/to/existing_file/.. + +var slashAfterFilePathError = errors.New("attempting to walk past file path.") + func evalSymlinks(path string) (string, error) { newpath, err := walkSymlinks(path) + if err == slashAfterFilePathError { + return "", syscall.ENOTDIR + } if err != nil { newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path) if err2 == nil { |