-
Notifications
You must be signed in to change notification settings - Fork 565
Expand file tree
/
Copy pathmirror.go
More file actions
170 lines (154 loc) · 5.5 KB
/
Copy pathmirror.go
File metadata and controls
170 lines (154 loc) · 5.5 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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package pom
import (
"net/url"
"strings"
"github.com/samber/lo"
"github.com/aquasecurity/trivy/pkg/log"
)
// mirror is the runtime representation of a <mirror> from settings.xml.
// Compared to Mirror, the matching rules are pre-split and the URL is parsed
// with credentials from the matching <server> already embedded, so the hot
// path in mirrorFor only needs to walk patterns and compare strings.
type mirror struct {
id string
patterns []string // trimmed, non-empty entries from <mirrorOf>
url url.URL // parsed URL with userinfo from the matching <server>
}
// mirrors holds the resolved mirrors from settings.xml and from the config file.
type mirrors struct {
settings []mirror // settings.xml mirrors
configFile map[string][]url.URL // config-file mirrors; key: mirrorKey(source), value: ordered parsed mirror URL
}
// resolveMirrors resolves and validates both mirror sources into their runtime form:
// it parses every URL — embedding <server> credentials into settings.xml mirrors and
// normalizing config-file keys via mirrorKey — and drops any entry with an unusable
// pattern or an unparsable URL.
func resolveMirrors(settingsMirrors []Mirror, servers []Server, configFileMirrors map[string][]string) mirrors {
logger := log.WithPrefix("pom")
var resolved mirrors
for _, m := range settingsMirrors {
var patterns []string
for p := range strings.SplitSeq(m.MirrorOf, ",") {
p = strings.TrimSpace(p)
if p == "" {
continue
}
patterns = append(patterns, p)
}
if len(patterns) == 0 {
continue
}
u, err := url.Parse(m.URL)
if err != nil {
// Don't log the wrapped error: url.Error.Error() prints the raw URL,
// which would leak any userinfo configured in <mirror><url>.
logger.Debug("Unable to parse mirror url", log.String("id", m.ID))
continue
}
// Maven looks up credentials on the <server> whose id equals the mirror's id,
// not the original repository's id.
for _, srv := range servers {
if srv.ID == m.ID && srv.Username != "" && srv.Password != "" {
u.User = url.UserPassword(srv.Username, srv.Password)
break
}
}
logger.Debug("Adding mirror", log.String("id", m.ID), log.String("url", u.Redacted()))
resolved.settings = append(resolved.settings, mirror{
id: m.ID,
patterns: patterns,
url: *u,
})
}
for src, targets := range configFileMirrors {
// Config-file mirror URLs are validated when the config file is parsed (fail-fast).
srcURL, err := url.Parse(src)
if err != nil {
continue
}
var mirrorURLs []url.URL
for _, target := range targets {
mirrorURL, err := url.Parse(target)
if err != nil {
continue
}
mirrorURLs = append(mirrorURLs, *mirrorURL)
}
if len(mirrorURLs) == 0 {
continue
}
logger.Debug("Added config-file mirror", log.String("source", srcURL.Redacted()),
log.Any("mirrors", lo.Map(mirrorURLs, func(u url.URL, _ int) string {
return u.Redacted()
})))
if resolved.configFile == nil {
resolved.configFile = make(map[string][]url.URL)
}
resolved.configFile[mirrorKey(*srcURL)] = mirrorURLs
}
return resolved
}
// mirrorKey normalizes a repository URL to the key used for config-file mirror
// lookup: its string form with any trailing slash trimmed, so that
// "https://host/maven2/" and "https://host/maven2" resolve to the same key.
func mirrorKey(u url.URL) string {
return strings.TrimRight(u.String(), "/")
}
// matches reports whether this mirror should serve the given repository.
// See https://maven.apache.org/guides/mini/guide-mirror-settings.html
//
// Implements the same order-sensitive semantics as Maven's
// DefaultMirrorSelector.matchPattern in maven-resolver. Patterns are walked
// left-to-right; the loop terminates as soon as either an exact id or an
// exclusion fires. Non-terminal tokens just set the flag and keep iterating
// so that a later "!<id>" can still veto.
//
// Terminal tokens:
// - "<id>" — exact match. Returns true.
// - "!<id>" — exclusion of an exact id. Returns false.
//
// Non-terminal tokens (set flag, continue):
// - "*" — any repository.
// - "external:*" — any URL that is not file:// and not localhost /
// 127.0.0.1 / ::1.
// - "external:http:*" — same as external:*, restricted to the http scheme.
func (m mirror) matches(repoID string, repoURL *url.URL) bool {
result := false
for _, p := range m.patterns {
switch {
// Exclusion token. A bare "!" without an id is not a valid exclusion,
// so the length check skips it (matches Maven's repo.length() > 1).
case len(p) > 1 && p[0] == '!':
if p[1:] == repoID {
return false
}
case p == repoID:
return true
case p == "*":
result = true
case p == "external:*":
if isExternalRepo(repoURL) {
result = true
}
case p == "external:http:*":
// external:http:* is external:* restricted to the http scheme;
// https and other schemes must not match.
if isExternalRepo(repoURL) && repoURL.Scheme == "http" {
result = true
}
}
}
return result
}
// isExternalRepo reports whether the URL points to an external repository.
// A repository is considered external when its scheme is not "file" and its
// hostname is not one of the loopback addresses (localhost, 127.0.0.1, ::1).
// A nil URL is treated as non-external so that unparsable URLs never trigger
// an external:* match.
func isExternalRepo(u *url.URL) bool {
if u == nil || u.Scheme == "file" {
return false
}
h := u.Hostname()
return h != "localhost" && h != "127.0.0.1" && h != "::1"
}