View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /*
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution, if
22   *    any, must include the following acknowledgement:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.codehaus.org/)."
25   *    Alternately, this acknowledgement may appear in the software itself,
26   *    if and wherever such third-party acknowledgements normally appear.
27   *
28   * 4. The names "The Jakarta Project", "Ant", and "Apache Software
29   *    Foundation" must not be used to endorse or promote products derived
30   *    from this software without prior written permission. For written
31   *    permission, please contact codehaus@codehaus.org.
32   *
33   * 5. Products derived from this software may not be called "Apache"
34   *    nor may "Apache" appear in their names without prior written
35   *    permission of the Apache Group.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.codehaus.org/>.
55   */
56  
57  import java.io.File;
58  import java.io.FileNotFoundException;
59  import java.io.IOException;
60  import java.io.InputStream;
61  import java.io.OutputStream;
62  import java.nio.file.Files;
63  import java.util.Date;
64  import java.util.zip.ZipEntry;
65  import java.util.zip.ZipInputStream;
66  
67  /**
68   * Unzip a file.
69   *
70   * @author costin@dnt.ro
71   * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
72   * @author <a href="mailto:umagesh@codehaus.org">Magesh Umasankar</a>
73   * @since Ant 1.1 @ant.task category="packaging" name="unzip" name="unjar" name="unwar"
74   *
75   */
76  public class Expand {
77  
78      private File dest; // req
79  
80      private File source; // req
81  
82      private boolean overwrite = true;
83  
84      /**
85       * Do the work.
86       *
87       * @exception Exception Thrown in unrecoverable error.
88       */
89      public void execute() throws Exception {
90          expandFile(source, dest);
91      }
92  
93      protected void expandFile(final File srcF, final File dir) throws Exception {
94          // code from WarExpand
95          try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(srcF.toPath()))) {
96              for (ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
97                  extractFile(srcF, dir, zis, ze.getName(), new Date(ze.getTime()), ze.isDirectory());
98              }
99          } catch (IOException ioe) {
100             throw new Exception("Error while expanding " + srcF.getPath(), ioe);
101         }
102     }
103 
104     protected void extractFile(
105             File srcF,
106             File dir,
107             InputStream compressedInputStream,
108             String entryName,
109             Date entryDate,
110             boolean isDirectory)
111             throws Exception {
112         File f = FileUtils.resolveFile(dir, entryName);
113 
114         try {
115             String canonicalDirPath = dir.getCanonicalPath();
116             String canonicalFilePath = f.getCanonicalPath();
117 
118             // Ensure the file is within the target directory
119             // We need to check that the canonical file path starts with the canonical directory path
120             // followed by a file separator to prevent path traversal attacks
121             if (!canonicalFilePath.startsWith(canonicalDirPath + File.separator)
122                     && !canonicalFilePath.equals(canonicalDirPath)) {
123                 throw new IOException("Entry '" + entryName + "' outside the target directory.");
124             }
125         } catch (IOException e) {
126             throw new IOException("Failed to verify entry path for '" + entryName + "'", e);
127         }
128 
129         try {
130             if (!overwrite && f.exists() && f.lastModified() >= entryDate.getTime()) {
131                 return;
132             }
133 
134             // create intermediary directories - sometimes zip don't add them
135             File dirF = f.getParentFile();
136             dirF.mkdirs();
137 
138             if (isDirectory) {
139                 f.mkdirs();
140             } else {
141                 byte[] buffer = new byte[65536];
142 
143                 try (OutputStream fos = Files.newOutputStream(f.toPath())) {
144                     for (int length = compressedInputStream.read(buffer);
145                             length >= 0;
146                             fos.write(buffer, 0, length), length = compressedInputStream.read(buffer))
147                         ;
148                 }
149             }
150 
151             f.setLastModified(entryDate.getTime());
152         } catch (FileNotFoundException ex) {
153             throw new Exception("Can't extract file " + srcF.getPath(), ex);
154         }
155     }
156 
157     /**
158      * Set the destination directory. File will be unzipped into the destination directory.
159      *
160      * @param d Path to the directory.
161      */
162     public void setDest(File d) {
163         this.dest = d;
164     }
165 
166     /**
167      * Set the path to zip-file.
168      *
169      * @param s Path to zip-file.
170      */
171     public void setSrc(File s) {
172         this.source = s;
173     }
174 
175     /**
176      * @param b Should we overwrite files in dest, even if they are newer than the corresponding entries in the archive?
177      */
178     public void setOverwrite(boolean b) {
179         overwrite = b;
180     }
181 }