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