1
2
3
4
5 package filepathlite
6
7 import (
8 "internal/bytealg"
9 "internal/stringslite"
10 "internal/syscall/windows"
11 "syscall"
12 )
13
14 const (
15 Separator = '\\'
16 ListSeparator = ';'
17 )
18
19 func IsPathSeparator(c uint8) bool {
20 return c == '\\' || c == '/'
21 }
22
23 func isLocal(path string) bool {
24 if path == "" {
25 return false
26 }
27 if IsPathSeparator(path[0]) {
28
29 return false
30 }
31 if stringslite.IndexByte(path, ':') >= 0 {
32
33
34 return false
35 }
36 hasDots := false
37 for p := path; p != ""; {
38 var part string
39 part, p, _ = cutPath(p)
40 if part == "." || part == ".." {
41 hasDots = true
42 }
43 if isReservedName(part) {
44 return false
45 }
46 }
47 if hasDots {
48 path = Clean(path)
49 }
50 if path == ".." || stringslite.HasPrefix(path, `..\`) {
51 return false
52 }
53 return true
54 }
55
56 func localize(path string) (string, error) {
57 for i := 0; i < len(path); i++ {
58 switch path[i] {
59 case ':', '\\', 0:
60 return "", errInvalidPath
61 }
62 }
63 containsSlash := false
64 for p := path; p != ""; {
65
66 var element string
67 i := bytealg.IndexByteString(p, '/')
68 if i < 0 {
69 element = p
70 p = ""
71 } else {
72 containsSlash = true
73 element = p[:i]
74 p = p[i+1:]
75 }
76 if isReservedName(element) {
77 return "", errInvalidPath
78 }
79 }
80 if containsSlash {
81
82 buf := []byte(path)
83 for i, b := range buf {
84 if b == '/' {
85 buf[i] = '\\'
86 }
87 }
88 path = string(buf)
89 }
90 return path, nil
91 }
92
93
94
95
96
97
98 func isReservedName(name string) bool {
99
100 base := name
101 for i := 0; i < len(base); i++ {
102 switch base[i] {
103 case ':', '.':
104 base = base[:i]
105 }
106 }
107
108 for len(base) > 0 && base[len(base)-1] == ' ' {
109 base = base[:len(base)-1]
110 }
111 if !isReservedBaseName(base) {
112 return false
113 }
114 if len(base) == len(name) {
115 return true
116 }
117
118
119
120
121 p, err := syscall.UTF16PtrFromString(name)
122 if err != nil {
123 return false
124 }
125 return windows.RtlIsDosDeviceName_U(p) > 0
126 }
127
128 func isReservedBaseName(name string) bool {
129 if len(name) == 3 {
130 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
131 case "CON", "PRN", "AUX", "NUL":
132 return true
133 }
134 }
135 if len(name) >= 4 {
136 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
137 case "COM", "LPT":
138 if len(name) == 4 && '1' <= name[3] && name[3] <= '9' {
139 return true
140 }
141
142 switch name[3:] {
143 case "\u00b2", "\u00b3", "\u00b9":
144 return true
145 }
146 return false
147 }
148 }
149
150
151
152
153
154
155 if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") {
156 return true
157 }
158 if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") {
159 return true
160 }
161 return false
162 }
163
164 func equalFold(a, b string) bool {
165 if len(a) != len(b) {
166 return false
167 }
168 for i := 0; i < len(a); i++ {
169 if toUpper(a[i]) != toUpper(b[i]) {
170 return false
171 }
172 }
173 return true
174 }
175
176 func toUpper(c byte) byte {
177 if 'a' <= c && c <= 'z' {
178 return c - ('a' - 'A')
179 }
180 return c
181 }
182
183
184 func IsAbs(path string) (b bool) {
185 l := volumeNameLen(path)
186 if l == 0 {
187 return false
188 }
189
190 if IsPathSeparator(path[0]) && IsPathSeparator(path[1]) {
191 return true
192 }
193 path = path[l:]
194 if path == "" {
195 return false
196 }
197 return IsPathSeparator(path[0])
198 }
199
200
201
202
203
204
205
206 func volumeNameLen(path string) int {
207 switch {
208 case len(path) >= 2 && path[1] == ':':
209
210
211
212
213
214
215
216 return 2
217
218 case len(path) == 0 || !IsPathSeparator(path[0]):
219
220 return 0
221
222 case pathHasPrefixFold(path, `\\.\UNC`):
223
224
225
226
227
228 return uncLen(path, len(`\\.\UNC\`))
229
230 case pathHasPrefixFold(path, `\\.`) ||
231 pathHasPrefixFold(path, `\\?`) || pathHasPrefixFold(path, `\??`):
232
233
234
235
236
237
238 if len(path) == 3 {
239 return 3
240 }
241 _, rest, ok := cutPath(path[4:])
242 if !ok {
243 return len(path)
244 }
245 return len(path) - len(rest) - 1
246
247 case len(path) >= 2 && IsPathSeparator(path[1]):
248
249 return uncLen(path, 2)
250 }
251 return 0
252 }
253
254
255
256
257 func pathHasPrefixFold(s, prefix string) bool {
258 if len(s) < len(prefix) {
259 return false
260 }
261 for i := 0; i < len(prefix); i++ {
262 if IsPathSeparator(prefix[i]) {
263 if !IsPathSeparator(s[i]) {
264 return false
265 }
266 } else if toUpper(prefix[i]) != toUpper(s[i]) {
267 return false
268 }
269 }
270 if len(s) > len(prefix) && !IsPathSeparator(s[len(prefix)]) {
271 return false
272 }
273 return true
274 }
275
276
277
278
279 func uncLen(path string, prefixLen int) int {
280 count := 0
281 for i := prefixLen; i < len(path); i++ {
282 if IsPathSeparator(path[i]) {
283 count++
284 if count == 2 {
285 return i
286 }
287 }
288 }
289 return len(path)
290 }
291
292
293 func cutPath(path string) (before, after string, found bool) {
294 for i := range path {
295 if IsPathSeparator(path[i]) {
296 return path[:i], path[i+1:], true
297 }
298 }
299 return path, "", false
300 }
301
302
303
304 func postClean(out *lazybuf) {
305 if out.volLen != 0 || out.buf == nil {
306 return
307 }
308
309
310
311 for _, c := range out.buf {
312 if IsPathSeparator(c) {
313 break
314 }
315 if c == ':' {
316 out.prepend('.', Separator)
317 return
318 }
319 }
320
321
322
323 if len(out.buf) >= 3 && IsPathSeparator(out.buf[0]) && out.buf[1] == '?' && out.buf[2] == '?' {
324 out.prepend(Separator, '.')
325 }
326 }
327
View as plain text