1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.utils;
19
20 import org.apache.hc.client5.http.HttpResponseException;
21 import org.apache.hc.client5.http.auth.AuthCache;
22 import org.apache.hc.client5.http.auth.AuthScope;
23 import org.apache.hc.client5.http.auth.Credentials;
24 import org.apache.hc.client5.http.auth.CredentialsStore;
25 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
26 import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
27 import org.apache.hc.client5.http.impl.auth.BasicScheme;
28 import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
29 import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
30 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
31 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
32 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
33 import org.apache.hc.client5.http.protocol.HttpClientContext;
34 import org.apache.hc.core5.http.ClassicHttpResponse;
35 import org.apache.hc.core5.http.ContentType;
36 import org.apache.hc.core5.http.Header;
37 import org.apache.hc.core5.http.HttpEntity;
38 import org.apache.hc.core5.http.HttpException;
39 import org.apache.hc.core5.http.HttpHeaders;
40 import org.apache.hc.core5.http.HttpHost;
41 import org.apache.hc.core5.http.Method;
42 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
43 import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
44 import org.apache.hc.core5.http.io.entity.StringEntity;
45 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
46 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
47 import org.jetbrains.annotations.NotNull;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import javax.net.ssl.SSLHandshakeException;
52 import java.io.File;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.net.InetSocketAddress;
56 import java.net.MalformedURLException;
57 import java.net.Proxy;
58 import java.net.ProxySelector;
59 import java.net.SocketAddress;
60 import java.net.URI;
61 import java.net.URISyntaxException;
62 import java.net.URL;
63 import java.nio.charset.Charset;
64 import java.nio.file.Files;
65 import java.nio.file.Path;
66 import java.nio.file.Paths;
67 import java.nio.file.StandardCopyOption;
68 import java.util.ArrayList;
69 import java.util.Collections;
70 import java.util.List;
71 import java.util.Locale;
72
73 import static java.lang.String.format;
74
75
76
77
78
79 public final class Downloader {
80
81
82
83
84 private final HttpClientBuilder httpClientBuilder;
85
86
87
88
89 private final HttpClientBuilder httpClientBuilderExplicitNoproxy;
90
91
92
93
94
95 private final AuthCache authCache = new BasicAuthCache();
96
97
98
99
100
101 private final SystemDefaultCredentialsProvider credentialsProvider = new SystemDefaultCredentialsProvider();
102
103
104
105
106 private Settings settings;
107
108
109
110
111 private static final Logger LOGGER = LoggerFactory.getLogger(Downloader.class);
112
113
114
115
116 private static final Downloader INSTANCE = new Downloader();
117
118
119
120 private Credentials proxyCreds = null;
121
122
123
124 private BasicScheme proxyPreEmptAuth = null;
125
126
127
128 private AuthScope proxyAuthScope = null;
129
130
131
132 private HttpHost proxyHttpHost = null;
133
134 private Downloader() {
135
136 final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
137
138 httpClientBuilder = HttpClientBuilder.create()
139 .useSystemProperties()
140 .setConnectionManager(connectionManager)
141 .setConnectionManagerShared(true);
142 httpClientBuilderExplicitNoproxy = HttpClientBuilder.create()
143 .useSystemProperties()
144 .setConnectionManager(connectionManager)
145 .setConnectionManagerShared(true)
146 .setProxySelector(new ProxySelector() {
147 @Override
148 public List<Proxy> select(URI uri) {
149 return Collections.singletonList(Proxy.NO_PROXY);
150 }
151
152 @Override
153 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
154
155 }
156 });
157 }
158
159
160
161
162
163
164 public static Downloader getInstance() {
165 return INSTANCE;
166 }
167
168
169
170
171
172
173
174
175
176 public void configure(Settings settings) throws InvalidSettingException {
177 this.settings = settings;
178
179 if (settings.getString(Settings.KEYS.PROXY_SERVER) != null) {
180
181
182 final String proxyHost = settings.getString(Settings.KEYS.PROXY_SERVER);
183 final int proxyPort = settings.getInt(Settings.KEYS.PROXY_PORT, -1);
184 final String nonProxyHosts = settings.getString(Settings.KEYS.PROXY_NON_PROXY_HOSTS);
185 if (nonProxyHosts != null && !nonProxyHosts.isEmpty()) {
186 final ProxySelector selector = new SelectiveProxySelector(
187 new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)),
188 nonProxyHosts.split("\\|")
189 );
190 httpClientBuilder.setProxySelector(selector);
191 } else {
192 httpClientBuilder.setProxy(new HttpHost(proxyHost, proxyPort));
193 }
194 if (settings.getString(Settings.KEYS.PROXY_USERNAME) != null) {
195 final String proxyuser = settings.getString(Settings.KEYS.PROXY_USERNAME);
196 final char[] proxypass = settings.getString(Settings.KEYS.PROXY_PASSWORD).toCharArray();
197 this.proxyHttpHost = new HttpHost(null, proxyHost, proxyPort);
198 this.proxyCreds = new UsernamePasswordCredentials(proxyuser, proxypass);
199 this.proxyAuthScope = new AuthScope(proxyHttpHost);
200 this.proxyPreEmptAuth = new BasicScheme();
201 this.proxyPreEmptAuth.initPreemptive(proxyCreds);
202 tryConfigureProxyCredentials(credentialsProvider, authCache);
203 }
204 }
205 tryAddRetireJSCredentials();
206 tryAddHostedSuppressionCredentials();
207 tryAddKEVCredentials();
208 tryAddNexusAnalyzerCredentials();
209 tryAddCentralAnalyzerCredentials();
210 tryAddCentralContentCredentials();
211 tryAddNVDApiDatafeed();
212 httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
213 httpClientBuilderExplicitNoproxy.setDefaultCredentialsProvider(credentialsProvider);
214 }
215
216 private void tryAddRetireJSCredentials() throws InvalidSettingException {
217 if (!settings.getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, "").isBlank()) {
218 configureCredentials(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, "RetireJS repo.js",
219 Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_USER, Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_PASSWORD,
220 Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_BEARER_TOKEN
221 );
222 }
223 }
224
225 private void tryAddHostedSuppressionCredentials() throws InvalidSettingException {
226 if (!settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, "").isBlank()) {
227 configureCredentials(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, "Hosted suppressions",
228 Settings.KEYS.HOSTED_SUPPRESSIONS_USER, Settings.KEYS.HOSTED_SUPPRESSIONS_PASSWORD,
229 Settings.KEYS.HOSTED_SUPPRESSIONS_BEARER_TOKEN
230 );
231 }
232 }
233
234 private void tryAddKEVCredentials() throws InvalidSettingException {
235 if (!settings.getString(Settings.KEYS.KEV_URL, "").isBlank()) {
236 configureCredentials(Settings.KEYS.KEV_URL, "Known Exploited Vulnerabilities",
237 Settings.KEYS.KEV_USER, Settings.KEYS.KEV_PASSWORD,
238 Settings.KEYS.KEV_BEARER_TOKEN
239 );
240 }
241 }
242
243 private void tryAddNexusAnalyzerCredentials() throws InvalidSettingException {
244 if (!settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL, "").isBlank()) {
245 configureCredentials(Settings.KEYS.ANALYZER_NEXUS_URL, "Nexus Analyzer",
246 Settings.KEYS.ANALYZER_NEXUS_USER, Settings.KEYS.ANALYZER_NEXUS_PASSWORD,
247 null
248 );
249 }
250 }
251
252 private void tryAddCentralAnalyzerCredentials() throws InvalidSettingException {
253 if (!settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL, "").isBlank()) {
254 configureCredentials(Settings.KEYS.ANALYZER_CENTRAL_URL, "Central Analyzer",
255 Settings.KEYS.ANALYZER_CENTRAL_USER, Settings.KEYS.ANALYZER_CENTRAL_PASSWORD,
256 Settings.KEYS.ANALYZER_CENTRAL_BEARER_TOKEN
257 );
258 }
259 }
260
261 private void tryAddCentralContentCredentials() throws InvalidSettingException {
262 if (!settings.getString(Settings.KEYS.CENTRAL_CONTENT_URL, "").isBlank()) {
263 configureCredentials(Settings.KEYS.CENTRAL_CONTENT_URL, "Central Content",
264 Settings.KEYS.CENTRAL_CONTENT_USER, Settings.KEYS.CENTRAL_CONTENT_PASSWORD,
265 Settings.KEYS.CENTRAL_CONTENT_BEARER_TOKEN
266
267 );
268 }
269 }
270
271 private void tryAddNVDApiDatafeed() throws InvalidSettingException {
272 if (!settings.getString(Settings.KEYS.NVD_API_DATAFEED_URL, "").isBlank()) {
273 configureCredentials(Settings.KEYS.NVD_API_DATAFEED_URL, "NVD API Datafeed",
274 Settings.KEYS.NVD_API_DATAFEED_USER, Settings.KEYS.NVD_API_DATAFEED_PASSWORD,
275 Settings.KEYS.NVD_API_DATAFEED_BEARER_TOKEN
276 );
277 }
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291 private void configureCredentials(String urlKey, String scopeDescription, String userKey, String passwordKey, String tokenKey)
292 throws InvalidSettingException {
293 final URL theURL;
294 try {
295 theURL = new URL(settings.getString(urlKey, ""));
296 } catch (MalformedURLException e) {
297 throw new InvalidSettingException(scopeDescription + " URL must be a valid URL (was: " + settings.getString(urlKey, "") + ")", e);
298 }
299 configureCredentials(theURL, scopeDescription, userKey, passwordKey, tokenKey, credentialsProvider, authCache);
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315 private void configureCredentials(URL theURL, String scopeDescription, String userKey, String passwordKey, String tokenKey,
316 CredentialsStore theCredentialsStore, AuthCache theAuthCache)
317 throws InvalidSettingException {
318 final String theUser = settings.getString(userKey);
319 final String thePass = settings.getString(passwordKey);
320 final String theToken = tokenKey != null ? settings.getString(tokenKey) : null;
321 if (theUser == null && thePass == null && theToken == null) {
322
323 return;
324 }
325 final String theProtocol = theURL.getProtocol();
326 if ("file".equals(theProtocol)) {
327
328 return;
329 } else if ("http".equals(theProtocol) && (theUser != null && thePass != null)) {
330 LOGGER.warn("Insecure configuration: Basic Credentials are configured to be used over a plain http connection for {}. "
331 + "Consider migrating to https to guard the credentials.", scopeDescription);
332 } else if ("http".equals(theProtocol) && (theToken != null)) {
333 LOGGER.warn("Insecure configuration: Bearer Credentials are configured to be used over a plain http connection for {}. "
334 + "Consider migrating to https to guard the credentials.", scopeDescription);
335 } else if (!"https".equals(theProtocol)) {
336 throw new InvalidSettingException("Unsupported protocol in the " + scopeDescription
337 + " URL; only file, http and https are supported");
338 }
339 if (theToken != null) {
340 HC5CredentialHelper.configurePreEmptiveBearerAuth(theURL, theToken, theCredentialsStore, theAuthCache);
341 } else if (theUser != null && thePass != null) {
342 HC5CredentialHelper.configurePreEmptiveBasicAuth(theURL, theUser, thePass, theCredentialsStore, theAuthCache);
343 }
344 }
345
346
347
348
349
350
351
352
353
354
355
356 public void fetchFile(URL url, File outputPath)
357 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
358 fetchFile(url, outputPath, true);
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException,
374 TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
375 try {
376 if ("file".equals(url.getProtocol())) {
377 final Path p = Paths.get(url.toURI());
378 Files.copy(p, outputPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
379 } else {
380 final BasicClassicHttpRequest req;
381 req = new BasicClassicHttpRequest(Method.GET, url.toURI());
382 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
383 final SaveToFileResponseHandler responseHandler = new SaveToFileResponseHandler(outputPath);
384 hc.execute(req, getPreEmptiveAuthContext(), responseHandler);
385 }
386 }
387 } catch (HttpResponseException hre) {
388 wrapAndThrowHttpResponseException(url.toString(), hre);
389 } catch (SSLHandshakeException ex) {
390 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
391 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
392 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
393 throw new URLConnectionFailureException(msg, ex);
394 }
395 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
396 throw new DownloadFailedException(msg, ex);
397 } catch (RuntimeException | URISyntaxException | IOException ex) {
398 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
399 throw new DownloadFailedException(msg, ex);
400 }
401 }
402
403 private static void wrapAndThrowHttpResponseException(String url, HttpResponseException hre)
404 throws ResourceNotFoundException, TooManyRequestsException, DownloadFailedException {
405 final String messageFormat = "%s - Server status: %d - Server reason: %s";
406 switch (hre.getStatusCode()) {
407 case 404:
408 throw new ResourceNotFoundException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
409 case 429:
410 throw new TooManyRequestsException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
411 default:
412 throw new DownloadFailedException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
413 }
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434 public void fetchFile(URL url, File outputPath, boolean useProxy, String userKey, String passwordKey, String tokenKey)
435 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
436 final boolean basicConfigured = userKey != null && settings.getString(userKey) != null
437 && passwordKey != null && settings.getString(passwordKey) != null;
438 final boolean tokenConfigured = tokenKey != null && settings.getString(tokenKey) != null;
439 if ("file".equals(url.getProtocol()) || (!basicConfigured && !tokenConfigured)) {
440
441 fetchFile(url, outputPath, useProxy);
442 return;
443 }
444 final String theProtocol = url.getProtocol();
445 if (!("http".equals(theProtocol) || "https".equals(theProtocol))) {
446 throw new DownloadFailedException("Unsupported protocol in the URL; only file, http and https are supported");
447 }
448 try {
449 final HttpClientContext dedicatedAuthContext = HttpClientContext.create();
450 final CredentialsStore dedicatedCredentialStore = new SystemDefaultCredentialsProvider();
451 final AuthCache dedicatedAuthCache = new BasicAuthCache();
452 configureCredentials(url, url.toString(), userKey, passwordKey, tokenKey, dedicatedCredentialStore, dedicatedAuthCache);
453 if (useProxy && proxyAuthScope != null) {
454 tryConfigureProxyCredentials(dedicatedCredentialStore, dedicatedAuthCache);
455 }
456 dedicatedAuthContext.setCredentialsProvider(dedicatedCredentialStore);
457 dedicatedAuthContext.setAuthCache(dedicatedAuthCache);
458 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
459 final BasicClassicHttpRequest req = new BasicClassicHttpRequest(Method.GET, url.toURI());
460 final SaveToFileResponseHandler responseHandler = new SaveToFileResponseHandler(outputPath);
461 hc.execute(req, dedicatedAuthContext, responseHandler);
462 }
463 } catch (HttpResponseException hre) {
464 wrapAndThrowHttpResponseException(url.toString(), hre);
465 } catch (SSLHandshakeException ex) {
466 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
467 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
468 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
469 throw new URLConnectionFailureException(msg, ex);
470 }
471 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
472 throw new DownloadFailedException(msg, ex);
473 } catch (RuntimeException | URISyntaxException | IOException ex) {
474 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
475 throw new DownloadFailedException(msg, ex);
476 }
477 }
478
479
480
481
482
483
484 private void tryConfigureProxyCredentials(@NotNull CredentialsStore credentialsProvider, @NotNull AuthCache authCache) {
485 if (proxyPreEmptAuth != null) {
486 credentialsProvider.setCredentials(proxyAuthScope, proxyCreds);
487 authCache.put(proxyHttpHost, proxyPreEmptAuth);
488 }
489 }
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504 public String postBasedFetchContent(URI url, String payload, ContentType payloadType, List<Header> hdr)
505 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
506 try {
507 if (url.getScheme() == null || !url.getScheme().toLowerCase(Locale.ROOT).matches("^https?")) {
508 throw new IllegalArgumentException("Unsupported protocol in the URL; only http and https are supported");
509 } else {
510 final BasicClassicHttpRequest req;
511 req = new BasicClassicHttpRequest(Method.POST, url);
512 req.setEntity(new StringEntity(payload, payloadType));
513 for (Header h : hdr) {
514 req.addHeader(h);
515 }
516 final String result;
517 try (CloseableHttpClient hc = httpClientBuilder.build()) {
518 result = hc.execute(req, getPreEmptiveAuthContext(), new BasicHttpClientResponseHandler());
519 }
520 return result;
521 }
522 } catch (HttpResponseException hre) {
523 wrapAndThrowHttpResponseException(url.toString(), hre);
524 throw new InternalError("wrapAndThrowHttpResponseException will always throw an exception but Java compiler fails to spot it");
525 } catch (SSLHandshakeException ex) {
526 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
527 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
528 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
529 throw new URLConnectionFailureException(msg, ex);
530 }
531 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
532 throw new DownloadFailedException(msg, ex);
533 } catch (IOException | RuntimeException ex) {
534 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
535 throw new DownloadFailedException(msg, ex);
536 }
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550 public String fetchContent(URL url, Charset charset) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException {
551 return fetchContent(url, true, charset);
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567 public String fetchContent(URL url, boolean useProxy, Charset charset)
568 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException {
569 try {
570 final String result;
571 if ("file".equals(url.getProtocol())) {
572 final Path p = Paths.get(url.toURI());
573 result = Files.readString(p, charset);
574 } else {
575 final BasicClassicHttpRequest req;
576 req = new BasicClassicHttpRequest(Method.GET, url.toURI());
577 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
578 req.addHeader(HttpHeaders.ACCEPT_CHARSET, charset.name());
579 final ExplicitCharsetToStringResponseHandler responseHandler = new ExplicitCharsetToStringResponseHandler(charset);
580 result = hc.execute(req, getPreEmptiveAuthContext(), responseHandler);
581 }
582 }
583 return result;
584 } catch (HttpResponseException hre) {
585 wrapAndThrowHttpResponseException(url.toString(), hre);
586 throw new InternalError("wrapAndThrowHttpResponseException will always throw an exception but Java compiler fails to spot it");
587 } catch (RuntimeException | URISyntaxException | IOException ex) {
588 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
589 throw new DownloadFailedException(msg, ex);
590 }
591 }
592
593
594
595
596
597 public HttpClientContext getPreEmptiveAuthContext() {
598 final HttpClientContext context = HttpClientContext.create();
599 context.setCredentialsProvider(credentialsProvider);
600 context.setAuthCache(authCache);
601 return context;
602 }
603
604
605
606
607
608
609
610 public CloseableHttpClient getHttpClient(boolean useProxy) {
611 return useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build();
612 }
613
614
615
616
617
618
619
620
621
622
623
624
625 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler)
626 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
627 return fetchAndHandle(url, handler, Collections.emptyList(), true);
628 }
629
630
631
632
633
634
635
636
637
638
639
640
641
642 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler, @NotNull List<Header> hdr)
643 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
644 return fetchAndHandle(url, handler, hdr, true);
645 }
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler, @NotNull List<Header> hdr, boolean useProxy)
661 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
662 final T data;
663 if ("file".equals(url.getProtocol())) {
664 final Path p = Paths.get(url.toURI());
665 try (InputStream is = Files.newInputStream(p)) {
666 final HttpEntity dummyEntity = new BasicHttpEntity(is, ContentType.APPLICATION_JSON);
667 final ClassicHttpResponse dummyResponse = new BasicClassicHttpResponse(200);
668 dummyResponse.setEntity(dummyEntity);
669 data = handler.handleResponse(dummyResponse);
670 } catch (HttpException e) {
671 throw new IllegalStateException("HttpException encountered emulating a HTTP response from a file", e);
672 }
673 } else {
674 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
675 return fetchAndHandle(hc, url, handler, hdr);
676 }
677 }
678 return data;
679 }
680
681
682
683
684
685
686
687
688
689
690
691
692
693 public <T> T fetchAndHandle(@NotNull CloseableHttpClient client, @NotNull URL url, @NotNull HttpClientResponseHandler<T> handler,
694 @NotNull List<Header> hdr) throws IOException, TooManyRequestsException, ResourceNotFoundException {
695 try {
696 final String theProtocol = url.getProtocol();
697 if (!("http".equals(theProtocol) || "https".equals(theProtocol))) {
698 throw new DownloadFailedException("Unsupported protocol in the URL; only http and https are supported");
699 }
700 final BasicClassicHttpRequest req = new BasicClassicHttpRequest(Method.GET, url.toURI());
701 for (Header h : hdr) {
702 req.addHeader(h);
703 }
704 final HttpClientContext context = getPreEmptiveAuthContext();
705 return client.execute(req, context, handler);
706 } catch (HttpResponseException hre) {
707 final String messageFormat = "%s - Server status: %d - Server reason: %s";
708 switch (hre.getStatusCode()) {
709 case 404:
710 throw new ResourceNotFoundException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
711 case 429:
712 throw new TooManyRequestsException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
713 default:
714 throw new IOException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
715 }
716 } catch (RuntimeException | URISyntaxException ex) {
717 final String msg = format("Download failed, unable to retrieve and parse '%s'; %s", url, ex.getMessage());
718 throw new IOException(msg, ex);
719 }
720 }
721
722 private static class SelectiveProxySelector extends ProxySelector {
723
724
725
726
727 private final List<String> suffixMatch = new ArrayList<>();
728
729
730
731 private final List<String> fullmatch = new ArrayList<>();
732
733
734
735 private final Proxy configuredProxy;
736
737 SelectiveProxySelector(Proxy httpHost, String[] nonProxyHostsPatterns) {
738 for (String nonProxyHostPattern : nonProxyHostsPatterns) {
739 if (nonProxyHostPattern.startsWith("*")) {
740 suffixMatch.add(nonProxyHostPattern.substring(1));
741 } else {
742 fullmatch.add(nonProxyHostPattern);
743 }
744 }
745 this.configuredProxy = httpHost;
746 }
747
748 @Override
749 public List<Proxy> select(URI uri) {
750 final String theHost = uri.getHost();
751 if (fullmatch.contains(theHost)) {
752 return Collections.singletonList(Proxy.NO_PROXY);
753 } else {
754 for (String suffix : suffixMatch) {
755 if (theHost.endsWith(suffix)) {
756 return Collections.singletonList(Proxy.NO_PROXY);
757 }
758 }
759 }
760 return List.of(configuredProxy);
761 }
762
763 @Override
764 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
765
766 }
767 }
768 }