cuelang.org/go@v0.10.1/pkg/path/path_win.go (about) 1 // Copyright 2020 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Copyright 2010 The Go Authors. All rights reserved. 16 // Use of this source code is governed by a BSD-style 17 // license that can be found in the LICENSE file. 18 19 package path 20 21 import ( 22 "strings" 23 ) 24 25 type windowsInfo struct{} 26 27 var _ osInfo = windowsInfo{} 28 29 const ( 30 windowsSeparator = '\\' 31 windowsListSeparator = ';' 32 ) 33 34 func isSlash(c uint8) bool { 35 return c == '\\' || c == '/' 36 } 37 38 func (os windowsInfo) IsPathSeparator(b byte) bool { 39 return isSlash(b) 40 } 41 42 // reservedNames lists reserved Windows names. Search for PRN in 43 // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file 44 // for details. 45 var reservedNames = []string{ 46 "CON", "PRN", "AUX", "NUL", 47 "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", 48 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", 49 } 50 51 // isReservedName returns true, if path is Windows reserved name. 52 // See reservedNames for the full list. 53 func (os windowsInfo) isReservedName(path string) bool { 54 if len(path) == 0 { 55 return false 56 } 57 for _, reserved := range reservedNames { 58 if strings.EqualFold(path, reserved) { 59 return true 60 } 61 } 62 return false 63 } 64 65 // IsAbs reports whether the path is absolute. 66 func (os windowsInfo) IsAbs(path string) (b bool) { 67 if os.isReservedName(path) { 68 return true 69 } 70 l := os.volumeNameLen(path) 71 if l == 0 { 72 return false 73 } 74 path = path[l:] 75 if path == "" { 76 return false 77 } 78 return isSlash(path[0]) 79 } 80 81 // volumeNameLen returns length of the leading volume name on Windows. 82 // It returns 0 elsewhere. 83 func (os windowsInfo) volumeNameLen(path string) int { 84 if len(path) < 2 { 85 return 0 86 } 87 // with drive letter 88 c := path[0] 89 if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { 90 return 2 91 } 92 // is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 93 if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) && 94 !isSlash(path[2]) && path[2] != '.' { 95 // first, leading `\\` and next shouldn't be `\`. its server name. 96 for n := 3; n < l-1; n++ { 97 // second, next '\' shouldn't be repeated. 98 if isSlash(path[n]) { 99 n++ 100 // third, following something characters. its share name. 101 if !isSlash(path[n]) { 102 if path[n] == '.' { 103 break 104 } 105 for ; n < l; n++ { 106 if isSlash(path[n]) { 107 break 108 } 109 } 110 return n 111 } 112 break 113 } 114 } 115 } 116 return 0 117 } 118 119 // HasPrefix exists for historical compatibility and should not be used. 120 // 121 // Deprecated: HasPrefix does not respect path boundaries and 122 // does not ignore case when required. 123 func (os windowsInfo) HasPrefix(p, prefix string) bool { 124 if strings.HasPrefix(p, prefix) { 125 return true 126 } 127 return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) 128 } 129 130 func (os windowsInfo) splitList(path string) []string { 131 // The same implementation is used in LookPath in os/exec; 132 // consider changing os/exec when changing this. 133 134 if path == "" { 135 return []string{} 136 } 137 138 // Split path, respecting but preserving quotes. 139 list := []string{} 140 start := 0 141 quo := false 142 for i := 0; i < len(path); i++ { 143 switch c := path[i]; { 144 case c == '"': 145 quo = !quo 146 case c == windowsListSeparator && !quo: 147 list = append(list, path[start:i]) 148 start = i + 1 149 } 150 } 151 list = append(list, path[start:]) 152 153 // Remove quotes. 154 for i, s := range list { 155 list[i] = strings.ReplaceAll(s, `"`, ``) 156 } 157 158 return list 159 } 160 161 func (os windowsInfo) join(elem []string) string { 162 for i, e := range elem { 163 if e != "" { 164 return os.joinNonEmpty(elem[i:]) 165 } 166 } 167 return "" 168 } 169 170 // joinNonEmpty is like join, but it assumes that the first element is non-empty. 171 func (o windowsInfo) joinNonEmpty(elem []string) string { 172 if len(elem[0]) == 2 && elem[0][1] == ':' { 173 // First element is drive letter without terminating slash. 174 // Keep path relative to current directory on that drive. 175 // Skip empty elements. 176 i := 1 177 for ; i < len(elem); i++ { 178 if elem[i] != "" { 179 break 180 } 181 } 182 return clean(elem[0]+strings.Join(elem[i:], string(windowsSeparator)), windows) 183 } 184 // The following logic prevents Join from inadvertently creating a 185 // UNC path on Windows. Unless the first element is a UNC path, Join 186 // shouldn't create a UNC path. See golang.org/issue/9167. 187 p := clean(strings.Join(elem, string(windowsSeparator)), windows) 188 if !isUNC(p) { 189 return p 190 } 191 // p == UNC only allowed when the first element is a UNC path. 192 head := clean(elem[0], windows) 193 if isUNC(head) { 194 return p 195 } 196 // head + tail == UNC, but joining two non-UNC paths should not result 197 // in a UNC path. Undo creation of UNC path. 198 tail := clean(strings.Join(elem[1:], string(windowsSeparator)), windows) 199 if head[len(head)-1] == windowsSeparator { 200 return head + tail 201 } 202 return head + string(windowsSeparator) + tail 203 } 204 205 // isUNC reports whether path is a UNC path. 206 func isUNC(path string) bool { 207 return windows.volumeNameLen(path) > 2 208 } 209 210 func (o windowsInfo) sameWord(a, b string) bool { 211 return strings.EqualFold(a, b) 212 }