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, `\\.`) ||
223 pathHasPrefixFold(path, `\\?`) || pathHasPrefixFold(path, `\??`):
224
225
226 switch {
227 case len(path) == 3:
228 return 3
229 case pathHasPrefixFold(path[4:], `UNC`):
230
231
232
233
234
235 return validVolumeNameLen(path, uncLen(path, len(`\\.\UNC\`)))
236 case pathHasPrefixFold(path[4:], `GLOBALROOT`):
237
238
239
240
241
242
243 return validVolumeNameLen(path, uncLen(path, len(`\\?\GLOBALROOT\`)))
244 }
245
246
247
248
249 _, rest, ok := cutPath(path[4:])
250 if !ok {
251 return validVolumeNameLen(path, len(path))
252 }
253 return validVolumeNameLen(path, len(path)-len(rest)-1)
254
255 case len(path) >= 2 && IsPathSeparator(path[1]):
256
257 return validVolumeNameLen(path, uncLen(path, 2))
258 }
259 return 0
260 }
261
262
263
264 func validVolumeNameLen(path string, n int) int {
265 for p := path[:n]; p != ""; {
266 var part string
267 part, p, _ = cutPath(p)
268 if part == ".." {
269 return 0
270 }
271 }
272 return n
273 }
274
275
276
277
278 func pathHasPrefixFold(s, prefix string) bool {
279 if len(s) < len(prefix) {
280 return false
281 }
282 for i := 0; i < len(prefix); i++ {
283 if IsPathSeparator(prefix[i]) {
284 if !IsPathSeparator(s[i]) {
285 return false
286 }
287 } else if toUpper(prefix[i]) != toUpper(s[i]) {
288 return false
289 }
290 }
291 if len(s) > len(prefix) && !IsPathSeparator(s[len(prefix)]) {
292 return false
293 }
294 return true
295 }
296
297
298
299
300 func uncLen(path string, prefixLen int) int {
301 count := 0
302 for i := prefixLen; i < len(path); i++ {
303 if IsPathSeparator(path[i]) {
304 count++
305 if count == 2 {
306 return i
307 }
308 }
309 }
310 return len(path)
311 }
312
313
314 func cutPath(path string) (before, after string, found bool) {
315 for i := range path {
316 if IsPathSeparator(path[i]) {
317 return path[:i], path[i+1:], true
318 }
319 }
320 return path, "", false
321 }
322
323
324
325 func postClean(out *lazybuf) {
326 if out.volLen != 0 || out.buf == nil {
327 return
328 }
329
330
331
332 for _, c := range out.buf {
333 if IsPathSeparator(c) {
334 break
335 }
336 if c == ':' {
337 out.prepend('.', Separator)
338 return
339 }
340 }
341
342
343
344 if len(out.buf) >= 3 && IsPathSeparator(out.buf[0]) && out.buf[1] == '?' && out.buf[2] == '?' {
345 out.prepend(Separator, '.')
346 }
347 }
348
View as plain text