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