github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/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 "strings" 9 "syscall" 10 ) 11 12 func isSlash(c uint8) bool { 13 return c == '\\' || c == '/' 14 } 15 16 // isReservedName returns true, if path is Windows reserved name. 17 // See reservedNames for the full list. 18 func isReservedName(path string) bool { 19 toUpper := func(c byte) byte { 20 if 'a' <= c && c <= 'z' { 21 return c - ('a' - 'A') 22 } 23 return c 24 } 25 26 // For details, search for PRN in 27 // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file. 28 if 3 <= len(path) && len(path) <= 4 { 29 switch string([]byte{toUpper(path[0]), toUpper(path[1]), toUpper(path[2])}) { 30 case "CON", "PRN", "AUX", "NUL": 31 return len(path) == 3 32 case "COM", "LPT": 33 return len(path) == 4 && '1' <= path[3] && path[3] <= '9' 34 } 35 } 36 return false 37 } 38 39 // IsAbs reports whether the path is absolute. 40 func IsAbs(path string) (b bool) { 41 if isReservedName(path) { 42 return true 43 } 44 l := volumeNameLen(path) 45 if l == 0 { 46 return false 47 } 48 // If the volume name starts with a double slash, this is a UNC path. 49 if isSlash(path[0]) && isSlash(path[1]) { 50 return true 51 } 52 path = path[l:] 53 if path == "" { 54 return false 55 } 56 return isSlash(path[0]) 57 } 58 59 // volumeNameLen returns length of the leading volume name on Windows. 60 // It returns 0 elsewhere. 61 func volumeNameLen(path string) int { 62 if len(path) < 2 { 63 return 0 64 } 65 // with drive letter 66 c := path[0] 67 if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { 68 return 2 69 } 70 // is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 71 if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) && 72 !isSlash(path[2]) && path[2] != '.' { 73 // first, leading `\\` and next shouldn't be `\`. its server name. 74 for n := 3; n < l-1; n++ { 75 // second, next '\' shouldn't be repeated. 76 if isSlash(path[n]) { 77 n++ 78 // third, following something characters. its share name. 79 if !isSlash(path[n]) { 80 if path[n] == '.' { 81 break 82 } 83 for ; n < l; n++ { 84 if isSlash(path[n]) { 85 break 86 } 87 } 88 return n 89 } 90 break 91 } 92 } 93 } 94 return 0 95 } 96 97 // HasPrefix exists for historical compatibility and should not be used. 98 // 99 // Deprecated: HasPrefix does not respect path boundaries and 100 // does not ignore case when required. 101 func HasPrefix(p, prefix string) bool { 102 if strings.HasPrefix(p, prefix) { 103 return true 104 } 105 return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) 106 } 107 108 func splitList(path string) []string { 109 // The same implementation is used in LookPath in os/exec; 110 // consider changing os/exec when changing this. 111 112 if path == "" { 113 return []string{} 114 } 115 116 // Split path, respecting but preserving quotes. 117 list := []string{} 118 start := 0 119 quo := false 120 for i := 0; i < len(path); i++ { 121 switch c := path[i]; { 122 case c == '"': 123 quo = !quo 124 case c == ListSeparator && !quo: 125 list = append(list, path[start:i]) 126 start = i + 1 127 } 128 } 129 list = append(list, path[start:]) 130 131 // Remove quotes. 132 for i, s := range list { 133 list[i] = strings.ReplaceAll(s, `"`, ``) 134 } 135 136 return list 137 } 138 139 func abs(path string) (string, error) { 140 if path == "" { 141 // syscall.FullPath returns an error on empty path, because it's not a valid path. 142 // To implement Abs behavior of returning working directory on empty string input, 143 // special-case empty path by changing it to "." path. See golang.org/issue/24441. 144 path = "." 145 } 146 fullPath, err := syscall.FullPath(path) 147 if err != nil { 148 return "", err 149 } 150 return Clean(fullPath), nil 151 } 152 153 func join(elem []string) string { 154 for i, e := range elem { 155 if e != "" { 156 return joinNonEmpty(elem[i:]) 157 } 158 } 159 return "" 160 } 161 162 // joinNonEmpty is like join, but it assumes that the first element is non-empty. 163 func joinNonEmpty(elem []string) string { 164 if len(elem[0]) == 2 && elem[0][1] == ':' { 165 // First element is drive letter without terminating slash. 166 // Keep path relative to current directory on that drive. 167 // Skip empty elements. 168 i := 1 169 for ; i < len(elem); i++ { 170 if elem[i] != "" { 171 break 172 } 173 } 174 return Clean(elem[0] + strings.Join(elem[i:], string(Separator))) 175 } 176 // The following logic prevents Join from inadvertently creating a 177 // UNC path on Windows. Unless the first element is a UNC path, Join 178 // shouldn't create a UNC path. See golang.org/issue/9167. 179 p := Clean(strings.Join(elem, string(Separator))) 180 if !isUNC(p) { 181 return p 182 } 183 // p == UNC only allowed when the first element is a UNC path. 184 head := Clean(elem[0]) 185 if isUNC(head) { 186 return p 187 } 188 // head + tail == UNC, but joining two non-UNC paths should not result 189 // in a UNC path. Undo creation of UNC path. 190 tail := Clean(strings.Join(elem[1:], string(Separator))) 191 if head[len(head)-1] == Separator { 192 return head + tail 193 } 194 return head + string(Separator) + tail 195 } 196 197 // isUNC reports whether path is a UNC path. 198 func isUNC(path string) bool { 199 return volumeNameLen(path) > 2 200 } 201 202 func sameWord(a, b string) bool { 203 return strings.EqualFold(a, b) 204 }