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