View Javadoc
1   /**
2    *
3    * Copyright 2004 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.codehaus.plexus.archiver.zip;
18  
19  import javax.annotation.Nonnull;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.URL;
25  import java.util.Date;
26  import java.util.Enumeration;
27  
28  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
29  import org.apache.commons.compress.archivers.zip.ZipFile;
30  import org.apache.commons.io.input.BoundedInputStream;
31  import org.apache.commons.io.input.CountingInputStream;
32  import org.codehaus.plexus.archiver.AbstractUnArchiver;
33  import org.codehaus.plexus.archiver.ArchiverException;
34  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
35  
36  /**
37   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
38   */
39  public abstract class AbstractZipUnArchiver extends AbstractUnArchiver {
40  
41      private static final String NATIVE_ENCODING = "native-encoding";
42  
43      private String encoding = "UTF8";
44  
45      private long maxOutputSize = Long.MAX_VALUE;
46  
47      public AbstractZipUnArchiver() {}
48  
49      public AbstractZipUnArchiver(final File sourceFile) {
50          super(sourceFile);
51      }
52  
53      /**
54       * Sets the encoding to assume for file names and comments.
55       * <p>
56       * Set to <code>native-encoding</code> if you want your platform's native encoding, defaults to UTF8.
57       * </p>
58       */
59      public void setEncoding(String encoding) {
60          if (NATIVE_ENCODING.equals(encoding)) {
61              encoding = null;
62          }
63          this.encoding = encoding;
64      }
65  
66      /**
67       * Set maximum allowed size of the produced output.
68       *
69       * It may be used as a protection against <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>.
70       *
71       * @param maxOutputSize max size of the produced output, in bytes. Must be greater than 0
72       * @throws IllegalArgumentException if specified output size is less or equal to 0
73       */
74      public void setMaxOutputSize(long maxOutputSize) {
75          if (maxOutputSize <= 0) {
76              throw new IllegalArgumentException("Invalid max output size specified: " + maxOutputSize);
77          }
78          this.maxOutputSize = maxOutputSize;
79      }
80  
81      private static class ZipEntryFileInfo implements PlexusIoResource {
82  
83          private final org.apache.commons.compress.archivers.zip.ZipFile zipFile;
84  
85          private final ZipArchiveEntry zipEntry;
86  
87          ZipEntryFileInfo(
88                  final org.apache.commons.compress.archivers.zip.ZipFile zipFile, final ZipArchiveEntry zipEntry) {
89              this.zipFile = zipFile;
90              this.zipEntry = zipEntry;
91          }
92  
93          public String getName() {
94              return zipEntry.getName();
95          }
96  
97          @Override
98          public boolean isDirectory() {
99              return zipEntry.isDirectory();
100         }
101 
102         @Override
103         public boolean isFile() {
104             return !zipEntry.isDirectory() && !zipEntry.isUnixSymlink();
105         }
106 
107         @Override
108         public boolean isSymbolicLink() {
109             return zipEntry.isUnixSymlink();
110         }
111 
112         @Nonnull
113         @Override
114         public InputStream getContents() throws IOException {
115             return zipFile.getInputStream(zipEntry);
116         }
117 
118         @Override
119         public long getLastModified() {
120             final long l = zipEntry.getTime();
121             return l == 0 ? PlexusIoResource.UNKNOWN_MODIFICATION_DATE : l;
122         }
123 
124         @Override
125         public long getSize() {
126             final long l = zipEntry.getSize();
127             return l == -1 ? PlexusIoResource.UNKNOWN_RESOURCE_SIZE : l;
128         }
129 
130         @Override
131         public URL getURL() throws IOException {
132             return null;
133         }
134 
135         @Override
136         public boolean isExisting() {
137             return true;
138         }
139     }
140 
141     @Override
142     protected void execute() throws ArchiverException {
143         execute("", getDestDirectory());
144     }
145 
146     private String resolveSymlink(ZipFile zf, ZipArchiveEntry ze) throws IOException {
147         if (ze.isUnixSymlink()) {
148             return zf.getUnixSymlink(ze);
149         } else {
150             return null;
151         }
152     }
153 
154     @Override
155     protected void execute(final String path, final File outputDirectory) throws ArchiverException {
156         getLogger().debug("Expanding: " + getSourceFile() + " into " + outputDirectory);
157         try (ZipFile zipFile = new ZipFile(getSourceFile(), encoding, true)) {
158             long remainingSpace = maxOutputSize;
159             final Enumeration<ZipArchiveEntry> e = zipFile.getEntriesInPhysicalOrder();
160 
161             while (e.hasMoreElements()) {
162                 final ZipArchiveEntry ze = e.nextElement();
163                 final ZipEntryFileInfo fileInfo = new ZipEntryFileInfo(zipFile, ze);
164                 if (!isSelected(ze.getName(), fileInfo)) {
165                     continue;
166                 }
167 
168                 if (ze.getName().startsWith(path)) {
169                     try (InputStream in = zipFile.getInputStream(ze)) {
170                         BoundedInputStream bis = new BoundedInputStream(in, remainingSpace + 1);
171                         CountingInputStream cis = new CountingInputStream(bis);
172                         extractFile(
173                                 getSourceFile(),
174                                 outputDirectory,
175                                 cis,
176                                 ze.getName(),
177                                 new Date(ze.getTime()),
178                                 ze.isDirectory(),
179                                 ze.getUnixMode() != 0 ? ze.getUnixMode() : null,
180                                 resolveSymlink(zipFile, ze),
181                                 getFileMappers());
182 
183                         remainingSpace -= cis.getByteCount();
184                         if (remainingSpace < 0) {
185                             throw new ArchiverException("Maximum output size limit reached");
186                         }
187                     }
188                 }
189             }
190             getLogger().debug("expand complete");
191         } catch (final IOException ioe) {
192             throw new ArchiverException(
193                     "Error while expanding " + getSourceFile().getAbsolutePath(), ioe);
194         }
195     }
196 }