View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truevfs.comp.zip;
6   
7   import java.io.EOFException;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.nio.channels.SeekableByteChannel;
11  import java.nio.charset.Charset;
12  import static java.nio.file.Files.newByteChannel;
13  import java.nio.file.Path;
14  import java.util.Enumeration;
15  import java.util.Iterator;
16  import java.util.concurrent.locks.Lock;
17  import java.util.concurrent.locks.ReentrantLock;
18  import java.util.zip.ZipException;
19  import javax.annotation.CheckForNull;
20  import javax.annotation.Nullable;
21  import javax.annotation.concurrent.ThreadSafe;
22  import net.java.truecommons.io.AbstractSource;
23  import net.java.truecommons.io.LockInputStream;
24  import net.java.truecommons.io.OneTimeSource;
25  
26  /**
27   * Replacement for {@link java.util.zip.ZipFile java.util.zip.ZipFile}.
28   * <p>
29   * Where the constructors of this class accept a {@code charset}
30   * parameter, this is used to decode comments and entry names in the ZIP file.
31   * However, if an entry has bit 11 set in its General Purpose Bit Flag,
32   * then this parameter is ignored and "UTF-8" is used for this entry.
33   * This is in accordance to Appendix D of PKWARE's ZIP File Format
34   * Specification, version 6.3.0 and later.
35   * <p>
36   * This class is able to skip a preamble like the one found in self extracting
37   * archives.
38   * <p>
39   * Note that the entries returned by this class are instances of
40   * {@code net.truevfs.kernel.io.zip.ZipEntry} instead of
41   * {@code java.util.zip.ZipEntry}.
42   *
43   * @see    ZipOutputStream
44   * @author Christian Schlichtherle
45   */
46  @ThreadSafe
47  public class ZipFile extends AbstractZipFile<ZipEntry> {
48  
49      /** The lock on which this object synchronizes. */
50      private final Lock lock = new ReentrantLock();
51  
52      private final String name;
53  
54      private volatile @CheckForNull ZipCryptoParameters cryptoParameters;
55  
56      /**
57       * Equivalent to {@link #ZipFile(Path, Charset, boolean, boolean)
58       * ZipFile(file, DEFAULT_CHARSET, true, false)}
59       */
60      public ZipFile(Path file)
61      throws IOException {
62          this(file, DEFAULT_CHARSET, true, false);
63      }
64  
65      /**
66       * Equivalent to {@link #ZipFile(Path, Charset, boolean, boolean)
67       * ZipFile(file, charset, true, false)}
68       */
69      public ZipFile(Path file, Charset charset)
70      throws IOException {
71          this(file, charset, true, false);
72      }
73  
74      /**
75       * Opens the given {@code file} for reading its entries.
76       *
77       * @param  file the file.
78       * @param  charset the charset to use for decoding entry names and ZIP file
79       *         comment.
80       * @param  preambled if this is {@code true}, then the ZIP file may have a
81       *         preamble.
82       *         Otherwise, the ZIP file must start with either a Local File
83       *         Header (LFH) signature or an End Of Central Directory (EOCD)
84       *         Header, causing this constructor to fail if the file is actually
85       *         a false positive ZIP file, i.e. not compatible to the ZIP File
86       *         Format Specification.
87       *         This may be useful to read Self Extracting ZIP files (SFX),
88       *         which usually contain the application code required for
89       *         extraction in the preamble.
90       * @param  postambled if this is {@code true}, then the ZIP file may have a
91       *         postamble of arbitrary length.
92       *         Otherwise, the ZIP file must not have a postamble which exceeds
93       *         64KB size, including the End Of Central Directory record
94       *         (i.e. including the ZIP file comment), causing this constructor
95       *         to fail if the file is actually a false positive ZIP file, i.e.
96       *         not compatible to the ZIP File Format Specification.
97       *         This may be useful to read Self Extracting ZIP files (SFX) with
98       *         large postambles.
99       * @throws ZipException if the file data is not compatible with the ZIP
100      *         File Format Specification.
101      * @throws EOFException on unexpected end-of-file.
102      * @throws IOException on any I/O error.
103      * @see    #recoverLostEntries()
104      */
105     public ZipFile(
106             final Path file,
107             final Charset charset,
108             final boolean preambled,
109             final boolean postambled)
110     throws ZipException, EOFException, IOException {
111         super(  new ZipSource(file),
112                 new DefaultZipFileParameters(charset, preambled, postambled));
113         this.name = file.toString();
114     }
115 
116     /**
117      * Equivalent to {@link #ZipFile(SeekableByteChannel, Charset, boolean, boolean)
118      * ZipFile(rof, DEFAULT_CHARSET, true, false)}
119      */
120     public ZipFile(SeekableByteChannel channel)
121     throws IOException {
122         this(channel, DEFAULT_CHARSET, true, false);
123     }
124 
125     /**
126      * Equivalent to {@link #ZipFile(SeekableByteChannel, Charset, boolean, boolean)
127      * ZipFile(rof, charset, true, false)}
128      */
129     public ZipFile(SeekableByteChannel channel, Charset charset)
130     throws IOException {
131         this(channel, charset, true, false);
132     }
133 
134     /**
135      * Opens the given {@link SeekableByteChannel} for reading its entries.
136      *
137      * @param  channel the channel to read.
138      * @param  charset the charset to use for decoding entry names and ZIP file
139      *         comment.
140      * @param  preambled if this is {@code true}, then the ZIP file may have a
141      *         preamble.
142      *         Otherwise, the ZIP file must start with either a Local File
143      *         Header (LFH) signature or an End Of Central Directory (EOCD)
144      *         Header, causing this constructor to fail if the file is actually
145      *         a false positive ZIP file, i.e. not compatible to the ZIP File
146      *         Format Specification.
147      *         This may be useful to read Self Extracting ZIP files (SFX),
148      *         which usually contain the application code required for
149      *         extraction in the preamble.
150      * @param  postambled if this is {@code true}, then the ZIP file may have a
151      *         postamble of arbitrary length.
152      *         Otherwise, the ZIP file must not have a postamble which exceeds
153      *         64KB size, including the End Of Central Directory record
154      *         (i.e. including the ZIP file comment), causing this constructor
155      *         to fail if the file is actually a false positive ZIP file, i.e.
156      *         not compatible to the ZIP File Format Specification.
157      *         This may be useful to read Self Extracting ZIP files (SFX) with
158      *         large postambles.
159      * @throws ZipException if the channel data is not compatible with the ZIP
160      *         File Format Specification.
161      * @throws EOFException on unexpected end-of-file.
162      * @throws IOException on any I/O error.
163      * @see    #recoverLostEntries()
164      */
165     public ZipFile(
166             SeekableByteChannel channel,
167             Charset charset,
168             boolean preambled,
169             boolean postambled)
170     throws ZipException, EOFException, IOException {
171         super(  new OneTimeSource(channel),
172                 new DefaultZipFileParameters(charset, preambled, postambled));
173         this.name = channel.toString();
174     }
175 
176     /**
177      * {@inheritDoc}
178      * <p>
179      * Note that this method is <em>not</em> thread-safe!
180      */
181     @Override
182     public ZipFile recoverLostEntries() throws IOException {
183         super.recoverLostEntries();
184         return this;
185     }
186 
187     /**
188      * Returns the {@link Object#toString() string representation} of whatever
189      * input source object was used to construct this ZIP file.
190      * For {@link String} and {@link Path} objects, this is a path name.
191      */
192     public String getName() {
193         return name;
194     }
195 
196     /**
197      * Enumerates clones of all entries in this ZIP file.
198      *
199      * @see #iterator()
200      */
201     public Enumeration<? extends ZipEntry> entries() {
202         final class CloneEnumeration implements Enumeration<ZipEntry> {
203             final Iterator<ZipEntry> i = ZipFile.super.iterator();
204 
205             @Override
206             public boolean hasMoreElements() {
207                 return i.hasNext();
208             }
209 
210             @Override
211             public ZipEntry nextElement() {
212                 return i.next().clone();
213             }
214         } // CloneEnumeration
215 
216         return new CloneEnumeration();
217     }
218 
219     /**
220      * Iterates through clones for all entries in this ZIP file.
221      * The iteration does not support element removal.
222      */
223     @Override
224     public Iterator<ZipEntry> iterator() {
225         final class EntryIterator implements Iterator<ZipEntry> {
226             final Iterator<ZipEntry> i = ZipFile.super.iterator();
227 
228             @Override
229             public boolean hasNext() {
230                 return i.hasNext();
231             }
232 
233             @Override
234             public ZipEntry next() {
235                 return i.next().clone();
236             }
237 
238             @Override
239             public void remove() {
240                 throw new UnsupportedOperationException();
241             }
242         } // EntryIterator
243 
244         return new EntryIterator();
245     }
246 
247     /**
248      * Returns a clone of the entry for the given {@code name} or {@code null}
249      * if no entry with this name exists in this ZIP file.
250      *
251      * @param  name the name of the ZIP entry.
252      * @return A clone of the entry for the given {@code name} or {@code null}
253      *         if no entry with this name exists in this ZIP file.
254      */
255     @Override
256     public ZipEntry entry(String name) {
257         final ZipEntry ze = super.entry(name);
258         return ze != null ? ze.clone() : null;
259     }
260 
261     @Override
262     @SuppressWarnings("deprecation")
263     public InputStream getPreambleInputStream() throws IOException {
264         final InputStream in;
265         lock.lock();
266         try {
267             in = super.getPreambleInputStream();
268         } finally {
269             lock.unlock();
270         }
271         return new LockInputStream(lock, in);
272     }
273 
274     @Override
275     @SuppressWarnings("deprecation")
276     public InputStream getPostambleInputStream() throws IOException {
277         final InputStream in;
278         lock.lock();
279         try {
280             in = super.getPostambleInputStream();
281         } finally {
282             lock.unlock();
283         }
284         return new LockInputStream(lock, in);
285     }
286 
287     @Override
288     public boolean busy() {
289         lock.lock();
290         try {
291             return super.busy();
292         } finally {
293             lock.unlock();
294         }
295     }
296 
297     @Override
298     public @Nullable ZipCryptoParameters getCryptoParameters() {
299         return cryptoParameters;
300     }
301 
302     /**
303      * Sets the parameters for encryption or authentication of entries.
304      *
305      * @param cryptoParameters the parameters for encryption or authentication
306      *        of entries.
307      */
308     public void setCryptoParameters(
309             final @CheckForNull ZipCryptoParameters cryptoParameters) {
310         this.cryptoParameters = cryptoParameters;
311     }
312 
313     @Override
314     @SuppressWarnings("deprecation")
315     protected InputStream getInputStream(
316             String name, Boolean check, boolean process)
317     throws  IOException {
318         final InputStream in;
319         lock.lock();
320         try {
321             in = super.getInputStream(name, check, process);
322         } finally {
323             lock.unlock();
324         }
325         return in == null ? null : new LockInputStream(lock, in);
326     }
327 
328     @Override
329     public void close() throws IOException {
330         lock.lock();
331         try {
332             super.close();
333         } finally {
334             lock.unlock();
335         }
336     }
337 
338     /**
339      * A pool which allocates {@link SeekableByteChannel} objects for the
340      * file provided to its constructor.
341      */
342     private static final class ZipSource extends AbstractSource {
343         final Path file;
344 
345         ZipSource(final Path file) {
346             this.file = file;
347         }
348 
349         @Override
350         public SeekableByteChannel channel() throws IOException {
351             return newByteChannel(file);
352         }
353     } // ZipSource
354 }