1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.settings.validation;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.net.URI;
25 import java.net.URISyntaxException;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.regex.Pattern;
30
31 import org.apache.maven.settings.Mirror;
32 import org.apache.maven.settings.Profile;
33 import org.apache.maven.settings.Proxy;
34 import org.apache.maven.settings.Repository;
35 import org.apache.maven.settings.Server;
36 import org.apache.maven.settings.Settings;
37 import org.apache.maven.settings.building.SettingsProblem.Severity;
38 import org.apache.maven.settings.building.SettingsProblemCollector;
39 import org.codehaus.plexus.util.StringUtils;
40
41
42
43
44 @Named
45 @Singleton
46 public class DefaultSettingsValidator implements SettingsValidator {
47
48 private static final String ID = "[\\w.-]+";
49 private static final Pattern ID_REGEX = Pattern.compile(ID);
50
51 private static final String ILLEGAL_REPO_ID_CHARS = "\\/:\"<>|?*";
52
53 @Override
54 public void validate(Settings settings, SettingsProblemCollector problems) {
55 if (settings.isUsePluginRegistry()) {
56 addViolation(problems, Severity.WARNING, "usePluginRegistry", null, "is deprecated and has no effect.");
57 }
58
59 List<String> pluginGroups = settings.getPluginGroups();
60
61 if (pluginGroups != null) {
62 for (int i = 0; i < pluginGroups.size(); i++) {
63 String pluginGroup = pluginGroups.get(i).trim();
64
65 if (StringUtils.isBlank(pluginGroup)) {
66 addViolation(
67 problems, Severity.ERROR, "pluginGroups.pluginGroup[" + i + "]", null, "must not be empty");
68 } else if (!ID_REGEX.matcher(pluginGroup).matches()) {
69 addViolation(
70 problems,
71 Severity.ERROR,
72 "pluginGroups.pluginGroup[" + i + "]",
73 null,
74 "must denote a valid group id and match the pattern " + ID);
75 }
76 }
77 }
78
79 List<Server> servers = settings.getServers();
80
81 if (servers != null) {
82 Set<String> serverIds = new HashSet<>();
83
84 for (int i = 0; i < servers.size(); i++) {
85 Server server = servers.get(i);
86
87 validateStringNotEmpty(problems, "servers.server[" + i + "].id", server.getId(), null);
88
89 if (!serverIds.add(server.getId())) {
90 addViolation(
91 problems,
92 Severity.WARNING,
93 "servers.server.id",
94 null,
95 "must be unique but found duplicate server with id " + server.getId());
96 }
97 }
98
99 for (int i = 0; i < servers.size(); i++) {
100 Server server = servers.get(i);
101 for (int a = 0; a < server.getAliases().size(); a++) {
102 String alias = server.getAliases().get(a);
103 validateStringNotEmpty(
104 problems, "servers.server[" + i + "].aliases[" + a + "]", alias, server.getId());
105 if (!serverIds.add(alias)) {
106 addViolation(
107 problems,
108 Severity.WARNING,
109 "servers.server[" + i + "].aliases[" + a + "]",
110 server.getId(),
111 "must be unique across all server ids and aliases but found duplicate alias " + alias);
112 }
113 }
114
115 validateRepositoryOrigins(problems, server, i);
116 }
117 }
118
119 List<Mirror> mirrors = settings.getMirrors();
120
121 if (mirrors != null) {
122 for (Mirror mirror : mirrors) {
123 validateStringNotEmpty(problems, "mirrors.mirror.id", mirror.getId(), mirror.getUrl());
124
125 validateBannedCharacters(
126 problems, "mirrors.mirror.id", Severity.WARNING, mirror.getId(), null, ILLEGAL_REPO_ID_CHARS);
127
128 if ("local".equals(mirror.getId())) {
129 addViolation(
130 problems,
131 Severity.WARNING,
132 "mirrors.mirror.id",
133 null,
134 "must not be 'local'"
135 + ", this identifier is reserved for the local repository"
136 + ", using it for other repositories will corrupt your repository metadata.");
137 }
138
139 validateStringNotEmpty(problems, "mirrors.mirror.url", mirror.getUrl(), mirror.getId());
140
141 validateStringNotEmpty(problems, "mirrors.mirror.mirrorOf", mirror.getMirrorOf(), mirror.getId());
142 }
143 }
144
145 List<Profile> profiles = settings.getProfiles();
146
147 if (profiles != null) {
148 Set<String> profileIds = new HashSet<>();
149
150 for (Profile profile : profiles) {
151 if (!profileIds.add(profile.getId())) {
152 addViolation(
153 problems,
154 Severity.WARNING,
155 "profiles.profile.id",
156 null,
157 "must be unique but found duplicate profile with id " + profile.getId());
158 }
159
160 String prefix = "profiles.profile[" + profile.getId() + "].";
161
162 validateRepositories(problems, profile.getRepositories(), prefix + "repositories.repository");
163 validateRepositories(
164 problems, profile.getPluginRepositories(), prefix + "pluginRepositories.pluginRepository");
165 }
166 }
167
168 List<Proxy> proxies = settings.getProxies();
169
170 if (proxies != null) {
171 Set<String> proxyIds = new HashSet<>();
172
173 for (Proxy proxy : proxies) {
174 if (!proxyIds.add(proxy.getId())) {
175 addViolation(
176 problems,
177 Severity.WARNING,
178 "proxies.proxy.id",
179 null,
180 "must be unique but found duplicate proxy with id " + proxy.getId());
181 }
182 validateStringNotEmpty(problems, "proxies.proxy.host", proxy.getHost(), proxy.getId());
183 }
184 }
185 }
186
187 private void validateRepositories(SettingsProblemCollector problems, List<Repository> repositories, String prefix) {
188 Set<String> repoIds = new HashSet<>();
189
190 for (Repository repository : repositories) {
191 validateStringNotEmpty(problems, prefix + ".id", repository.getId(), repository.getUrl());
192
193 validateBannedCharacters(
194 problems, prefix + ".id", Severity.WARNING, repository.getId(), null, ILLEGAL_REPO_ID_CHARS);
195
196 if ("local".equals(repository.getId())) {
197 addViolation(
198 problems,
199 Severity.WARNING,
200 prefix + ".id",
201 null,
202 "must not be 'local'"
203 + ", this identifier is reserved for the local repository"
204 + ", using it for other repositories will corrupt your repository metadata.");
205 }
206
207 if (!repoIds.add(repository.getId())) {
208 addViolation(
209 problems,
210 Severity.WARNING,
211 prefix + ".id",
212 null,
213 "must be unique but found duplicate repository with id " + repository.getId());
214 }
215
216 validateStringNotEmpty(problems, prefix + ".url", repository.getUrl(), repository.getId());
217
218 if ("legacy".equals(repository.getLayout())) {
219 addViolation(
220 problems,
221 Severity.WARNING,
222 prefix + ".layout",
223 repository.getId(),
224 "uses the unsupported value 'legacy', artifact resolution might fail.");
225 }
226 }
227 }
228
229 private static void validateRepositoryOrigins(SettingsProblemCollector problems, Server server, int index) {
230 for (int o = 0; o < server.getRepositoryOrigins().size(); o++) {
231 String repositoryOrigin = server.getRepositoryOrigins().get(o);
232 String fieldName = "servers.server[" + index + "].repositoryOrigins[" + o + "]";
233
234 if (!validateStringNotEmpty(problems, fieldName, repositoryOrigin, server.getId())) {
235 continue;
236 }
237
238
239
240 if (repositoryOrigin.contains("${")) {
241 continue;
242 }
243
244 String invalid = invalidRepositoryOriginReason(repositoryOrigin);
245 if (invalid != null) {
246 addViolation(problems, Severity.ERROR, fieldName, server.getId(), invalid);
247 continue;
248 }
249
250 String ignored = ignoredRepositoryOriginPartsReason(repositoryOrigin);
251 if (ignored != null) {
252 addViolation(problems, Severity.WARNING, fieldName, server.getId(), ignored);
253 }
254 }
255 }
256
257
258
259
260
261
262 private static URI parseRepositoryOrigin(String value) {
263 try {
264 return new URI(value).parseServerAuthority();
265 } catch (URISyntaxException e) {
266 return null;
267 }
268 }
269
270
271
272
273 private static String invalidRepositoryOriginReason(String value) {
274 URI uri = parseRepositoryOrigin(value);
275 if (uri == null) {
276 return "must be a repository origin of the form scheme://host[:port] but found '" + value + "'";
277 }
278 if (uri.getScheme() == null) {
279 return "must start with a scheme, for example https://repo.example.org, but found '" + value + "'";
280 }
281 if (uri.getHost() == null) {
282 return "must name a host, for example https://repo.example.org, but found '" + value + "'";
283 }
284 if (uri.getUserInfo() != null) {
285 return "must not carry user information but found '" + value + "'";
286 }
287 return null;
288 }
289
290
291
292
293 private static String ignoredRepositoryOriginPartsReason(String value) {
294 URI uri = parseRepositoryOrigin(value);
295 String path = uri.getRawPath();
296 boolean extraPath = path != null && !path.isEmpty() && !"/".equals(path);
297 if (!extraPath && uri.getRawQuery() == null && uri.getRawFragment() == null) {
298 return null;
299 }
300 return "is a repository origin, not a repository URL; only '" + uri.getScheme() + "://" + uri.getAuthority()
301 + "' of '" + value + "' is used";
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316 private static boolean validateStringNotEmpty(
317 SettingsProblemCollector problems, String fieldName, String string, String sourceHint) {
318 if (!validateNotNull(problems, fieldName, string, sourceHint)) {
319 return false;
320 }
321
322 if (string.length() > 0) {
323 return true;
324 }
325
326 addViolation(problems, Severity.ERROR, fieldName, sourceHint, "is missing");
327
328 return false;
329 }
330
331
332
333
334
335
336
337
338 private static boolean validateNotNull(
339 SettingsProblemCollector problems, String fieldName, Object object, String sourceHint) {
340 if (object != null) {
341 return true;
342 }
343
344 addViolation(problems, Severity.ERROR, fieldName, sourceHint, "is missing");
345
346 return false;
347 }
348
349 private static boolean validateBannedCharacters(
350 SettingsProblemCollector problems,
351 String fieldName,
352 Severity severity,
353 String string,
354 String sourceHint,
355 String banned) {
356 if (string != null) {
357 for (int i = string.length() - 1; i >= 0; i--) {
358 if (banned.indexOf(string.charAt(i)) >= 0) {
359 addViolation(
360 problems,
361 severity,
362 fieldName,
363 sourceHint,
364 "must not contain any of these characters " + banned + " but found " + string.charAt(i));
365 return false;
366 }
367 }
368 }
369
370 return true;
371 }
372
373 private static void addViolation(
374 SettingsProblemCollector problems, Severity severity, String fieldName, String sourceHint, String message) {
375 StringBuilder buffer = new StringBuilder(256);
376 buffer.append('\'').append(fieldName).append('\'');
377
378 if (sourceHint != null) {
379 buffer.append(" for ").append(sourceHint);
380 }
381
382 buffer.append(' ').append(message);
383
384 problems.add(severity, buffer.toString(), -1, -1, null);
385 }
386 }