Initial commit: Go 1.23 release state
This commit is contained in:
121
src/path/example_test.go
Normal file
121
src/path/example_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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 path_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
func ExampleBase() {
|
||||
fmt.Println(path.Base("/a/b"))
|
||||
fmt.Println(path.Base("/"))
|
||||
fmt.Println(path.Base(""))
|
||||
// Output:
|
||||
// b
|
||||
// /
|
||||
// .
|
||||
}
|
||||
|
||||
func ExampleClean() {
|
||||
paths := []string{
|
||||
"a/c",
|
||||
"a//c",
|
||||
"a/c/.",
|
||||
"a/c/b/..",
|
||||
"/../a/c",
|
||||
"/../a/b/../././/c",
|
||||
"",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
fmt.Printf("Clean(%q) = %q\n", p, path.Clean(p))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Clean("a/c") = "a/c"
|
||||
// Clean("a//c") = "a/c"
|
||||
// Clean("a/c/.") = "a/c"
|
||||
// Clean("a/c/b/..") = "a/c"
|
||||
// Clean("/../a/c") = "/a/c"
|
||||
// Clean("/../a/b/../././/c") = "/a/c"
|
||||
// Clean("") = "."
|
||||
}
|
||||
|
||||
func ExampleDir() {
|
||||
fmt.Println(path.Dir("/a/b/c"))
|
||||
fmt.Println(path.Dir("a/b/c"))
|
||||
fmt.Println(path.Dir("/a/"))
|
||||
fmt.Println(path.Dir("a/"))
|
||||
fmt.Println(path.Dir("/"))
|
||||
fmt.Println(path.Dir(""))
|
||||
// Output:
|
||||
// /a/b
|
||||
// a/b
|
||||
// /a
|
||||
// a
|
||||
// /
|
||||
// .
|
||||
}
|
||||
|
||||
func ExampleExt() {
|
||||
fmt.Println(path.Ext("/a/b/c/bar.css"))
|
||||
fmt.Println(path.Ext("/"))
|
||||
fmt.Println(path.Ext(""))
|
||||
// Output:
|
||||
// .css
|
||||
//
|
||||
//
|
||||
}
|
||||
|
||||
func ExampleIsAbs() {
|
||||
fmt.Println(path.IsAbs("/dev/null"))
|
||||
// Output: true
|
||||
}
|
||||
|
||||
func ExampleJoin() {
|
||||
fmt.Println(path.Join("a", "b", "c"))
|
||||
fmt.Println(path.Join("a", "b/c"))
|
||||
fmt.Println(path.Join("a/b", "c"))
|
||||
|
||||
fmt.Println(path.Join("a/b", "../../../xyz"))
|
||||
|
||||
fmt.Println(path.Join("", ""))
|
||||
fmt.Println(path.Join("a", ""))
|
||||
fmt.Println(path.Join("", "a"))
|
||||
|
||||
// Output:
|
||||
// a/b/c
|
||||
// a/b/c
|
||||
// a/b/c
|
||||
// ../xyz
|
||||
//
|
||||
// a
|
||||
// a
|
||||
}
|
||||
|
||||
func ExampleMatch() {
|
||||
fmt.Println(path.Match("abc", "abc"))
|
||||
fmt.Println(path.Match("a*", "abc"))
|
||||
fmt.Println(path.Match("a*/b", "a/c/b"))
|
||||
// Output:
|
||||
// true <nil>
|
||||
// true <nil>
|
||||
// false <nil>
|
||||
}
|
||||
|
||||
func ExampleSplit() {
|
||||
split := func(s string) {
|
||||
dir, file := path.Split(s)
|
||||
fmt.Printf("path.Split(%q) = dir: %q, file: %q\n", s, dir, file)
|
||||
}
|
||||
split("static/myfile.css")
|
||||
split("myfile.css")
|
||||
split("")
|
||||
// Output:
|
||||
// path.Split("static/myfile.css") = dir: "static/", file: "myfile.css"
|
||||
// path.Split("myfile.css") = dir: "", file: "myfile.css"
|
||||
// path.Split("") = dir: "", file: ""
|
||||
}
|
||||
20
src/path/filepath/example_test.go
Normal file
20
src/path/filepath/example_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017 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 (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ExampleExt() {
|
||||
fmt.Printf("No dots: %q\n", filepath.Ext("index"))
|
||||
fmt.Printf("One dot: %q\n", filepath.Ext("index.js"))
|
||||
fmt.Printf("Two dots: %q\n", filepath.Ext("main.test.js"))
|
||||
// Output:
|
||||
// No dots: ""
|
||||
// One dot: ".js"
|
||||
// Two dots: ".js"
|
||||
}
|
||||
171
src/path/filepath/example_unix_test.go
Normal file
171
src/path/filepath/example_unix_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
//go:build !windows && !plan9
|
||||
|
||||
package filepath_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ExampleSplitList() {
|
||||
fmt.Println("On Unix:", filepath.SplitList("/a/b/c:/usr/bin"))
|
||||
// Output:
|
||||
// On Unix: [/a/b/c /usr/bin]
|
||||
}
|
||||
|
||||
func ExampleRel() {
|
||||
paths := []string{
|
||||
"/a/b/c",
|
||||
"/b/c",
|
||||
"./b/c",
|
||||
}
|
||||
base := "/a"
|
||||
|
||||
fmt.Println("On Unix:")
|
||||
for _, p := range paths {
|
||||
rel, err := filepath.Rel(base, p)
|
||||
fmt.Printf("%q: %q %v\n", p, rel, err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// On Unix:
|
||||
// "/a/b/c": "b/c" <nil>
|
||||
// "/b/c": "../b/c" <nil>
|
||||
// "./b/c": "" Rel: can't make ./b/c relative to /a
|
||||
}
|
||||
|
||||
func ExampleSplit() {
|
||||
paths := []string{
|
||||
"/home/arnie/amelia.jpg",
|
||||
"/mnt/photos/",
|
||||
"rabbit.jpg",
|
||||
"/usr/local//go",
|
||||
}
|
||||
fmt.Println("On Unix:")
|
||||
for _, p := range paths {
|
||||
dir, file := filepath.Split(p)
|
||||
fmt.Printf("input: %q\n\tdir: %q\n\tfile: %q\n", p, dir, file)
|
||||
}
|
||||
// Output:
|
||||
// On Unix:
|
||||
// input: "/home/arnie/amelia.jpg"
|
||||
// dir: "/home/arnie/"
|
||||
// file: "amelia.jpg"
|
||||
// input: "/mnt/photos/"
|
||||
// dir: "/mnt/photos/"
|
||||
// file: ""
|
||||
// input: "rabbit.jpg"
|
||||
// dir: ""
|
||||
// file: "rabbit.jpg"
|
||||
// input: "/usr/local//go"
|
||||
// dir: "/usr/local//"
|
||||
// file: "go"
|
||||
}
|
||||
|
||||
func ExampleJoin() {
|
||||
fmt.Println("On Unix:")
|
||||
fmt.Println(filepath.Join("a", "b", "c"))
|
||||
fmt.Println(filepath.Join("a", "b/c"))
|
||||
fmt.Println(filepath.Join("a/b", "c"))
|
||||
fmt.Println(filepath.Join("a/b", "/c"))
|
||||
|
||||
fmt.Println(filepath.Join("a/b", "../../../xyz"))
|
||||
|
||||
// Output:
|
||||
// On Unix:
|
||||
// a/b/c
|
||||
// a/b/c
|
||||
// a/b/c
|
||||
// a/b/c
|
||||
// ../xyz
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
func ExampleBase() {
|
||||
fmt.Println("On Unix:")
|
||||
fmt.Println(filepath.Base("/foo/bar/baz.js"))
|
||||
fmt.Println(filepath.Base("/foo/bar/baz"))
|
||||
fmt.Println(filepath.Base("/foo/bar/baz/"))
|
||||
fmt.Println(filepath.Base("dev.txt"))
|
||||
fmt.Println(filepath.Base("../todo.txt"))
|
||||
fmt.Println(filepath.Base(".."))
|
||||
fmt.Println(filepath.Base("."))
|
||||
fmt.Println(filepath.Base("/"))
|
||||
fmt.Println(filepath.Base(""))
|
||||
|
||||
// Output:
|
||||
// On Unix:
|
||||
// baz.js
|
||||
// baz
|
||||
// baz
|
||||
// dev.txt
|
||||
// todo.txt
|
||||
// ..
|
||||
// .
|
||||
// /
|
||||
// .
|
||||
}
|
||||
|
||||
func ExampleDir() {
|
||||
fmt.Println("On Unix:")
|
||||
fmt.Println(filepath.Dir("/foo/bar/baz.js"))
|
||||
fmt.Println(filepath.Dir("/foo/bar/baz"))
|
||||
fmt.Println(filepath.Dir("/foo/bar/baz/"))
|
||||
fmt.Println(filepath.Dir("/dirty//path///"))
|
||||
fmt.Println(filepath.Dir("dev.txt"))
|
||||
fmt.Println(filepath.Dir("../todo.txt"))
|
||||
fmt.Println(filepath.Dir(".."))
|
||||
fmt.Println(filepath.Dir("."))
|
||||
fmt.Println(filepath.Dir("/"))
|
||||
fmt.Println(filepath.Dir(""))
|
||||
|
||||
// Output:
|
||||
// On Unix:
|
||||
// /foo/bar
|
||||
// /foo/bar
|
||||
// /foo/bar/baz
|
||||
// /dirty/path
|
||||
// .
|
||||
// ..
|
||||
// .
|
||||
// .
|
||||
// /
|
||||
// .
|
||||
}
|
||||
|
||||
func ExampleIsAbs() {
|
||||
fmt.Println("On Unix:")
|
||||
fmt.Println(filepath.IsAbs("/home/gopher"))
|
||||
fmt.Println(filepath.IsAbs(".bashrc"))
|
||||
fmt.Println(filepath.IsAbs(".."))
|
||||
fmt.Println(filepath.IsAbs("."))
|
||||
fmt.Println(filepath.IsAbs("/"))
|
||||
fmt.Println(filepath.IsAbs(""))
|
||||
|
||||
// Output:
|
||||
// On Unix:
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// false
|
||||
}
|
||||
66
src/path/filepath/example_unix_walk_test.go
Normal file
66
src/path/filepath/example_unix_walk_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
//go:build !windows && !plan9
|
||||
|
||||
package filepath_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func prepareTestDirTree(tree string) (string, error) {
|
||||
tmpDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating temp directory: %v\n", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Join(tmpDir, tree), 0755)
|
||||
if err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
func ExampleWalk() {
|
||||
tmpDir, err := prepareTestDirTree("dir/to/walk/skip")
|
||||
if err != nil {
|
||||
fmt.Printf("unable to create test dir tree: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
os.Chdir(tmpDir)
|
||||
|
||||
subDirToSkip := "skip"
|
||||
|
||||
fmt.Println("On Unix:")
|
||||
err = filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
|
||||
return err
|
||||
}
|
||||
if info.IsDir() && info.Name() == subDirToSkip {
|
||||
fmt.Printf("skipping a dir without errors: %+v \n", info.Name())
|
||||
return filepath.SkipDir
|
||||
}
|
||||
fmt.Printf("visited file or dir: %q\n", path)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("error walking the path %q: %v\n", tmpDir, err)
|
||||
return
|
||||
}
|
||||
// Output:
|
||||
// On Unix:
|
||||
// visited file or dir: "."
|
||||
// visited file or dir: "dir"
|
||||
// visited file or dir: "dir/to"
|
||||
// visited file or dir: "dir/to/walk"
|
||||
// skipping a dir without errors: skip
|
||||
}
|
||||
7
src/path/filepath/export_test.go
Normal file
7
src/path/filepath/export_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2013 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
|
||||
|
||||
var LstatP = &lstat
|
||||
10
src/path/filepath/export_windows_test.go
Normal file
10
src/path/filepath/export_windows_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2016 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
|
||||
|
||||
var (
|
||||
ToNorm = toNorm
|
||||
NormBase = normBase
|
||||
)
|
||||
370
src/path/filepath/match.go
Normal file
370
src/path/filepath/match.go
Normal file
@@ -0,0 +1,370 @@
|
||||
// Copyright 2010 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"
|
||||
"internal/filepathlite"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrBadPattern indicates a pattern was malformed.
|
||||
var ErrBadPattern = errors.New("syntax error in pattern")
|
||||
|
||||
// Match reports whether name matches the shell file name pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-Separator characters
|
||||
// '?' matches any single non-Separator character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The only possible returned error is [ErrBadPattern], when pattern
|
||||
// is malformed.
|
||||
//
|
||||
// On Windows, escaping is disabled. Instead, '\\' is treated as
|
||||
// path separator.
|
||||
func Match(pattern, name string) (matched bool, err error) {
|
||||
Pattern:
|
||||
for len(pattern) > 0 {
|
||||
var star bool
|
||||
var chunk string
|
||||
star, chunk, pattern = scanChunk(pattern)
|
||||
if star && chunk == "" {
|
||||
// Trailing * matches rest of string unless it has a /.
|
||||
return !strings.Contains(name, string(Separator)), nil
|
||||
}
|
||||
// Look for match at current position.
|
||||
t, ok, err := matchChunk(chunk, name)
|
||||
// if we're the last chunk, make sure we've exhausted the name
|
||||
// otherwise we'll give a false result even if we could still match
|
||||
// using the star
|
||||
if ok && (len(t) == 0 || len(pattern) > 0) {
|
||||
name = t
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if star {
|
||||
// Look for match skipping i+1 bytes.
|
||||
// Cannot skip /.
|
||||
for i := 0; i < len(name) && name[i] != Separator; i++ {
|
||||
t, ok, err := matchChunk(chunk, name[i+1:])
|
||||
if ok {
|
||||
// if we're the last chunk, make sure we exhausted the name
|
||||
if len(pattern) == 0 && len(t) > 0 {
|
||||
continue
|
||||
}
|
||||
name = t
|
||||
continue Pattern
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return len(name) == 0, nil
|
||||
}
|
||||
|
||||
// scanChunk gets the next segment of pattern, which is a non-star string
|
||||
// possibly preceded by a star.
|
||||
func scanChunk(pattern string) (star bool, chunk, rest string) {
|
||||
for len(pattern) > 0 && pattern[0] == '*' {
|
||||
pattern = pattern[1:]
|
||||
star = true
|
||||
}
|
||||
inrange := false
|
||||
var i int
|
||||
Scan:
|
||||
for i = 0; i < len(pattern); i++ {
|
||||
switch pattern[i] {
|
||||
case '\\':
|
||||
if runtime.GOOS != "windows" {
|
||||
// error check handled in matchChunk: bad pattern.
|
||||
if i+1 < len(pattern) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
case '[':
|
||||
inrange = true
|
||||
case ']':
|
||||
inrange = false
|
||||
case '*':
|
||||
if !inrange {
|
||||
break Scan
|
||||
}
|
||||
}
|
||||
}
|
||||
return star, pattern[0:i], pattern[i:]
|
||||
}
|
||||
|
||||
// matchChunk checks whether chunk matches the beginning of s.
|
||||
// If so, it returns the remainder of s (after the match).
|
||||
// Chunk is all single-character operators: literals, char classes, and ?.
|
||||
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
|
||||
// failed records whether the match has failed.
|
||||
// After the match fails, the loop continues on processing chunk,
|
||||
// checking that the pattern is well-formed but no longer reading s.
|
||||
failed := false
|
||||
for len(chunk) > 0 {
|
||||
if !failed && len(s) == 0 {
|
||||
failed = true
|
||||
}
|
||||
switch chunk[0] {
|
||||
case '[':
|
||||
// character class
|
||||
var r rune
|
||||
if !failed {
|
||||
var n int
|
||||
r, n = utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
// possibly negated
|
||||
negated := false
|
||||
if len(chunk) > 0 && chunk[0] == '^' {
|
||||
negated = true
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
// parse all ranges
|
||||
match := false
|
||||
nrange := 0
|
||||
for {
|
||||
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
|
||||
chunk = chunk[1:]
|
||||
break
|
||||
}
|
||||
var lo, hi rune
|
||||
if lo, chunk, err = getEsc(chunk); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
hi = lo
|
||||
if chunk[0] == '-' {
|
||||
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
}
|
||||
if lo <= r && r <= hi {
|
||||
match = true
|
||||
}
|
||||
nrange++
|
||||
}
|
||||
if match == negated {
|
||||
failed = true
|
||||
}
|
||||
|
||||
case '?':
|
||||
if !failed {
|
||||
if s[0] == Separator {
|
||||
failed = true
|
||||
}
|
||||
_, n := utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
|
||||
case '\\':
|
||||
if runtime.GOOS != "windows" {
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
return "", false, ErrBadPattern
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if !failed {
|
||||
if chunk[0] != s[0] {
|
||||
failed = true
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return "", false, nil
|
||||
}
|
||||
return s, true, nil
|
||||
}
|
||||
|
||||
// getEsc gets a possibly-escaped character from chunk, for a character class.
|
||||
func getEsc(chunk string) (r rune, nchunk string, err error) {
|
||||
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
if chunk[0] == '\\' && runtime.GOOS != "windows" {
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(chunk)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
nchunk = chunk[n:]
|
||||
if len(nchunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Glob returns the names of all files matching pattern or nil
|
||||
// if there is no matching file. The syntax of patterns is the same
|
||||
// as in [Match]. The pattern may describe hierarchical names such as
|
||||
// /usr/*/bin/ed (assuming the [Separator] is '/').
|
||||
//
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is [ErrBadPattern], when pattern
|
||||
// is malformed.
|
||||
func Glob(pattern string) (matches []string, err error) {
|
||||
return globWithLimit(pattern, 0)
|
||||
}
|
||||
|
||||
func globWithLimit(pattern string, depth int) (matches []string, err error) {
|
||||
// This limit is used prevent stack exhaustion issues. See CVE-2022-30632.
|
||||
const pathSeparatorsLimit = 10000
|
||||
if depth == pathSeparatorsLimit {
|
||||
return nil, ErrBadPattern
|
||||
}
|
||||
|
||||
// Check pattern is well-formed.
|
||||
if _, err := Match(pattern, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasMeta(pattern) {
|
||||
if _, err = os.Lstat(pattern); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{pattern}, nil
|
||||
}
|
||||
|
||||
dir, file := Split(pattern)
|
||||
volumeLen := 0
|
||||
if runtime.GOOS == "windows" {
|
||||
volumeLen, dir = cleanGlobPathWindows(dir)
|
||||
} else {
|
||||
dir = cleanGlobPath(dir)
|
||||
}
|
||||
|
||||
if !hasMeta(dir[volumeLen:]) {
|
||||
return glob(dir, file, nil)
|
||||
}
|
||||
|
||||
// Prevent infinite recursion. See issue 15879.
|
||||
if dir == pattern {
|
||||
return nil, ErrBadPattern
|
||||
}
|
||||
|
||||
var m []string
|
||||
m, err = globWithLimit(dir, depth+1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, d := range m {
|
||||
matches, err = glob(d, file, matches)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cleanGlobPath prepares path for glob matching.
|
||||
func cleanGlobPath(path string) string {
|
||||
switch path {
|
||||
case "":
|
||||
return "."
|
||||
case string(Separator):
|
||||
// do nothing to the path
|
||||
return path
|
||||
default:
|
||||
return path[0 : len(path)-1] // chop off trailing separator
|
||||
}
|
||||
}
|
||||
|
||||
// cleanGlobPathWindows is windows version of cleanGlobPath.
|
||||
func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
|
||||
vollen := filepathlite.VolumeNameLen(path)
|
||||
switch {
|
||||
case path == "":
|
||||
return 0, "."
|
||||
case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
|
||||
// do nothing to the path
|
||||
return vollen + 1, path
|
||||
case vollen == len(path) && len(path) == 2: // C:
|
||||
return vollen, path + "." // convert C: into C:.
|
||||
default:
|
||||
if vollen >= len(path) {
|
||||
vollen = len(path) - 1
|
||||
}
|
||||
return vollen, path[0 : len(path)-1] // chop off trailing separator
|
||||
}
|
||||
}
|
||||
|
||||
// glob searches for files matching pattern in the directory dir
|
||||
// and appends them to matches. If the directory cannot be
|
||||
// opened, it returns the existing matches. New matches are
|
||||
// added in lexicographical order.
|
||||
func glob(dir, pattern string, matches []string) (m []string, e error) {
|
||||
m = matches
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return // ignore I/O error
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return // ignore I/O error
|
||||
}
|
||||
d, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return // ignore I/O error
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
names, _ := d.Readdirnames(-1)
|
||||
slices.Sort(names)
|
||||
|
||||
for _, n := range names {
|
||||
matched, err := Match(pattern, n)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
if matched {
|
||||
m = append(m, Join(dir, n))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// hasMeta reports whether path contains any of the magic characters
|
||||
// recognized by Match.
|
||||
func hasMeta(path string) bool {
|
||||
magicChars := `*?[`
|
||||
if runtime.GOOS != "windows" {
|
||||
magicChars = `*?[\`
|
||||
}
|
||||
return strings.ContainsAny(path, magicChars)
|
||||
}
|
||||
373
src/path/filepath/match_test.go
Normal file
373
src/path/filepath/match_test.go
Normal file
@@ -0,0 +1,373 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"os"
|
||||
. "path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MatchTest struct {
|
||||
pattern, s string
|
||||
match bool
|
||||
err error
|
||||
}
|
||||
|
||||
var matchTests = []MatchTest{
|
||||
{"abc", "abc", true, nil},
|
||||
{"*", "abc", true, nil},
|
||||
{"*c", "abc", true, nil},
|
||||
{"a*", "a", true, nil},
|
||||
{"a*", "abc", true, nil},
|
||||
{"a*", "ab/c", false, nil},
|
||||
{"a*/b", "abc/b", true, nil},
|
||||
{"a*/b", "a/c/b", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
||||
{"ab[c]", "abc", true, nil},
|
||||
{"ab[b-d]", "abc", true, nil},
|
||||
{"ab[e-g]", "abc", false, nil},
|
||||
{"ab[^c]", "abc", false, nil},
|
||||
{"ab[^b-d]", "abc", false, nil},
|
||||
{"ab[^e-g]", "abc", true, nil},
|
||||
{"a\\*b", "a*b", true, nil},
|
||||
{"a\\*b", "ab", false, nil},
|
||||
{"a?b", "a☺b", true, nil},
|
||||
{"a[^a]b", "a☺b", true, nil},
|
||||
{"a???b", "a☺b", false, nil},
|
||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
||||
{"[a-ζ]*", "α", true, nil},
|
||||
{"*[a-ζ]", "A", false, nil},
|
||||
{"a?b", "a/b", false, nil},
|
||||
{"a*b", "a/b", false, nil},
|
||||
{"[\\]a]", "]", true, nil},
|
||||
{"[\\-]", "-", true, nil},
|
||||
{"[x\\-]", "x", true, nil},
|
||||
{"[x\\-]", "-", true, nil},
|
||||
{"[x\\-]", "z", false, nil},
|
||||
{"[\\-x]", "x", true, nil},
|
||||
{"[\\-x]", "-", true, nil},
|
||||
{"[\\-x]", "a", false, nil},
|
||||
{"[]a]", "]", false, ErrBadPattern},
|
||||
{"[-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "x", false, ErrBadPattern},
|
||||
{"[x-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "z", false, ErrBadPattern},
|
||||
{"[-x]", "x", false, ErrBadPattern},
|
||||
{"[-x]", "-", false, ErrBadPattern},
|
||||
{"[-x]", "a", false, ErrBadPattern},
|
||||
{"\\", "a", false, ErrBadPattern},
|
||||
{"[a-b-c]", "a", false, ErrBadPattern},
|
||||
{"[", "a", false, ErrBadPattern},
|
||||
{"[^", "a", false, ErrBadPattern},
|
||||
{"[^bc", "a", false, ErrBadPattern},
|
||||
{"a[", "a", false, ErrBadPattern},
|
||||
{"a[", "ab", false, ErrBadPattern},
|
||||
{"a[", "x", false, ErrBadPattern},
|
||||
{"a/b[", "x", false, ErrBadPattern},
|
||||
{"*x", "xxx", true, nil},
|
||||
}
|
||||
|
||||
func errp(e error) string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, tt := range matchTests {
|
||||
pattern := tt.pattern
|
||||
s := tt.s
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.Contains(pattern, "\\") {
|
||||
// no escape allowed on windows.
|
||||
continue
|
||||
}
|
||||
pattern = Clean(pattern)
|
||||
s = Clean(s)
|
||||
}
|
||||
ok, err := Match(pattern, s)
|
||||
if ok != tt.match || err != tt.err {
|
||||
t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var globTests = []struct {
|
||||
pattern, result string
|
||||
}{
|
||||
{"match.go", "match.go"},
|
||||
{"mat?h.go", "match.go"},
|
||||
{"*", "match.go"},
|
||||
{"../*/match.go", "../filepath/match.go"},
|
||||
}
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
for _, tt := range globTests {
|
||||
pattern := tt.pattern
|
||||
result := tt.result
|
||||
if runtime.GOOS == "windows" {
|
||||
pattern = Clean(pattern)
|
||||
result = Clean(result)
|
||||
}
|
||||
matches, err := Glob(pattern)
|
||||
if err != nil {
|
||||
t.Errorf("Glob error for %q: %s", pattern, err)
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(matches, result) {
|
||||
t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
|
||||
}
|
||||
}
|
||||
for _, pattern := range []string{"no_match", "../*/no_match"} {
|
||||
matches, err := Glob(pattern)
|
||||
if err != nil {
|
||||
t.Errorf("Glob error for %q: %s", pattern, err)
|
||||
continue
|
||||
}
|
||||
if len(matches) != 0 {
|
||||
t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCVE202230632(t *testing.T) {
|
||||
// Prior to CVE-2022-30632, this would cause a stack exhaustion given a
|
||||
// large number of separators (more than 4,000,000). There is now a limit
|
||||
// of 10,000.
|
||||
_, err := Glob("/*" + strings.Repeat("/", 10001))
|
||||
if err != ErrBadPattern {
|
||||
t.Fatalf("Glob returned err=%v, want ErrBadPattern", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobError(t *testing.T) {
|
||||
bad := []string{`[]`, `nonexist/[]`}
|
||||
for _, pattern := range bad {
|
||||
if _, err := Glob(pattern); err != ErrBadPattern {
|
||||
t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobUNC(t *testing.T) {
|
||||
// Just make sure this runs without crashing for now.
|
||||
// See issue 15879.
|
||||
Glob(`\\?\C:\*`)
|
||||
}
|
||||
|
||||
var globSymlinkTests = []struct {
|
||||
path, dest string
|
||||
brokenLink bool
|
||||
}{
|
||||
{"test1", "link1", false},
|
||||
{"test2", "link2", true},
|
||||
}
|
||||
|
||||
func TestGlobSymlink(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
for _, tt := range globSymlinkTests {
|
||||
path := Join(tmpDir, tt.path)
|
||||
dest := Join(tmpDir, tt.dest)
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.Symlink(path, dest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tt.brokenLink {
|
||||
// Break the symlink.
|
||||
os.Remove(path)
|
||||
}
|
||||
matches, err := Glob(dest)
|
||||
if err != nil {
|
||||
t.Errorf("GlobSymlink error for %q: %s", dest, err)
|
||||
}
|
||||
if !slices.Contains(matches, dest) {
|
||||
t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type globTest struct {
|
||||
pattern string
|
||||
matches []string
|
||||
}
|
||||
|
||||
func (test *globTest) buildWant(root string) []string {
|
||||
want := make([]string, 0)
|
||||
for _, m := range test.matches {
|
||||
want = append(want, root+FromSlash(m))
|
||||
}
|
||||
slices.Sort(want)
|
||||
return want
|
||||
}
|
||||
|
||||
func (test *globTest) globAbs(root, rootPattern string) error {
|
||||
p := FromSlash(rootPattern + `\` + test.pattern)
|
||||
have, err := Glob(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slices.Sort(have)
|
||||
want := test.buildWant(root + `\`)
|
||||
if strings.Join(want, "_") == strings.Join(have, "_") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
|
||||
}
|
||||
|
||||
func (test *globTest) globRel(root string) error {
|
||||
p := root + FromSlash(test.pattern)
|
||||
have, err := Glob(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slices.Sort(have)
|
||||
want := test.buildWant(root)
|
||||
if strings.Join(want, "_") == strings.Join(have, "_") {
|
||||
return nil
|
||||
}
|
||||
// try also matching version without root prefix
|
||||
wantWithNoRoot := test.buildWant("")
|
||||
if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
|
||||
}
|
||||
|
||||
func TestWindowsGlob(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skipf("skipping windows specific test")
|
||||
}
|
||||
|
||||
tmpDir := tempDirCanonical(t)
|
||||
if len(tmpDir) < 3 {
|
||||
t.Fatalf("tmpDir path %q is too short", tmpDir)
|
||||
}
|
||||
if tmpDir[1] != ':' {
|
||||
t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
|
||||
}
|
||||
|
||||
dirs := []string{
|
||||
"a",
|
||||
"b",
|
||||
"dir/d/bin",
|
||||
}
|
||||
files := []string{
|
||||
"dir/d/bin/git.exe",
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
err := os.MkdirAll(Join(tmpDir, dir), 0777)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, file := range files {
|
||||
err := os.WriteFile(Join(tmpDir, file), nil, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []globTest{
|
||||
{"a", []string{"a"}},
|
||||
{"b", []string{"b"}},
|
||||
{"c", []string{}},
|
||||
{"*", []string{"a", "b", "dir"}},
|
||||
{"d*", []string{"dir"}},
|
||||
{"*i*", []string{"dir"}},
|
||||
{"*r", []string{"dir"}},
|
||||
{"?ir", []string{"dir"}},
|
||||
{"?r", []string{}},
|
||||
{"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}},
|
||||
}
|
||||
|
||||
// test absolute paths
|
||||
for _, test := range tests {
|
||||
var p string
|
||||
if err := test.globAbs(tmpDir, tmpDir); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// test C:\*Documents and Settings\...
|
||||
p = tmpDir
|
||||
p = strings.Replace(p, `:\`, `:\*`, 1)
|
||||
if err := test.globAbs(tmpDir, p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// test C:\Documents and Settings*\...
|
||||
p = tmpDir
|
||||
p = strings.Replace(p, `:\`, `:`, 1)
|
||||
p = strings.Replace(p, `\`, `*\`, 1)
|
||||
p = strings.Replace(p, `:`, `:\`, 1)
|
||||
if err := test.globAbs(tmpDir, p); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// test relative paths
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = os.Chdir(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := os.Chdir(wd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
for _, test := range tests {
|
||||
err := test.globRel("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = test.globRel(`.\`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = test.globRel(tmpDir[:2]) // C:
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonWindowsGlobEscape(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skipf("skipping non-windows specific test")
|
||||
}
|
||||
pattern := `\match.go`
|
||||
want := []string{"match.go"}
|
||||
matches, err := Glob(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Glob error for %q: %s", pattern, err)
|
||||
}
|
||||
if !reflect.DeepEqual(matches, want) {
|
||||
t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want)
|
||||
}
|
||||
}
|
||||
475
src/path/filepath/path.go
Normal file
475
src/path/filepath/path.go
Normal file
@@ -0,0 +1,475 @@
|
||||
// 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 implements utility routines for manipulating filename paths
|
||||
// in a way compatible with the target operating system-defined file paths.
|
||||
//
|
||||
// The filepath package uses either forward slashes or backslashes,
|
||||
// depending on the operating system. To process paths such as URLs
|
||||
// that always use forward slashes regardless of the operating
|
||||
// system, see the [path] package.
|
||||
package filepath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/bytealg"
|
||||
"internal/filepathlite"
|
||||
"io/fs"
|
||||
"os"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const (
|
||||
Separator = os.PathSeparator
|
||||
ListSeparator = os.PathListSeparator
|
||||
)
|
||||
|
||||
// Clean returns the shortest path name equivalent to path
|
||||
// by purely lexical processing. It applies the following rules
|
||||
// iteratively until no further processing can be done:
|
||||
//
|
||||
// 1. Replace multiple [Separator] elements with a single one.
|
||||
// 2. Eliminate each . path name element (the current directory).
|
||||
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||
// along with the non-.. element that precedes it.
|
||||
// 4. Eliminate .. elements that begin a rooted path:
|
||||
// that is, replace "/.." by "/" at the beginning of a path,
|
||||
// assuming Separator is '/'.
|
||||
//
|
||||
// The returned path ends in a slash only if it represents a root directory,
|
||||
// such as "/" on Unix or `C:\` on Windows.
|
||||
//
|
||||
// Finally, any occurrences of slash are replaced by Separator.
|
||||
//
|
||||
// If the result of this process is an empty string, Clean
|
||||
// returns the string ".".
|
||||
//
|
||||
// On Windows, Clean does not modify the volume name other than to replace
|
||||
// occurrences of "/" with `\`.
|
||||
// For example, Clean("//host/share/../x") returns `\\host\share\x`.
|
||||
//
|
||||
// See also Rob Pike, “Lexical File Names in Plan 9 or
|
||||
// Getting Dot-Dot Right,”
|
||||
// https://9p.io/sys/doc/lexnames.html
|
||||
func Clean(path string) string {
|
||||
return filepathlite.Clean(path)
|
||||
}
|
||||
|
||||
// IsLocal reports whether path, using lexical analysis only, has all of these properties:
|
||||
//
|
||||
// - is within the subtree rooted at the directory in which path is evaluated
|
||||
// - is not an absolute path
|
||||
// - is not empty
|
||||
// - on Windows, is not a reserved name such as "NUL"
|
||||
//
|
||||
// If IsLocal(path) returns true, then
|
||||
// Join(base, path) will always produce a path contained within base and
|
||||
// Clean(path) will always produce an unrooted path with no ".." path elements.
|
||||
//
|
||||
// IsLocal is a purely lexical operation.
|
||||
// In particular, it does not account for the effect of any symbolic links
|
||||
// that may exist in the filesystem.
|
||||
func IsLocal(path string) bool {
|
||||
return filepathlite.IsLocal(path)
|
||||
}
|
||||
|
||||
// Localize converts a slash-separated path into an operating system path.
|
||||
// The input path must be a valid path as reported by [io/fs.ValidPath].
|
||||
//
|
||||
// Localize returns an error if the path cannot be represented by the operating system.
|
||||
// For example, the path a\b is rejected on Windows, on which \ is a separator
|
||||
// character and cannot be part of a filename.
|
||||
//
|
||||
// The path returned by Localize will always be local, as reported by IsLocal.
|
||||
func Localize(path string) (string, error) {
|
||||
return filepathlite.Localize(path)
|
||||
}
|
||||
|
||||
// ToSlash returns the result of replacing each separator character
|
||||
// in path with a slash ('/') character. Multiple separators are
|
||||
// replaced by multiple slashes.
|
||||
func ToSlash(path string) string {
|
||||
return filepathlite.ToSlash(path)
|
||||
}
|
||||
|
||||
// FromSlash returns the result of replacing each slash ('/') character
|
||||
// in path with a separator character. Multiple slashes are replaced
|
||||
// by multiple separators.
|
||||
//
|
||||
// See also the Localize function, which converts a slash-separated path
|
||||
// as used by the io/fs package to an operating system path.
|
||||
func FromSlash(path string) string {
|
||||
return filepathlite.FromSlash(path)
|
||||
}
|
||||
|
||||
// SplitList splits a list of paths joined by the OS-specific [ListSeparator],
|
||||
// usually found in PATH or GOPATH environment variables.
|
||||
// Unlike strings.Split, SplitList returns an empty slice when passed an empty
|
||||
// string.
|
||||
func SplitList(path string) []string {
|
||||
return splitList(path)
|
||||
}
|
||||
|
||||
// Split splits path immediately following the final [Separator],
|
||||
// separating it into a directory and file name component.
|
||||
// If there is no Separator in path, Split returns an empty dir
|
||||
// and file set to path.
|
||||
// The returned values have the property that path = dir+file.
|
||||
func Split(path string) (dir, file string) {
|
||||
return filepathlite.Split(path)
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path,
|
||||
// separating them with an OS specific [Separator]. Empty elements
|
||||
// are ignored. The result is Cleaned. However, if the argument
|
||||
// list is empty or all its elements are empty, Join returns
|
||||
// an empty string.
|
||||
// On Windows, the result will only be a UNC path if the first
|
||||
// non-empty element is a UNC path.
|
||||
func Join(elem ...string) string {
|
||||
return join(elem)
|
||||
}
|
||||
|
||||
// Ext returns the file name extension used by path.
|
||||
// The extension is the suffix beginning at the final dot
|
||||
// in the final element of path; it is empty if there is
|
||||
// no dot.
|
||||
func Ext(path string) string {
|
||||
return filepathlite.Ext(path)
|
||||
}
|
||||
|
||||
// EvalSymlinks returns the path name after the evaluation of any symbolic
|
||||
// links.
|
||||
// If path is relative the result will be relative to the current directory,
|
||||
// unless one of the components is an absolute symbolic link.
|
||||
// EvalSymlinks calls [Clean] on the result.
|
||||
func EvalSymlinks(path string) (string, error) {
|
||||
return evalSymlinks(path)
|
||||
}
|
||||
|
||||
// IsAbs reports whether the path is absolute.
|
||||
func IsAbs(path string) bool {
|
||||
return filepathlite.IsAbs(path)
|
||||
}
|
||||
|
||||
// Abs returns an absolute representation of path.
|
||||
// If the path is not absolute it will be joined with the current
|
||||
// working directory to turn it into an absolute path. The absolute
|
||||
// path name for a given file is not guaranteed to be unique.
|
||||
// Abs calls [Clean] on the result.
|
||||
func Abs(path string) (string, error) {
|
||||
return abs(path)
|
||||
}
|
||||
|
||||
func unixAbs(path string) (string, error) {
|
||||
if IsAbs(path) {
|
||||
return Clean(path), nil
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Join(wd, path), nil
|
||||
}
|
||||
|
||||
// Rel returns a relative path that is lexically equivalent to targpath when
|
||||
// joined to basepath with an intervening separator. That is,
|
||||
// [Join](basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
|
||||
// On success, the returned path will always be relative to basepath,
|
||||
// even if basepath and targpath share no elements.
|
||||
// An error is returned if targpath can't be made relative to basepath or if
|
||||
// knowing the current working directory would be necessary to compute it.
|
||||
// Rel calls [Clean] on the result.
|
||||
func Rel(basepath, targpath string) (string, error) {
|
||||
baseVol := VolumeName(basepath)
|
||||
targVol := VolumeName(targpath)
|
||||
base := Clean(basepath)
|
||||
targ := Clean(targpath)
|
||||
if sameWord(targ, base) {
|
||||
return ".", nil
|
||||
}
|
||||
base = base[len(baseVol):]
|
||||
targ = targ[len(targVol):]
|
||||
if base == "." {
|
||||
base = ""
|
||||
} else if base == "" && filepathlite.VolumeNameLen(baseVol) > 2 /* isUNC */ {
|
||||
// Treat any targetpath matching `\\host\share` basepath as absolute path.
|
||||
base = string(Separator)
|
||||
}
|
||||
|
||||
// Can't use IsAbs - `\a` and `a` are both relative in Windows.
|
||||
baseSlashed := len(base) > 0 && base[0] == Separator
|
||||
targSlashed := len(targ) > 0 && targ[0] == Separator
|
||||
if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
|
||||
return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
|
||||
}
|
||||
// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
|
||||
bl := len(base)
|
||||
tl := len(targ)
|
||||
var b0, bi, t0, ti int
|
||||
for {
|
||||
for bi < bl && base[bi] != Separator {
|
||||
bi++
|
||||
}
|
||||
for ti < tl && targ[ti] != Separator {
|
||||
ti++
|
||||
}
|
||||
if !sameWord(targ[t0:ti], base[b0:bi]) {
|
||||
break
|
||||
}
|
||||
if bi < bl {
|
||||
bi++
|
||||
}
|
||||
if ti < tl {
|
||||
ti++
|
||||
}
|
||||
b0 = bi
|
||||
t0 = ti
|
||||
}
|
||||
if base[b0:bi] == ".." {
|
||||
return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
|
||||
}
|
||||
if b0 != bl {
|
||||
// Base elements left. Must go up before going down.
|
||||
seps := bytealg.CountString(base[b0:bl], Separator)
|
||||
size := 2 + seps*3
|
||||
if tl != t0 {
|
||||
size += 1 + tl - t0
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
n := copy(buf, "..")
|
||||
for i := 0; i < seps; i++ {
|
||||
buf[n] = Separator
|
||||
copy(buf[n+1:], "..")
|
||||
n += 3
|
||||
}
|
||||
if t0 != tl {
|
||||
buf[n] = Separator
|
||||
copy(buf[n+1:], targ[t0:])
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
return targ[t0:], nil
|
||||
}
|
||||
|
||||
// SkipDir is used as a return value from [WalkFunc] to indicate that
|
||||
// the directory named in the call is to be skipped. It is not returned
|
||||
// as an error by any function.
|
||||
var SkipDir error = fs.SkipDir
|
||||
|
||||
// SkipAll is used as a return value from [WalkFunc] to indicate that
|
||||
// all remaining files and directories are to be skipped. It is not returned
|
||||
// as an error by any function.
|
||||
var SkipAll error = fs.SkipAll
|
||||
|
||||
// WalkFunc is the type of the function called by [Walk] to visit each
|
||||
// file or directory.
|
||||
//
|
||||
// The path argument contains the argument to Walk as a prefix.
|
||||
// That is, if Walk is called with root argument "dir" and finds a file
|
||||
// named "a" in that directory, the walk function will be called with
|
||||
// argument "dir/a".
|
||||
//
|
||||
// The directory and file are joined with Join, which may clean the
|
||||
// directory name: if Walk is called with the root argument "x/../dir"
|
||||
// and finds a file named "a" in that directory, the walk function will
|
||||
// be called with argument "dir/a", not "x/../dir/a".
|
||||
//
|
||||
// The info argument is the fs.FileInfo for the named path.
|
||||
//
|
||||
// The error result returned by the function controls how Walk continues.
|
||||
// If the function returns the special value [SkipDir], Walk skips the
|
||||
// current directory (path if info.IsDir() is true, otherwise path's
|
||||
// parent directory). If the function returns the special value [SkipAll],
|
||||
// Walk skips all remaining files and directories. Otherwise, if the function
|
||||
// returns a non-nil error, Walk stops entirely and returns that error.
|
||||
//
|
||||
// The err argument reports an error related to path, signaling that Walk
|
||||
// will not walk into that directory. The function can decide how to
|
||||
// handle that error; as described earlier, returning the error will
|
||||
// cause Walk to stop walking the entire tree.
|
||||
//
|
||||
// Walk calls the function with a non-nil err argument in two cases.
|
||||
//
|
||||
// First, if an [os.Lstat] on the root directory or any directory or file
|
||||
// in the tree fails, Walk calls the function with path set to that
|
||||
// directory or file's path, info set to nil, and err set to the error
|
||||
// from os.Lstat.
|
||||
//
|
||||
// Second, if a directory's Readdirnames method fails, Walk calls the
|
||||
// function with path set to the directory's path, info, set to an
|
||||
// [fs.FileInfo] describing the directory, and err set to the error from
|
||||
// Readdirnames.
|
||||
type WalkFunc func(path string, info fs.FileInfo, err error) error
|
||||
|
||||
var lstat = os.Lstat // for testing
|
||||
|
||||
// walkDir recursively descends path, calling walkDirFn.
|
||||
func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
|
||||
if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
|
||||
if err == SkipDir && d.IsDir() {
|
||||
// Successfully skipped directory.
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
// Second call, to report ReadDir error.
|
||||
err = walkDirFn(path, d, err)
|
||||
if err != nil {
|
||||
if err == SkipDir && d.IsDir() {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, d1 := range dirs {
|
||||
path1 := Join(path, d1.Name())
|
||||
if err := walkDir(path1, d1, walkDirFn); err != nil {
|
||||
if err == SkipDir {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
|
||||
if !info.IsDir() {
|
||||
return walkFn(path, info, nil)
|
||||
}
|
||||
|
||||
names, err := readDirNames(path)
|
||||
err1 := walkFn(path, info, err)
|
||||
// If err != nil, walk can't walk into this directory.
|
||||
// err1 != nil means walkFn want walk to skip this directory or stop walking.
|
||||
// Therefore, if one of err and err1 isn't nil, walk will return.
|
||||
if err != nil || err1 != nil {
|
||||
// The caller's behavior is controlled by the return value, which is decided
|
||||
// by walkFn. walkFn may ignore err and return nil.
|
||||
// If walkFn returns SkipDir or SkipAll, it will be handled by the caller.
|
||||
// So walk should return whatever walkFn returns.
|
||||
return err1
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := Join(path, name)
|
||||
fileInfo, err := lstat(filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walk(filename, fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
||||
// directory in the tree, including root.
|
||||
//
|
||||
// All errors that arise visiting files and directories are filtered by fn:
|
||||
// see the [fs.WalkDirFunc] documentation for details.
|
||||
//
|
||||
// The files are walked in lexical order, which makes the output deterministic
|
||||
// but requires WalkDir to read an entire directory into memory before proceeding
|
||||
// to walk that directory.
|
||||
//
|
||||
// WalkDir does not follow symbolic links.
|
||||
//
|
||||
// WalkDir calls fn with paths that use the separator character appropriate
|
||||
// for the operating system. This is unlike [io/fs.WalkDir], which always
|
||||
// uses slash separated paths.
|
||||
func WalkDir(root string, fn fs.WalkDirFunc) error {
|
||||
info, err := os.Lstat(root)
|
||||
if err != nil {
|
||||
err = fn(root, nil, err)
|
||||
} else {
|
||||
err = walkDir(root, fs.FileInfoToDirEntry(info), fn)
|
||||
}
|
||||
if err == SkipDir || err == SkipAll {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Walk walks the file tree rooted at root, calling fn for each file or
|
||||
// directory in the tree, including root.
|
||||
//
|
||||
// All errors that arise visiting files and directories are filtered by fn:
|
||||
// see the [WalkFunc] documentation for details.
|
||||
//
|
||||
// The files are walked in lexical order, which makes the output deterministic
|
||||
// but requires Walk to read an entire directory into memory before proceeding
|
||||
// to walk that directory.
|
||||
//
|
||||
// Walk does not follow symbolic links.
|
||||
//
|
||||
// Walk is less efficient than [WalkDir], introduced in Go 1.16,
|
||||
// which avoids calling os.Lstat on every visited file or directory.
|
||||
func Walk(root string, fn WalkFunc) error {
|
||||
info, err := os.Lstat(root)
|
||||
if err != nil {
|
||||
err = fn(root, nil, err)
|
||||
} else {
|
||||
err = walk(root, info, fn)
|
||||
}
|
||||
if err == SkipDir || err == SkipAll {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// readDirNames reads the directory named by dirname and returns
|
||||
// a sorted list of directory entry names.
|
||||
func readDirNames(dirname string) ([]string, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names, err := f.Readdirnames(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slices.Sort(names)
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// Base returns the last element of path.
|
||||
// Trailing path separators are removed before extracting the last element.
|
||||
// If the path is empty, Base returns ".".
|
||||
// If the path consists entirely of separators, Base returns a single separator.
|
||||
func Base(path string) string {
|
||||
return filepathlite.Base(path)
|
||||
}
|
||||
|
||||
// Dir returns all but the last element of path, typically the path's directory.
|
||||
// After dropping the final element, Dir calls [Clean] on the path and trailing
|
||||
// slashes are removed.
|
||||
// If the path is empty, Dir returns ".".
|
||||
// If the path consists entirely of separators, Dir returns a single separator.
|
||||
// The returned path does not end in a separator unless it is the root directory.
|
||||
func Dir(path string) string {
|
||||
return filepathlite.Dir(path)
|
||||
}
|
||||
|
||||
// VolumeName returns leading volume name.
|
||||
// Given "C:\foo\bar" it returns "C:" on Windows.
|
||||
// Given "\\host\share\foo" it returns "\\host\share".
|
||||
// On other platforms it returns "".
|
||||
func VolumeName(path string) string {
|
||||
return filepathlite.VolumeName(path)
|
||||
}
|
||||
42
src/path/filepath/path_plan9.go
Normal file
42
src/path/filepath/path_plan9.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2010 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 (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HasPrefix exists for historical compatibility and should not be used.
|
||||
//
|
||||
// Deprecated: HasPrefix does not respect path boundaries and
|
||||
// does not ignore case when required.
|
||||
func HasPrefix(p, prefix string) bool {
|
||||
return strings.HasPrefix(p, prefix)
|
||||
}
|
||||
|
||||
func splitList(path string) []string {
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, string(ListSeparator))
|
||||
}
|
||||
|
||||
func abs(path string) (string, error) {
|
||||
return unixAbs(path)
|
||||
}
|
||||
|
||||
func join(elem []string) string {
|
||||
// If there's a bug here, fix the logic in ./path_unix.go too.
|
||||
for i, e := range elem {
|
||||
if e != "" {
|
||||
return Clean(strings.Join(elem[i:], string(Separator)))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sameWord(a, b string) bool {
|
||||
return a == b
|
||||
}
|
||||
1994
src/path/filepath/path_test.go
Normal file
1994
src/path/filepath/path_test.go
Normal file
File diff suppressed because it is too large
Load Diff
44
src/path/filepath/path_unix.go
Normal file
44
src/path/filepath/path_unix.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
//go:build unix || (js && wasm) || wasip1
|
||||
|
||||
package filepath
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HasPrefix exists for historical compatibility and should not be used.
|
||||
//
|
||||
// Deprecated: HasPrefix does not respect path boundaries and
|
||||
// does not ignore case when required.
|
||||
func HasPrefix(p, prefix string) bool {
|
||||
return strings.HasPrefix(p, prefix)
|
||||
}
|
||||
|
||||
func splitList(path string) []string {
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, string(ListSeparator))
|
||||
}
|
||||
|
||||
func abs(path string) (string, error) {
|
||||
return unixAbs(path)
|
||||
}
|
||||
|
||||
func join(elem []string) string {
|
||||
// If there's a bug here, fix the logic in ./path_plan9.go too.
|
||||
for i, e := range elem {
|
||||
if e != "" {
|
||||
return Clean(strings.Join(elem[i:], string(Separator)))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sameWord(a, b string) bool {
|
||||
return a == b
|
||||
}
|
||||
114
src/path/filepath/path_windows.go
Normal file
114
src/path/filepath/path_windows.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package filepath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// HasPrefix exists for historical compatibility and should not be used.
|
||||
//
|
||||
// Deprecated: HasPrefix does not respect path boundaries and
|
||||
// does not ignore case when required.
|
||||
func HasPrefix(p, prefix string) bool {
|
||||
if strings.HasPrefix(p, prefix) {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
|
||||
}
|
||||
|
||||
func splitList(path string) []string {
|
||||
// The same implementation is used in LookPath in os/exec;
|
||||
// consider changing os/exec when changing this.
|
||||
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Split path, respecting but preserving quotes.
|
||||
list := []string{}
|
||||
start := 0
|
||||
quo := false
|
||||
for i := 0; i < len(path); i++ {
|
||||
switch c := path[i]; {
|
||||
case c == '"':
|
||||
quo = !quo
|
||||
case c == ListSeparator && !quo:
|
||||
list = append(list, path[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
list = append(list, path[start:])
|
||||
|
||||
// Remove quotes.
|
||||
for i, s := range list {
|
||||
list[i] = strings.ReplaceAll(s, `"`, ``)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func abs(path string) (string, error) {
|
||||
if path == "" {
|
||||
// syscall.FullPath returns an error on empty path, because it's not a valid path.
|
||||
// To implement Abs behavior of returning working directory on empty string input,
|
||||
// special-case empty path by changing it to "." path. See golang.org/issue/24441.
|
||||
path = "."
|
||||
}
|
||||
fullPath, err := syscall.FullPath(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Clean(fullPath), nil
|
||||
}
|
||||
|
||||
func join(elem []string) string {
|
||||
var b strings.Builder
|
||||
var lastChar byte
|
||||
for _, e := range elem {
|
||||
switch {
|
||||
case b.Len() == 0:
|
||||
// Add the first non-empty path element unchanged.
|
||||
case os.IsPathSeparator(lastChar):
|
||||
// If the path ends in a slash, strip any leading slashes from the next
|
||||
// path element to avoid creating a UNC path (any path starting with "\\")
|
||||
// from non-UNC elements.
|
||||
//
|
||||
// The correct behavior for Join when the first element is an incomplete UNC
|
||||
// path (for example, "\\") is underspecified. We currently join subsequent
|
||||
// elements so Join("\\", "host", "share") produces "\\host\share".
|
||||
for len(e) > 0 && os.IsPathSeparator(e[0]) {
|
||||
e = e[1:]
|
||||
}
|
||||
// If the path is \ and the next path element is ??,
|
||||
// add an extra .\ to create \.\?? rather than \??\
|
||||
// (a Root Local Device path).
|
||||
if b.Len() == 1 && strings.HasPrefix(e, "??") && (len(e) == len("??") || os.IsPathSeparator(e[2])) {
|
||||
b.WriteString(`.\`)
|
||||
}
|
||||
case lastChar == ':':
|
||||
// If the path ends in a colon, keep the path relative to the current directory
|
||||
// on a drive and don't add a separator. Preserve leading slashes in the next
|
||||
// path element, which may make the path absolute.
|
||||
//
|
||||
// Join(`C:`, `f`) = `C:f`
|
||||
// Join(`C:`, `\f`) = `C:\f`
|
||||
default:
|
||||
// In all other cases, add a separator between elements.
|
||||
b.WriteByte('\\')
|
||||
lastChar = '\\'
|
||||
}
|
||||
if len(e) > 0 {
|
||||
b.WriteString(e)
|
||||
lastChar = e[len(e)-1]
|
||||
}
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
return Clean(b.String())
|
||||
}
|
||||
|
||||
func sameWord(a, b string) bool {
|
||||
return strings.EqualFold(a, b)
|
||||
}
|
||||
709
src/path/filepath/path_windows_test.go
Normal file
709
src/path/filepath/path_windows_test.go
Normal file
@@ -0,0 +1,709 @@
|
||||
// Copyright 2013 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"internal/godebug"
|
||||
"internal/testenv"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWinSplitListTestsAreValid(t *testing.T) {
|
||||
comspec := os.Getenv("ComSpec")
|
||||
if comspec == "" {
|
||||
t.Fatal("%ComSpec% must be set")
|
||||
}
|
||||
|
||||
for ti, tt := range winsplitlisttests {
|
||||
testWinSplitListTestIsValid(t, ti, tt, comspec)
|
||||
}
|
||||
}
|
||||
|
||||
func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
|
||||
comspec string) {
|
||||
|
||||
const (
|
||||
cmdfile = `printdir.cmd`
|
||||
perm fs.FileMode = 0700
|
||||
)
|
||||
|
||||
tmp := t.TempDir()
|
||||
for i, d := range tt.result {
|
||||
if d == "" {
|
||||
continue
|
||||
}
|
||||
if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" ||
|
||||
cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) {
|
||||
t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d)
|
||||
return
|
||||
}
|
||||
dd := filepath.Join(tmp, d)
|
||||
if _, err := os.Stat(dd); err == nil {
|
||||
t.Errorf("%d,%d: %#q already exists", ti, i, d)
|
||||
return
|
||||
}
|
||||
if err := os.MkdirAll(dd, perm); err != nil {
|
||||
t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err)
|
||||
return
|
||||
}
|
||||
fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
|
||||
if err := os.WriteFile(fn, data, perm); err != nil {
|
||||
t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// on some systems, SystemRoot is required for cmd to work
|
||||
systemRoot := os.Getenv("SystemRoot")
|
||||
|
||||
for i, d := range tt.result {
|
||||
if d == "" {
|
||||
continue
|
||||
}
|
||||
exp := []byte(d + "\r\n")
|
||||
cmd := &exec.Cmd{
|
||||
Path: comspec,
|
||||
Args: []string{`/c`, cmdfile},
|
||||
Env: []string{`Path=` + systemRoot + "/System32;" + tt.list, `SystemRoot=` + systemRoot},
|
||||
Dir: tmp,
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
switch {
|
||||
case err != nil:
|
||||
t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out)
|
||||
return
|
||||
case !reflect.DeepEqual(out, exp):
|
||||
t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out)
|
||||
return
|
||||
default:
|
||||
// unshadow cmdfile in next directory
|
||||
err = os.Remove(filepath.Join(tmp, d, cmdfile))
|
||||
if err != nil {
|
||||
t.Fatalf("Remove test command failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowsEvalSymlinks(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
|
||||
tmpDir := tempDirCanonical(t)
|
||||
|
||||
if len(tmpDir) < 3 {
|
||||
t.Fatalf("tmpDir path %q is too short", tmpDir)
|
||||
}
|
||||
if tmpDir[1] != ':' {
|
||||
t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
|
||||
}
|
||||
test := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]}
|
||||
|
||||
// Create the symlink farm using relative paths.
|
||||
testdirs := append(EvalSymlinksTestDirs, test)
|
||||
for _, d := range testdirs {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
path := simpleJoin(tmpDir, test.path)
|
||||
|
||||
testEvalSymlinks(t, path, test.dest)
|
||||
|
||||
testEvalSymlinksAfterChdir(t, path, ".", test.dest)
|
||||
|
||||
testEvalSymlinksAfterChdir(t,
|
||||
path,
|
||||
filepath.VolumeName(tmpDir)+".",
|
||||
test.dest)
|
||||
|
||||
testEvalSymlinksAfterChdir(t,
|
||||
simpleJoin(tmpDir, "test"),
|
||||
simpleJoin("..", test.path),
|
||||
test.dest)
|
||||
|
||||
testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
|
||||
}
|
||||
|
||||
// TestEvalSymlinksCanonicalNames verify that EvalSymlinks
|
||||
// returns "canonical" path names on windows.
|
||||
func TestEvalSymlinksCanonicalNames(t *testing.T) {
|
||||
ctmp := tempDirCanonical(t)
|
||||
dirs := []string{
|
||||
"test",
|
||||
"test/dir",
|
||||
"testing_long_dir",
|
||||
"TEST2",
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
dir := filepath.Join(ctmp, d)
|
||||
err := os.Mkdir(dir, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cname, err := filepath.EvalSymlinks(dir)
|
||||
if err != nil {
|
||||
t.Errorf("EvalSymlinks(%q) error: %v", dir, err)
|
||||
continue
|
||||
}
|
||||
if dir != cname {
|
||||
t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir)
|
||||
continue
|
||||
}
|
||||
// test non-canonical names
|
||||
test := strings.ToUpper(dir)
|
||||
p, err := filepath.EvalSymlinks(test)
|
||||
if err != nil {
|
||||
t.Errorf("EvalSymlinks(%q) error: %v", test, err)
|
||||
continue
|
||||
}
|
||||
if p != cname {
|
||||
t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
|
||||
continue
|
||||
}
|
||||
// another test
|
||||
test = strings.ToLower(dir)
|
||||
p, err = filepath.EvalSymlinks(test)
|
||||
if err != nil {
|
||||
t.Errorf("EvalSymlinks(%q) error: %v", test, err)
|
||||
continue
|
||||
}
|
||||
if p != cname {
|
||||
t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkVolume8dot3Setting runs "fsutil 8dot3name query c:" command
|
||||
// (where c: is vol parameter) to discover "8dot3 name creation state".
|
||||
// The state is combination of 2 flags. The global flag controls if it
|
||||
// is per volume or global setting:
|
||||
//
|
||||
// 0 - Enable 8dot3 name creation on all volumes on the system
|
||||
// 1 - Disable 8dot3 name creation on all volumes on the system
|
||||
// 2 - Set 8dot3 name creation on a per volume basis
|
||||
// 3 - Disable 8dot3 name creation on all volumes except the system volume
|
||||
//
|
||||
// If global flag is set to 2, then per-volume flag needs to be examined:
|
||||
//
|
||||
// 0 - Enable 8dot3 name creation on this volume
|
||||
// 1 - Disable 8dot3 name creation on this volume
|
||||
//
|
||||
// checkVolume8dot3Setting verifies that "8dot3 name creation" flags
|
||||
// are set to 2 and 0, if enabled parameter is true, or 2 and 1, if enabled
|
||||
// is false. Otherwise checkVolume8dot3Setting returns error.
|
||||
func checkVolume8dot3Setting(vol string, enabled bool) error {
|
||||
// It appears, on some systems "fsutil 8dot3name query ..." command always
|
||||
// exits with error. Ignore exit code, and look at fsutil output instead.
|
||||
out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput()
|
||||
// Check that system has "Volume level setting" set.
|
||||
expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)"
|
||||
if !strings.Contains(string(out), expected) {
|
||||
// Windows 10 version of fsutil has different output message.
|
||||
expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)"
|
||||
if !strings.Contains(string(out), expectedWindow10) {
|
||||
return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out))
|
||||
}
|
||||
}
|
||||
// Now check the volume setting.
|
||||
expected = "Based on the above two settings, 8dot3 name creation is %s on %s"
|
||||
if enabled {
|
||||
expected = fmt.Sprintf(expected, "enabled", vol)
|
||||
} else {
|
||||
expected = fmt.Sprintf(expected, "disabled", vol)
|
||||
}
|
||||
if !strings.Contains(string(out), expected) {
|
||||
return fmt.Errorf("unexpected fsutil output: %q", string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setVolume8dot3Setting(vol string, enabled bool) error {
|
||||
cmd := []string{"fsutil", "8dot3name", "set", vol}
|
||||
if enabled {
|
||||
cmd = append(cmd, "0")
|
||||
} else {
|
||||
cmd = append(cmd, "1")
|
||||
}
|
||||
// It appears, on some systems "fsutil 8dot3name set ..." command always
|
||||
// exits with error. Ignore exit code, and look at fsutil output instead.
|
||||
out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||
if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" {
|
||||
// Windows 10 version of fsutil has different output message.
|
||||
expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n"
|
||||
if enabled {
|
||||
expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol)
|
||||
} else {
|
||||
expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol)
|
||||
}
|
||||
if string(out) != expectedWindow10 {
|
||||
return fmt.Errorf("%v command failed: %q", cmd, string(out))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters")
|
||||
|
||||
// This test assumes registry state of NtfsDisable8dot3NameCreation is 2,
|
||||
// the default (Volume level setting).
|
||||
func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) {
|
||||
if !*runFSModifyTests {
|
||||
t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests")
|
||||
}
|
||||
tempVol := filepath.VolumeName(os.TempDir())
|
||||
if len(tempVol) != 2 {
|
||||
t.Fatalf("unexpected temp volume name %q", tempVol)
|
||||
}
|
||||
|
||||
err := checkVolume8dot3Setting(tempVol, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = setVolume8dot3Setting(tempVol, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := setVolume8dot3Setting(tempVol, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = checkVolume8dot3Setting(tempVol, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
err = checkVolume8dot3Setting(tempVol, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
TestEvalSymlinksCanonicalNames(t)
|
||||
}
|
||||
|
||||
func TestToNorm(t *testing.T) {
|
||||
stubBase := func(path string) (string, error) {
|
||||
vol := filepath.VolumeName(path)
|
||||
path = path[len(vol):]
|
||||
|
||||
if strings.Contains(path, "/") {
|
||||
return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
|
||||
}
|
||||
|
||||
if path == "" || path == "." || path == `\` {
|
||||
return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
|
||||
}
|
||||
|
||||
i := strings.LastIndexByte(path, filepath.Separator)
|
||||
if i == len(path)-1 { // trailing '\' is invalid
|
||||
return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
|
||||
}
|
||||
if i == -1 {
|
||||
return strings.ToUpper(path), nil
|
||||
}
|
||||
|
||||
return strings.ToUpper(path[i+1:]), nil
|
||||
}
|
||||
|
||||
// On this test, toNorm should be same as string.ToUpper(filepath.Clean(path)) except empty string.
|
||||
tests := []struct {
|
||||
arg string
|
||||
want string
|
||||
}{
|
||||
{"", ""},
|
||||
{".", "."},
|
||||
{"./foo/bar", `FOO\BAR`},
|
||||
{"/", `\`},
|
||||
{"/foo/bar", `\FOO\BAR`},
|
||||
{"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`},
|
||||
{"foo/bar", `FOO\BAR`},
|
||||
{"C:/foo/bar", `C:\FOO\BAR`},
|
||||
{"C:foo/bar", `C:FOO\BAR`},
|
||||
{"c:/foo/bar", `C:\FOO\BAR`},
|
||||
{"C:/foo/bar", `C:\FOO\BAR`},
|
||||
{"C:/foo/bar/", `C:\FOO\BAR`},
|
||||
{`C:\foo\bar`, `C:\FOO\BAR`},
|
||||
{`C:\foo/bar\`, `C:\FOO\BAR`},
|
||||
{"C:/ふー/バー", `C:\ふー\バー`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var path string
|
||||
if test.arg != "" {
|
||||
path = filepath.Clean(test.arg)
|
||||
}
|
||||
got, err := filepath.ToNorm(path, stubBase)
|
||||
if err != nil {
|
||||
t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
|
||||
} else if got != test.want {
|
||||
t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
|
||||
}
|
||||
}
|
||||
|
||||
testPath := `{{tmp}}\test\foo\bar`
|
||||
|
||||
testsDir := []struct {
|
||||
wd string
|
||||
arg string
|
||||
want string
|
||||
}{
|
||||
// test absolute paths
|
||||
{".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
|
||||
{".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
|
||||
{".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
|
||||
{".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
|
||||
|
||||
// test relative paths begin with drive letter
|
||||
{`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
|
||||
{`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
|
||||
{`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
|
||||
{`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
|
||||
{`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
|
||||
{`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
|
||||
|
||||
// test relative paths begin with '\'
|
||||
{"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
|
||||
{"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
|
||||
{"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
|
||||
{"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
|
||||
|
||||
// test relative paths begin without '\'
|
||||
{`{{tmp}}\test`, ".", `.`},
|
||||
{`{{tmp}}\test`, "..", `..`},
|
||||
{`{{tmp}}\test`, `foo\bar`, `foo\bar`},
|
||||
{`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
|
||||
{`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
|
||||
{`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
|
||||
|
||||
// test UNC paths
|
||||
{".", `\\localhost\c$`, `\\localhost\c$`},
|
||||
}
|
||||
|
||||
ctmp := tempDirCanonical(t)
|
||||
if err := os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := os.Chdir(cwd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
tmpVol := filepath.VolumeName(ctmp)
|
||||
if len(tmpVol) != 2 {
|
||||
t.Fatalf("unexpected temp volume name %q", tmpVol)
|
||||
}
|
||||
|
||||
tmpNoVol := ctmp[len(tmpVol):]
|
||||
|
||||
replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol)
|
||||
|
||||
for _, test := range testsDir {
|
||||
wd := replacer.Replace(test.wd)
|
||||
arg := replacer.Replace(test.arg)
|
||||
want := replacer.Replace(test.want)
|
||||
|
||||
if test.wd == "." {
|
||||
err := os.Chdir(cwd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
err := os.Chdir(wd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
if arg != "" {
|
||||
arg = filepath.Clean(arg)
|
||||
}
|
||||
got, err := filepath.ToNorm(arg, filepath.NormBase)
|
||||
if err != nil {
|
||||
t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
|
||||
} else if got != want {
|
||||
t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUNC(t *testing.T) {
|
||||
// Test that this doesn't go into an infinite recursion.
|
||||
// See golang.org/issue/15879.
|
||||
defer debug.SetMaxStack(debug.SetMaxStack(1e6))
|
||||
filepath.Glob(`\\?\c:\*`)
|
||||
}
|
||||
|
||||
func testWalkMklink(t *testing.T, linktype string) {
|
||||
output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
|
||||
if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) {
|
||||
t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype)
|
||||
}
|
||||
testWalkSymlink(t, func(target, link string) error {
|
||||
output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalkDirectoryJunction(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
testWalkMklink(t, "J")
|
||||
}
|
||||
|
||||
func TestWalkDirectorySymlink(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
testWalkMklink(t, "D")
|
||||
}
|
||||
|
||||
func createMountPartition(t *testing.T, vhd string, args string) []byte {
|
||||
testenv.MustHaveExecPath(t, "powershell")
|
||||
t.Cleanup(func() {
|
||||
cmd := testenv.Command(t, "powershell", "-Command", fmt.Sprintf("Dismount-VHD %q", vhd))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if t.Skipped() {
|
||||
// Probably failed to dismount because we never mounted it in
|
||||
// the first place. Log the error, but ignore it.
|
||||
t.Logf("%v: %v (skipped)\n%s", cmd, err, out)
|
||||
} else {
|
||||
// Something went wrong, and we don't want to leave dangling VHDs.
|
||||
// Better to fail the test than to just log the error and continue.
|
||||
t.Errorf("%v: %v\n%s", cmd, err, out)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
script := filepath.Join(t.TempDir(), "test.ps1")
|
||||
cmd := strings.Join([]string{
|
||||
"$ErrorActionPreference = \"Stop\"",
|
||||
fmt.Sprintf("$vhd = New-VHD -Path %q -SizeBytes 3MB -Fixed", vhd),
|
||||
"$vhd | Mount-VHD",
|
||||
fmt.Sprintf("$vhd = Get-VHD %q", vhd),
|
||||
"$vhd | Get-Disk | Initialize-Disk -PartitionStyle GPT",
|
||||
"$part = $vhd | Get-Disk | New-Partition -UseMaximumSize -AssignDriveLetter:$false",
|
||||
"$vol = $part | Format-Volume -FileSystem NTFS",
|
||||
args,
|
||||
}, "\n")
|
||||
|
||||
err := os.WriteFile(script, []byte(cmd), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output, err := testenv.Command(t, "powershell", "-File", script).CombinedOutput()
|
||||
if err != nil {
|
||||
// This can happen if Hyper-V is not installed or enabled.
|
||||
t.Skip("skipping test because failed to create VHD: ", err, string(output))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
var winsymlink = godebug.New("winsymlink")
|
||||
var winreadlinkvolume = godebug.New("winreadlinkvolume")
|
||||
|
||||
func TestEvalSymlinksJunctionToVolumeID(t *testing.T) {
|
||||
// Test that EvalSymlinks resolves a directory junction which
|
||||
// is mapped to volumeID (instead of drive letter). See go.dev/issue/39786.
|
||||
if winsymlink.Value() == "0" {
|
||||
t.Skip("skipping test because winsymlink is not enabled")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
|
||||
if !strings.Contains(string(output), " /J ") {
|
||||
t.Skip("skipping test because mklink command does not support junctions")
|
||||
}
|
||||
|
||||
tmpdir := tempDirCanonical(t)
|
||||
vhd := filepath.Join(tmpdir, "Test.vhdx")
|
||||
output = createMountPartition(t, vhd, "Write-Host $vol.Path -NoNewline")
|
||||
vol := string(output)
|
||||
|
||||
dirlink := filepath.Join(tmpdir, "dirlink")
|
||||
output, err := testenv.Command(t, "cmd", "/c", "mklink", "/J", dirlink, vol).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, vol, err, output)
|
||||
}
|
||||
got, err := filepath.EvalSymlinks(dirlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != dirlink {
|
||||
t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, dirlink)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalSymlinksMountPointRecursion(t *testing.T) {
|
||||
// Test that EvalSymlinks doesn't follow recursive mount points.
|
||||
// See go.dev/issue/40176.
|
||||
if winsymlink.Value() == "0" {
|
||||
t.Skip("skipping test because winsymlink is not enabled")
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
tmpdir := tempDirCanonical(t)
|
||||
dirlink := filepath.Join(tmpdir, "dirlink")
|
||||
err := os.Mkdir(dirlink, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vhd := filepath.Join(tmpdir, "Test.vhdx")
|
||||
createMountPartition(t, vhd, fmt.Sprintf("$part | Add-PartitionAccessPath -AccessPath %q\n", dirlink))
|
||||
|
||||
got, err := filepath.EvalSymlinks(dirlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != dirlink {
|
||||
t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, dirlink)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNTNamespaceSymlink(t *testing.T) {
|
||||
output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
|
||||
if !strings.Contains(string(output), " /J ") {
|
||||
t.Skip("skipping test because mklink command does not support junctions")
|
||||
}
|
||||
|
||||
tmpdir := tempDirCanonical(t)
|
||||
|
||||
vol := filepath.VolumeName(tmpdir)
|
||||
output, err := exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
|
||||
}
|
||||
target := strings.Trim(string(output), " \n\r")
|
||||
|
||||
dirlink := filepath.Join(tmpdir, "dirlink")
|
||||
output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output)
|
||||
}
|
||||
|
||||
got, err := filepath.EvalSymlinks(dirlink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var want string
|
||||
if winsymlink.Value() == "0" {
|
||||
if winreadlinkvolume.Value() == "0" {
|
||||
want = vol + `\`
|
||||
} else {
|
||||
want = target
|
||||
}
|
||||
} else {
|
||||
want = dirlink
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want)
|
||||
}
|
||||
|
||||
// Make sure we have sufficient privilege to run mklink command.
|
||||
testenv.MustHaveSymlink(t)
|
||||
|
||||
file := filepath.Join(tmpdir, "file")
|
||||
err = os.WriteFile(file, []byte(""), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
target = filepath.Join(target, file[len(filepath.VolumeName(file)):])
|
||||
|
||||
filelink := filepath.Join(tmpdir, "filelink")
|
||||
output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output)
|
||||
}
|
||||
|
||||
got, err = filepath.EvalSymlinks(filelink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if winreadlinkvolume.Value() == "0" {
|
||||
want = file
|
||||
} else {
|
||||
want = target
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue52476(t *testing.T) {
|
||||
tests := []struct {
|
||||
lhs, rhs string
|
||||
want string
|
||||
}{
|
||||
{`..\.`, `C:`, `..\C:`},
|
||||
{`..`, `C:`, `..\C:`},
|
||||
{`.`, `:`, `.\:`},
|
||||
{`.`, `C:`, `.\C:`},
|
||||
{`.`, `C:/a/b/../c`, `.\C:\a\c`},
|
||||
{`.`, `\C:`, `.\C:`},
|
||||
{`C:\`, `.`, `C:\`},
|
||||
{`C:\`, `C:\`, `C:\C:`},
|
||||
{`C`, `:`, `C\:`},
|
||||
{`\.`, `C:`, `\C:`},
|
||||
{`\`, `C:`, `\C:`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := filepath.Join(test.lhs, test.rhs)
|
||||
if got != test.want {
|
||||
t.Errorf(`Join(%q, %q): got %q, want %q`, test.lhs, test.rhs, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsWindows(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{`C:\foo`, `C:\foo`},
|
||||
{`\\host\share\foo`, `\\host\share\foo`},
|
||||
{`\\host`, `\\host`},
|
||||
{`\\.\NUL`, `\\.\NUL`},
|
||||
{`NUL`, `\\.\NUL`},
|
||||
{`COM1`, `\\.\COM1`},
|
||||
{`a/NUL`, `\\.\NUL`},
|
||||
} {
|
||||
got, err := filepath.Abs(test.path)
|
||||
if err != nil || got != test.want {
|
||||
t.Errorf("Abs(%q) = %q, %v; want %q, nil", test.path, got, err, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/path/filepath/symlink.go
Normal file
150
src/path/filepath/symlink.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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"
|
||||
"internal/filepathlite"
|
||||
"io/fs"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func walkSymlinks(path string) (string, error) {
|
||||
volLen := filepathlite.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[filepathlite.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) > filepathlite.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()&fs.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 := filepathlite.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
|
||||
vol = link[:1]
|
||||
volLen = 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
|
||||
}
|
||||
27
src/path/filepath/symlink_plan9.go
Normal file
27
src/path/filepath/symlink_plan9.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2022 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 (
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
// Plan 9 doesn't have symbolic links, so no need for substitutions.
|
||||
if len(path) > 0 {
|
||||
// Check validity of path
|
||||
_, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
// Return the same error value as on other operating systems
|
||||
if strings.HasSuffix(err.Error(), "not a directory") {
|
||||
err = syscall.ENOTDIR
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return Clean(path), nil
|
||||
}
|
||||
11
src/path/filepath/symlink_unix.go
Normal file
11
src/path/filepath/symlink_unix.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
//go:build !windows && !plan9
|
||||
|
||||
package filepath
|
||||
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
return walkSymlinks(path)
|
||||
}
|
||||
118
src/path/filepath/symlink_windows.go
Normal file
118
src/path/filepath/symlink_windows.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// normVolumeName is like VolumeName, but makes drive letter upper case.
|
||||
// result of EvalSymlinks must be unique, so we have
|
||||
// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
|
||||
func normVolumeName(path string) string {
|
||||
volume := VolumeName(path)
|
||||
|
||||
if len(volume) > 2 { // isUNC
|
||||
return volume
|
||||
}
|
||||
|
||||
return strings.ToUpper(volume)
|
||||
}
|
||||
|
||||
// normBase returns the last element of path with correct case.
|
||||
func normBase(path string) (string, error) {
|
||||
p, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data syscall.Win32finddata
|
||||
|
||||
h, err := syscall.FindFirstFile(p, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
syscall.FindClose(h)
|
||||
|
||||
return syscall.UTF16ToString(data.FileName[:]), nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
return path[i+1:] == ".."
|
||||
}
|
||||
|
||||
// toNorm returns the normalized path that is guaranteed to be unique.
|
||||
// It should accept the following formats:
|
||||
// - UNC paths (e.g \\server\share\foo\bar)
|
||||
// - absolute paths (e.g C:\foo\bar)
|
||||
// - relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
|
||||
// - relative paths begin with '\' (e.g \foo\bar)
|
||||
// - relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
|
||||
//
|
||||
// The returned normalized path will be in the same form (of 5 listed above) as the input path.
|
||||
// If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
|
||||
// The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
|
||||
func toNorm(path string, normBase func(string) (string, error)) (string, error) {
|
||||
if path == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
volume := normVolumeName(path)
|
||||
path = path[len(volume):]
|
||||
|
||||
// skip special cases
|
||||
if path == "" || path == "." || path == `\` {
|
||||
return volume + path, nil
|
||||
}
|
||||
|
||||
var normPath string
|
||||
|
||||
for {
|
||||
if baseIsDotDot(path) {
|
||||
normPath = path + `\` + normPath
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
name, err := normBase(volume + path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
normPath = name + `\` + normPath
|
||||
|
||||
i := strings.LastIndexByte(path, Separator)
|
||||
if i == -1 {
|
||||
break
|
||||
}
|
||||
if i == 0 { // `\Go` or `C:\Go`
|
||||
normPath = `\` + normPath
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
path = path[:i]
|
||||
}
|
||||
|
||||
normPath = normPath[:len(normPath)-1] // remove trailing '\'
|
||||
|
||||
return volume + normPath, nil
|
||||
}
|
||||
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
newpath, err := walkSymlinks(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
newpath, err = toNorm(newpath, normBase)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return newpath, nil
|
||||
}
|
||||
230
src/path/match.go
Normal file
230
src/path/match.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2010 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 path
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/bytealg"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrBadPattern indicates a pattern was malformed.
|
||||
var ErrBadPattern = errors.New("syntax error in pattern")
|
||||
|
||||
// Match reports whether name matches the shell pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-/ characters
|
||||
// '?' matches any single non-/ character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The only possible returned error is [ErrBadPattern], when pattern
|
||||
// is malformed.
|
||||
func Match(pattern, name string) (matched bool, err error) {
|
||||
Pattern:
|
||||
for len(pattern) > 0 {
|
||||
var star bool
|
||||
var chunk string
|
||||
star, chunk, pattern = scanChunk(pattern)
|
||||
if star && chunk == "" {
|
||||
// Trailing * matches rest of string unless it has a /.
|
||||
return bytealg.IndexByteString(name, '/') < 0, nil
|
||||
}
|
||||
// Look for match at current position.
|
||||
t, ok, err := matchChunk(chunk, name)
|
||||
// if we're the last chunk, make sure we've exhausted the name
|
||||
// otherwise we'll give a false result even if we could still match
|
||||
// using the star
|
||||
if ok && (len(t) == 0 || len(pattern) > 0) {
|
||||
name = t
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if star {
|
||||
// Look for match skipping i+1 bytes.
|
||||
// Cannot skip /.
|
||||
for i := 0; i < len(name) && name[i] != '/'; i++ {
|
||||
t, ok, err := matchChunk(chunk, name[i+1:])
|
||||
if ok {
|
||||
// if we're the last chunk, make sure we exhausted the name
|
||||
if len(pattern) == 0 && len(t) > 0 {
|
||||
continue
|
||||
}
|
||||
name = t
|
||||
continue Pattern
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Before returning false with no error,
|
||||
// check that the remainder of the pattern is syntactically valid.
|
||||
for len(pattern) > 0 {
|
||||
_, chunk, pattern = scanChunk(pattern)
|
||||
if _, _, err := matchChunk(chunk, ""); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return len(name) == 0, nil
|
||||
}
|
||||
|
||||
// scanChunk gets the next segment of pattern, which is a non-star string
|
||||
// possibly preceded by a star.
|
||||
func scanChunk(pattern string) (star bool, chunk, rest string) {
|
||||
for len(pattern) > 0 && pattern[0] == '*' {
|
||||
pattern = pattern[1:]
|
||||
star = true
|
||||
}
|
||||
inrange := false
|
||||
var i int
|
||||
Scan:
|
||||
for i = 0; i < len(pattern); i++ {
|
||||
switch pattern[i] {
|
||||
case '\\':
|
||||
// error check handled in matchChunk: bad pattern.
|
||||
if i+1 < len(pattern) {
|
||||
i++
|
||||
}
|
||||
case '[':
|
||||
inrange = true
|
||||
case ']':
|
||||
inrange = false
|
||||
case '*':
|
||||
if !inrange {
|
||||
break Scan
|
||||
}
|
||||
}
|
||||
}
|
||||
return star, pattern[0:i], pattern[i:]
|
||||
}
|
||||
|
||||
// matchChunk checks whether chunk matches the beginning of s.
|
||||
// If so, it returns the remainder of s (after the match).
|
||||
// Chunk is all single-character operators: literals, char classes, and ?.
|
||||
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
|
||||
// failed records whether the match has failed.
|
||||
// After the match fails, the loop continues on processing chunk,
|
||||
// checking that the pattern is well-formed but no longer reading s.
|
||||
failed := false
|
||||
for len(chunk) > 0 {
|
||||
if !failed && len(s) == 0 {
|
||||
failed = true
|
||||
}
|
||||
switch chunk[0] {
|
||||
case '[':
|
||||
// character class
|
||||
var r rune
|
||||
if !failed {
|
||||
var n int
|
||||
r, n = utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
// possibly negated
|
||||
negated := false
|
||||
if len(chunk) > 0 && chunk[0] == '^' {
|
||||
negated = true
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
// parse all ranges
|
||||
match := false
|
||||
nrange := 0
|
||||
for {
|
||||
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
|
||||
chunk = chunk[1:]
|
||||
break
|
||||
}
|
||||
var lo, hi rune
|
||||
if lo, chunk, err = getEsc(chunk); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
hi = lo
|
||||
if chunk[0] == '-' {
|
||||
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
}
|
||||
if lo <= r && r <= hi {
|
||||
match = true
|
||||
}
|
||||
nrange++
|
||||
}
|
||||
if match == negated {
|
||||
failed = true
|
||||
}
|
||||
|
||||
case '?':
|
||||
if !failed {
|
||||
if s[0] == '/' {
|
||||
failed = true
|
||||
}
|
||||
_, n := utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
|
||||
case '\\':
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
return "", false, ErrBadPattern
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if !failed {
|
||||
if chunk[0] != s[0] {
|
||||
failed = true
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return "", false, nil
|
||||
}
|
||||
return s, true, nil
|
||||
}
|
||||
|
||||
// getEsc gets a possibly-escaped character from chunk, for a character class.
|
||||
func getEsc(chunk string) (r rune, nchunk string, err error) {
|
||||
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
if chunk[0] == '\\' {
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(chunk)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
nchunk = chunk[n:]
|
||||
if len(nchunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
return
|
||||
}
|
||||
84
src/path/match_test.go
Normal file
84
src/path/match_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// 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 path_test
|
||||
|
||||
import (
|
||||
. "path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MatchTest struct {
|
||||
pattern, s string
|
||||
match bool
|
||||
err error
|
||||
}
|
||||
|
||||
var matchTests = []MatchTest{
|
||||
{"abc", "abc", true, nil},
|
||||
{"*", "abc", true, nil},
|
||||
{"*c", "abc", true, nil},
|
||||
{"a*", "a", true, nil},
|
||||
{"a*", "abc", true, nil},
|
||||
{"a*", "ab/c", false, nil},
|
||||
{"a*/b", "abc/b", true, nil},
|
||||
{"a*/b", "a/c/b", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
||||
{"ab[c]", "abc", true, nil},
|
||||
{"ab[b-d]", "abc", true, nil},
|
||||
{"ab[e-g]", "abc", false, nil},
|
||||
{"ab[^c]", "abc", false, nil},
|
||||
{"ab[^b-d]", "abc", false, nil},
|
||||
{"ab[^e-g]", "abc", true, nil},
|
||||
{"a\\*b", "a*b", true, nil},
|
||||
{"a\\*b", "ab", false, nil},
|
||||
{"a?b", "a☺b", true, nil},
|
||||
{"a[^a]b", "a☺b", true, nil},
|
||||
{"a???b", "a☺b", false, nil},
|
||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
||||
{"[a-ζ]*", "α", true, nil},
|
||||
{"*[a-ζ]", "A", false, nil},
|
||||
{"a?b", "a/b", false, nil},
|
||||
{"a*b", "a/b", false, nil},
|
||||
{"[\\]a]", "]", true, nil},
|
||||
{"[\\-]", "-", true, nil},
|
||||
{"[x\\-]", "x", true, nil},
|
||||
{"[x\\-]", "-", true, nil},
|
||||
{"[x\\-]", "z", false, nil},
|
||||
{"[\\-x]", "x", true, nil},
|
||||
{"[\\-x]", "-", true, nil},
|
||||
{"[\\-x]", "a", false, nil},
|
||||
{"[]a]", "]", false, ErrBadPattern},
|
||||
{"[-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "x", false, ErrBadPattern},
|
||||
{"[x-]", "-", false, ErrBadPattern},
|
||||
{"[x-]", "z", false, ErrBadPattern},
|
||||
{"[-x]", "x", false, ErrBadPattern},
|
||||
{"[-x]", "-", false, ErrBadPattern},
|
||||
{"[-x]", "a", false, ErrBadPattern},
|
||||
{"\\", "a", false, ErrBadPattern},
|
||||
{"[a-b-c]", "a", false, ErrBadPattern},
|
||||
{"[", "a", false, ErrBadPattern},
|
||||
{"[^", "a", false, ErrBadPattern},
|
||||
{"[^bc", "a", false, ErrBadPattern},
|
||||
{"a[", "a", false, ErrBadPattern},
|
||||
{"a[", "ab", false, ErrBadPattern},
|
||||
{"a[", "x", false, ErrBadPattern},
|
||||
{"a/b[", "x", false, ErrBadPattern},
|
||||
{"*x", "xxx", true, nil},
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, tt := range matchTests {
|
||||
ok, err := Match(tt.pattern, tt.s)
|
||||
if ok != tt.match || err != tt.err {
|
||||
t.Errorf("Match(%#q, %#q) = %v, %v want %v, %v", tt.pattern, tt.s, ok, err, tt.match, tt.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/path/path.go
Normal file
226
src/path/path.go
Normal file
@@ -0,0 +1,226 @@
|
||||
// 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 path implements utility routines for manipulating slash-separated
|
||||
// paths.
|
||||
//
|
||||
// The path package should only be used for paths separated by forward
|
||||
// slashes, such as the paths in URLs. This package does not deal with
|
||||
// Windows paths with drive letters or backslashes; to manipulate
|
||||
// operating system paths, use the [path/filepath] package.
|
||||
package path
|
||||
|
||||
import "internal/bytealg"
|
||||
|
||||
// A lazybuf is a lazily constructed path buffer.
|
||||
// It supports append, reading previously appended bytes,
|
||||
// and retrieving the final string. It does not allocate a buffer
|
||||
// to hold the output until that output diverges from s.
|
||||
type lazybuf struct {
|
||||
s string
|
||||
buf []byte
|
||||
w int
|
||||
}
|
||||
|
||||
func (b *lazybuf) index(i int) byte {
|
||||
if b.buf != nil {
|
||||
return b.buf[i]
|
||||
}
|
||||
return b.s[i]
|
||||
}
|
||||
|
||||
func (b *lazybuf) append(c byte) {
|
||||
if b.buf == nil {
|
||||
if b.w < len(b.s) && b.s[b.w] == c {
|
||||
b.w++
|
||||
return
|
||||
}
|
||||
b.buf = make([]byte, len(b.s))
|
||||
copy(b.buf, b.s[:b.w])
|
||||
}
|
||||
b.buf[b.w] = c
|
||||
b.w++
|
||||
}
|
||||
|
||||
func (b *lazybuf) string() string {
|
||||
if b.buf == nil {
|
||||
return b.s[:b.w]
|
||||
}
|
||||
return string(b.buf[:b.w])
|
||||
}
|
||||
|
||||
// Clean returns the shortest path name equivalent to path
|
||||
// by purely lexical processing. It applies the following rules
|
||||
// iteratively until no further processing can be done:
|
||||
//
|
||||
// 1. Replace multiple slashes with a single slash.
|
||||
// 2. Eliminate each . path name element (the current directory).
|
||||
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||
// along with the non-.. element that precedes it.
|
||||
// 4. Eliminate .. elements that begin a rooted path:
|
||||
// that is, replace "/.." by "/" at the beginning of a path.
|
||||
//
|
||||
// The returned path ends in a slash only if it is the root "/".
|
||||
//
|
||||
// If the result of this process is an empty string, Clean
|
||||
// returns the string ".".
|
||||
//
|
||||
// See also Rob Pike, “Lexical File Names in Plan 9 or
|
||||
// Getting Dot-Dot Right,”
|
||||
// https://9p.io/sys/doc/lexnames.html
|
||||
func Clean(path string) string {
|
||||
if path == "" {
|
||||
return "."
|
||||
}
|
||||
|
||||
rooted := path[0] == '/'
|
||||
n := len(path)
|
||||
|
||||
// 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
|
||||
// it is the leading slash or it is a leading ../../.. prefix.
|
||||
out := lazybuf{s: path}
|
||||
r, dotdot := 0, 0
|
||||
if rooted {
|
||||
out.append('/')
|
||||
r, dotdot = 1, 1
|
||||
}
|
||||
|
||||
for r < n {
|
||||
switch {
|
||||
case path[r] == '/':
|
||||
// empty path element
|
||||
r++
|
||||
case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
|
||||
// . element
|
||||
r++
|
||||
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
|
||||
// .. element: remove to last /
|
||||
r += 2
|
||||
switch {
|
||||
case out.w > dotdot:
|
||||
// can backtrack
|
||||
out.w--
|
||||
for out.w > dotdot && out.index(out.w) != '/' {
|
||||
out.w--
|
||||
}
|
||||
case !rooted:
|
||||
// cannot backtrack, but not rooted, so append .. element.
|
||||
if out.w > 0 {
|
||||
out.append('/')
|
||||
}
|
||||
out.append('.')
|
||||
out.append('.')
|
||||
dotdot = out.w
|
||||
}
|
||||
default:
|
||||
// real path element.
|
||||
// add slash if needed
|
||||
if rooted && out.w != 1 || !rooted && out.w != 0 {
|
||||
out.append('/')
|
||||
}
|
||||
// copy element
|
||||
for ; r < n && path[r] != '/'; r++ {
|
||||
out.append(path[r])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn empty string into "."
|
||||
if out.w == 0 {
|
||||
return "."
|
||||
}
|
||||
|
||||
return out.string()
|
||||
}
|
||||
|
||||
// Split splits path immediately following the final slash,
|
||||
// separating it into a directory and file name component.
|
||||
// If there is no slash in path, Split returns an empty dir and
|
||||
// file set to path.
|
||||
// The returned values have the property that path = dir+file.
|
||||
func Split(path string) (dir, file string) {
|
||||
i := bytealg.LastIndexByteString(path, '/')
|
||||
return path[:i+1], path[i+1:]
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path,
|
||||
// separating them with slashes. Empty elements are ignored.
|
||||
// The result is Cleaned. However, if the argument list is
|
||||
// empty or all its elements are empty, Join returns
|
||||
// an empty string.
|
||||
func Join(elem ...string) string {
|
||||
size := 0
|
||||
for _, e := range elem {
|
||||
size += len(e)
|
||||
}
|
||||
if size == 0 {
|
||||
return ""
|
||||
}
|
||||
buf := make([]byte, 0, size+len(elem)-1)
|
||||
for _, e := range elem {
|
||||
if len(buf) > 0 || e != "" {
|
||||
if len(buf) > 0 {
|
||||
buf = append(buf, '/')
|
||||
}
|
||||
buf = append(buf, e...)
|
||||
}
|
||||
}
|
||||
return Clean(string(buf))
|
||||
}
|
||||
|
||||
// Ext returns the file name extension used by path.
|
||||
// The extension is the suffix beginning at the final dot
|
||||
// in the final slash-separated element of path;
|
||||
// it is empty if there is no dot.
|
||||
func Ext(path string) string {
|
||||
for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
|
||||
if path[i] == '.' {
|
||||
return path[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Base returns the last element of path.
|
||||
// Trailing slashes are removed before extracting the last element.
|
||||
// If the path is empty, Base returns ".".
|
||||
// If the path consists entirely of slashes, Base returns "/".
|
||||
func Base(path string) string {
|
||||
if path == "" {
|
||||
return "."
|
||||
}
|
||||
// Strip trailing slashes.
|
||||
for len(path) > 0 && path[len(path)-1] == '/' {
|
||||
path = path[0 : len(path)-1]
|
||||
}
|
||||
// Find the last element
|
||||
if i := bytealg.LastIndexByteString(path, '/'); i >= 0 {
|
||||
path = path[i+1:]
|
||||
}
|
||||
// If empty now, it had only slashes.
|
||||
if path == "" {
|
||||
return "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// IsAbs reports whether the path is absolute.
|
||||
func IsAbs(path string) bool {
|
||||
return len(path) > 0 && path[0] == '/'
|
||||
}
|
||||
|
||||
// Dir returns all but the last element of path, typically the path's directory.
|
||||
// After dropping the final element using [Split], the path is Cleaned and trailing
|
||||
// slashes are removed.
|
||||
// If the path is empty, Dir returns ".".
|
||||
// If the path consists entirely of slashes followed by non-slash bytes, Dir
|
||||
// returns a single slash. In any other case, the returned path does not end in a
|
||||
// slash.
|
||||
func Dir(path string) string {
|
||||
dir, _ := Split(path)
|
||||
return Clean(dir)
|
||||
}
|
||||
236
src/path/path_test.go
Normal file
236
src/path/path_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
// 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 path_test
|
||||
|
||||
import (
|
||||
. "path"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type PathTest struct {
|
||||
path, result string
|
||||
}
|
||||
|
||||
var cleantests = []PathTest{
|
||||
// Already clean
|
||||
{"", "."},
|
||||
{"abc", "abc"},
|
||||
{"abc/def", "abc/def"},
|
||||
{"a/b/c", "a/b/c"},
|
||||
{".", "."},
|
||||
{"..", ".."},
|
||||
{"../..", "../.."},
|
||||
{"../../abc", "../../abc"},
|
||||
{"/abc", "/abc"},
|
||||
{"/", "/"},
|
||||
|
||||
// Remove trailing slash
|
||||
{"abc/", "abc"},
|
||||
{"abc/def/", "abc/def"},
|
||||
{"a/b/c/", "a/b/c"},
|
||||
{"./", "."},
|
||||
{"../", ".."},
|
||||
{"../../", "../.."},
|
||||
{"/abc/", "/abc"},
|
||||
|
||||
// Remove doubled slash
|
||||
{"abc//def//ghi", "abc/def/ghi"},
|
||||
{"//abc", "/abc"},
|
||||
{"///abc", "/abc"},
|
||||
{"//abc//", "/abc"},
|
||||
{"abc//", "abc"},
|
||||
|
||||
// Remove . elements
|
||||
{"abc/./def", "abc/def"},
|
||||
{"/./abc/def", "/abc/def"},
|
||||
{"abc/.", "abc"},
|
||||
|
||||
// Remove .. elements
|
||||
{"abc/def/ghi/../jkl", "abc/def/jkl"},
|
||||
{"abc/def/../ghi/../jkl", "abc/jkl"},
|
||||
{"abc/def/..", "abc"},
|
||||
{"abc/def/../..", "."},
|
||||
{"/abc/def/../..", "/"},
|
||||
{"abc/def/../../..", ".."},
|
||||
{"/abc/def/../../..", "/"},
|
||||
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
|
||||
|
||||
// Combinations
|
||||
{"abc/./../def", "def"},
|
||||
{"abc//./../def", "def"},
|
||||
{"abc/../../././../def", "../../def"},
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
for _, test := range cleantests {
|
||||
if s := Clean(test.path); s != test.result {
|
||||
t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
|
||||
}
|
||||
if s := Clean(test.result); s != test.result {
|
||||
t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanMallocs(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping malloc count in short mode")
|
||||
}
|
||||
if runtime.GOMAXPROCS(0) > 1 {
|
||||
t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
|
||||
return
|
||||
}
|
||||
|
||||
for _, test := range cleantests {
|
||||
allocs := testing.AllocsPerRun(100, func() { Clean(test.result) })
|
||||
if allocs > 0 {
|
||||
t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SplitTest struct {
|
||||
path, dir, file string
|
||||
}
|
||||
|
||||
var splittests = []SplitTest{
|
||||
{"a/b", "a/", "b"},
|
||||
{"a/b/", "a/b/", ""},
|
||||
{"a/", "a/", ""},
|
||||
{"a", "", "a"},
|
||||
{"/", "/", ""},
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
for _, test := range splittests {
|
||||
if d, f := Split(test.path); d != test.dir || f != test.file {
|
||||
t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type JoinTest struct {
|
||||
elem []string
|
||||
path string
|
||||
}
|
||||
|
||||
var jointests = []JoinTest{
|
||||
// zero parameters
|
||||
{[]string{}, ""},
|
||||
|
||||
// one parameter
|
||||
{[]string{""}, ""},
|
||||
{[]string{"a"}, "a"},
|
||||
|
||||
// two parameters
|
||||
{[]string{"a", "b"}, "a/b"},
|
||||
{[]string{"a", ""}, "a"},
|
||||
{[]string{"", "b"}, "b"},
|
||||
{[]string{"/", "a"}, "/a"},
|
||||
{[]string{"/", ""}, "/"},
|
||||
{[]string{"a/", "b"}, "a/b"},
|
||||
{[]string{"a/", ""}, "a"},
|
||||
{[]string{"", ""}, ""},
|
||||
}
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
for _, test := range jointests {
|
||||
if p := Join(test.elem...); p != test.path {
|
||||
t.Errorf("Join(%q) = %q, want %q", test.elem, p, test.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ExtTest struct {
|
||||
path, ext string
|
||||
}
|
||||
|
||||
var exttests = []ExtTest{
|
||||
{"path.go", ".go"},
|
||||
{"path.pb.go", ".go"},
|
||||
{"a.dir/b", ""},
|
||||
{"a.dir/b.go", ".go"},
|
||||
{"a.dir/", ""},
|
||||
}
|
||||
|
||||
func TestExt(t *testing.T) {
|
||||
for _, test := range exttests {
|
||||
if x := Ext(test.path); x != test.ext {
|
||||
t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var basetests = []PathTest{
|
||||
// Already clean
|
||||
{"", "."},
|
||||
{".", "."},
|
||||
{"/.", "."},
|
||||
{"/", "/"},
|
||||
{"////", "/"},
|
||||
{"x/", "x"},
|
||||
{"abc", "abc"},
|
||||
{"abc/def", "def"},
|
||||
{"a/b/.x", ".x"},
|
||||
{"a/b/c.", "c."},
|
||||
{"a/b/c.x", "c.x"},
|
||||
}
|
||||
|
||||
func TestBase(t *testing.T) {
|
||||
for _, test := range basetests {
|
||||
if s := Base(test.path); s != test.result {
|
||||
t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dirtests = []PathTest{
|
||||
{"", "."},
|
||||
{".", "."},
|
||||
{"/.", "/"},
|
||||
{"/", "/"},
|
||||
{"////", "/"},
|
||||
{"/foo", "/"},
|
||||
{"x/", "x"},
|
||||
{"abc", "."},
|
||||
{"abc/def", "abc"},
|
||||
{"abc////def", "abc"},
|
||||
{"a/b/.x", "a/b"},
|
||||
{"a/b/c.", "a/b"},
|
||||
{"a/b/c.x", "a/b"},
|
||||
}
|
||||
|
||||
func TestDir(t *testing.T) {
|
||||
for _, test := range dirtests {
|
||||
if s := Dir(test.path); s != test.result {
|
||||
t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type IsAbsTest struct {
|
||||
path string
|
||||
isAbs bool
|
||||
}
|
||||
|
||||
var isAbsTests = []IsAbsTest{
|
||||
{"", false},
|
||||
{"/", true},
|
||||
{"/usr/bin/gcc", true},
|
||||
{"..", false},
|
||||
{"/a/../bb", true},
|
||||
{".", false},
|
||||
{"./", false},
|
||||
{"lala", false},
|
||||
}
|
||||
|
||||
func TestIsAbs(t *testing.T) {
|
||||
for _, test := range isAbsTests {
|
||||
if r := IsAbs(test.path); r != test.isAbs {
|
||||
t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user