go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/tar/path.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package tar
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  )
    10  
    11  // the following code is adapted from golang but removes the cross-compile code so that it
    12  // always behaves unix style no matter for which platform it was compiled
    13  
    14  // docker images only use relative paths, we need to make them absolute here
    15  func Abs(path string) string {
    16  	return join("/", path)
    17  }
    18  
    19  const (
    20  	Separator = '/'
    21  )
    22  
    23  // to ensure we are always using unix style for tar
    24  // NOTE: this is relevant for extracting stopped linux container or images
    25  func join(elem ...string) string {
    26  	// If there's a bug here, fix the logic in ./path_plan9.go too.
    27  	for i, e := range elem {
    28  		if e != "" {
    29  			return Clean(strings.Join(elem[i:], string(Separator)))
    30  		}
    31  	}
    32  	return ""
    33  }
    34  
    35  // A lazybuf is a lazily constructed path buffer.
    36  // It supports append, reading previously appended bytes,
    37  // and retrieving the final string. It does not allocate a buffer
    38  // to hold the output until that output diverges from s.
    39  type lazybuf struct {
    40  	path       string
    41  	buf        []byte
    42  	w          int
    43  	volAndPath string
    44  	volLen     int
    45  }
    46  
    47  func (b *lazybuf) index(i int) byte {
    48  	if b.buf != nil {
    49  		return b.buf[i]
    50  	}
    51  	return b.path[i]
    52  }
    53  
    54  func (b *lazybuf) append(c byte) {
    55  	if b.buf == nil {
    56  		if b.w < len(b.path) && b.path[b.w] == c {
    57  			b.w++
    58  			return
    59  		}
    60  		b.buf = make([]byte, len(b.path))
    61  		copy(b.buf, b.path[:b.w])
    62  	}
    63  	b.buf[b.w] = c
    64  	b.w++
    65  }
    66  
    67  func (b *lazybuf) string() string {
    68  	if b.buf == nil {
    69  		return b.volAndPath[:b.volLen+b.w]
    70  	}
    71  	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
    72  }
    73  
    74  // volumeNameLen returns length of the leading volume name on Windows.
    75  // It returns 0 elsewhere.
    76  func volumeNameLen(path string) int {
    77  	return 0
    78  }
    79  
    80  // FromSlash returns the result of replacing each slash ('/') character
    81  // in path with a separator character. Multiple slashes are replaced
    82  // by multiple separators.
    83  func FromSlash(path string) string {
    84  	if Separator == '/' {
    85  		return path
    86  	}
    87  	return strings.ReplaceAll(path, "/", string(Separator))
    88  }
    89  
    90  // Clean returns the shortest path name equivalent to path
    91  // by purely lexical processing. It applies the following rules
    92  // iteratively until no further processing can be done:
    93  //
    94  //  1. Replace multiple Separator elements with a single one.
    95  //  2. Eliminate each . path name element (the current directory).
    96  //  3. Eliminate each inner .. path name element (the parent directory)
    97  //     along with the non-.. element that precedes it.
    98  //  4. Eliminate .. elements that begin a rooted path:
    99  //     that is, replace "/.." by "/" at the beginning of a path,
   100  //     assuming Separator is '/'.
   101  //
   102  // The returned path ends in a slash only if it represents a root directory,
   103  // such as "/" on Unix or `C:\` on Windows.
   104  //
   105  // Finally, any occurrences of slash are replaced by Separator.
   106  //
   107  // If the result of this process is an empty string, Clean
   108  // returns the string ".".
   109  //
   110  // See also Rob Pike, “Lexical File Names in Plan 9 or
   111  // Getting Dot-Dot Right,”
   112  // https://9p.io/sys/doc/lexnames.html
   113  func Clean(path string) string {
   114  	originalPath := path
   115  	volLen := volumeNameLen(path)
   116  	path = path[volLen:]
   117  	if path == "" {
   118  		if volLen > 1 && originalPath[1] != ':' {
   119  			// should be UNC
   120  			return FromSlash(originalPath)
   121  		}
   122  		return originalPath + "."
   123  	}
   124  	rooted := os.IsPathSeparator(path[0])
   125  
   126  	// Invariants:
   127  	//	reading from path; r is index of next byte to process.
   128  	//	writing to buf; w is index of next byte to write.
   129  	//	dotdot is index in buf where .. must stop, either because
   130  	//		it is the leading slash or it is a leading ../../.. prefix.
   131  	n := len(path)
   132  	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
   133  	r, dotdot := 0, 0
   134  	if rooted {
   135  		out.append(Separator)
   136  		r, dotdot = 1, 1
   137  	}
   138  
   139  	for r < n {
   140  		switch {
   141  		case os.IsPathSeparator(path[r]):
   142  			// empty path element
   143  			r++
   144  		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
   145  			// . element
   146  			r++
   147  		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
   148  			// .. element: remove to last separator
   149  			r += 2
   150  			switch {
   151  			case out.w > dotdot:
   152  				// can backtrack
   153  				out.w--
   154  				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
   155  					out.w--
   156  				}
   157  			case !rooted:
   158  				// cannot backtrack, but not rooted, so append .. element.
   159  				if out.w > 0 {
   160  					out.append(Separator)
   161  				}
   162  				out.append('.')
   163  				out.append('.')
   164  				dotdot = out.w
   165  			}
   166  		default:
   167  			// real path element.
   168  			// add slash if needed
   169  			if rooted && out.w != 1 || !rooted && out.w != 0 {
   170  				out.append(Separator)
   171  			}
   172  			// copy element
   173  			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
   174  				out.append(path[r])
   175  			}
   176  		}
   177  	}
   178  
   179  	// Turn empty string into "."
   180  	if out.w == 0 {
   181  		out.append('.')
   182  	}
   183  
   184  	return FromSlash(out.string())
   185  }