github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/path/filepath/path_windows.go (about) 1 // Copyright 2010 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 6 7 import ( 8 "internal/safefilepath" 9 "os" 10 "strings" 11 "syscall" 12 ) 13 14 func isSlash(c uint8) bool { 15 return c == '\\' || c == '/' 16 } 17 18 func toUpper(c byte) byte { 19 if 'a' <= c && c <= 'z' { 20 return c - ('a' - 'A') 21 } 22 return c 23 } 24 25 func isLocal(path string) bool { 26 if path == "" { 27 return false 28 } 29 if isSlash(path[0]) { 30 // Path rooted in the current drive. 31 return false 32 } 33 if strings.IndexByte(path, ':') >= 0 { 34 // Colons are only valid when marking a drive letter ("C:foo"). 35 // Rejecting any path with a colon is conservative but safe. 36 return false 37 } 38 hasDots := false // contains . or .. path elements 39 for p := path; p != ""; { 40 var part string 41 part, p, _ = cutPath(p) 42 if part == "." || part == ".." { 43 hasDots = true 44 } 45 if safefilepath.IsReservedName(part) { 46 return false 47 } 48 } 49 if hasDots { 50 path = Clean(path) 51 } 52 if path == ".." || strings.HasPrefix(path, `..\`) { 53 return false 54 } 55 return true 56 } 57 58 // IsAbs reports whether the path is absolute. 59 func IsAbs(path string) (b bool) { 60 l := volumeNameLen(path) 61 if l == 0 { 62 return false 63 } 64 // If the volume name starts with a double slash, this is an absolute path. 65 if isSlash(path[0]) && isSlash(path[1]) { 66 return true 67 } 68 path = path[l:] 69 if path == "" { 70 return false 71 } 72 return isSlash(path[0]) 73 } 74 75 // volumeNameLen returns length of the leading volume name on Windows. 76 // It returns 0 elsewhere. 77 // 78 // See: 79 // https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats 80 // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html 81 func volumeNameLen(path string) int { 82 switch { 83 case len(path) >= 2 && path[1] == ':': 84 // Path starts with a drive letter. 85 // 86 // Not all Windows functions necessarily enforce the requirement that 87 // drive letters be in the set A-Z, and we don't try to here. 88 // 89 // We don't handle the case of a path starting with a non-ASCII character, 90 // in which case the "drive letter" might be multiple bytes long. 91 return 2 92 93 case len(path) == 0 || !isSlash(path[0]): 94 // Path does not have a volume component. 95 return 0 96 97 case pathHasPrefixFold(path, `\\.\UNC`): 98 // We're going to treat the UNC host and share as part of the volume 99 // prefix for historical reasons, but this isn't really principled; 100 // Windows's own GetFullPathName will happily remove the first 101 // component of the path in this space, converting 102 // \\.\unc\a\b\..\c into \\.\unc\a\c. 103 return uncLen(path, len(`\\.\UNC\`)) 104 105 case pathHasPrefixFold(path, `\\.`) || 106 pathHasPrefixFold(path, `\\?`) || pathHasPrefixFold(path, `\??`): 107 // Path starts with \\.\, and is a Local Device path; or 108 // path starts with \\?\ or \??\ and is a Root Local Device path. 109 // 110 // We treat the next component after the \\.\ prefix as 111 // part of the volume name, which means Clean(`\\?\c:\`) 112 // won't remove the trailing \. (See #64028.) 113 if len(path) == 3 { 114 return 3 // exactly \\. 115 } 116 _, rest, ok := cutPath(path[4:]) 117 if !ok { 118 return len(path) 119 } 120 return len(path) - len(rest) - 1 121 122 case len(path) >= 2 && isSlash(path[1]): 123 // Path starts with \\, and is a UNC path. 124 return uncLen(path, 2) 125 } 126 return 0 127 } 128 129 // pathHasPrefixFold tests whether the path s begins with prefix, 130 // ignoring case and treating all path separators as equivalent. 131 // If s is longer than prefix, then s[len(prefix)] must be a path separator. 132 func pathHasPrefixFold(s, prefix string) bool { 133 if len(s) < len(prefix) { 134 return false 135 } 136 for i := 0; i < len(prefix); i++ { 137 if isSlash(prefix[i]) { 138 if !isSlash(s[i]) { 139 return false 140 } 141 } else if toUpper(prefix[i]) != toUpper(s[i]) { 142 return false 143 } 144 } 145 if len(s) > len(prefix) && !isSlash(s[len(prefix)]) { 146 return false 147 } 148 return true 149 } 150 151 // uncLen returns the length of the volume prefix of a UNC path. 152 // prefixLen is the prefix prior to the start of the UNC host; 153 // for example, for "//host/share", the prefixLen is len("//")==2. 154 func uncLen(path string, prefixLen int) int { 155 count := 0 156 for i := prefixLen; i < len(path); i++ { 157 if isSlash(path[i]) { 158 count++ 159 if count == 2 { 160 return i 161 } 162 } 163 } 164 return len(path) 165 } 166 167 // cutPath slices path around the first path separator. 168 func cutPath(path string) (before, after string, found bool) { 169 for i := range path { 170 if isSlash(path[i]) { 171 return path[:i], path[i+1:], true 172 } 173 } 174 return path, "", false 175 } 176 177 // HasPrefix exists for historical compatibility and should not be used. 178 // 179 // Deprecated: HasPrefix does not respect path boundaries and 180 // does not ignore case when required. 181 func HasPrefix(p, prefix string) bool { 182 if strings.HasPrefix(p, prefix) { 183 return true 184 } 185 return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) 186 } 187 188 func splitList(path string) []string { 189 // The same implementation is used in LookPath in os/exec; 190 // consider changing os/exec when changing this. 191 192 if path == "" { 193 return []string{} 194 } 195 196 // Split path, respecting but preserving quotes. 197 list := []string{} 198 start := 0 199 quo := false 200 for i := 0; i < len(path); i++ { 201 switch c := path[i]; { 202 case c == '"': 203 quo = !quo 204 case c == ListSeparator && !quo: 205 list = append(list, path[start:i]) 206 start = i + 1 207 } 208 } 209 list = append(list, path[start:]) 210 211 // Remove quotes. 212 for i, s := range list { 213 list[i] = strings.ReplaceAll(s, `"`, ``) 214 } 215 216 return list 217 } 218 219 func abs(path string) (string, error) { 220 if path == "" { 221 // syscall.FullPath returns an error on empty path, because it's not a valid path. 222 // To implement Abs behavior of returning working directory on empty string input, 223 // special-case empty path by changing it to "." path. See golang.org/issue/24441. 224 path = "." 225 } 226 fullPath, err := syscall.FullPath(path) 227 if err != nil { 228 return "", err 229 } 230 return Clean(fullPath), nil 231 } 232 233 func join(elem []string) string { 234 var b strings.Builder 235 var lastChar byte 236 for _, e := range elem { 237 switch { 238 case b.Len() == 0: 239 // Add the first non-empty path element unchanged. 240 case isSlash(lastChar): 241 // If the path ends in a slash, strip any leading slashes from the next 242 // path element to avoid creating a UNC path (any path starting with "\\") 243 // from non-UNC elements. 244 // 245 // The correct behavior for Join when the first element is an incomplete UNC 246 // path (for example, "\\") is underspecified. We currently join subsequent 247 // elements so Join("\\", "host", "share") produces "\\host\share". 248 for len(e) > 0 && isSlash(e[0]) { 249 e = e[1:] 250 } 251 // If the path is \ and the next path element is ??, 252 // add an extra .\ to create \.\?? rather than \??\ 253 // (a Root Local Device path). 254 if b.Len() == 1 && pathHasPrefixFold(e, "??") { 255 b.WriteString(`.\`) 256 } 257 case lastChar == ':': 258 // If the path ends in a colon, keep the path relative to the current directory 259 // on a drive and don't add a separator. Preserve leading slashes in the next 260 // path element, which may make the path absolute. 261 // 262 // Join(`C:`, `f`) = `C:f` 263 // Join(`C:`, `\f`) = `C:\f` 264 default: 265 // In all other cases, add a separator between elements. 266 b.WriteByte('\\') 267 lastChar = '\\' 268 } 269 if len(e) > 0 { 270 b.WriteString(e) 271 lastChar = e[len(e)-1] 272 } 273 } 274 if b.Len() == 0 { 275 return "" 276 } 277 return Clean(b.String()) 278 } 279 280 func sameWord(a, b string) bool { 281 return strings.EqualFold(a, b) 282 } 283 284 // postClean adjusts the results of Clean to avoid turning a relative path 285 // into an absolute or rooted one. 286 func postClean(out *lazybuf) { 287 if out.volLen != 0 || out.buf == nil { 288 return 289 } 290 // If a ':' appears in the path element at the start of a path, 291 // insert a .\ at the beginning to avoid converting relative paths 292 // like a/../c: into c:. 293 for _, c := range out.buf { 294 if os.IsPathSeparator(c) { 295 break 296 } 297 if c == ':' { 298 out.prepend('.', Separator) 299 return 300 } 301 } 302 // If a path begins with \??\, insert a \. at the beginning 303 // to avoid converting paths like \a\..\??\c:\x into \??\c:\x 304 // (equivalent to c:\x). 305 if len(out.buf) >= 3 && os.IsPathSeparator(out.buf[0]) && out.buf[1] == '?' && out.buf[2] == '?' { 306 out.prepend(Separator, '.') 307 } 308 }