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.Closeable;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.FilenameFilter;
27  import java.io.IOException;
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 extracting the files
65       */
66      public static void extractFiles(File archive, File extractTo) throws ExtractionException {
67          extractFiles(archive, extractTo, null);
68      }
69  
70      /**
71       * Extracts the contents of an archive into the specified directory. The files are only extracted if they are supported by the
72       * analyzers loaded into the specified engine. If the engine is specified as null then all files are extracted.
73       *
74       * @param archive an archive file such as a WAR or EAR
75       * @param extractTo a directory to extract the contents to
76       * @param engine the scanning engine
77       * @throws ExtractionException thrown if there is an error extracting the files
78       */
79      public static void extractFiles(File archive, File extractTo, Engine engine) throws ExtractionException {
80          if (archive == null || extractTo == null) {
81              return;
82          }
83  
84          FileInputStream fis = null;
85          ZipInputStream zis = null;
86  
87          try {
88              fis = new FileInputStream(archive);
89          } catch (FileNotFoundException ex) {
90              LOGGER.debug("", ex);
91              throw new ExtractionException("Archive file was not found.", ex);
92          }
93          zis = new ZipInputStream(new BufferedInputStream(fis));
94          ZipEntry entry;
95          try {
96              while ((entry = zis.getNextEntry()) != null) {
97                  if (entry.isDirectory()) {
98                      final File d = new File(extractTo, entry.getName());
99                      if (!d.exists() && !d.mkdirs()) {
100                         final String msg = String.format("Unable to create '%s'.", d.getAbsolutePath());
101                         throw new ExtractionException(msg);
102                     }
103                 } else {
104                     final File file = new File(extractTo, entry.getName());
105                     if (engine == null || engine.accept(file)) {
106                         FileOutputStream fos = null;
107                         try {
108                             fos = new FileOutputStream(file);
109                             IOUtils.copy(zis, fos);
110                         } catch (FileNotFoundException ex) {
111                             LOGGER.debug("", ex);
112                             final String msg = String.format("Unable to find file '%s'.", file.getName());
113                             throw new ExtractionException(msg, ex);
114                         } catch (IOException ex) {
115                             LOGGER.debug("", ex);
116                             final String msg = String.format("IO Exception while parsing file '%s'.", file.getName());
117                             throw new ExtractionException(msg, ex);
118                         } finally {
119                             closeStream(fos);
120                         }
121                     }
122                 }
123             }
124         } catch (IOException ex) {
125             final String msg = String.format("Exception reading archive '%s'.", archive.getName());
126             LOGGER.debug("", ex);
127             throw new ExtractionException(msg, ex);
128         } finally {
129             closeStream(zis);
130         }
131     }
132 
133     /**
134      * Extracts the contents of an archive into the specified directory.
135      *
136      * @param archive an archive file such as a WAR or EAR
137      * @param destination a directory to extract the contents to
138      * @param filter determines which files get extracted
139      * @throws ExtractionException thrown if the archive is not found
140      */
141     public static void extractFilesUsingFilter(File archive, File destination,
142             FilenameFilter filter) throws ExtractionException {
143         if (archive == null || destination == null) {
144             return;
145         }
146 
147         FileInputStream fis = null;
148         try {
149             fis = new FileInputStream(archive);
150         } catch (FileNotFoundException ex) {
151             LOGGER.debug("", ex);
152             throw new ExtractionException("Archive file was not found.", ex);
153         }
154         try {
155             extractArchive(new ZipArchiveInputStream(new BufferedInputStream(
156                     fis)), destination, filter);
157         } catch (ArchiveExtractionException ex) {
158             LOGGER.warn("Exception extracting archive '{}'.", archive.getName());
159             LOGGER.debug("", ex);
160         } finally {
161             try {
162                 fis.close();
163             } catch (IOException ex) {
164                 LOGGER.debug("", ex);
165             }
166         }
167     }
168 
169     /**
170      * Extracts files from an archive.
171      *
172      * @param input the archive to extract files from
173      * @param destination the location to write the files too
174      * @param filter determines which files get extracted
175      * @throws ArchiveExtractionException thrown if there is an exception extracting files from the archive
176      */
177     private static void extractArchive(ArchiveInputStream input,
178             File destination, FilenameFilter filter)
179             throws ArchiveExtractionException {
180         ArchiveEntry entry;
181         try {
182             while ((entry = input.getNextEntry()) != null) {
183                 if (entry.isDirectory()) {
184                     final File dir = new File(destination, entry.getName());
185                     if (!dir.exists()) {
186                         if (!dir.mkdirs()) {
187                             final String msg = String.format(
188                                     "Unable to create directory '%s'.",
189                                     dir.getAbsolutePath());
190                             throw new AnalysisException(msg);
191                         }
192                     }
193                 } else {
194                     extractFile(input, destination, filter, entry);
195                 }
196             }
197         } catch (IOException ex) {
198             throw new ArchiveExtractionException(ex);
199         } catch (Throwable ex) {
200             throw new ArchiveExtractionException(ex);
201         } finally {
202             closeStream(input);
203         }
204     }
205 
206     /**
207      * Extracts a file from an archive (input stream) and correctly builds the directory structure.
208      *
209      * @param input the archive input stream
210      * @param destination where to write the file
211      * @param filter the file filter to apply to the files being extracted
212      * @param entry the entry from the archive to extract
213      * @throws ExtractionException thrown if there is an error reading from the archive stream
214      */
215     private static void extractFile(ArchiveInputStream input, File destination,
216             FilenameFilter filter, ArchiveEntry entry) throws ExtractionException {
217         final File file = new File(destination, entry.getName());
218         if (filter.accept(file.getParentFile(), file.getName())) {
219             LOGGER.debug("Extracting '{}'",
220                     file.getPath());
221             FileOutputStream fos = null;
222             try {
223                 createParentFile(file);
224                 fos = new FileOutputStream(file);
225                 IOUtils.copy(input, fos);
226             } catch (FileNotFoundException ex) {
227                 LOGGER.debug("", ex);
228                 final String msg = String.format("Unable to find file '%s'.",
229                         file.getName());
230                 throw new ExtractionException(msg, ex);
231             } catch (IOException ex) {
232                 LOGGER.debug("", ex);
233                 final String msg = String
234                         .format("IO Exception while parsing file '%s'.",
235                                 file.getName());
236                 throw new ExtractionException(msg, ex);
237             } finally {
238                 closeStream(fos);
239             }
240         }
241     }
242 
243     /**
244      * Closes the stream.
245      *
246      * @param stream the stream to close
247      */
248     private static void closeStream(Closeable stream) {
249         if (stream != null) {
250             try {
251                 stream.close();
252             } catch (IOException ex) {
253                 LOGGER.trace("", ex);
254             }
255         }
256     }
257 
258     /**
259      * Ensures the parent path is correctly created on disk so that the file can be extracted to the correct location.
260      *
261      * @param file the file path
262      * @throws ExtractionException thrown if the parent paths could not be created
263      */
264     private static void createParentFile(final File file)
265             throws ExtractionException {
266         final File parent = file.getParentFile();
267         if (!parent.isDirectory()) {
268             if (!parent.mkdirs()) {
269                 final String msg = String.format(
270                         "Unable to build directory '%s'.",
271                         parent.getAbsolutePath());
272                 throw new ExtractionException(msg);
273             }
274         }
275     }
276 }