// 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 downloader import ( "io/fs" "os" "runtime" "strings" ) // 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 { path string buf []byte w int volAndPath string volLen int } func (b *lazybuf) index(i int) byte { if b.buf != nil { return b.buf[i] } return b.path[i] } func (b *lazybuf) append(c byte) { if b.buf == nil { if b.w < len(b.path) && b.path[b.w] == c { b.w++ return } b.buf = make([]byte, len(b.path)) copy(b.buf, b.path[:b.w]) } b.buf[b.w] = c b.w++ } func (b *lazybuf) string() string { if b.buf == nil { return b.volAndPath[:b.volLen+b.w] } return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) } 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 ".". // // 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 { originalPath := path volLen := volumeNameLen(path) path = path[volLen:] if path == "" { if volLen > 1 && os.IsPathSeparator(originalPath[0]) && os.IsPathSeparator(originalPath[1]) { // should be UNC return FromSlash(originalPath) } return originalPath + "." } rooted := os.IsPathSeparator(path[0]) // Invariants: // reading from path; r is index of next byte to process. // writing to buf; w is index of next byte to write. // dotdot is index in buf where .. must stop, either because // it is the leading slash or it is a leading ../../.. prefix. n := len(path) out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} r, dotdot := 0, 0 if rooted { out.append(Separator) r, dotdot = 1, 1 } for r < n { switch { case os.IsPathSeparator(path[r]): // empty path element r++ case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): // . element r++ case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): // .. element: remove to last separator r += 2 switch { case out.w > dotdot: // can backtrack out.w-- for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { out.w-- } case !rooted: // cannot backtrack, but not rooted, so append .. element. if out.w > 0 { out.append(Separator) } 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(Separator) } // If a ':' appears in the path element at the start of a Windows path, // insert a .\ at the beginning to avoid converting relative paths // like a/../c: into c:. if runtime.GOOS == "windows" && out.w == 0 && out.volLen == 0 && r != 0 { for i := r; i < n && !os.IsPathSeparator(path[i]); i++ { if path[i] == ':' { out.append('.') out.append(Separator) break } } } // copy element for ; r < n && !os.IsPathSeparator(path[r]); r++ { out.append(path[r]) } } } // Turn empty string into "." if out.w == 0 { out.append('.') } return FromSlash(out.string()) } func unixIsLocal(path string) bool { if IsAbs(path) || path == "" { return false } hasDots := false for p := path; p != ""; { var part string part, p, _ = strings.Cut(p, "/") if part == "." || part == ".." { hasDots = true break } } if hasDots { path = Clean(path) } if path == ".." || strings.HasPrefix(path, "../") { return false } return true } // FromSlash returns the result of replacing each slash ('/') character // in path with a separator character. Multiple slashes are replaced // by multiple separators. func FromSlash(path string) string { if Separator == '/' { return path } return strings.ReplaceAll(path, "/", string(Separator)) } // 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) } // nolint 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 } // SkipDir is used as a return value from WalkFuncs 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 // 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