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.codehaus.plexus.archiver.AbstractUnArchiver;
32  import org.codehaus.plexus.archiver.ArchiverException;
33  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
34  
35  /**
36   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
37   */
38  public abstract class AbstractZipUnArchiver extends AbstractUnArchiver {
39  
40      private static final String NATIVE_ENCODING = "native-encoding";
41  
42      private String encoding = "UTF8";
43  
44      private long maxOutputSize = Long.MAX_VALUE;
45  
46      public AbstractZipUnArchiver() {}
47  
48      public AbstractZipUnArchiver(final File sourceFile) {
49          super(sourceFile);
50      }
51  
52      /**
53       * Sets the encoding to assume for file names and comments.
54       * <p>
55       * Set to <code>native-encoding</code> if you want your platform's native encoding, defaults to UTF8.
56       * </p>
57       */
58      public void setEncoding(String encoding) {
59          if (NATIVE_ENCODING.equals(encoding)) {
60              encoding = null;
61          }
62          this.encoding = encoding;
63      }
64  
65      /**
66       * Set maximum allowed size of the produced output.
67       *
68       * It may be used as a protection against <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>.
69       *
70       * @param maxOutputSize max size of the produced output, in bytes. Must be greater than 0
71       * @throws IllegalArgumentException if specified output size is less or equal to 0
72       */
73      public void setMaxOutputSize(long maxOutputSize) {
74          if (maxOutputSize <= 0) {
75              throw new IllegalArgumentException("Invalid max output size specified: " + maxOutputSize);
76          }
77          this.maxOutputSize = maxOutputSize;
78      }
79  
80      private static class ZipEntryFileInfo implements PlexusIoResource {
81  
82          private final org.apache.commons.compress.archivers.zip.ZipFile zipFile;
83  
84          private final ZipArchiveEntry zipEntry;
85  
86          ZipEntryFileInfo(
87                  final org.apache.commons.compress.archivers.zip.ZipFile zipFile, final ZipArchiveEntry zipEntry) {
88              this.zipFile = zipFile;
89              this.zipEntry = zipEntry;
90          }
91  
92          public String getName() {
93              return zipEntry.getName();
94          }
95  
96          @Override
97          public boolean isDirectory() {
98              return zipEntry.isDirectory();
99          }
100 
101         @Override
102         public boolean isFile() {
103             return !zipEntry.isDirectory() && !zipEntry.isUnixSymlink();
104         }
105 
106         @Override
107         public boolean isSymbolicLink() {
108             return zipEntry.isUnixSymlink();
109         }
110 
111         @Nonnull
112         @Override
113         public InputStream getContents() throws IOException {
114             return zipFile.getInputStream(zipEntry);
115         }
116 
117         @Override
118         public long getLastModified() {
119             final long l = zipEntry.getTime();
120             return l == 0 ? PlexusIoResource.UNKNOWN_MODIFICATION_DATE : l;
121         }
122 
123         @Override
124         public long getSize() {
125             final long l = zipEntry.getSize();
126             return l == -1 ? PlexusIoResource.UNKNOWN_RESOURCE_SIZE : l;
127         }
128 
129         @Override
130         public URL getURL() throws IOException {
131             return null;
132         }
133 
134         @Override
135         public boolean isExisting() {
136             return true;
137         }
138     }
139 
140     @Override
141     protected void execute() throws ArchiverException {
142         execute("", getDestDirectory());
143     }
144 
145     private String resolveSymlink(ZipFile zf, ZipArchiveEntry ze) throws IOException {
146         if (ze.isUnixSymlink()) {
147             return zf.getUnixSymlink(ze);
148         } else {
149             return null;
150         }
151     }
152 
153     @Override
154     protected void execute(final String path, final File outputDirectory) throws ArchiverException {
155         getLogger().debug("Expanding: " + getSourceFile() + " into " + outputDirectory);
156         try (ZipFile zipFile = ZipFile.builder()
157                 .setFile(getSourceFile())
158                 .setCharset(encoding)
159                 .setUseUnicodeExtraFields(true)
160                 .get()) {
161             long remainingSpace = maxOutputSize;
162             final Enumeration<ZipArchiveEntry> e = zipFile.getEntriesInPhysicalOrder();
163 
164             while (e.hasMoreElements()) {
165                 final ZipArchiveEntry ze = e.nextElement();
166                 final ZipEntryFileInfo fileInfo = new ZipEntryFileInfo(zipFile, ze);
167                 if (!isSelected(ze.getName(), fileInfo)) {
168                     continue;
169                 }
170 
171                 if (ze.getName().startsWith(path)) {
172                     try (InputStream in = zipFile.getInputStream(ze)) {
173                         BoundedInputStream bis = BoundedInputStream.builder()
174                                 .setInputStream(in)
175                                 .setMaxCount(remainingSpace + 1)
176                                 .get();
177                         extractFile(
178                                 getSourceFile(),
179                                 outputDirectory,
180                                 bis,
181                                 ze.getName(),
182                                 new Date(ze.getTime()),
183                                 ze.isDirectory(),
184                                 ze.getUnixMode() != 0 ? ze.getUnixMode() : null,
185                                 resolveSymlink(zipFile, ze),
186                                 getFileMappers());
187 
188                         remainingSpace -= bis.getCount();
189                         if (remainingSpace < 0) {
190                             throw new ArchiverException("Maximum output size limit reached");
191                         }
192                     }
193                 }
194             }
195             getLogger().debug("expand complete");
196         } catch (final IOException ioe) {
197             throw new ArchiverException(
198                     "Error while expanding " + getSourceFile().getAbsolutePath(), ioe);
199         }
200     }
201 }