github.com/ledgerwatch/erigon-lib@v1.0.0/downloader/path.go (about)

     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 downloader
    13  
    14  import (
    15  	"io/fs"
    16  	"os"
    17  	"runtime"
    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.
    25  type lazybuf struct {
    26  	path       string
    27  	buf        []byte
    28  	w          int
    29  	volAndPath string
    30  	volLen     int
    31  }
    32  
    33  func (b *lazybuf) index(i int) byte {
    34  	if b.buf != nil {
    35  		return b.buf[i]
    36  	}
    37  	return b.path[i]
    38  }
    39  
    40  func (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  
    53  func (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  
    60  const (
    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
    88  func Clean(path string) string {
    89  	originalPath := path
    90  	volLen := volumeNameLen(path)
    91  	path = path[volLen:]
    92  	if path == "" {
    93  		if volLen > 1 && os.IsPathSeparator(originalPath[0]) && os.IsPathSeparator(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  			// If a ':' appears in the path element at the start of a Windows path,
   148  			// insert a .\ at the beginning to avoid converting relative paths
   149  			// like a/../c: into c:.
   150  			if runtime.GOOS == "windows" && out.w == 0 && out.volLen == 0 && r != 0 {
   151  				for i := r; i < n && !os.IsPathSeparator(path[i]); i++ {
   152  					if path[i] == ':' {
   153  						out.append('.')
   154  						out.append(Separator)
   155  						break
   156  					}
   157  				}
   158  			}
   159  			// copy element
   160  			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
   161  				out.append(path[r])
   162  			}
   163  		}
   164  	}
   165  
   166  	// Turn empty string into "."
   167  	if out.w == 0 {
   168  		out.append('.')
   169  	}
   170  
   171  	return FromSlash(out.string())
   172  }
   173  
   174  func unixIsLocal(path string) bool {
   175  	if IsAbs(path) || path == "" {
   176  		return false
   177  	}
   178  	hasDots := false
   179  	for p := path; p != ""; {
   180  		var part string
   181  		part, p, _ = strings.Cut(p, "/")
   182  		if part == "." || part == ".." {
   183  			hasDots = true
   184  			break
   185  		}
   186  	}
   187  	if hasDots {
   188  		path = Clean(path)
   189  	}
   190  	if path == ".." || strings.HasPrefix(path, "../") {
   191  		return false
   192  	}
   193  	return true
   194  }
   195  
   196  // FromSlash returns the result of replacing each slash ('/') character
   197  // in path with a separator character. Multiple slashes are replaced
   198  // by multiple separators.
   199  func FromSlash(path string) string {
   200  	if Separator == '/' {
   201  		return path
   202  	}
   203  	return strings.ReplaceAll(path, "/", string(Separator))
   204  }
   205  
   206  // Join joins any number of path elements into a single path,
   207  // separating them with an OS specific Separator. Empty elements
   208  // are ignored. The result is Cleaned. However, if the argument
   209  // list is empty or all its elements are empty, Join returns
   210  // an empty string.
   211  // On Windows, the result will only be a UNC path if the first
   212  // non-empty element is a UNC path.
   213  func Join(elem ...string) string {
   214  	return join(elem)
   215  }
   216  
   217  // nolint
   218  func unixAbs(path string) (string, error) {
   219  	if IsAbs(path) {
   220  		return Clean(path), nil
   221  	}
   222  	wd, err := os.Getwd()
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  	return Join(wd, path), nil
   227  }
   228  
   229  // SkipDir is used as a return value from WalkFuncs to indicate that
   230  // the directory named in the call is to be skipped. It is not returned
   231  // as an error by any function.
   232  var SkipDir error = fs.SkipDir
   233  
   234  // WalkFunc is the type of the function called by Walk to visit each
   235  // file or directory.
   236  //
   237  // The path argument contains the argument to Walk as a prefix.
   238  // That is, if Walk is called with root argument "dir" and finds a file
   239  // named "a" in that directory, the walk function will be called with
   240  // argument "dir/a".
   241  //
   242  // The directory and file are joined with Join, which may clean the
   243  // directory name: if Walk is called with the root argument "x/../dir"
   244  // and finds a file named "a" in that directory, the walk function will
   245  // be called with argument "dir/a", not "x/../dir/a".
   246  //
   247  // The info argument is the fs.FileInfo for the named path.
   248  //
   249  // The error result returned by the function controls how Walk continues.
   250  // If the function returns the special value SkipDir, Walk skips the
   251  // current directory (path if info.IsDir() is true, otherwise path's
   252  // parent directory). If the function returns the special value SkipAll,
   253  // Walk skips all remaining files and directories. Otherwise, if the function
   254  // returns a non-nil error, Walk stops entirely and returns that error.
   255  //
   256  // The err argument reports an error related to path, signaling that Walk
   257  // will not walk into that directory. The function can decide how to
   258  // handle that error; as described earlier, returning the error will
   259  // cause Walk to stop walking the entire tree.
   260  //
   261  // Walk calls the function with a non-nil err argument in two cases.
   262  //
   263  // First, if an os.Lstat on the root directory or any directory or file
   264  // in the tree fails, Walk calls the function with path set to that
   265  // directory or file's path, info set to nil, and err set to the error
   266  // from os.Lstat.
   267  //
   268  // Second, if a directory's Readdirnames method fails, Walk calls the
   269  // function with path set to the directory's path, info, set to an
   270  // fs.FileInfo describing the directory, and err set to the error from
   271  // Readdirnames.
   272  type WalkFunc func(path string, info fs.FileInfo, err error) error