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);
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 final String msg = format("Error downloading file %s; unable to connect.", url.toString());
155 throw new DownloadFailedException(msg, ex);
156 }
157
158 final String encoding = conn.getContentEncoding();
159 BufferedOutputStream writer = null;
160 InputStream reader = null;
161 try {
162 if (encoding != null && "gzip".equalsIgnoreCase(encoding)) {
163 reader = new GZIPInputStream(conn.getInputStream());
164 } else if (encoding != null && "deflate".equalsIgnoreCase(encoding)) {
165 reader = new InflaterInputStream(conn.getInputStream());
166 } else {
167 reader = conn.getInputStream();
168 }
169
170 writer = new BufferedOutputStream(new FileOutputStream(outputPath));
171 final byte[] buffer = new byte[4096];
172 int bytesRead;
173 while ((bytesRead = reader.read(buffer)) > 0) {
174 writer.write(buffer, 0, bytesRead);
175 }
176 LOGGER.debug("Download of {} complete", url.toString());
177 } catch (IOException ex) {
178 checkForCommonExceptionTypes(ex);
179 final String msg = format("Error saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
180 url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
181 throw new DownloadFailedException(msg, ex);
182 } catch (Throwable ex) {
183 final String msg = format("Unexpected exception saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
184 url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
185 throw new DownloadFailedException(msg, ex);
186 } finally {
187 if (writer != null) {
188 try {
189 writer.close();
190 } catch (IOException ex) {
191 LOGGER.trace("Error closing the writer in Downloader.", ex);
192 }
193 }
194 if (reader != null) {
195 try {
196 reader.close();
197 } catch (IOException ex) {
198 LOGGER.trace("Error closing the reader in Downloader.", ex);
199 }
200 }
201 try {
202 conn.disconnect();
203 } finally {
204 conn = null;
205 }
206 }
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219
220 public static long getLastModified(URL url) throws DownloadFailedException {
221 return getLastModified(url, false);
222 }
223
224
225
226
227
228
229
230
231
232
233
234
235
236 private static long getLastModified(URL url, boolean isRetry) throws DownloadFailedException {
237 long timestamp = 0;
238
239 if ("file".equalsIgnoreCase(url.getProtocol())) {
240 File lastModifiedFile;
241 try {
242 lastModifiedFile = new File(url.toURI());
243 } catch (URISyntaxException ex) {
244 final String msg = format("Unable to locate '%s'", url.toString());
245 throw new DownloadFailedException(msg);
246 }
247 timestamp = lastModifiedFile.lastModified();
248 } else {
249 final String httpMethod = determineHttpMethod();
250 HttpURLConnection conn = null;
251 try {
252 conn = URLConnectionFactory.createHttpURLConnection(url);
253 conn.setRequestMethod(httpMethod);
254 conn.connect();
255 final int t = conn.getResponseCode();
256 if (t >= 200 && t < 300) {
257 timestamp = conn.getLastModified();
258 } else {
259 throw new DownloadFailedException(format("%s request returned a non-200 status code", httpMethod));
260 }
261 } catch (URLConnectionFailureException ex) {
262 throw new DownloadFailedException(format("Error creating URL Connection for HTTP %s request.", httpMethod), ex);
263 } catch (IOException ex) {
264 checkForCommonExceptionTypes(ex);
265 LOGGER.error("IO Exception: " + ex.getMessage());
266 LOGGER.debug("Exception details", ex);
267 if (ex.getCause() != null) {
268 LOGGER.debug("IO Exception cause: " + ex.getCause().getMessage(), ex.getCause());
269 }
270 try {
271
272 if (!isRetry && Settings.getBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP)) {
273 Settings.setBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP, false);
274 return getLastModified(url, true);
275 }
276 } catch (InvalidSettingException ex1) {
277 LOGGER.debug("invalid setting?", ex);
278 }
279 throw new DownloadFailedException(format("Error making HTTP %s request.", httpMethod), ex);
280 } finally {
281 if (conn != null) {
282 try {
283 conn.disconnect();
284 } finally {
285 conn = null;
286 }
287 }
288 }
289 }
290 return timestamp;
291 }
292
293
294
295
296
297
298
299
300
301
302
303 protected static synchronized void checkForCommonExceptionTypes(IOException ex) throws DownloadFailedException {
304 Throwable cause = ex;
305 while (cause != null) {
306 if (cause instanceof java.net.UnknownHostException) {
307 final String msg = format("Unable to resolve domain '%s'", cause.getMessage());
308 LOGGER.error(msg);
309 throw new DownloadFailedException(msg);
310 }
311 if (cause instanceof InvalidAlgorithmParameterException) {
312 final String keystore = System.getProperty("javax.net.ssl.keyStore");
313 final String version = System.getProperty("java.version");
314 final String vendor = System.getProperty("java.vendor");
315 LOGGER.info("Error making HTTPS request - InvalidAlgorithmParameterException");
316 LOGGER.info("There appears to be an issue with the installation of Java and the cacerts."
317 + "See closed issue #177 here: https://github.com/jeremylong/DependencyCheck/issues/177");
318 LOGGER.info("Java Info:\njavax.net.ssl.keyStore='{}'\njava.version='{}'\njava.vendor='{}'",
319 keystore, version, vendor);
320 throw new DownloadFailedException("Error making HTTPS request. Please see the log for more details.");
321 }
322 cause = cause.getCause();
323 }
324 }
325
326
327
328
329
330
331 private static String determineHttpMethod() {
332 return isQuickQuery() ? HEAD : GET;
333 }
334
335
336
337
338
339
340
341 private static boolean isQuickQuery() {
342 boolean quickQuery;
343
344 try {
345 quickQuery = Settings.getBoolean(Settings.KEYS.DOWNLOADER_QUICK_QUERY_TIMESTAMP, true);
346 } catch (InvalidSettingException e) {
347 quickQuery = true;
348 }
349 return quickQuery;
350 }
351 }