gitech

Форк
0
/
path.go 
322 строки · 9.0 Кб
1
// Copyright 2017 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
3

4
package util
5

6
import (
7
	"errors"
8
	"fmt"
9
	"net/url"
10
	"os"
11
	"path"
12
	"path/filepath"
13
	"regexp"
14
	"runtime"
15
	"strings"
16
)
17

18
// PathJoinRel joins the path elements into a single path, each element is cleaned by path.Clean separately.
19
// It only returns the following values (like path.Join), any redundant part (empty, relative dots, slashes) is removed.
20
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
21
//
22
//	empty => ``
23
//	`` => ``
24
//	`..` => `.`
25
//	`dir` => `dir`
26
//	`/dir/` => `dir`
27
//	`foo\..\bar` => `foo\..\bar`
28
//	{`foo`, ``, `bar`} => `foo/bar`
29
//	{`foo`, `..`, `bar`} => `foo/bar`
30
func PathJoinRel(elem ...string) string {
31
	elems := make([]string, len(elem))
32
	for i, e := range elem {
33
		if e == "" {
34
			continue
35
		}
36
		elems[i] = path.Clean("/" + e)
37
	}
38
	p := path.Join(elems...)
39
	if p == "" {
40
		return ""
41
	} else if p == "/" {
42
		return "."
43
	}
44
	return p[1:]
45
}
46

47
// PathJoinRelX joins the path elements into a single path like PathJoinRel,
48
// and covert all backslashes to slashes. (X means "extended", also means the combination of `\` and `/`).
49
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
50
// It returns similar results as PathJoinRel except:
51
//
52
//	`foo\..\bar` => `bar`  (because it's processed as `foo/../bar`)
53
//
54
// All backslashes are handled as slashes, the result only contains slashes.
55
func PathJoinRelX(elem ...string) string {
56
	elems := make([]string, len(elem))
57
	for i, e := range elem {
58
		if e == "" {
59
			continue
60
		}
61
		elems[i] = path.Clean("/" + strings.ReplaceAll(e, "\\", "/"))
62
	}
63
	return PathJoinRel(elems...)
64
}
65

66
const pathSeparator = string(os.PathSeparator)
67

68
// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
69
// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators.
70
// The first element must be an absolute path, caller should prepare the base path.
71
// It's caller's duty to make every element not bypass its own directly level, to avoid security issues.
72
// Like PathJoinRel, any redundant part (empty, relative dots, slashes) is removed.
73
//
74
//	{`/foo`, ``, `bar`} => `/foo/bar`
75
//	{`/foo`, `..`, `bar`} => `/foo/bar`
76
func FilePathJoinAbs(base string, sub ...string) string {
77
	elems := make([]string, 1, len(sub)+1)
78

79
	// POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators
80
	// to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/`
81
	if isOSWindows() {
82
		elems[0] = filepath.Clean(base)
83
	} else {
84
		elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator))
85
	}
86
	if !filepath.IsAbs(elems[0]) {
87
		// This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead
88
		panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems))
89
	}
90
	for _, s := range sub {
91
		if s == "" {
92
			continue
93
		}
94
		if isOSWindows() {
95
			elems = append(elems, filepath.Clean(pathSeparator+s))
96
		} else {
97
			elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator)))
98
		}
99
	}
100
	// the elems[0] must be an absolute path, just join them together
101
	return filepath.Join(elems...)
102
}
103

104
// IsDir returns true if given path is a directory,
105
// or returns false when it's a file or does not exist.
106
func IsDir(dir string) (bool, error) {
107
	f, err := os.Stat(dir)
108
	if err == nil {
109
		return f.IsDir(), nil
110
	}
111
	if os.IsNotExist(err) {
112
		return false, nil
113
	}
114
	return false, err
115
}
116

117
// IsFile returns true if given path is a file,
118
// or returns false when it's a directory or does not exist.
119
func IsFile(filePath string) (bool, error) {
120
	f, err := os.Stat(filePath)
121
	if err == nil {
122
		return !f.IsDir(), nil
123
	}
124
	if os.IsNotExist(err) {
125
		return false, nil
126
	}
127
	return false, err
128
}
129

130
// IsExist checks whether a file or directory exists.
131
// It returns false when the file or directory does not exist.
132
func IsExist(path string) (bool, error) {
133
	_, err := os.Stat(path)
134
	if err == nil || os.IsExist(err) {
135
		return true, nil
136
	}
137
	if os.IsNotExist(err) {
138
		return false, nil
139
	}
140
	return false, err
141
}
142

