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 tryAddArtifactoryCredentials();
210 tryAddCentralAnalyzerCredentials();
211 tryAddCentralContentCredentials();
212 tryAddNVDApiDatafeed();
213 httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
214 httpClientBuilderExplicitNoproxy.setDefaultCredentialsProvider(credentialsProvider);
215 }
216
217 private void tryAddRetireJSCredentials() throws InvalidSettingException {
218 if (!settings.getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, "").isBlank()) {
219 configureCredentials(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, "RetireJS repo.js",
220 Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_USER, Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_PASSWORD,
221 Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_BEARER_TOKEN
222 );
223 }
224 }
225
226 private void tryAddHostedSuppressionCredentials() throws InvalidSettingException {
227 if (!settings.getString(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, "").isBlank()) {
228 configureCredentials(Settings.KEYS.HOSTED_SUPPRESSIONS_URL, "Hosted suppressions",
229 Settings.KEYS.HOSTED_SUPPRESSIONS_USER, Settings.KEYS.HOSTED_SUPPRESSIONS_PASSWORD,
230 Settings.KEYS.HOSTED_SUPPRESSIONS_BEARER_TOKEN
231 );
232 }
233 }
234
235 private void tryAddKEVCredentials() throws InvalidSettingException {
236 if (!settings.getString(Settings.KEYS.KEV_URL, "").isBlank()) {
237 configureCredentials(Settings.KEYS.KEV_URL, "Known Exploited Vulnerabilities",
238 Settings.KEYS.KEV_USER, Settings.KEYS.KEV_PASSWORD,
239 Settings.KEYS.KEV_BEARER_TOKEN
240 );
241 }
242 }
243
244 private void tryAddNexusAnalyzerCredentials() throws InvalidSettingException {
245 if (!settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL, "").isBlank()) {
246 configureCredentials(Settings.KEYS.ANALYZER_NEXUS_URL, "Nexus Analyzer",
247 Settings.KEYS.ANALYZER_NEXUS_USER, Settings.KEYS.ANALYZER_NEXUS_PASSWORD,
248 null
249 );
250 }
251 }
252
253 private void tryAddCentralAnalyzerCredentials() throws InvalidSettingException {
254 if (!settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL, "").isBlank()) {
255 configureCredentials(Settings.KEYS.ANALYZER_CENTRAL_URL, "Central Analyzer",
256 Settings.KEYS.ANALYZER_CENTRAL_USER, Settings.KEYS.ANALYZER_CENTRAL_PASSWORD,
257 Settings.KEYS.ANALYZER_CENTRAL_BEARER_TOKEN
258 );
259 }
260 }
261
262 private void tryAddArtifactoryCredentials() throws InvalidSettingException {
263 if (!settings.getString(Settings.KEYS.ANALYZER_ARTIFACTORY_URL, "").isBlank()) {
264 configureCredentials(Settings.KEYS.ANALYZER_ARTIFACTORY_URL, "Artifactory Analyzer",
265 Settings.KEYS.ANALYZER_ARTIFACTORY_API_USERNAME, Settings.KEYS.ANALYZER_ARTIFACTORY_API_TOKEN,
266 Settings.KEYS.ANALYZER_ARTIFACTORY_BEARER_TOKEN
267 );
268 }
269 }
270
271 private void tryAddCentralContentCredentials() throws InvalidSettingException {
272 if (!settings.getString(Settings.KEYS.CENTRAL_CONTENT_URL, "").isBlank()) {
273 configureCredentials(Settings.KEYS.CENTRAL_CONTENT_URL, "Central Content",
274 Settings.KEYS.CENTRAL_CONTENT_USER, Settings.KEYS.CENTRAL_CONTENT_PASSWORD,
275 Settings.KEYS.CENTRAL_CONTENT_BEARER_TOKEN
276
277 );
278 }
279 }
280
281 private void tryAddNVDApiDatafeed() throws InvalidSettingException {
282 if (!settings.getString(Settings.KEYS.NVD_API_DATAFEED_URL, "").isBlank()) {
283 configureCredentials(Settings.KEYS.NVD_API_DATAFEED_URL, "NVD API Datafeed",
284 Settings.KEYS.NVD_API_DATAFEED_USER, Settings.KEYS.NVD_API_DATAFEED_PASSWORD,
285 Settings.KEYS.NVD_API_DATAFEED_BEARER_TOKEN
286 );
287 }
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301 private void configureCredentials(String urlKey, String scopeDescription, String userKey, String passwordKey, String tokenKey)
302 throws InvalidSettingException {
303 final URL theURL;
304 try {
305 theURL = new URL(settings.getString(urlKey, ""));
306 } catch (MalformedURLException e) {
307 throw new InvalidSettingException(scopeDescription + " URL must be a valid URL (was: " + settings.getString(urlKey, "") + ")", e);
308 }
309 configureCredentials(theURL, scopeDescription, userKey, passwordKey, tokenKey, credentialsProvider, authCache);
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 private void configureCredentials(URL theURL, String scopeDescription, String userKey, String passwordKey, String tokenKey,
326 CredentialsStore theCredentialsStore, AuthCache theAuthCache)
327 throws InvalidSettingException {
328 final String theUser = settings.getString(userKey);
329 final String thePass = settings.getString(passwordKey);
330 final String theToken = tokenKey != null ? settings.getString(tokenKey) : null;
331 if (theUser == null && thePass == null && theToken == null) {
332
333 return;
334 }
335 final String theProtocol = theURL.getProtocol();
336 if ("file".equals(theProtocol)) {
337
338 return;
339 } else if ("http".equals(theProtocol) && (theUser != null && thePass != null)) {
340 LOGGER.warn("Insecure configuration: Basic Credentials are configured to be used over a plain http connection for {}. "
341 + "Consider migrating to https to guard the credentials.", scopeDescription);
342 } else if ("http".equals(theProtocol) && (theToken != null)) {
343 LOGGER.warn("Insecure configuration: Bearer Credentials are configured to be used over a plain http connection for {}. "
344 + "Consider migrating to https to guard the credentials.", scopeDescription);
345 } else if (!"https".equals(theProtocol)) {
346 throw new InvalidSettingException("Unsupported protocol in the " + scopeDescription
347 + " URL; only file, http and https are supported");
348 }
349 if (theToken != null) {
350 HC5CredentialHelper.configurePreEmptiveBearerAuth(theURL, theToken, theCredentialsStore, theAuthCache);
351 } else if (theUser != null && thePass != null) {
352 HC5CredentialHelper.configurePreEmptiveBasicAuth(theURL, theUser, thePass, theCredentialsStore, theAuthCache);
353 }
354 }
355
356
357
358
359
360
361
362
363
364
365
366 public void fetchFile(URL url, File outputPath)
367 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
368 fetchFile(url, outputPath, true);
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383 public void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException,
384 TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
385 try {
386 if ("file".equals(url.getProtocol())) {
387 final Path p = Paths.get(url.toURI());
388 Files.copy(p, outputPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
389 } else {
390 final BasicClassicHttpRequest req;
391 req = new BasicClassicHttpRequest(Method.GET, url.toURI());
392 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
393 final SaveToFileResponseHandler responseHandler = new SaveToFileResponseHandler(outputPath);
394 hc.execute(req, getPreEmptiveAuthContext(), responseHandler);
395 }
396 }
397 } catch (HttpResponseException hre) {
398 wrapAndThrowHttpResponseException(url.toString(), hre);
399 } catch (SSLHandshakeException ex) {
400 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
401 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
402 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
403 throw new URLConnectionFailureException(msg, ex);
404 }
405 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
406 throw new DownloadFailedException(msg, ex);
407 } catch (RuntimeException | URISyntaxException | IOException ex) {
408 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
409 throw new DownloadFailedException(msg, ex);
410 }
411 }
412
413 private static void wrapAndThrowHttpResponseException(String url, HttpResponseException hre)
414 throws ResourceNotFoundException, TooManyRequestsException, DownloadFailedException {
415 final String messageFormat = "%s - Server status: %d - Server reason: %s";
416 switch (hre.getStatusCode()) {
417 case 404:
418 throw new ResourceNotFoundException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
419 case 429:
420 throw new TooManyRequestsException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
421 default:
422 throw new DownloadFailedException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()), hre);
423 }
424 }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444 public void fetchFile(URL url, File outputPath, boolean useProxy, String userKey, String passwordKey, String tokenKey)
445 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
446 final boolean basicConfigured = userKey != null && settings.getString(userKey) != null
447 && passwordKey != null && settings.getString(passwordKey) != null;
448 final boolean tokenConfigured = tokenKey != null && settings.getString(tokenKey) != null;
449 if ("file".equals(url.getProtocol()) || (!basicConfigured && !tokenConfigured)) {
450
451 fetchFile(url, outputPath, useProxy);
452 return;
453 }
454 final String theProtocol = url.getProtocol();
455 if (!("http".equals(theProtocol) || "https".equals(theProtocol))) {
456 throw new DownloadFailedException("Unsupported protocol in the URL; only file, http and https are supported");
457 }
458 try {
459 final HttpClientContext dedicatedAuthContext = HttpClientContext.create();
460 final CredentialsStore dedicatedCredentialStore = new SystemDefaultCredentialsProvider();
461 final AuthCache dedicatedAuthCache = new BasicAuthCache();
462 configureCredentials(url, url.toString(), userKey, passwordKey, tokenKey, dedicatedCredentialStore, dedicatedAuthCache);
463 if (useProxy && proxyAuthScope != null) {
464 tryConfigureProxyCredentials(dedicatedCredentialStore, dedicatedAuthCache);
465 }
466 dedicatedAuthContext.setCredentialsProvider(dedicatedCredentialStore);
467 dedicatedAuthContext.setAuthCache(dedicatedAuthCache);
468 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
469 final BasicClassicHttpRequest req = new BasicClassicHttpRequest(Method.GET, url.toURI());
470 final SaveToFileResponseHandler responseHandler = new SaveToFileResponseHandler(outputPath);
471 hc.execute(req, dedicatedAuthContext, responseHandler);
472 }
473 } catch (HttpResponseException hre) {
474 wrapAndThrowHttpResponseException(url.toString(), hre);
475 } catch (SSLHandshakeException ex) {
476 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
477 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
478 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
479 throw new URLConnectionFailureException(msg, ex);
480 }
481 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
482 throw new DownloadFailedException(msg, ex);
483 } catch (RuntimeException | URISyntaxException | IOException ex) {
484 final String msg = format("Download failed, unable to copy '%s' to '%s'; %s", url, outputPath.getAbsolutePath(), ex.getMessage());
485 throw new DownloadFailedException(msg, ex);
486 }
487 }
488
489
490
491
492
493
494 private void tryConfigureProxyCredentials(@NotNull CredentialsStore credentialsProvider, @NotNull AuthCache authCache) {
495 if (proxyPreEmptAuth != null) {
496 credentialsProvider.setCredentials(proxyAuthScope, proxyCreds);
497 authCache.put(proxyHttpHost, proxyPreEmptAuth);
498 }
499 }
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514 public String postBasedFetchContent(URI url, String payload, ContentType payloadType, List<Header> hdr)
515 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException, URLConnectionFailureException {
516 try {
517 if (url.getScheme() == null || !url.getScheme().toLowerCase(Locale.ROOT).matches("^https?")) {
518 throw new IllegalArgumentException("Unsupported protocol in the URL; only http and https are supported");
519 } else {
520 final BasicClassicHttpRequest req;
521 req = new BasicClassicHttpRequest(Method.POST, url);
522 req.setEntity(new StringEntity(payload, payloadType));
523 for (Header h : hdr) {
524 req.addHeader(h);
525 }
526 final String result;
527 try (CloseableHttpClient hc = httpClientBuilder.build()) {
528 result = hc.execute(req, getPreEmptiveAuthContext(), new BasicHttpClientResponseHandler());
529 }
530 return result;
531 }
532 } catch (HttpResponseException hre) {
533 wrapAndThrowHttpResponseException(url.toString(), hre);
534 throw new InternalError("wrapAndThrowHttpResponseException will always throw an exception but Java compiler fails to spot it");
535 } catch (SSLHandshakeException ex) {
536 if (ex.getMessage().contains("unable to find valid certification path to requested target")) {
537 final String msg = String.format("Unable to connect to '%s' - the Java trust store does not contain a trusted root for the cert. "
538 + "Please see https://github.com/jeremylong/InstallCert for one method of updating the trusted certificates.", url);
539 throw new URLConnectionFailureException(msg, ex);
540 }
541 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
542 throw new DownloadFailedException(msg, ex);
543 } catch (IOException | RuntimeException ex) {
544 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
545 throw new DownloadFailedException(msg, ex);
546 }
547 }
548
549
550
551
552
553
554
555
556
557
558
559
560 public String fetchContent(URL url, Charset charset) throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException {
561 return fetchContent(url, true, charset);
562 }
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577 public String fetchContent(URL url, boolean useProxy, Charset charset)
578 throws DownloadFailedException, TooManyRequestsException, ResourceNotFoundException {
579 try {
580 final String result;
581 if ("file".equals(url.getProtocol())) {
582 final Path p = Paths.get(url.toURI());
583 result = Files.readString(p, charset);
584 } else {
585 final BasicClassicHttpRequest req;
586 req = new BasicClassicHttpRequest(Method.GET, url.toURI());
587 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
588 req.addHeader(HttpHeaders.ACCEPT_CHARSET, charset.name());
589 final ExplicitCharsetToStringResponseHandler responseHandler = new ExplicitCharsetToStringResponseHandler(charset);
590 result = hc.execute(req, getPreEmptiveAuthContext(), responseHandler);
591 }
592 }
593 return result;
594 } catch (HttpResponseException hre) {
595 wrapAndThrowHttpResponseException(url.toString(), hre);
596 throw new InternalError("wrapAndThrowHttpResponseException will always throw an exception but Java compiler fails to spot it");
597 } catch (RuntimeException | URISyntaxException | IOException ex) {
598 final String msg = format("Download failed, error downloading '%s'; %s", url, ex.getMessage());
599 throw new DownloadFailedException(msg, ex);
600 }
601 }
602
603
604
605
606
607 public HttpClientContext getPreEmptiveAuthContext() {
608 final HttpClientContext context = HttpClientContext.create();
609 context.setCredentialsProvider(credentialsProvider);
610 context.setAuthCache(authCache);
611 return context;
612 }
613
614
615
616
617
618
619
620 public CloseableHttpClient getHttpClient(boolean useProxy) {
621 return useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build();
622 }
623
624
625
626
627
628
629
630
631
632
633
634
635 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler)
636 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
637 return fetchAndHandle(url, handler, Collections.emptyList(), true);
638 }
639
640
641
642
643
644
645
646
647
648
649
650
651
652 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler, @NotNull List<Header> hdr)
653 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
654 return fetchAndHandle(url, handler, hdr, true);
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670 public <T> T fetchAndHandle(@NotNull URL url, @NotNull HttpClientResponseHandler<T> handler, @NotNull List<Header> hdr, boolean useProxy)
671 throws IOException, TooManyRequestsException, ResourceNotFoundException, URISyntaxException {
672 final T data;
673 if ("file".equals(url.getProtocol())) {
674 final Path p = Paths.get(url.toURI());
675 try (InputStream is = Files.newInputStream(p)) {
676 final HttpEntity dummyEntity = new BasicHttpEntity(is, ContentType.APPLICATION_JSON);
677 final ClassicHttpResponse dummyResponse = new BasicClassicHttpResponse(200);
678 dummyResponse.setEntity(dummyEntity);
679 data = handler.handleResponse(dummyResponse);
680 } catch (HttpException e) {
681 throw new IllegalStateException("HttpException encountered emulating a HTTP response from a file", e);
682 }
683 } else {
684 try (CloseableHttpClient hc = useProxy ? httpClientBuilder.build() : httpClientBuilderExplicitNoproxy.build()) {
685 return fetchAndHandle(hc, url, handler, hdr);
686 }
687 }
688 return data;
689 }
690
691
692
693
694
695
696
697
698
699
700
701
702
703 public <T> T fetchAndHandle(@NotNull CloseableHttpClient client, @NotNull URL url, @NotNull HttpClientResponseHandler<T> handler,
704 @NotNull List<Header> hdr) throws IOException, TooManyRequestsException, ResourceNotFoundException {
705 try {
706 final String theProtocol = url.getProtocol();
707 if (!("http".equals(theProtocol) || "https".equals(theProtocol))) {
708 throw new DownloadFailedException("Unsupported protocol in the URL; only http and https are supported");
709 }
710 final BasicClassicHttpRequest req = new BasicClassicHttpRequest(Method.GET, url.toURI());
711 for (Header h : hdr) {
712 req.addHeader(h);
713 }
714 final HttpClientContext context = getPreEmptiveAuthContext();
715 return client.execute(req, context, handler);
716 } catch (HttpResponseException hre) {
717 final String messageFormat = "%s - Server status: %d - Server reason: %s";
718 switch (hre.getStatusCode()) {
719 case 404:
720 throw new ResourceNotFoundException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
721 case 429:
722 throw new TooManyRequestsException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
723 default:
724 throw new IOException(String.format(messageFormat, url, hre.getStatusCode(), hre.getReasonPhrase()));
725 }
726 } catch (RuntimeException | URISyntaxException ex) {
727 final String msg = format("Download failed, unable to retrieve and parse '%s'; %s", url, ex.getMessage());
728 throw new IOException(msg, ex);
729 }
730 }
731
732 private static class SelectiveProxySelector extends ProxySelector {
733
734
735
736
737 private final List<String> suffixMatch = new ArrayList<>();
738
739
740
741 private final List<String> fullmatch = new ArrayList<>();
742
743
744
745 private final Proxy configuredProxy;
746
747 SelectiveProxySelector(Proxy httpHost, String[] nonProxyHostsPatterns) {
748 for (String nonProxyHostPattern : nonProxyHostsPatterns) {
749 if (nonProxyHostPattern.startsWith("*")) {
750 suffixMatch.add(nonProxyHostPattern.substring(1));
751 } else {
752 fullmatch.add(nonProxyHostPattern);
753 }
754 }
755 this.configuredProxy = httpHost;
756 }
757
758 @Override
759 public List<Proxy> select(URI uri) {
760 final String theHost = uri.getHost();
761 if (fullmatch.contains(theHost)) {
762 return Collections.singletonList(Proxy.NO_PROXY);
763 } else {
764 for (String suffix : suffixMatch) {
765 if (theHost.endsWith(suffix)) {
766 return Collections.singletonList(Proxy.NO_PROXY);
767 }
768 }
769 }
770 return List.of(configuredProxy);
771 }
772
773 @Override
774 public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
775
776 }
777 }
778 }