-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvalidate.go
More file actions
98 lines (91 loc) · 2.83 KB
/
Copy pathvalidate.go
File metadata and controls
98 lines (91 loc) · 2.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package clone
import (
"fmt"
"net/url"
"path"
"regexp"
"slices"
"strings"
)
var commitRE = regexp.MustCompile(`^[0-9a-f]{4,64}$`)
// ValidateURL rejects Git URLs that do not use HTTPS, do not parse, or contain
// control bytes. Embedded userinfo is accepted (some callers use
// https://<token>@host/... for private repos) but should be redacted before
// logging; UnreachableError.Error and this package's own error strings do so
// via RedactURL.
func ValidateURL(raw string) error {
for i := range len(raw) {
if c := raw[i]; c < 0x20 || c == 0x7f {
return fmt.Errorf("URL contains control byte 0x%02x at offset %d", c, i)
}
}
u, err := url.Parse(raw)
if err != nil {
return fmt.Errorf("invalid URL %q: %w", RedactURL(raw), err)
}
if u.Scheme != "https" {
return fmt.Errorf("only https:// URLs are allowed, got %q", RedactURL(raw))
}
if u.Host == "" {
return fmt.Errorf("URL %q has no host", RedactURL(raw))
}
return nil
}
// RedactURL replaces any userinfo in raw with a fixed placeholder so error
// messages and logs cannot leak an embedded token. A URL that fails to parse
// is returned unchanged: url.Parse does not accept control bytes, so an
// unparseable string here is one ValidateURL would already have rejected for a
// reason unrelated to its credential.
func RedactURL(raw string) string {
u, err := url.Parse(raw)
if err != nil || u.User == nil {
return raw
}
u.User = url.User("REDACTED")
return u.String()
}
// ValidateRef restricts refs to a conservative branch and tag name character
// set before they are passed to Git.
func ValidateRef(ref string) error {
if ref == "" {
return nil
}
if strings.HasPrefix(ref, "-") {
return fmt.Errorf("invalid ref %q: must not start with -", ref)
}
if strings.Contains(ref, "..") {
return fmt.Errorf(`invalid ref %q: must not contain ".."`, ref)
}
for _, char := range ref {
switch {
case char >= 'a' && char <= 'z',
char >= 'A' && char <= 'Z',
char >= '0' && char <= '9',
char == '.', char == '_', char == '/', char == '-':
default:
return fmt.Errorf("invalid ref %q: contains disallowed character %q", ref, char)
}
}
return nil
}
// ValidCommit reports whether sha is a lowercase hexadecimal object ID or
// abbreviated object ID between 4 and 64 characters long.
func ValidCommit(sha string) bool {
return commitRE.MatchString(sha)
}
// SanitizePath returns a slash-form path safe to use in a Git object
// expression. It rejects empty and absolute paths, NUL bytes, and traversal.
func SanitizePath(value string) (string, bool) {
if value == "" || strings.ContainsRune(value, 0) || strings.HasPrefix(value, "/") {
return "", false
}
value = strings.TrimPrefix(value, "./")
if slices.Contains(strings.Split(value, "/"), "..") {
return "", false
}
clean := path.Clean(value)
if clean == "." || clean == "" {
return "", false
}
return clean, true
}