143
func statDir(dirPath, recPath string, includeDir, isDirOnly, followSymlinks bool) ([]string, error) {
144
	dir, err := os.Open(dirPath)
145
	if err != nil {
146
		return nil, err
147
	}
148
	defer dir.Close()
149

150
	fis, err := dir.Readdir(0)
151
	if err != nil {
152
		return nil, err
153
	}
154

155
	statList := make([]string, 0)
156
	for _, fi := range fis {
157
		if CommonSkip(fi.Name()) {
158
			continue
159
		}
160

161
		relPath := path.Join(recPath, fi.Name())
162
		curPath := path.Join(dirPath, fi.Name())
163
		if fi.IsDir() {
164
			if includeDir {
165
				statList = append(statList, relPath+"/")
166
			}
167
			s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks)
168
			if err != nil {
169
				return nil, err
170
			}
171
			statList = append(statList, s...)
172
		} else if !isDirOnly {
173
			statList = append(statList, relPath)
174
		} else if followSymlinks && fi.Mode()&os.ModeSymlink != 0 {
175
			link, err := os.Readlink(curPath)
176
			if err != nil {
177
				return nil, err
178
			}
179

180
			isDir, err := IsDir(link)
181
			if err != nil {
182
				return nil, err
183
			}
184
			if isDir {
185
				if includeDir {
186
					statList = append(statList, relPath+"/")
187
				}
188
				s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks)
189
				if err != nil {
190
					return nil, err
191
				}
192
				statList = append(statList, s...)
193
			}
194
		}
195
	}
196
	return statList, nil
197
}
198

199
// StatDir gathers information of given directory by depth-first.
200
// It returns slice of file list and includes subdirectories if enabled;
201
// it returns error and nil slice when error occurs in underlying functions,
202
// or given path is not a directory or does not exist.
203
//
204
// Slice does not include given path itself.
205
// If subdirectories is enabled, they will have suffix '/'.
206
func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
207
	if isDir, err := IsDir(rootPath); err != nil {
208
		return nil, err
209
	} else if !isDir {
210
		return nil, errors.New("not a directory or does not exist: " + rootPath)
211
	}
212

213
	isIncludeDir := false
214
	if len(includeDir) != 0 {
215
		isIncludeDir = includeDir[0]
216
	}
217
	return statDir(rootPath, "", isIncludeDir, false, false)
218
}
219

220
func isOSWindows() bool {
221
	return runtime.GOOS == "windows"
222
}
223

224
var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/")
225

226
// FileURLToPath extracts the path information from a file://... url.
227
// It returns an error only if the URL is not a file URL.
228
func FileURLToPath(u *url.URL) (string, error) {
229
	if u.Scheme != "file" {
230
		return "", errors.New("URL scheme is not 'file': " + u.String())
231
	}
232

233
	path := u.Path
234

235
	if !isOSWindows() {
236
		return path, nil
237
	}
238

239
	// If it looks like there's a Windows drive letter at the beginning, strip off the leading slash.
240
	if driveLetterRegexp.MatchString(path) {
241
		return path[1:], nil
242
	}
243
	return path, nil
244
}
245

246
// HomeDir returns path of '~'(in Linux) on Windows,
247
// it returns error when the variable does not exist.
248
func HomeDir() (home string, err error) {
249
	// TODO: some users run Gitea with mismatched uid  and "HOME=xxx" (they set HOME=xxx by environment manually)
250
	// TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory
251
	// so at the moment we can not use `user.Current().HomeDir`
252
	if isOSWindows() {
253
		home = os.Getenv("USERPROFILE")
254
		if home == "" {
255
			home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
256
		}
257
	} else {
258
		home = os.Getenv("HOME")
259
	}
260

261
	if home == "" {
262
		return "", errors.New("cannot get home directory")
263
	}
264

265
	return home, nil
266
}
267

268
// CommonSkip will check a provided name to see if it represents file or directory that should not be watched
269
func CommonSkip(name string) bool {
270
	if name == "" {
271
		return true
272
	}
273

274
	switch name[0] {
275
	case '.':
276
		return true
277
	case 't', 'T':
278
		return name[1:] == "humbs.db"
279
	case 'd', 'D':
280
		return name[1:] == "esktop.ini"
281
	}
282

283
	return false
284
}
285

286
// IsReadmeFileName reports whether name looks like a README file
287
// based on its name.
288
func IsReadmeFileName(name string) bool {
289
	name = strings.ToLower(name)
290
	if len(name) < 6 {
291
		return false
292
	} else if len(name) == 6 {
293
		return name == "readme"
294
	}
295
	return name[:7] == "readme."
296
}
297

298
// IsReadmeFileExtension reports whether name looks like a README file
299
// based on its name. It will look through the provided extensions and check if the file matches
300
// one of the extensions and provide the index in the extension list.
301
// If the filename is `readme.` with an unmatched extension it will match with the index equaling
302
// the length of the provided extension list.
303
// Note that the '.' should be provided in ext, e.g ".md"
304
func IsReadmeFileExtension(name string, ext ...string) (int, bool) {
305
	name = strings.ToLower(name)
306
	if len(name) < 6 || name[:6] != "readme" {
307
		return 0, false
308
	}
309

310
	for i, extension := range ext {
311
		extension = strings.ToLower(extension)
312
		if name[6:] == extension {
313
			return i, true
314
		}
315
	}
316

317
	if name[6] == '.' {
318
		return len(ext), true
319
	}
320

321
	return 0, false
322
}
323

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.