View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.utils;
19  
20  import java.io.BufferedOutputStream;
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.HttpURLConnection;
26  import java.net.URISyntaxException;
27  import java.net.URL;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  import java.util.zip.GZIPInputStream;
31  import java.util.zip.InflaterInputStream;
32  
33  /**
34   * A utility to download files from the Internet.
35   *
36   * @author Jeremy Long <jeremy.long@owasp.org>
37   */
38  public final class Downloader {
39  
40      /**
41       * The logger.
42       */
43      private static final Logger LOGGER = Logger.getLogger(Downloader.class.getName());
44  
45      /**
46       * Private constructor for utility class.
47       */
48      private Downloader() {
49      }
50  
51      /**
52       * Retrieves a file from a given URL and saves it to the outputPath.
53       *
54       * @param url the URL of the file to download
55       * @param outputPath the path to the save the file to
56       * @throws DownloadFailedException is thrown if there is an error downloading the file
57       */
58      public static void fetchFile(URL url, File outputPath) throws DownloadFailedException {
59          fetchFile(url, outputPath, true);
60      }
61  
62      /**
63       * Retrieves a file from a given URL and saves it to the outputPath.
64       *
65       * @param url the URL of the file to download
66       * @param outputPath the path to the save the file to
67       * @param useProxy whether to use the configured proxy when downloading files
68       * @throws DownloadFailedException is thrown if there is an error downloading the file
69       */
70      public static void fetchFile(URL url, File outputPath, boolean useProxy) throws DownloadFailedException {
71          if ("file".equalsIgnoreCase(url.getProtocol())) {
72              File file;
73              try {
74                  file = new File(url.toURI());
75              } catch (URISyntaxException ex) {
76                  final String msg = String.format("Download failed, unable to locate '%s'", url.toString());
77                  throw new DownloadFailedException(msg);
78              }
79              if (file.exists()) {
80                  try {
81                      org.apache.commons.io.FileUtils.copyFile(file, outputPath);
82                  } catch (IOException ex) {
83                      final String msg = String.format("Download failed, unable to copy '%s' to '%s'", url.toString(), outputPath.getAbsolutePath());
84                      throw new DownloadFailedException(msg);
85                  }
86              } else {
87                  final String msg = String.format("Download failed, file ('%s') does not exist", url.toString());
88                  throw new DownloadFailedException(msg);
89              }
90          } else {
91              HttpURLConnection conn = null;
92              try {
93                  conn = URLConnectionFactory.createHttpURLConnection(url, useProxy);
94                  conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
95                  conn.connect();
96              } catch (IOException ex) {
97                  try {
98                      if (conn != null) {
99                          conn.disconnect();
100                     }
101                 } finally {
102                     conn = null;
103                 }
104                 final String msg = String.format("Error downloading file %s; unable to connect.", url.toString());
105                 throw new DownloadFailedException(msg, ex);
106             }
107             final String encoding = conn.getContentEncoding();
108 
109             BufferedOutputStream writer = null;
110             InputStream reader = null;
111             try {
112                 if (encoding != null && "gzip".equalsIgnoreCase(encoding)) {
113                     reader = new GZIPInputStream(conn.getInputStream());
114                 } else if (encoding != null && "deflate".equalsIgnoreCase(encoding)) {
115                     reader = new InflaterInputStream(conn.getInputStream());
116                 } else {
117                     reader = conn.getInputStream();
118                 }
119 
120                 writer = new BufferedOutputStream(new FileOutputStream(outputPath));
121                 final byte[] buffer = new byte[4096];
122                 int bytesRead;
123                 while ((bytesRead = reader.read(buffer)) > 0) {
124                     writer.write(buffer, 0, bytesRead);
125                 }
126             } catch (IOException ex) {
127                 final String msg = String.format("Error saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
128                         url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
129                 throw new DownloadFailedException(msg, ex);
130             } catch (Throwable ex) {
131                 final String msg = String.format("Unexpected exception saving '%s' to file '%s'%nConnection Timeout: %d%nEncoding: %s%n",
132                         url.toString(), outputPath.getAbsolutePath(), conn.getConnectTimeout(), encoding);
133                 throw new DownloadFailedException(msg, ex);
134             } finally {
135                 if (writer != null) {
136                     try {
137                         writer.close();
138                     } catch (IOException ex) {
139                         LOGGER.log(Level.FINEST,
140                                 "Error closing the writer in Downloader.", ex);
141                     }
142                 }
143                 if (reader != null) {
144                     try {
145                         reader.close();
146                     } catch (IOException ex) {
147                         LOGGER.log(Level.FINEST,
148                                 "Error closing the reader in Downloader.", ex);
149                     }
150                 }
151                 try {
152                     conn.disconnect();
153                 } finally {
154                     conn = null;
155                 }
156             }
157         }
158     }
159 
160     /**
161      * Makes an HTTP Head request to retrieve the last modified date of the given URL. If the file:// protocol is
162      * specified, then the lastTimestamp of the file is returned.
163      *
164      * @param url the URL to retrieve the timestamp from
165      * @return an epoch timestamp
166      * @throws DownloadFailedException is thrown if an exception occurs making the HTTP request
167      */
168     public static long getLastModified(URL url) throws DownloadFailedException {
169         long timestamp = 0;
170         //TODO add the FTP protocol?
171         if ("file".equalsIgnoreCase(url.getProtocol())) {
172             File lastModifiedFile;
173             try {
174                 lastModifiedFile = new File(url.toURI());
175             } catch (URISyntaxException ex) {
176                 final String msg = String.format("Unable to locate '%s'", url.toString());
177                 throw new DownloadFailedException(msg);
178             }
179             timestamp = lastModifiedFile.lastModified();
180         } else {
181             HttpURLConnection conn = null;
182             try {
183                 conn = URLConnectionFactory.createHttpURLConnection(url);
184                 conn.setRequestMethod("HEAD");
185                 conn.connect();
186                 final int t = conn.getResponseCode();
187                 if (t >= 200 && t < 300) {
188                     timestamp = conn.getLastModified();
189                 } else {
190                     throw new DownloadFailedException("HEAD request returned a non-200 status code");
191                 }
192             } catch (URLConnectionFailureException ex) {
193                 throw new DownloadFailedException("Error creating URL Connection for HTTP HEAD request.", ex);
194             } catch (IOException ex) {
195                 throw new DownloadFailedException("Error making HTTP HEAD request.", ex);
196             } finally {
197                 if (conn != null) {
198                     try {
199                         conn.disconnect();
200                     } finally {
201                         conn = null;
202                     }
203                 }
204             }
205         }
206         return timestamp;
207     }
208 }