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 }