]> Cypherpunks.ru repositories - gostls13.git/blob - src/path/filepath/path.go
path/filepath: add IsLocal
[gostls13.git] / src / path / filepath / path.go
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package filepath implements utility routines for manipulating filename paths
6 // in a way compatible with the target operating system-defined file paths.
7 //
8 // The filepath package uses either forward slashes or backslashes,
9 // depending on the operating system. To process paths such as URLs
10 // that always use forward slashes regardless of the operating
11 // system, see the path package.
12 package filepath
13
14 import (
15         "errors"
16         "io/fs"
17         "os"
18         "sort"
19         "strings"
20 )
21
22 // A lazybuf is a lazily constructed path buffer.
23 // It supports append, reading previously appended bytes,
24 // and retrieving the final string. It does not allocate a buffer
25 // to hold the output until that output diverges from s.
26 type lazybuf struct {
27         path       string
28         buf        []byte
29         w          int
30         volAndPath string
31         volLen     int
32 }
33
34 func (b *lazybuf) index(i int) byte {
35         if b.buf != nil {
36                 return b.buf[i]
37         }
38         return b.path[i]
39 }
40
41 func (b *lazybuf) append(c byte) {
42         if b.buf == nil {
43                 if b.w < len(b.path) && b.path[b.w] == c {
44                         b.w++
45                         return
46                 }
47                 b.buf = make([]byte, len(b.path))
48                 copy(b.buf, b.path[:b.w])
49         }
50         b.buf[b.w] = c
51         b.w++
52 }
53
54 func (b *lazybuf) string() string {
55         if b.buf == nil {
56                 return b.volAndPath[:b.volLen+b.w]
57         }
58         return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
59 }
60
61 const (
62         Separator     = os.PathSeparator
63         ListSeparator = os.PathListSeparator
64 )
65
66 // Clean returns the shortest path name equivalent to path
67 // by purely lexical processing. It applies the following rules
68 // iteratively until no further processing can be done:
69 //
70 //  1. Replace multiple Separator elements with a single one.
71 //  2. Eliminate each . path name element (the current directory).
72 //  3. Eliminate each inner .. path name element (the parent directory)
73 //     along with the non-.. element that precedes it.
74 //  4. Eliminate .. elements that begin a rooted path:
75 //     that is, replace "/.." by "/" at the beginning of a path,
76 //     assuming Separator is '/'.
77 //
78 // The returned path ends in a slash only if it represents a root directory,
79 // such as "/" on Unix or `C:\` on Windows.
80 //
81 // Finally, any occurrences of slash are replaced by Separator.
82 //
83 // If the result of this process is an empty string, Clean
84 // returns the string ".".
85 //
86 // See also Rob Pike, “Lexical File Names in Plan 9 or
87 // Getting Dot-Dot Right,”
88 // https://9p.io/sys/doc/lexnames.html
89 func Clean(path string) string {
90         originalPath := path
91         volLen := volumeNameLen(path)
92         path = path[volLen:]
93         if path == "" {
94                 if volLen > 1 && os.IsPathSeparator(originalPath[0]) && os.IsPathSeparator(originalPath[1]) {
95                         // should be UNC
96                         return FromSlash(originalPath)
97                 }
98                 return originalPath + "."
99         }
100         rooted := os.IsPathSeparator(path[0])
101
102         // Invariants:
103         //      reading from path; r is index of next byte to process.
104         //      writing to buf; w is index of next byte to write.
105         //      dotdot is index in buf where .. must stop, either because
106         //              it is the leading slash or it is a leading ../../.. prefix.
107         n := len(path)
108         out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
109         r, dotdot := 0, 0
110         if rooted {
111                 out.append(Separator)
112                 r, dotdot = 1, 1
113         }
114
115         for r < n {
116                 switch {
117                 case os.IsPathSeparator(path[r]):
118                         // empty path element
119                         r++
120                 case path[r] == '.' && r+1 == n:
121                         // . element
122                         r++
123                 case path[r] == '.' && os.IsPathSeparator(path[r+1]):
124                         // ./ element
125                         r++
126
127                         for r < len(path) && os.IsPathSeparator(path[r]) {
128                                 r++
129                         }
130                         if out.w == 0 && volumeNameLen(path[r:]) > 0 {
131                                 // When joining prefix "." and an absolute path on Windows,
132                                 // the prefix should not be removed.
133                                 out.append('.')
134                         }
135                 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
136                         // .. element: remove to last separator
137                         r += 2
138                         switch {
139                         case out.w > dotdot:
140                                 // can backtrack
141                                 out.w--
142                                 for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
143                                         out.w--
144                                 }
145                         case !rooted:
146                                 // cannot backtrack, but not rooted, so append .. element.
147                                 if out.w > 0 {
148                                         out.append(Separator)
149                                 }
150                                 out.append('.')
151                                 out.append('.')
152                                 dotdot = out.w
153                         }
154                 default:
155                         // real path element.
156                         // add slash if needed
157                         if rooted && out.w != 1 || !rooted && out.w != 0 {
158                                 out.append(Separator)
159                         }
160                         // copy element
161                         for ; r < n && !os.IsPathSeparator(path[r]); r++ {
162                                 out.append(path[r])
163                         }
164                 }
165         }
166
167         // Turn empty string into "."
168         if out.w == 0 {
169                 out.append('.')
170         }
171
172         return FromSlash(out.string())
173 }
174
175 // IsLocal reports whether path, using lexical analysis only, has all of these properties:
176 //
177 //   - is within the subtree rooted at the directory in which path is evaluated
178 //   - is not an absolute path
179 //   - is not empty
180 //   - on Windows, is not a reserved name such as "NUL"
181 //
182 // If IsLocal(path) returns true, then
183 // Join(base, path) will always produce a path contained within base and
184 // Clean(path) will always produce an unrooted path with no ".." path elements.
185 //
186 // IsLocal is a purely lexical operation.
187 // In particular, it does not account for the effect of any symbolic links
188 // that may exist in the filesystem.
189 func IsLocal(path string) bool {
190         return isLocal(path)
191 }
192
193 func unixIsLocal(path string) bool {
194         if IsAbs(path) || path == "" {
195                 return false
196         }
197         hasDots := false
198         for p := path; p != ""; {
199                 var part string
200                 part, p, _ = strings.Cut(p, "/")
201                 if part == "." || part == ".." {
202                         hasDots = true
203                         break
204                 }
205         }
206         if hasDots {
207                 path = Clean(path)
208         }
209         if path == ".." || strings.HasPrefix(path, "../") {
210                 return false
211         }
212         return true
213 }
214
215 // ToSlash returns the result of replacing each separator character
216 // in path with a slash ('/') character. Multiple separators are
217 // replaced by multiple slashes.
218 func ToSlash(path string) string {
219         if Separator == '/' {
220                 return path
221         }
222         return strings.ReplaceAll(path, string(Separator), "/")
223 }
224
225 // FromSlash returns the result of replacing each slash ('/') character
226 // in path with a separator character. Multiple slashes are replaced
227 // by multiple separators.
228 func FromSlash(path string) string {
229         if Separator == '/' {
230                 return path
231         }
232         return strings.ReplaceAll(path, "/", string(Separator))
233 }
234
235 // SplitList splits a list of paths joined by the OS-specific ListSeparator,
236 // usually found in PATH or GOPATH environment variables.
237 // Unlike strings.Split, SplitList returns an empty slice when passed an empty
238 // string.
239 func SplitList(path string) []string {
240         return splitList(path)
241 }
242
243 // Split splits path immediately following the final Separator,
244 // separating it into a directory and file name component.
245 // If there is no Separator in path, Split returns an empty dir
246 // and file set to path.
247 // The returned values have the property that path = dir+file.
248 func Split(path string) (dir, file string) {
249         vol := VolumeName(path)
250         i := len(path) - 1
251         for i >= len(vol) && !os.IsPathSeparator(path[i]) {
252                 i--
253         }
254         return path[:i+1], path[i+1:]
255 }
256
257 // Join joins any number of path elements into a single path,
258 // separating them with an OS specific Separator. Empty elements
259 // are ignored. The result is Cleaned. However, if the argument
260 // list is empty or all its elements are empty, Join returns
261 // an empty string.
262 // On Windows, the result will only be a UNC path if the first
263 // non-empty element is a UNC path.
264 func Join(elem ...string) string {
265         return join(elem)
266 }
267
268 // Ext returns the file name extension used by path.
269 // The extension is the suffix beginning at the final dot
270 // in the final element of path; it is empty if there is
271 // no dot.
272 func Ext(path string) string {
273         for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
274                 if path[i] == '.' {
275                         return path[i:]
276                 }
277         }
278         return ""
279 }
280
281 // EvalSymlinks returns the path name after the evaluation of any symbolic
282 // links.
283 // If path is relative the result will be relative to the current directory,
284 // unless one of the components is an absolute symbolic link.
285 // EvalSymlinks calls Clean on the result.
286 func EvalSymlinks(path string) (string, error) {
287         return evalSymlinks(path)
288 }
289
290 // Abs returns an absolute representation of path.
291 // If the path is not absolute it will be joined with the current
292 // working directory to turn it into an absolute path. The absolute
293 // path name for a given file is not guaranteed to be unique.
294 // Abs calls Clean on the result.
295 func Abs(path string) (string, error) {
296         return abs(path)
297 }
298
299 func unixAbs(path string) (string, error) {
300         if IsAbs(path) {
301                 return Clean(path), nil
302         }
303         wd, err := os.Getwd()
304         if err != nil {
305                 return "", err
306         }
307         return Join(wd, path), nil
308 }
309
310 // Rel returns a relative path that is lexically equivalent to targpath when
311 // joined to basepath with an intervening separator. That is,
312 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
313 // On success, the returned path will always be relative to basepath,
314 // even if basepath and targpath share no elements.
315 // An error is returned if targpath can't be made relative to basepath or if
316 // knowing the current working directory would be necessary to compute it.
317 // Rel calls Clean on the result.
318 func Rel(basepath, targpath string) (string, error) {
319         baseVol := VolumeName(basepath)
320         targVol := VolumeName(targpath)
321         base := Clean(basepath)
322         targ := Clean(targpath)
323         if sameWord(targ, base) {
324                 return ".", nil
325         }
326         base = base[len(baseVol):]
327         targ = targ[len(targVol):]
328         if base == "." {
329                 base = ""
330         } else if base == "" && volumeNameLen(baseVol) > 2 /* isUNC */ {
331                 // Treat any targetpath matching `\\host\share` basepath as absolute path.
332                 base = string(Separator)
333         }
334
335         // Can't use IsAbs - `\a` and `a` are both relative in Windows.
336         baseSlashed := len(base) > 0 && base[0] == Separator
337         targSlashed := len(targ) > 0 && targ[0] == Separator
338         if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
339                 return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
340         }
341         // Position base[b0:bi] and targ[t0:ti] at the first differing elements.
342         bl := len(base)
343         tl := len(targ)
344         var b0, bi, t0, ti int
345         for {
346                 for bi < bl && base[bi] != Separator {
347                         bi++
348                 }
349                 for ti < tl && targ[ti] != Separator {
350                         ti++
351                 }
352                 if !sameWord(targ[t0:ti], base[b0:bi]) {
353                         break
354                 }
355                 if bi < bl {
356                         bi++
357                 }
358                 if ti < tl {
359                         ti++
360                 }
361                 b0 = bi
362                 t0 = ti
363         }
364         if base[b0:bi] == ".." {
365                 return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
366         }
367         if b0 != bl {
368                 // Base elements left. Must go up before going down.
369                 seps := strings.Count(base[b0:bl], string(Separator))
370                 size := 2 + seps*3
371                 if tl != t0 {
372                         size += 1 + tl - t0
373                 }
374                 buf := make([]byte, size)
375                 n := copy(buf, "..")
376                 for i := 0; i < seps; i++ {
377                         buf[n] = Separator
378                         copy(buf[n+1:], "..")
379                         n += 3
380                 }
381                 if t0 != tl {
382                         buf[n] = Separator
383                         copy(buf[n+1:], targ[t0:])
384                 }
385                 return string(buf), nil
386         }
387         return targ[t0:], nil
388 }
389
390 // SkipDir is used as a return value from WalkFuncs to indicate that
391 // the directory named in the call is to be skipped. It is not returned
392 // as an error by any function.
393 var SkipDir error = fs.SkipDir
394
395 // SkipAll is used as a return value from WalkFuncs to indicate that
396 // all remaining files and directories are to be skipped. It is not returned
397 // as an error by any function.
398 var SkipAll error = fs.SkipAll
399
400 // WalkFunc is the type of the function called by Walk to visit each
401 // file or directory.
402 //
403 // The path argument contains the argument to Walk as a prefix.
404 // That is, if Walk is called with root argument "dir" and finds a file
405 // named "a" in that directory, the walk function will be called with
406 // argument "dir/a".
407 //
408 // The directory and file are joined with Join, which may clean the
409 // directory name: if Walk is called with the root argument "x/../dir"
410 // and finds a file named "a" in that directory, the walk function will
411 // be called with argument "dir/a", not "x/../dir/a".
412 //
413 // The info argument is the fs.FileInfo for the named path.
414 //
415 // The error result returned by the function controls how Walk continues.
416 // If the function returns the special value SkipDir, Walk skips the
417 // current directory (path if info.IsDir() is true, otherwise path's
418 // parent directory). If the function returns the special value SkipAll,
419 // Walk skips all remaining files and directories. Otherwise, if the function
420 // returns a non-nil error, Walk stops entirely and returns that error.
421 //
422 // The err argument reports an error related to path, signaling that Walk
423 // will not walk into that directory. The function can decide how to
424 // handle that error; as described earlier, returning the error will
425 // cause Walk to stop walking the entire tree.
426 //
427 // Walk calls the function with a non-nil err argument in two cases.
428 //
429 // First, if an os.Lstat on the root directory or any directory or file
430 // in the tree fails, Walk calls the function with path set to that
431 // directory or file's path, info set to nil, and err set to the error
432 // from os.Lstat.
433 //
434 // Second, if a directory's Readdirnames method fails, Walk calls the
435 // function with path set to the directory's path, info, set to an
436 // fs.FileInfo describing the directory, and err set to the error from
437 // Readdirnames.
438 type WalkFunc func(path string, info fs.FileInfo, err error) error
439
440 var lstat = os.Lstat // for testing
441
442 // walkDir recursively descends path, calling walkDirFn.
443 func walkDir(path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
444         if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
445                 if err == SkipDir && d.IsDir() {
446                         // Successfully skipped directory.
447                         err = nil
448                 }
449                 return err
450         }
451
452         dirs, err := readDir(path)
453         if err != nil {
454                 // Second call, to report ReadDir error.
455                 err = walkDirFn(path, d, err)
456                 if err != nil {
457                         if err == SkipDir && d.IsDir() {
458                                 err = nil
459                         }
460                         return err
461                 }
462         }
463
464         for _, d1 := range dirs {
465                 path1 := Join(path, d1.Name())
466                 if err := walkDir(path1, d1, walkDirFn); err != nil {
467                         if err == SkipDir {
468                                 break
469                         }
470                         return err
471                 }
472         }
473         return nil
474 }
475
476 // walk recursively descends path, calling walkFn.
477 func walk(path string, info fs.FileInfo, walkFn WalkFunc) error {
478         if !info.IsDir() {
479                 return walkFn(path, info, nil)
480         }
481
482         names, err := readDirNames(path)
483         err1 := walkFn(path, info, err)
484         // If err != nil, walk can't walk into this directory.
485         // err1 != nil means walkFn want walk to skip this directory or stop walking.
486         // Therefore, if one of err and err1 isn't nil, walk will return.
487         if err != nil || err1 != nil {
488                 // The caller's behavior is controlled by the return value, which is decided
489                 // by walkFn. walkFn may ignore err and return nil.
490                 // If walkFn returns SkipDir or SkipAll, it will be handled by the caller.
491                 // So walk should return whatever walkFn returns.
492                 return err1
493         }
494
495         for _, name := range names {
496                 filename := Join(path, name)
497                 fileInfo, err := lstat(filename)
498                 if err != nil {
499                         if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
500                                 return err
501                         }
502                 } else {
503                         err = walk(filename, fileInfo, walkFn)
504                         if err != nil {
505                                 if !fileInfo.IsDir() || err != SkipDir {
506                                         return err
507                                 }
508                         }
509                 }
510         }
511         return nil
512 }
513
514 // WalkDir walks the file tree rooted at root, calling fn for each file or
515 // directory in the tree, including root.
516 //
517 // All errors that arise visiting files and directories are filtered by fn:
518 // see the fs.WalkDirFunc documentation for details.
519 //
520 // The files are walked in lexical order, which makes the output deterministic
521 // but requires WalkDir to read an entire directory into memory before proceeding
522 // to walk that directory.
523 //
524 // WalkDir does not follow symbolic links.
525 //
526 // WalkDir calls fn with paths that use the separator character appropriate
527 // for the operating system. This is unlike [io/fs.WalkDir], which always
528 // uses slash separated paths.
529 func WalkDir(root string, fn fs.WalkDirFunc) error {
530         info, err := os.Lstat(root)
531         if err != nil {
532                 err = fn(root, nil, err)
533         } else {
534                 err = walkDir(root, &statDirEntry{info}, fn)
535         }
536         if err == SkipDir || err == SkipAll {
537                 return nil
538         }
539         return err
540 }
541
542 type statDirEntry struct {
543         info fs.FileInfo
544 }
545
546 func (d *statDirEntry) Name() string               { return d.info.Name() }
547 func (d *statDirEntry) IsDir() bool                { return d.info.IsDir() }
548 func (d *statDirEntry) Type() fs.FileMode          { return d.info.Mode().Type() }
549 func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
550
551 // Walk walks the file tree rooted at root, calling fn for each file or
552 // directory in the tree, including root.
553 //
554 // All errors that arise visiting files and directories are filtered by fn:
555 // see the WalkFunc documentation for details.
556 //
557 // The files are walked in lexical order, which makes the output deterministic
558 // but requires Walk to read an entire directory into memory before proceeding
559 // to walk that directory.
560 //
561 // Walk does not follow symbolic links.
562 //
563 // Walk is less efficient than WalkDir, introduced in Go 1.16,
564 // which avoids calling os.Lstat on every visited file or directory.
565 func Walk(root string, fn WalkFunc) error {
566         info, err := os.Lstat(root)
567         if err != nil {
568                 err = fn(root, nil, err)
569         } else {
570                 err = walk(root, info, fn)
571         }
572         if err == SkipDir || err == SkipAll {
573                 return nil
574         }
575         return err
576 }
577
578 // readDir reads the directory named by dirname and returns
579 // a sorted list of directory entries.
580 func readDir(dirname string) ([]fs.DirEntry, error) {
581         f, err := os.Open(dirname)
582         if err != nil {
583                 return nil, err
584         }
585         dirs, err := f.ReadDir(-1)
586         f.Close()
587         if err != nil {
588                 return nil, err
589         }
590         sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
591         return dirs, nil
592 }
593
594 // readDirNames reads the directory named by dirname and returns
595 // a sorted list of directory entry names.
596 func readDirNames(dirname string) ([]string, error) {
597         f, err := os.Open(dirname)
598         if err != nil {
599                 return nil, err
600         }
601         names, err := f.Readdirnames(-1)
602         f.Close()
603         if err != nil {
604                 return nil, err
605         }
606         sort.Strings(names)
607         return names, nil
608 }
609
610 // Base returns the last element of path.
611 // Trailing path separators are removed before extracting the last element.
612 // If the path is empty, Base returns ".".
613 // If the path consists entirely of separators, Base returns a single separator.
614 func Base(path string) string {
615         if path == "" {
616                 return "."
617         }
618         // Strip trailing slashes.
619         for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
620                 path = path[0 : len(path)-1]
621         }
622         // Throw away volume name
623         path = path[len(VolumeName(path)):]
624         // Find the last element
625         i := len(path) - 1
626         for i >= 0 && !os.IsPathSeparator(path[i]) {
627                 i--
628         }
629         if i >= 0 {
630                 path = path[i+1:]
631         }
632         // If empty now, it had only slashes.
633         if path == "" {
634                 return string(Separator)
635         }
636         return path
637 }
638
639 // Dir returns all but the last element of path, typically the path's directory.
640 // After dropping the final element, Dir calls Clean on the path and trailing
641 // slashes are removed.
642 // If the path is empty, Dir returns ".".
643 // If the path consists entirely of separators, Dir returns a single separator.
644 // The returned path does not end in a separator unless it is the root directory.
645 func Dir(path string) string {
646         vol := VolumeName(path)
647         i := len(path) - 1
648         for i >= len(vol) && !os.IsPathSeparator(path[i]) {
649                 i--
650         }
651         dir := Clean(path[len(vol) : i+1])
652         if dir == "." && len(vol) > 2 {
653                 // must be UNC
654                 return vol
655         }
656         return vol + dir
657 }
658
659 // VolumeName returns leading volume name.
660 // Given "C:\foo\bar" it returns "C:" on Windows.
661 // Given "\\host\share\foo" it returns "\\host\share".
662 // On other platforms it returns "".
663 func VolumeName(path string) string {
664         return FromSlash(path[:volumeNameLen(path)])
665 }