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