1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package filepath
6
7import (
8	"strings"
9	"syscall"
10)
11
12func isSlash(c uint8) bool {
13	return c == '\\' || c == '/'
14}
15
16// reservedNames lists reserved Windows names. Search for PRN in
17// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
18// for details.
19var reservedNames = []string{
20	"CON", "PRN", "AUX", "NUL",
21	"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
22	"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
23}
24
25// isReservedName returns true, if path is Windows reserved name.
26// See reservedNames for the full list.
27func isReservedName(path string) bool {
28	if len(path) == 0 {
29		return false
30	}
31	for _, reserved := range reservedNames {
32		if strings.EqualFold(path, reserved) {
33			return true
34		}
35	}
36	return false
37}
38
39// IsAbs reports whether the path is absolute.
40func IsAbs(path string) (b bool) {
41	if isReservedName(path) {
42		return true
43	}
44	l := volumeNameLen(path)
45	if l == 0 {
46		return false
47	}
48	path = path[l:]
49	if path == "" {
50		return false
51	}
52	return isSlash(path[0])
53}
54
55// volumeNameLen returns length of the leading volume name on Windows.
56// It returns 0 elsewhere.
57func volumeNameLen(path string) int {
58	if len(path) < 2 {
59		return 0
60	}
61	// with drive letter
62	c := path[0]
63	if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
64		return 2
65	}
66	// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
67	if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
68		!isSlash(path[2]) && path[2] != '.' {
69		// first, leading `\\` and next shouldn't be `\`. its server name.
70		for n := 3; n < l-1; n++ {
71			// second, next '\' shouldn't be repeated.
72			if isSlash(path[n]) {
73				n++
74				// third, following something characters. its share name.
75				if !isSlash(path[n]) {
76					if path[n] == '.' {
77						break
78					}
79					for ; n < l; n++ {
80						if isSlash(path[n]) {
81							break
82						}
83					}
84					return n
85				}
86				break
87			}
88		}
89	}
90	return 0
91}
92
93// HasPrefix exists for historical compatibility and should not be used.
94//
95// Deprecated: HasPrefix does not respect path boundaries and
96// does not ignore case when required.
97func HasPrefix(p, prefix string) bool {
98	if strings.HasPrefix(p, prefix) {
99		return true
100	}
101	return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
102}
103
104func splitList(path string) []string {
105	// The same implementation is used in LookPath in os/exec;
106	// consider changing os/exec when changing this.
107
108	if path == "" {
109		return []string{}
110	}
111
112	// Split path, respecting but preserving quotes.
113	list := []string{}
114	start := 0
115	quo := false
116	for i := 0; i < len(path); i++ {
117		switch c := path[i]; {
118		case c == '"':
119			quo = !quo
120		case c == ListSeparator && !quo:
121			list = append(list, path[start:i])
122			start = i + 1
123		}
124	}
125	list = append(list, path[start:])
126
127	// Remove quotes.
128	for i, s := range list {
129		list[i] = strings.ReplaceAll(s, `"`, ``)
130	}
131
132	return list
133}
134
135func abs(path string) (string, error) {
136	if path == "" {
137		// syscall.FullPath returns an error on empty path, because it's not a valid path.
138		// To implement Abs behavior of returning working directory on empty string input,
139		// special-case empty path by changing it to "." path. See golang.org/issue/24441.
140		path = "."
141	}
142	fullPath, err := syscall.FullPath(path)
143	if err != nil {
144		return "", err
145	}
146	return Clean(fullPath), nil
147}
148
149func join(elem []string) string {
150	for i, e := range elem {
151		if e != "" {
152			return joinNonEmpty(elem[i:])
153		}
154	}
155	return ""
156}
157
158// joinNonEmpty is like join, but it assumes that the first element is non-empty.
159func joinNonEmpty(elem []string) string {
160	if len(elem[0]) == 2 && elem[0][1] == ':' {
161		// First element is drive letter without terminating slash.
162		// Keep path relative to current directory on that drive.
163		// Skip empty elements.
164		i := 1
165		for ; i < len(elem); i++ {
166			if elem[i] != "" {
167				break
168			}
169		}
170		return Clean(elem[0] + strings.Join(elem[i:], string(Separator)))
171	}
172	// The following logic prevents Join from inadvertently creating a
173	// UNC path on Windows. Unless the first element is a UNC path, Join
174	// shouldn't create a UNC path. See golang.org/issue/9167.
175	p := Clean(strings.Join(elem, string(Separator)))
176	if !isUNC(p) {
177		return p
178	}
179	// p == UNC only allowed when the first element is a UNC path.
180	head := Clean(elem[0])
181	if isUNC(head) {
182		return p
183	}
184	// head + tail == UNC, but joining two non-UNC paths should not result
185	// in a UNC path. Undo creation of UNC path.
186	tail := Clean(strings.Join(elem[1:], string(Separator)))
187	if head[len(head)-1] == Separator {
188		return head + tail
189	}
190	return head + string(Separator) + tail
191}
192
193// isUNC reports whether path is a UNC path.
194func isUNC(path string) bool {
195	return volumeNameLen(path) > 2
196}
197
198func sameWord(a, b string) bool {
199	return strings.EqualFold(a, b)
200}
201