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) 2013 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.utils;
19  
20  import java.io.BufferedInputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.FilenameFilter;
26  import java.io.IOException;
27  import java.util.zip.GZIPInputStream;
28  import java.util.zip.ZipEntry;
29  import java.util.zip.ZipInputStream;
30  
31  import org.apache.commons.compress.archivers.ArchiveEntry;
32  import org.apache.commons.compress.archivers.ArchiveInputStream;
33  import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
34  import org.apache.commons.compress.utils.IOUtils;
35  import org.owasp.dependencycheck.Engine;
36  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
37  import org.owasp.dependencycheck.analyzer.exception.ArchiveExtractionException;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Set of utilities to extract files from archives.
43   *
44   * @author Jeremy Long
45   */
46  public final class ExtractionUtil {
47  
48      /**
49       * The logger.
50       */
51      private static final Logger LOGGER = LoggerFactory.getLogger(ExtractionUtil.class);
52  
53      /**
54       * Private constructor for a utility class.
55       */
56      private ExtractionUtil() {
57      }
58  
59      /**
60       * Extracts the contents of an archive into the specified directory.
61       *
62       * @param archive an archive file such as a WAR or EAR
63       * @param extractTo a directory to extract the contents to
64       * @throws ExtractionException thrown if an exception occurs while
65       * extracting the files
66       */
67      public static void extractFiles(File archive, File extractTo) throws ExtractionException {
68          extractFiles(archive, extractTo, null);
69      }
70  
71      /**
72       * Extracts the contents of an archive into the specified directory. The
73       * files are only extracted if they are supported by the analyzers loaded
74       * into the specified engine. If the engine is specified as null then all
75       * files are extracted.
76       *
77       * @param archive an archive file such as a WAR or EAR
78       * @param extractTo a directory to extract the contents to
79       * @param engine the scanning engine
80       * @throws ExtractionException thrown if there is an error extracting the
81       * files
82       */
83      public static void extractFiles(File archive, File extractTo, Engine engine) throws ExtractionException {
84          if (archive == null || extractTo == null) {
85              return;
86          }
87  
88          FileInputStream fis = null;
89          ZipInputStream zis = null;
90  
91          try {
92              fis = new FileInputStream(archive);
93          } catch (FileNotFoundException ex) {
94              LOGGER.debug("", ex);
95              throw new ExtractionException("Archive file was not found.", ex);
96          }
97          zis = new ZipInputStream(new BufferedInputStream(fis));
98          ZipEntry entry;
99          try {
100             while ((entry = zis.getNextEntry()) != null) {
101                 if (entry.isDirectory()) {
102                     final File d = new File(extractTo, entry.getName());
103                     if (!d.exists() && !d.mkdirs()) {
104                         final String msg = String.format("Unable to create '%s'.", d.getAbsolutePath());
105                         throw new ExtractionException(msg);
106                     }
107                 } else {
108                     final File file = new File(extractTo, entry.getName());
109                     if (engine == null || engine.accept(file)) {
110                         FileOutputStream fos = null;
111                         try {
112                             fos = new FileOutputStream(file);
113                             IOUtils.copy(zis, fos);
114                         } catch (FileNotFoundException ex) {
115                             LOGGER.debug("", ex);
116                             final String msg = String.format("Unable to find file '%s'.", file.getName());
117                             throw new ExtractionException(msg, ex);
118                         } catch (IOException ex) {
119                             LOGGER.debug("", ex);
120                             final String msg = String.format("IO Exception while parsing file '%s'.", file.getName());
121                             throw new ExtractionException(msg, ex);
122                         } finally {
123                             FileUtils.close(fos);
124                         }
125                     }
126                 }
127             }
128         } catch (IOException ex) {
129             final String msg = String.format("Exception reading archive '%s'.", archive.getName());
130             LOGGER.debug("", ex);
131             throw new ExtractionException(msg, ex);
132         } finally {
133             FileUtils.close(zis);
134         }
135     }
136 
137     /**
138      * Extracts the contents of an archive into the specified directory.
139      *
140      * @param archive an archive file such as a WAR or EAR
141      * @param destination a directory to extract the contents to
142      * @param filter determines which files get extracted
143      * @throws ExtractionException thrown if the archive is not found
144      */
145     public static void extractFilesUsingFilter(File archive, File destination,
146             FilenameFilter filter) throws ExtractionException {
147         if (archive == null || destination == null) {
148             return;
149         }
150 
151         FileInputStream fis = null;
152         try {
153             fis = new FileInputStream(archive);
154         } catch (FileNotFoundException ex) {
155             LOGGER.debug("", ex);
156             throw new ExtractionException("Archive file was not found.", ex);
157         }
158         try {
159             extractArchive(new ZipArchiveInputStream(new BufferedInputStream(
160                     fis)), destination, filter);
161         } catch (ArchiveExtractionException ex) {
162             LOGGER.warn("Exception extracting archive '{}'.", archive.getName());
163             LOGGER.debug("", ex);
164         } finally {
165             try {
166                 fis.close();
167             } catch (IOException ex) {
168                 LOGGER.debug("", ex);
169             }
170         }
171     }
172 
173     /**
174      * Extracts files from an archive.
175      *
176      * @param input the archive to extract files from
177      * @param destination the location to write the files too
178      * @param filter determines which files get extracted
179      * @throws ArchiveExtractionException thrown if there is an exception
180      * extracting files from the archive
181      */
182     private static void extractArchive(ArchiveInputStream input,
183             File destination, FilenameFilter filter)
184             throws ArchiveExtractionException {
185         ArchiveEntry entry;
186         try {
187             while ((entry = input.getNextEntry()) != null) {
188                 if (entry.isDirectory()) {
189                     final File dir = new File(destination, entry.getName());
190                     if (!dir.exists() && !dir.mkdirs()) {
191                         final String msg = String.format(
192                                 "Unable to create directory '%s'.",
193                                 dir.getAbsolutePath());
194                         throw new AnalysisException(msg);
195                     }
196                 } else {
197                     extractFile(input, destination, filter, entry);
198                 }
199             }
200         } catch (IOException ex) {
201             throw new ArchiveExtractionException(ex);
202         } catch (Throwable ex) {
203             throw new ArchiveExtractionException(ex);
204         } finally {
205             FileUtils.close(input);
206         }
207     }
208 
209     /**
210      * Extracts a file from an archive (input stream) and correctly builds the
211      * directory structure.
212      *
213      * @param input the archive input stream
214      * @param destination where to write the file
215      * @param filter the file filter to apply to the files being extracted
216      * @param entry the entry from the archive to extract
217      * @throws ExtractionException thrown if there is an error reading from the
218      * archive stream
219      */
220     private static void extractFile(ArchiveInputStream input, File destination,
221             FilenameFilter filter, ArchiveEntry entry) throws ExtractionException {
222         final File file = new File(destination, entry.getName());
223         if (filter.accept(file.getParentFile(), file.getName())) {
224             LOGGER.debug("Extracting '{}'",
225                     file.getPath());
226             FileOutputStream fos = null;
227             try {
228                 createParentFile(file);
229                 fos = new FileOutputStream(file);
230                 IOUtils.copy(input, fos);
231             } catch (FileNotFoundException ex) {
232                 LOGGER.debug("", ex);
233                 final String msg = String.format("Unable to find file '%s'.",
234                         file.getName());
235                 throw new ExtractionException(msg, ex);
236             } catch (IOException ex) {
237                 LOGGER.debug("", ex);
238                 final String msg = String
239                         .format("IO Exception while parsing file '%s'.",
240                                 file.getName());
241                 throw new ExtractionException(msg, ex);
242             } finally {
243                 FileUtils.close(fos);
244             }
245         }
246     }
247 
248     /**
249      * Ensures the parent path is correctly created on disk so that the file can
250      * be extracted to the correct location.
251      *
252      * @param file the file path
253      * @throws ExtractionException thrown if the parent paths could not be
254      * created
255      */
256     private static void createParentFile(final File file)
257             throws ExtractionException {
258         final File parent = file.getParentFile();
259         if (!parent.isDirectory() && !parent.mkdirs()) {
260             final String msg = String.format(
261                     "Unable to build directory '%s'.",
262                     parent.getAbsolutePath());
263             throw new ExtractionException(msg);
264         }
265     }
266 
267     /**
268      * Extracts the file contained in a gzip archive. The extracted file is
269      * placed in the exact same path as the file specified.
270      *
271      * @param file the archive file
272      * @throws FileNotFoundException thrown if the file does not exist
273      * @throws IOException thrown if there is an error extracting the file.
274      */
275     public static void extractGzip(File file) throws FileNotFoundException, IOException {
276         final String originalPath = file.getPath();
277         final File gzip = new File(originalPath + ".gz");
278         if (gzip.isFile() && !gzip.delete()) {
279             LOGGER.debug("Failed to delete initial temporary file when extracting 'gz' {}", gzip.toString());
280             gzip.deleteOnExit();
281         }
282         if (!file.renameTo(gzip)) {
283             throw new IOException("Unable to rename '" + file.getPath() + "'");
284         }
285         final File newfile = new File(originalPath);
286 
287         final byte[] buffer = new byte[4096];
288 
289         GZIPInputStream cin = null;
290         FileOutputStream out = null;
291         try {
292             cin = new GZIPInputStream(new FileInputStream(gzip));
293             out = new FileOutputStream(newfile);
294 
295             int len;
296             while ((len = cin.read(buffer)) > 0) {
297                 out.write(buffer, 0, len);
298             }
299         } finally {
300             if (cin != null) {
301                 try {
302                     cin.close();
303                 } catch (IOException ex) {
304                     LOGGER.trace("ignore", ex);
305                 }
306             }
307             if (out != null) {
308                 try {
309                     out.close();
310                 } catch (IOException ex) {
311                     LOGGER.trace("ignore", ex);
312                 }
313             }
314             if (gzip.isFile() && !org.apache.commons.io.FileUtils.deleteQuietly(gzip)) {
315                 LOGGER.debug("Failed to delete temporary file when extracting 'gz' {}", gzip.toString());
316                 gzip.deleteOnExit();
317             }
318         }
319     }
320 }