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