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