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.
12package filepath
13
14import (
15	"errors"
16	"os"
17	"sort"
18	"strings"
19)
20
21// A lazybuf is a lazily constructed path buffer.
22// It supports append, reading previously appended bytes,
23// and retrieving the final string. It does not allocate a buffer
24// to hold the output until that output diverges from s.
25type lazybuf struct {
26	path       string
27	buf        []byte
28	w          int
29	volAndPath string
30	volLen     int
31}
32
33func (b *lazybuf) index(i int) byte {
34	if b.buf != nil {
35		return b.buf[i]
36	}
37	return b.path[i]
38}
39
40func (b *lazybuf) append(c byte) {
41	if b.buf == nil {
42		if b.w < len(b.path) && b.path[b.w] == c {
43			b.w++
44			return
45		}
46		b.buf = make([]byte, len(b.path))
47		copy(b.buf, b.path[:b.w])
48	}
49	b.buf[b.w] = c
50	b.w++
51}
52
53func (b *lazybuf) string() string {
54	if b.buf == nil {
55		return b.volAndPath[:b.volLen+b.w]
56	}
57	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
58}
59
60const (
61	Separator     = os.PathSeparator
62	ListSeparator = os.PathListSeparator
63)
64
65// Clean returns the shortest path name equivalent to path
66// by purely lexical processing. It applies the following rules
67// iteratively until no further processing can be done:
68//
69//	1. Replace multiple Separator elements with a single one.
70//	2. Eliminate each . path name element (the current directory).
71//	3. Eliminate each inner .. path name element (the parent directory)
72//	   along with the non-.. element that precedes it.
73//	4. Eliminate .. elements that begin a rooted path:
74//	   that is, replace "/.." by "/" at the beginning of a path,
75//	   assuming Separator is '/'.
76//
77// The returned path ends in a slash only if it represents a root directory,
78// such as "/" on Unix or `C:\` on Windows.
79//
80// Finally, any occurrences of slash are replaced by Separator.
81//
82// If the result of this process is an empty string, Clean
83// returns the string ".".
84//
85// See also Rob Pike, ``Lexical File Names in Plan 9 or
86// Getting Dot-Dot Right,''
87// https://9p.io/sys/doc/lexnames.html
88func Clean(path string) string {
89	originalPath := path
90	volLen := volumeNameLen(path)
91	path = path[volLen:]
92	if path == "" {
93		if volLen > 1 && originalPath[1] != ':' {
94			// should be UNC
95			return FromSlash(originalPath)
96		}
97		return originalPath + "."
98	}
99	rooted := os.IsPathSeparator(path[0])
100
101	// Invariants:
102	//	reading from path; r is index of next byte to process.
103	//	writing to buf; w is index of next byte to write.
104	//	dotdot is index in buf where .. must stop, either because
105	//		it is the leading slash or it is a leading ../../.. prefix.
106	n := len(path)
107	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
108	r, dotdot := 0, 0
109	if rooted {
110		out.append(Separator)
111		r, dotdot = 1, 1
112	}
113
114	for r < n {
115		switch {
116		case os.IsPathSeparator(path[r]):
117			// empty path element
118			r++
119		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
120			// . element
121			r++
122		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
123			// .. element: remove to last separator
124			r += 2
125			switch {
126			case out.w > dotdot:
127				// can backtrack
128				out.w--
129				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
130					out.w--
131				}
132			case !rooted:
133				// cannot backtrack, but not rooted, so append .. element.
134				if out.w > 0 {
135					out.append(Separator)
136				}
137				out.append('.')
138				out.append('.')
139				dotdot = out.w
140			}
141		default:
142			// real path element.
143			// add slash if needed
144			if rooted && out.w != 1 || !rooted && out.w != 0 {
145				out.append(Separator)
146			}
147			// copy element
148			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
149				out.append(path[r])
150			}
151		}
152	}
153
154	// Turn empty string into "."
155	if out.w == 0 {
156		out.append('.')
157	}
158
159	return FromSlash(out.string())
160}
161
162// ToSlash returns the result of replacing each separator character
163// in path with a slash ('/') character. Multiple separators are
164// replaced by multiple slashes.
165func ToSlash(path string) string {
166	if Separator == '/' {
167		return path
168	}
169	return strings.ReplaceAll(path, string(Separator), "/")
170}
171
172// FromSlash returns the result of replacing each slash ('/') character
173// in path with a separator character. Multiple slashes are replaced
174// by multiple separators.
175func FromSlash(path string) string {
176	if Separator == '/' {
177		return path
178	}
179	return strings.ReplaceAll(path, "/", string(Separator))
180}
181
182// SplitList splits a list of paths joined by the OS-specific ListSeparator,
183// usually found in PATH or GOPATH environment variables.
184// Unlike strings.Split, SplitList returns an empty slice when passed an empty
185// string.
186func SplitList(path string) []string {
187	return splitList(path)
188}
189
190// Split splits path immediately following the final Separator,
191// separating it into a directory and file name component.
192// If there is no Separator in path, Split returns an empty dir
193// and file set to path.
194// The returned values have the property that path = dir+file.
195func Split(path string) (dir, file string) {
196	vol := VolumeName(path)
197	i := len(path) - 1
198	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
199		i--
200	}
201	return path[:i+1], path[i+1:]
202}
203
204// Join joins any number of path elements into a single path,
205// separating them with an OS specific Separator. Empty elements
206// are ignored. The result is Cleaned. However, if the argument
207// list is empty or all its elements are empty, Join returns
208// an empty string.
209// On Windows, the result will only be a UNC path if the first
210// non-empty element is a UNC path.
211func Join(elem ...string) string {
212	return join(elem)
213}
214
215// Ext returns the file name extension used by path.
216// The extension is the suffix beginning at the final dot
217// in the final element of path; it is empty if there is
218// no dot.
219func Ext(path string) string {
220	for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
221		if path[i] == '.' {
222			return path[i:]
223		}
224	}
225	return ""
226}
227
228// EvalSymlinks returns the path name after the evaluation of any symbolic
229// links.
230// If path is relative the result will be relative to the current directory,
231// unless one of the components is an absolute symbolic link.
232// EvalSymlinks calls Clean on the result.
233func EvalSymlinks(path string) (string, error) {
234	return evalSymlinks(path)
235}
236
237// Abs returns an absolute representation of path.
238// If the path is not absolute it will be joined with the current
239// working directory to turn it into an absolute path. The absolute
240// path name for a given file is not guaranteed to be unique.
241// Abs calls Clean on the result.
242func Abs(path string) (string, error) {
243	return abs(path)
244}
245
246func unixAbs(path string) (string, error) {
247	if IsAbs(path) {
248		return Clean(path), nil
249	}
250	wd, err := os.Getwd()
251	if err != nil {
252		return "", err
253	}
254	return Join(wd, path), nil
255}
256
257// Rel returns a relative path that is lexically equivalent to targpath when
258// joined to basepath with an intervening separator. That is,
259// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
260// On success, the returned path will always be relative to basepath,
261// even if basepath and targpath share no elements.
262// An error is returned if targpath can't be made relative to basepath or if
263// knowing the current working directory would be necessary to compute it.
264// Rel calls Clean on the result.
265func Rel(basepath, targpath string) (string, error) {
266	baseVol := VolumeName(basepath)
267	targVol := VolumeName(targpath)
268	base := Clean(basepath)
269	targ := Clean(targpath)
270	if sameWord(targ, base) {
271		return ".", nil
272	}
273	base = base[len(baseVol):]
274	targ = targ[len(targVol):]
275	if base == "." {
276		base = ""
277	}
278	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
279	baseSlashed := len(base) > 0 && base[0] == Separator
280	targSlashed := len(targ) > 0 && targ[0] == Separator
281	if baseSlashed != targSlashed || !sameWord(baseVol, targVol) {
282		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
283	}
284	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
285	bl := len(base)
286	tl := len(targ)
287	var b0, bi, t0, ti int
288	for {
289		for bi < bl && base[bi] != Separator {
290			bi++
291		}
292		for ti < tl && targ[ti] != Separator {
293			ti++
294		}
295		if !sameWord(targ[t0:ti], base[b0:bi]) {
296			break
297		}
298		if bi < bl {
299			bi++
300		}
301		if ti < tl {
302			ti++
303		}
304		b0 = bi
305		t0 = ti
306	}
307	if base[b0:bi] == ".." {
308		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
309	}
310	if b0 != bl {
311		// Base elements left. Must go up before going down.
312		seps := strings.Count(base[b0:bl], string(Separator))
313		size := 2 + seps*3
314		if tl != t0 {
315			size += 1 + tl - t0
316		}
317		buf := make([]byte, size)
318		n := copy(buf, "..")
319		for i := 0; i < seps; i++ {
320			buf[n] = Separator
321			copy(buf[n+1:], "..")
322			n += 3
323		}
324		if t0 != tl {
325			buf[n] = Separator
326			copy(buf[n+1:], targ[t0:])
327		}
328		return string(buf), nil
329	}
330	return targ[t0:], nil
331}
332
333// SkipDir is used as a return value from WalkFuncs to indicate that
334// the directory named in the call is to be skipped. It is not returned
335// as an error by any function.
336var SkipDir = errors.New("skip this directory")
337
338// WalkFunc is the type of the function called for each file or directory
339// visited by Walk. The path argument contains the argument to Walk as a
340// prefix; that is, if Walk is called with "dir", which is a directory
341// containing the file "a", the walk function will be called with argument
342// "dir/a". The info argument is the os.FileInfo for the named path.
343//
344// If there was a problem walking to the file or directory named by path, the
345// incoming error will describe the problem and the function can decide how
346// to handle that error (and Walk will not descend into that directory). In the
347// case of an error, the info argument will be nil. If an error is returned,
348// processing stops. The sole exception is when the function returns the special
349// value SkipDir. If the function returns SkipDir when invoked on a directory,
350// Walk skips the directory's contents entirely. If the function returns SkipDir
351// when invoked on a non-directory file, Walk skips the remaining files in the
352// containing directory.
353type WalkFunc func(path string, info os.FileInfo, err error) error
354
355var lstat = os.Lstat // for testing
356
357// walk recursively descends path, calling walkFn.
358func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
359	if !info.IsDir() {
360		return walkFn(path, info, nil)
361	}
362
363	names, err := readDirNames(path)
364	err1 := walkFn(path, info, err)
365	// If err != nil, walk can't walk into this directory.
366	// err1 != nil means walkFn want walk to skip this directory or stop walking.
367	// Therefore, if one of err and err1 isn't nil, walk will return.
368	if err != nil || err1 != nil {
369		// The caller's behavior is controlled by the return value, which is decided
370		// by walkFn. walkFn may ignore err and return nil.
371		// If walkFn returns SkipDir, it will be handled by the caller.
372		// So walk should return whatever walkFn returns.
373		return err1
374	}
375
376	for _, name := range names {
377		filename := Join(path, name)
378		fileInfo, err := lstat(filename)
379		if err != nil {
380			if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
381				return err
382			}
383		} else {
384			err = walk(filename, fileInfo, walkFn)
385			if err != nil {
386				if !fileInfo.IsDir() || err != SkipDir {
387					return err
388				}
389			}
390		}
391	}
392	return nil
393}
394
395// Walk walks the file tree rooted at root, calling walkFn for each file or
396// directory in the tree, including root. All errors that arise visiting files
397// and directories are filtered by walkFn. The files are walked in lexical
398// order, which makes the output deterministic but means that for very
399// large directories Walk can be inefficient.
400// Walk does not follow symbolic links.
401func Walk(root string, walkFn WalkFunc) error {
402	info, err := os.Lstat(root)
403	if err != nil {
404		err = walkFn(root, nil, err)
405	} else {
406		err = walk(root, info, walkFn)
407	}
408	if err == SkipDir {
409		return nil
410	}
411	return err
412}
413
414// readDirNames reads the directory named by dirname and returns
415// a sorted list of directory entries.
416func readDirNames(dirname string) ([]string, error) {
417	f, err := os.Open(dirname)
418	if err != nil {
419		return nil, err
420	}
421	names, err := f.Readdirnames(-1)
422	f.Close()
423	if err != nil {
424		return nil, err
425	}
426	sort.Strings(names)
427	return names, nil
428}
429
430// Base returns the last element of path.
431// Trailing path separators are removed before extracting the last element.
432// If the path is empty, Base returns ".".
433// If the path consists entirely of separators, Base returns a single separator.
434func Base(path string) string {
435	if path == "" {
436		return "."
437	}
438	// Strip trailing slashes.
439	for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
440		path = path[0 : len(path)-1]
441	}
442	// Throw away volume name
443	path = path[len(VolumeName(path)):]
444	// Find the last element
445	i := len(path) - 1
446	for i >= 0 && !os.IsPathSeparator(path[i]) {
447		i--
448	}
449	if i >= 0 {
450		path = path[i+1:]
451	}
452	// If empty now, it had only slashes.
453	if path == "" {
454		return string(Separator)
455	}
456	return path
457}
458
459// Dir returns all but the last element of path, typically the path's directory.
460// After dropping the final element, Dir calls Clean on the path and trailing
461// slashes are removed.
462// If the path is empty, Dir returns ".".
463// If the path consists entirely of separators, Dir returns a single separator.
464// The returned path does not end in a separator unless it is the root directory.
465func Dir(path string) string {
466	vol := VolumeName(path)
467	i := len(path) - 1
468	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
469		i--
470	}
471	dir := Clean(path[len(vol) : i+1])
472	if dir == "." && len(vol) > 2 {
473		// must be UNC
474		return vol
475	}
476	return vol + dir
477}
478
479// VolumeName returns leading volume name.
480// Given "C:\foo\bar" it returns "C:" on Windows.
481// Given "\\host\share\foo" it returns "\\host\share".
482// On other platforms it returns "".
483func VolumeName(path string) string {
484	return path[:volumeNameLen(path)]
485}
486