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 }