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