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 java.io.BufferedOutputStream;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.net.HttpURLConnection;
29 import java.net.URISyntaxException;
30 import java.net.URL;
31 import java.security.InvalidAlgorithmParameterException;
32 import java.util.zip.GZIPInputStream;
33 import java.util.zip.InflaterInputStream;
34 import static java.lang.String.format;
35
36
37
38
39
40
41 public final class Downloader {
42
43
44
45
46 private static final Logger LOGGER = LoggerFactory.getLogger(Downloader.class);
47
48
49
50
51 private static final int MAX_REDIRECT_ATTEMPTS = 5;
52
53
54
55
56 private static final String HEAD = "HEAD";
57
58
59
60
61 private static final String GET = "GET";
62
63
64
65
66 private Downloader() {
67 }
68
69
70
71
72
73
74
75
76
77 public static void fetchFile(URL url, File outputPath) throws DownloadFailedException {
78 fetchFile(url, outputPath, true);
79 }
80
81
82
83
84
85
86
87
88
89
90
91 public static void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException {
92 if ("file".equalsIgnoreCase(url.getProtocol())) {
93 File file;
94 try {
95 file = new File(url.toURI());
96 } catch (URISyntaxException ex) {
97 final String msg = format("Download failed, unable to locate '%s'", url.toString());
98 throw new DownloadFailedException(msg);
99 }
100 if (file.exists()) {
101 try {
102 org.apache.commons.io.FileUtils.copyFile(file, outputPath);
103 } catch (IOException ex) {
104 final String msg = format("Download failed, unable to copy '%s' to '%s'", url.toString(), outputPath.getAbsolutePath());
105 throw new DownloadFailedException(msg, ex);
106 }
107 } else {
108 final String msg = format("Download failed, file ('%s') does not exist", url.toString());
109 throw new DownloadFailedException(msg);
110 }
111 } else {
112 HttpURLConnection conn = null;
113 try {
114 LOGGER.debug("Attempting download of {}", url.toString());
115 conn = URLConnectionFactory.createHttpURLConnection(url, useProxy);
116 conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
117 conn.connect();
118 int status = conn.getResponseCode();
119 int redirectCount = 0;
120 while ((status == HttpURLConnection.HTTP_MOVED_TEMP
121 || status == HttpURLConnection.HTTP_MOVED_PERM
122 || status == HttpURLConnection.HTTP_SEE_OTHER)
123 && MAX_REDIRECT_ATTEMPTS > redirectCount++) {
124 final String location = conn.getHeaderField("Location");
125 try {
126 conn.disconnect();
127 } finally {
128 conn = null;
129 }
130 LOGGER.debug("Download is being redirected from {} to {}", url.toString(), location);
131 conn = URLConnectionFactory.createHttpURLConnection(new URL(location), useProxy);
132 conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
133 conn.connect();
134 status = conn.getResponseCode();
135 }
136 if (status != 200) {
137 try {
138 conn.disconnect();
139 } finally {
140 conn = null;
141 }
142 final String msg = format("Error downloading file %s; received response code %s.", url.toString(), status);
143 throw new DownloadFailedException(msg);
144
145 }
146 } catch (IOException ex) {
147 try {
148 if (conn != null) {
149 conn.disconnect();
150 }
151 } finally {
152 conn = null;
153 }
154 if ("Connection reset".equalsIgnoreCase(ex.getMessage())) {
155 final String msg = format("TLS Connection Reset%nPlease see "
156 + "http://jeremylong.github.io/DependencyCheck/general/tlsfailures.html "
157 + "for more information regarding how to resolve the issue.");
158 LOGGER.error(msg);
159 throw new DownloadFailedException(msg, ex);
160 }
161 final String msg = format("Error downloading file %s; unable to connect.", url.toString());
162 throw new DownloadFailedException(msg, ex);
163 }
164
165 final String encoding = conn.getContentEncoding();
166 BufferedOutputStream writer = null;
167 InputStream reader = null;
168 try {
169 if (encoding != null && "gzip".equalsIgnoreCase(encoding)) {
170 reader = new GZIPInputStream(conn.getInputStream());
171 } else if (encoding != null && "deflate".equalsIgnoreCase(encoding)) {
172 reader = new InflaterInputStream(conn.getInputStream());
173 } else {
174 reader = conn.getInputStream();
175 }
176
177 writer = new BufferedOutputStream(new FileOutputStream(outputPath));
178 final byte[] buffer = new byte[4096];
179 int bytesRead;
180 while ((bytesRead = reader.read(buffer)) > 0) {
181 writer.write(buffer, 0, bytesRead);
182 }
183 LOGGER.debug("Download of {} complete", url.toString());
184 } catch (IOException ex) {
185 checkForCommonExceptionTypes(ex);
186 final String msg = format("Error saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
187 url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
188 throw new DownloadFailedException(msg, ex);
189 } catch (Exception ex) {
190 final String msg = format("Unexpected exception saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
191 url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
192 throw new DownloadFailedException(msg, ex);
193 } finally {
194 if (writer != null) {
195 try {
196 writer.close();
197 } catch (IOException ex) {
198 LOGGER.trace("Error closing the writer in Downloader.", ex);
199 }
200 }
201 if (reader != null) {
202 try {
203 reader.close();
204 } catch (IOException ex) {
205 LOGGER.trace("Error closing the reader in Downloader.", ex);
206 }
207 }
208 try {
209 conn.disconnect();
210 } finally {
211 conn = null;
212 }
213 }
214 }
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public static long getLastModified(URL url) throws DownloadFailedException {
228 return getLastModified(url, false);
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243 private static long getLastModified(URL url, boolean isRetry) throws DownloadFailedException {
244 long timestamp = 0;
245
246 if ("file".equalsIgnoreCase(url.getProtocol())) {
247 File lastModifiedFile;
248 try {
249 lastModifiedFile = new File(url.toURI());
250 } catch (URISyntaxException ex) {
251 final String msg = format("Unable to locate '%s'", url.toString());
252 throw new DownloadFailedException(msg, ex);
253 }
254 timestamp = lastModifiedFile.lastModified();
255 } else {
256 final String httpMethod = determineHttpMethod();
257 HttpURLConnection conn = null;
258 try {
259 conn = URLConnectionFactory.createHttpURLConnection(url);
260 conn.setRequestMethod(httpMethod);
261 conn.connect();
262 final int t = conn.getResponseCode();
263 if (t >= 200 && t < 300) {
264 timestamp = conn.getLastModified();
265 } else {
266 throw new DownloadFailedException(format("%s request returned a non-200 status code", httpMethod));
267 }
268 } catch (URLConnectionFailureException ex) {
269 throw new DownloadFailedException(format("Error creating URL Connection for HTTP %s request.", httpMethod), ex);
270 } catch (IOException ex) {
271 checkForCommonExceptionTypes(ex);
272 LOGGER.error("IO Exception: " + ex.getMessage());
273 LOGGER.debug("Exception details", ex);
274 if (ex.getCause() != null) {
275 LOGGER.debug("IO Exception cause: " + ex.getCause().getMessage(), ex.getCause());
276 }
277 try {
278
279 if (!isRetry && Settings.getBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP)) {
280 Settings.setBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP, false);
281 return getLastModified(url, true);
282 }
283 } catch (InvalidSettingException ex1) {
284 LOGGER.debug("invalid setting?", ex1);
285 }
286 throw new DownloadFailedException(format("Error making HTTP %s request.", httpMethod), ex);
287 } finally {
288 if (conn != null) {
289 try {
290 conn.disconnect();
291 } finally {
292 conn = null;
293 }
294 }
295 }
296 }
297 return timestamp;
298 }
299
300
301
302
303
304
305
306
307
308
309
310 protected static synchronized void checkForCommonExceptionTypes(IOException ex) throws DownloadFailedException {
311 Throwable cause = ex;
312 while (cause != null) {
313 if (cause instanceof java.net.UnknownHostException) {
314 final String msg = format("Unable to resolve domain '%s'", cause.getMessage());
315 LOGGER.error(msg);
316 throw new DownloadFailedException(msg);
317 }
318 if (cause instanceof InvalidAlgorithmParameterException) {
319 final String keystore = System.getProperty("javax.net.ssl.keyStore");
320 final String version = System.getProperty("java.version");
321 final String vendor = System.getProperty("java.vendor");
322 LOGGER.info("Error making HTTPS request - InvalidAlgorithmParameterException");
323 LOGGER.info("There appears to be an issue with the installation of Java and the cacerts."
324 + "See closed issue #177 here: https://github.com/jeremylong/DependencyCheck/issues/177");
325 LOGGER.info("Java Info:\njavax.net.ssl.keyStore='{}'\njava.version='{}'\njava.vendor='{}'",
326 keystore, version, vendor);
327 throw new DownloadFailedException("Error making HTTPS request. Please see the log for more details.");
328 }
329 cause = cause.getCause();
330 }
331 }
332
333
334
335
336
337
338 private static String determineHttpMethod() {
339 return isQuickQuery() ? HEAD : GET;
340 }
341
342
343
344
345
346
347
348 private static boolean isQuickQuery() {
349 boolean quickQuery;
350
351 try {
352 quickQuery = Settings.getBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP, true);
353 } catch (InvalidSettingException e) {
354 if (LOGGER.isTraceEnabled()) {
355 LOGGER.trace("Invalid settings : {}", e.getMessage(), e);
356 }
357 quickQuery = true;
358 }
359 return quickQuery;
360 }
361 }