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