|
1 /* |
|
2 * Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package java.util.zip; |
|
27 |
|
28 import java.io.Closeable; |
|
29 import java.io.InputStream; |
|
30 import java.io.IOException; |
|
31 import java.io.EOFException; |
|
32 import java.io.File; |
|
33 import java.io.RandomAccessFile; |
|
34 import java.nio.charset.Charset; |
|
35 import java.nio.charset.StandardCharsets; |
|
36 import java.nio.file.attribute.BasicFileAttributes; |
|
37 import java.nio.file.Path; |
|
38 import java.nio.file.Files; |
|
39 |
|
40 import java.util.ArrayDeque; |
|
41 import java.util.ArrayList; |
|
42 import java.util.Arrays; |
|
43 import java.util.Deque; |
|
44 import java.util.Enumeration; |
|
45 import java.util.HashMap; |
|
46 import java.util.Iterator; |
|
47 import java.util.Map; |
|
48 import java.util.Objects; |
|
49 import java.util.NoSuchElementException; |
|
50 import java.util.Spliterator; |
|
51 import java.util.Spliterators; |
|
52 import java.util.WeakHashMap; |
|
53 import java.util.stream.Stream; |
|
54 import java.util.stream.StreamSupport; |
|
55 import jdk.internal.misc.JavaUtilZipFileAccess; |
|
56 import jdk.internal.misc.SharedSecrets; |
|
57 import jdk.internal.misc.JavaIORandomAccessFileAccess; |
|
58 import jdk.internal.misc.VM; |
|
59 import jdk.internal.perf.PerfCounter; |
|
60 |
|
61 import static java.util.zip.ZipConstants.*; |
|
62 import static java.util.zip.ZipConstants64.*; |
|
63 import static java.util.zip.ZipUtils.*; |
|
64 |
|
65 /** |
|
66 * This class is used to read entries from a zip file. |
|
67 * |
|
68 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor |
|
69 * or method in this class will cause a {@link NullPointerException} to be |
|
70 * thrown. |
|
71 * |
|
72 * @author David Connelly |
|
73 * @since 1.1 |
|
74 */ |
|
75 public |
|
76 class ZipFile implements ZipConstants, Closeable { |
|
77 |
|
78 private final String name; // zip file name |
|
79 private volatile boolean closeRequested; |
|
80 private Source zsrc; |
|
81 private ZipCoder zc; |
|
82 |
|
83 private static final int STORED = ZipEntry.STORED; |
|
84 private static final int DEFLATED = ZipEntry.DEFLATED; |
|
85 |
|
86 /** |
|
87 * Mode flag to open a zip file for reading. |
|
88 */ |
|
89 public static final int OPEN_READ = 0x1; |
|
90 |
|
91 /** |
|
92 * Mode flag to open a zip file and mark it for deletion. The file will be |
|
93 * deleted some time between the moment that it is opened and the moment |
|
94 * that it is closed, but its contents will remain accessible via the |
|
95 * {@code ZipFile} object until either the close method is invoked or the |
|
96 * virtual machine exits. |
|
97 */ |
|
98 public static final int OPEN_DELETE = 0x4; |
|
99 |
|
100 /** |
|
101 * Opens a zip file for reading. |
|
102 * |
|
103 * <p>First, if there is a security manager, its {@code checkRead} |
|
104 * method is called with the {@code name} argument as its argument |
|
105 * to ensure the read is allowed. |
|
106 * |
|
107 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to |
|
108 * decode the entry names and comments. |
|
109 * |
|
110 * @param name the name of the zip file |
|
111 * @throws ZipException if a ZIP format error has occurred |
|
112 * @throws IOException if an I/O error has occurred |
|
113 * @throws SecurityException if a security manager exists and its |
|
114 * {@code checkRead} method doesn't allow read access to the file. |
|
115 * |
|
116 * @see SecurityManager#checkRead(java.lang.String) |
|
117 */ |
|
118 public ZipFile(String name) throws IOException { |
|
119 this(new File(name), OPEN_READ); |
|
120 } |
|
121 |
|
122 /** |
|
123 * Opens a new {@code ZipFile} to read from the specified |
|
124 * {@code File} object in the specified mode. The mode argument |
|
125 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. |
|
126 * |
|
127 * <p>First, if there is a security manager, its {@code checkRead} |
|
128 * method is called with the {@code name} argument as its argument to |
|
129 * ensure the read is allowed. |
|
130 * |
|
131 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to |
|
132 * decode the entry names and comments |
|
133 * |
|
134 * @param file the ZIP file to be opened for reading |
|
135 * @param mode the mode in which the file is to be opened |
|
136 * @throws ZipException if a ZIP format error has occurred |
|
137 * @throws IOException if an I/O error has occurred |
|
138 * @throws SecurityException if a security manager exists and |
|
139 * its {@code checkRead} method |
|
140 * doesn't allow read access to the file, |
|
141 * or its {@code checkDelete} method doesn't allow deleting |
|
142 * the file when the {@code OPEN_DELETE} flag is set. |
|
143 * @throws IllegalArgumentException if the {@code mode} argument is invalid |
|
144 * @see SecurityManager#checkRead(java.lang.String) |
|
145 * @since 1.3 |
|
146 */ |
|
147 public ZipFile(File file, int mode) throws IOException { |
|
148 this(file, mode, StandardCharsets.UTF_8); |
|
149 } |
|
150 |
|
151 /** |
|
152 * Opens a ZIP file for reading given the specified File object. |
|
153 * |
|
154 * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to |
|
155 * decode the entry names and comments. |
|
156 * |
|
157 * @param file the ZIP file to be opened for reading |
|
158 * @throws ZipException if a ZIP format error has occurred |
|
159 * @throws IOException if an I/O error has occurred |
|
160 */ |
|
161 public ZipFile(File file) throws ZipException, IOException { |
|
162 this(file, OPEN_READ); |
|
163 } |
|
164 |
|
165 /** |
|
166 * Opens a new {@code ZipFile} to read from the specified |
|
167 * {@code File} object in the specified mode. The mode argument |
|
168 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. |
|
169 * |
|
170 * <p>First, if there is a security manager, its {@code checkRead} |
|
171 * method is called with the {@code name} argument as its argument to |
|
172 * ensure the read is allowed. |
|
173 * |
|
174 * @param file the ZIP file to be opened for reading |
|
175 * @param mode the mode in which the file is to be opened |
|
176 * @param charset |
|
177 * the {@linkplain java.nio.charset.Charset charset} to |
|
178 * be used to decode the ZIP entry name and comment that are not |
|
179 * encoded by using UTF-8 encoding (indicated by entry's general |
|
180 * purpose flag). |
|
181 * |
|
182 * @throws ZipException if a ZIP format error has occurred |
|
183 * @throws IOException if an I/O error has occurred |
|
184 * |
|
185 * @throws SecurityException |
|
186 * if a security manager exists and its {@code checkRead} |
|
187 * method doesn't allow read access to the file,or its |
|
188 * {@code checkDelete} method doesn't allow deleting the |
|
189 * file when the {@code OPEN_DELETE} flag is set |
|
190 * |
|
191 * @throws IllegalArgumentException if the {@code mode} argument is invalid |
|
192 * |
|
193 * @see SecurityManager#checkRead(java.lang.String) |
|
194 * |
|
195 * @since 1.7 |
|
196 */ |
|
197 public ZipFile(File file, int mode, Charset charset) throws IOException |
|
198 { |
|
199 if (((mode & OPEN_READ) == 0) || |
|
200 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) { |
|
201 throw new IllegalArgumentException("Illegal mode: 0x"+ |
|
202 Integer.toHexString(mode)); |
|
203 } |
|
204 String name = file.getPath(); |
|
205 SecurityManager sm = System.getSecurityManager(); |
|
206 if (sm != null) { |
|
207 sm.checkRead(name); |
|
208 if ((mode & OPEN_DELETE) != 0) { |
|
209 sm.checkDelete(name); |
|
210 } |
|
211 } |
|
212 Objects.requireNonNull(charset, "charset"); |
|
213 this.zc = ZipCoder.get(charset); |
|
214 this.name = name; |
|
215 long t0 = System.nanoTime(); |
|
216 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0); |
|
217 PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); |
|
218 PerfCounter.getZipFileCount().increment(); |
|
219 } |
|
220 |
|
221 /** |
|
222 * Opens a zip file for reading. |
|
223 * |
|
224 * <p>First, if there is a security manager, its {@code checkRead} |
|
225 * method is called with the {@code name} argument as its argument |
|
226 * to ensure the read is allowed. |
|
227 * |
|
228 * @param name the name of the zip file |
|
229 * @param charset |
|
230 * the {@linkplain java.nio.charset.Charset charset} to |
|
231 * be used to decode the ZIP entry name and comment that are not |
|
232 * encoded by using UTF-8 encoding (indicated by entry's general |
|
233 * purpose flag). |
|
234 * |
|
235 * @throws ZipException if a ZIP format error has occurred |
|
236 * @throws IOException if an I/O error has occurred |
|
237 * @throws SecurityException |
|
238 * if a security manager exists and its {@code checkRead} |
|
239 * method doesn't allow read access to the file |
|
240 * |
|
241 * @see SecurityManager#checkRead(java.lang.String) |
|
242 * |
|
243 * @since 1.7 |
|
244 */ |
|
245 public ZipFile(String name, Charset charset) throws IOException |
|
246 { |
|
247 this(new File(name), OPEN_READ, charset); |
|
248 } |
|
249 |
|
250 /** |
|
251 * Opens a ZIP file for reading given the specified File object. |
|
252 * |
|
253 * @param file the ZIP file to be opened for reading |
|
254 * @param charset |
|
255 * The {@linkplain java.nio.charset.Charset charset} to be |
|
256 * used to decode the ZIP entry name and comment (ignored if |
|
257 * the <a href="package-summary.html#lang_encoding"> language |
|
258 * encoding bit</a> of the ZIP entry's general purpose bit |
|
259 * flag is set). |
|
260 * |
|
261 * @throws ZipException if a ZIP format error has occurred |
|
262 * @throws IOException if an I/O error has occurred |
|
263 * |
|
264 * @since 1.7 |
|
265 */ |
|
266 public ZipFile(File file, Charset charset) throws IOException |
|
267 { |
|
268 this(file, OPEN_READ, charset); |
|
269 } |
|
270 |
|
271 /** |
|
272 * Returns the zip file comment, or null if none. |
|
273 * |
|
274 * @return the comment string for the zip file, or null if none |
|
275 * |
|
276 * @throws IllegalStateException if the zip file has been closed |
|
277 * |
|
278 * @since 1.7 |
|
279 */ |
|
280 public String getComment() { |
|
281 synchronized (this) { |
|
282 ensureOpen(); |
|
283 if (zsrc.comment == null) { |
|
284 return null; |
|
285 } |
|
286 return zc.toString(zsrc.comment); |
|
287 } |
|
288 } |
|
289 |
|
290 /** |
|
291 * Returns the zip file entry for the specified name, or null |
|
292 * if not found. |
|
293 * |
|
294 * @param name the name of the entry |
|
295 * @return the zip file entry, or null if not found |
|
296 * @throws IllegalStateException if the zip file has been closed |
|
297 */ |
|
298 public ZipEntry getEntry(String name) { |
|
299 Objects.requireNonNull(name, "name"); |
|
300 synchronized (this) { |
|
301 ensureOpen(); |
|
302 byte[] bname = zc.getBytes(name); |
|
303 int pos = zsrc.getEntryPos(bname, true); |
|
304 if (pos != -1) { |
|
305 return getZipEntry(name, bname, pos); |
|
306 } |
|
307 } |
|
308 return null; |
|
309 } |
|
310 |
|
311 // The outstanding inputstreams that need to be closed, |
|
312 // mapped to the inflater objects they use. |
|
313 private final Map<InputStream, Inflater> streams = new WeakHashMap<>(); |
|
314 |
|
315 /** |
|
316 * Returns an input stream for reading the contents of the specified |
|
317 * zip file entry. |
|
318 * <p> |
|
319 * Closing this ZIP file will, in turn, close all input streams that |
|
320 * have been returned by invocations of this method. |
|
321 * |
|
322 * @param entry the zip file entry |
|
323 * @return the input stream for reading the contents of the specified |
|
324 * zip file entry. |
|
325 * @throws ZipException if a ZIP format error has occurred |
|
326 * @throws IOException if an I/O error has occurred |
|
327 * @throws IllegalStateException if the zip file has been closed |
|
328 */ |
|
329 public InputStream getInputStream(ZipEntry entry) throws IOException { |
|
330 Objects.requireNonNull(entry, "entry"); |
|
331 int pos = -1; |
|
332 ZipFileInputStream in = null; |
|
333 synchronized (this) { |
|
334 ensureOpen(); |
|
335 if (Objects.equals(lastEntryName, entry.name)) { |
|
336 pos = lastEntryPos; |
|
337 } else if (!zc.isUTF8() && (entry.flag & EFS) != 0) { |
|
338 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false); |
|
339 } else { |
|
340 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false); |
|
341 } |
|
342 if (pos == -1) { |
|
343 return null; |
|
344 } |
|
345 in = new ZipFileInputStream(zsrc.cen, pos); |
|
346 switch (CENHOW(zsrc.cen, pos)) { |
|
347 case STORED: |
|
348 synchronized (streams) { |
|
349 streams.put(in, null); |
|
350 } |
|
351 return in; |
|
352 case DEFLATED: |
|
353 // Inflater likes a bit of slack |
|
354 // MORE: Compute good size for inflater stream: |
|
355 long size = CENLEN(zsrc.cen, pos) + 2; |
|
356 if (size > 65536) { |
|
357 size = 8192; |
|
358 } |
|
359 if (size <= 0) { |
|
360 size = 4096; |
|
361 } |
|
362 Inflater inf = getInflater(); |
|
363 InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size); |
|
364 synchronized (streams) { |
|
365 streams.put(is, inf); |
|
366 } |
|
367 return is; |
|
368 default: |
|
369 throw new ZipException("invalid compression method"); |
|
370 } |
|
371 } |
|
372 } |
|
373 |
|
374 private class ZipFileInflaterInputStream extends InflaterInputStream { |
|
375 private volatile boolean closeRequested; |
|
376 private boolean eof = false; |
|
377 private final ZipFileInputStream zfin; |
|
378 |
|
379 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf, |
|
380 int size) { |
|
381 super(zfin, inf, size); |
|
382 this.zfin = zfin; |
|
383 } |
|
384 |
|
385 public void close() throws IOException { |
|
386 if (closeRequested) |
|
387 return; |
|
388 closeRequested = true; |
|
389 |
|
390 super.close(); |
|
391 Inflater inf; |
|
392 synchronized (streams) { |
|
393 inf = streams.remove(this); |
|
394 } |
|
395 if (inf != null) { |
|
396 releaseInflater(inf); |
|
397 } |
|
398 } |
|
399 |
|
400 // Override fill() method to provide an extra "dummy" byte |
|
401 // at the end of the input stream. This is required when |
|
402 // using the "nowrap" Inflater option. |
|
403 protected void fill() throws IOException { |
|
404 if (eof) { |
|
405 throw new EOFException("Unexpected end of ZLIB input stream"); |
|
406 } |
|
407 len = in.read(buf, 0, buf.length); |
|
408 if (len == -1) { |
|
409 buf[0] = 0; |
|
410 len = 1; |
|
411 eof = true; |
|
412 } |
|
413 inf.setInput(buf, 0, len); |
|
414 } |
|
415 |
|
416 public int available() throws IOException { |
|
417 if (closeRequested) |
|
418 return 0; |
|
419 long avail = zfin.size() - inf.getBytesWritten(); |
|
420 return (avail > (long) Integer.MAX_VALUE ? |
|
421 Integer.MAX_VALUE : (int) avail); |
|
422 } |
|
423 |
|
424 @SuppressWarnings("deprecation") |
|
425 protected void finalize() throws Throwable { |
|
426 close(); |
|
427 } |
|
428 } |
|
429 |
|
430 /* |
|
431 * Gets an inflater from the list of available inflaters or allocates |
|
432 * a new one. |
|
433 */ |
|
434 private Inflater getInflater() { |
|
435 Inflater inf; |
|
436 synchronized (inflaterCache) { |
|
437 while ((inf = inflaterCache.poll()) != null) { |
|
438 if (!inf.ended()) { |
|
439 return inf; |
|
440 } |
|
441 } |
|
442 } |
|
443 return new Inflater(true); |
|
444 } |
|
445 |
|
446 /* |
|
447 * Releases the specified inflater to the list of available inflaters. |
|
448 */ |
|
449 private void releaseInflater(Inflater inf) { |
|
450 if (!inf.ended()) { |
|
451 inf.reset(); |
|
452 synchronized (inflaterCache) { |
|
453 inflaterCache.add(inf); |
|
454 } |
|
455 } |
|
456 } |
|
457 |
|
458 // List of available Inflater objects for decompression |
|
459 private final Deque<Inflater> inflaterCache = new ArrayDeque<>(); |
|
460 |
|
461 /** |
|
462 * Returns the path name of the ZIP file. |
|
463 * @return the path name of the ZIP file |
|
464 */ |
|
465 public String getName() { |
|
466 return name; |
|
467 } |
|
468 |
|
469 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { |
|
470 private int i = 0; |
|
471 private final int entryCount; |
|
472 |
|
473 public ZipEntryIterator() { |
|
474 synchronized (ZipFile.this) { |
|
475 ensureOpen(); |
|
476 this.entryCount = zsrc.total; |
|
477 } |
|
478 } |
|
479 |
|
480 public boolean hasMoreElements() { |
|
481 return hasNext(); |
|
482 } |
|
483 |
|
484 public boolean hasNext() { |
|
485 return i < entryCount; |
|
486 } |
|
487 |
|
488 public ZipEntry nextElement() { |
|
489 return next(); |
|
490 } |
|
491 |
|
492 public ZipEntry next() { |
|
493 synchronized (ZipFile.this) { |
|
494 ensureOpen(); |
|
495 if (!hasNext()) { |
|
496 throw new NoSuchElementException(); |
|
497 } |
|
498 // each "entry" has 3 ints in table entries |
|
499 return getZipEntry(null, null, zsrc.getEntryPos(i++ * 3)); |
|
500 } |
|
501 } |
|
502 |
|
503 public Iterator<ZipEntry> asIterator() { |
|
504 return this; |
|
505 } |
|
506 } |
|
507 |
|
508 /** |
|
509 * Returns an enumeration of the ZIP file entries. |
|
510 * @return an enumeration of the ZIP file entries |
|
511 * @throws IllegalStateException if the zip file has been closed |
|
512 */ |
|
513 public Enumeration<? extends ZipEntry> entries() { |
|
514 return new ZipEntryIterator(); |
|
515 } |
|
516 |
|
517 /** |
|
518 * Returns an ordered {@code Stream} over the ZIP file entries. |
|
519 * Entries appear in the {@code Stream} in the order they appear in |
|
520 * the central directory of the ZIP file. |
|
521 * |
|
522 * @return an ordered {@code Stream} of entries in this ZIP file |
|
523 * @throws IllegalStateException if the zip file has been closed |
|
524 * @since 1.8 |
|
525 */ |
|
526 public Stream<? extends ZipEntry> stream() { |
|
527 return StreamSupport.stream(Spliterators.spliterator( |
|
528 new ZipEntryIterator(), size(), |
|
529 Spliterator.ORDERED | Spliterator.DISTINCT | |
|
530 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); |
|
531 } |
|
532 |
|
533 private String lastEntryName; |
|
534 private int lastEntryPos; |
|
535 |
|
536 /* Checks ensureOpen() before invoke this method */ |
|
537 private ZipEntry getZipEntry(String name, byte[] bname, int pos) { |
|
538 byte[] cen = zsrc.cen; |
|
539 int nlen = CENNAM(cen, pos); |
|
540 int elen = CENEXT(cen, pos); |
|
541 int clen = CENCOM(cen, pos); |
|
542 int flag = CENFLG(cen, pos); |
|
543 if (name == null || bname.length != nlen) { |
|
544 // to use the entry name stored in cen, if the passed in name is |
|
545 // (1) null, invoked from iterator, or |
|
546 // (2) not equal to the name stored, a slash is appended during |
|
547 // getEntryPos() search. |
|
548 if (!zc.isUTF8() && (flag & EFS) != 0) { |
|
549 name = zc.toStringUTF8(cen, pos + CENHDR, nlen); |
|
550 } else { |
|
551 name = zc.toString(cen, pos + CENHDR, nlen); |
|
552 } |
|
553 } |
|
554 ZipEntry e = new ZipEntry(name); |
|
555 e.flag = flag; |
|
556 e.xdostime = CENTIM(cen, pos); |
|
557 e.crc = CENCRC(cen, pos); |
|
558 e.size = CENLEN(cen, pos); |
|
559 e.csize = CENSIZ(cen, pos); |
|
560 e.method = CENHOW(cen, pos); |
|
561 if (elen != 0) { |
|
562 int start = pos + CENHDR + nlen; |
|
563 e.setExtra0(Arrays.copyOfRange(cen, start, start + elen), true); |
|
564 } |
|
565 if (clen != 0) { |
|
566 int start = pos + CENHDR + nlen + elen; |
|
567 if (!zc.isUTF8() && (flag & EFS) != 0) { |
|
568 e.comment = zc.toStringUTF8(cen, start, clen); |
|
569 } else { |
|
570 e.comment = zc.toString(cen, start, clen); |
|
571 } |
|
572 } |
|
573 lastEntryName = e.name; |
|
574 lastEntryPos = pos; |
|
575 return e; |
|
576 } |
|
577 |
|
578 /** |
|
579 * Returns the number of entries in the ZIP file. |
|
580 * |
|
581 * @return the number of entries in the ZIP file |
|
582 * @throws IllegalStateException if the zip file has been closed |
|
583 */ |
|
584 public int size() { |
|
585 synchronized (this) { |
|
586 ensureOpen(); |
|
587 return zsrc.total; |
|
588 } |
|
589 } |
|
590 |
|
591 /** |
|
592 * Closes the ZIP file. |
|
593 * <p> Closing this ZIP file will close all of the input streams |
|
594 * previously returned by invocations of the {@link #getInputStream |
|
595 * getInputStream} method. |
|
596 * |
|
597 * @throws IOException if an I/O error has occurred |
|
598 */ |
|
599 public void close() throws IOException { |
|
600 if (closeRequested) { |
|
601 return; |
|
602 } |
|
603 closeRequested = true; |
|
604 |
|
605 synchronized (this) { |
|
606 // Close streams, release their inflaters |
|
607 synchronized (streams) { |
|
608 if (!streams.isEmpty()) { |
|
609 Map<InputStream, Inflater> copy = new HashMap<>(streams); |
|
610 streams.clear(); |
|
611 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) { |
|
612 e.getKey().close(); |
|
613 Inflater inf = e.getValue(); |
|
614 if (inf != null) { |
|
615 inf.end(); |
|
616 } |
|
617 } |
|
618 } |
|
619 } |
|
620 // Release cached inflaters |
|
621 synchronized (inflaterCache) { |
|
622 Inflater inf; |
|
623 while ((inf = inflaterCache.poll()) != null) { |
|
624 inf.end(); |
|
625 } |
|
626 } |
|
627 // Release zip src |
|
628 if (zsrc != null) { |
|
629 Source.close(zsrc); |
|
630 zsrc = null; |
|
631 } |
|
632 } |
|
633 } |
|
634 |
|
635 /** |
|
636 * Ensures that the system resources held by this ZipFile object are |
|
637 * released when there are no more references to it. |
|
638 * |
|
639 * <p> |
|
640 * Since the time when GC would invoke this method is undetermined, |
|
641 * it is strongly recommended that applications invoke the {@code close} |
|
642 * method as soon they have finished accessing this {@code ZipFile}. |
|
643 * This will prevent holding up system resources for an undetermined |
|
644 * length of time. |
|
645 * |
|
646 * @deprecated The {@code finalize} method has been deprecated. |
|
647 * Subclasses that override {@code finalize} in order to perform cleanup |
|
648 * should be modified to use alternative cleanup mechanisms and |
|
649 * to remove the overriding {@code finalize} method. |
|
650 * When overriding the {@code finalize} method, its implementation must explicitly |
|
651 * ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}. |
|
652 * See the specification for {@link Object#finalize()} for further |
|
653 * information about migration options. |
|
654 * @throws IOException if an I/O error has occurred |
|
655 * @see java.util.zip.ZipFile#close() |
|
656 */ |
|
657 @Deprecated(since="9") |
|
658 protected void finalize() throws IOException { |
|
659 close(); |
|
660 } |
|
661 |
|
662 private void ensureOpen() { |
|
663 if (closeRequested) { |
|
664 throw new IllegalStateException("zip file closed"); |
|
665 } |
|
666 if (zsrc == null) { |
|
667 throw new IllegalStateException("The object is not initialized."); |
|
668 } |
|
669 } |
|
670 |
|
671 private void ensureOpenOrZipException() throws IOException { |
|
672 if (closeRequested) { |
|
673 throw new ZipException("ZipFile closed"); |
|
674 } |
|
675 } |
|
676 |
|
677 /* |
|
678 * Inner class implementing the input stream used to read a |
|
679 * (possibly compressed) zip file entry. |
|
680 */ |
|
681 private class ZipFileInputStream extends InputStream { |
|
682 private volatile boolean closeRequested; |
|
683 private long pos; // current position within entry data |
|
684 protected long rem; // number of remaining bytes within entry |
|
685 protected long size; // uncompressed size of this entry |
|
686 |
|
687 ZipFileInputStream(byte[] cen, int cenpos) throws IOException { |
|
688 rem = CENSIZ(cen, cenpos); |
|
689 size = CENLEN(cen, cenpos); |
|
690 pos = CENOFF(cen, cenpos); |
|
691 // zip64 |
|
692 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL || |
|
693 pos == ZIP64_MAGICVAL) { |
|
694 checkZIP64(cen, cenpos); |
|
695 } |
|
696 // negative for lazy initialization, see getDataOffset(); |
|
697 pos = - (pos + ZipFile.this.zsrc.locpos); |
|
698 } |
|
699 |
|
700 private void checkZIP64(byte[] cen, int cenpos) throws IOException { |
|
701 int off = cenpos + CENHDR + CENNAM(cen, cenpos); |
|
702 int end = off + CENEXT(cen, cenpos); |
|
703 while (off + 4 < end) { |
|
704 int tag = get16(cen, off); |
|
705 int sz = get16(cen, off + 2); |
|
706 off += 4; |
|
707 if (off + sz > end) // invalid data |
|
708 break; |
|
709 if (tag == EXTID_ZIP64) { |
|
710 if (size == ZIP64_MAGICVAL) { |
|
711 if (sz < 8 || (off + 8) > end) |
|
712 break; |
|
713 size = get64(cen, off); |
|
714 sz -= 8; |
|
715 off += 8; |
|
716 } |
|
717 if (rem == ZIP64_MAGICVAL) { |
|
718 if (sz < 8 || (off + 8) > end) |
|
719 break; |
|
720 rem = get64(cen, off); |
|
721 sz -= 8; |
|
722 off += 8; |
|
723 } |
|
724 if (pos == ZIP64_MAGICVAL) { |
|
725 if (sz < 8 || (off + 8) > end) |
|
726 break; |
|
727 pos = get64(cen, off); |
|
728 sz -= 8; |
|
729 off += 8; |
|
730 } |
|
731 break; |
|
732 } |
|
733 off += sz; |
|
734 } |
|
735 } |
|
736 |
|
737 /* The Zip file spec explicitly allows the LOC extra data size to |
|
738 * be different from the CEN extra data size. Since we cannot trust |
|
739 * the CEN extra data size, we need to read the LOC to determine |
|
740 * the entry data offset. |
|
741 */ |
|
742 private long initDataOffset() throws IOException { |
|
743 if (pos <= 0) { |
|
744 byte[] loc = new byte[LOCHDR]; |
|
745 pos = -pos; |
|
746 int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos); |
|
747 if (len != LOCHDR) { |
|
748 throw new ZipException("ZipFile error reading zip file"); |
|
749 } |
|
750 if (LOCSIG(loc) != LOCSIG) { |
|
751 throw new ZipException("ZipFile invalid LOC header (bad signature)"); |
|
752 } |
|
753 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc); |
|
754 } |
|
755 return pos; |
|
756 } |
|
757 |
|
758 public int read(byte b[], int off, int len) throws IOException { |
|
759 synchronized (ZipFile.this) { |
|
760 ensureOpenOrZipException(); |
|
761 initDataOffset(); |
|
762 if (rem == 0) { |
|
763 return -1; |
|
764 } |
|
765 if (len > rem) { |
|
766 len = (int) rem; |
|
767 } |
|
768 if (len <= 0) { |
|
769 return 0; |
|
770 } |
|
771 len = ZipFile.this.zsrc.readAt(b, off, len, pos); |
|
772 if (len > 0) { |
|
773 pos += len; |
|
774 rem -= len; |
|
775 } |
|
776 } |
|
777 if (rem == 0) { |
|
778 close(); |
|
779 } |
|
780 return len; |
|
781 } |
|
782 |
|
783 public int read() throws IOException { |
|
784 byte[] b = new byte[1]; |
|
785 if (read(b, 0, 1) == 1) { |
|
786 return b[0] & 0xff; |
|
787 } else { |
|
788 return -1; |
|
789 } |
|
790 } |
|
791 |
|
792 public long skip(long n) throws IOException { |
|
793 synchronized (ZipFile.this) { |
|
794 ensureOpenOrZipException(); |
|
795 initDataOffset(); |
|
796 if (n > rem) { |
|
797 n = rem; |
|
798 } |
|
799 pos += n; |
|
800 rem -= n; |
|
801 } |
|
802 if (rem == 0) { |
|
803 close(); |
|
804 } |
|
805 return n; |
|
806 } |
|
807 |
|
808 public int available() { |
|
809 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; |
|
810 } |
|
811 |
|
812 public long size() { |
|
813 return size; |
|
814 } |
|
815 |
|
816 public void close() { |
|
817 if (closeRequested) { |
|
818 return; |
|
819 } |
|
820 closeRequested = true; |
|
821 rem = 0; |
|
822 synchronized (streams) { |
|
823 streams.remove(this); |
|
824 } |
|
825 } |
|
826 |
|
827 @SuppressWarnings("deprecation") |
|
828 protected void finalize() { |
|
829 close(); |
|
830 } |
|
831 } |
|
832 |
|
833 /** |
|
834 * Returns the names of all non-directory entries that begin with |
|
835 * "META-INF/" (case ignored). This method is used in JarFile, via |
|
836 * SharedSecrets, as an optimization when looking up manifest and |
|
837 * signature file entries. Returns null if no entries were found. |
|
838 */ |
|
839 private String[] getMetaInfEntryNames() { |
|
840 synchronized (this) { |
|
841 ensureOpen(); |
|
842 if (zsrc.metanames == null) { |
|
843 return null; |
|
844 } |
|
845 String[] names = new String[zsrc.metanames.length]; |
|
846 byte[] cen = zsrc.cen; |
|
847 for (int i = 0; i < names.length; i++) { |
|
848 int pos = zsrc.metanames[i]; |
|
849 names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos), |
|
850 StandardCharsets.UTF_8); |
|
851 } |
|
852 return names; |
|
853 } |
|
854 } |
|
855 |
|
856 private static boolean isWindows; |
|
857 static { |
|
858 SharedSecrets.setJavaUtilZipFileAccess( |
|
859 new JavaUtilZipFileAccess() { |
|
860 public boolean startsWithLocHeader(ZipFile zip) { |
|
861 return zip.zsrc.startsWithLoc; |
|
862 } |
|
863 public String[] getMetaInfEntryNames(ZipFile zip) { |
|
864 return zip.getMetaInfEntryNames(); |
|
865 } |
|
866 } |
|
867 ); |
|
868 isWindows = VM.getSavedProperty("os.name").contains("Windows"); |
|
869 } |
|
870 |
|
871 private static class Source { |
|
872 private final Key key; // the key in files |
|
873 private int refs = 1; |
|
874 |
|
875 private RandomAccessFile zfile; // zfile of the underlying zip file |
|
876 private byte[] cen; // CEN & ENDHDR |
|
877 private long locpos; // position of first LOC header (usually 0) |
|
878 private byte[] comment; // zip file comment |
|
879 // list of meta entries in META-INF dir |
|
880 private int[] metanames; |
|
881 private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true) |
|
882 |
|
883 // A Hashmap for all entries. |
|
884 // |
|
885 // A cen entry of Zip/JAR file. As we have one for every entry in every active Zip/JAR, |
|
886 // We might have a lot of these in a typical system. In order to save space we don't |
|
887 // keep the name in memory, but merely remember a 32 bit {@code hash} value of the |
|
888 // entry name and its offset {@code pos} in the central directory hdeader. |
|
889 // |
|
890 // private static class Entry { |
|
891 // int hash; // 32 bit hashcode on name |
|
892 // int next; // hash chain: index into entries |
|
893 // int pos; // Offset of central directory file header |
|
894 // } |
|
895 // private Entry[] entries; // array of hashed cen entry |
|
896 // |
|
897 // To reduce the total size of entries further, we use a int[] here to store 3 "int" |
|
898 // {@code hash}, {@code next and {@code "pos for each entry. The entry can then be |
|
899 // referred by their index of their positions in the {@code entries}. |
|
900 // |
|
901 private int[] entries; // array of hashed cen entry |
|
902 private int addEntry(int index, int hash, int next, int pos) { |
|
903 entries[index++] = hash; |
|
904 entries[index++] = next; |
|
905 entries[index++] = pos; |
|
906 return index; |
|
907 } |
|
908 private int getEntryHash(int index) { return entries[index]; } |
|
909 private int getEntryNext(int index) { return entries[index + 1]; } |
|
910 private int getEntryPos(int index) { return entries[index + 2]; } |
|
911 private static final int ZIP_ENDCHAIN = -1; |
|
912 private int total; // total number of entries |
|
913 private int[] table; // Hash chain heads: indexes into entries |
|
914 private int tablelen; // number of hash heads |
|
915 |
|
916 private static class Key { |
|
917 BasicFileAttributes attrs; |
|
918 File file; |
|
919 |
|
920 public Key(File file, BasicFileAttributes attrs) { |
|
921 this.attrs = attrs; |
|
922 this.file = file; |
|
923 } |
|
924 |
|
925 public int hashCode() { |
|
926 long t = attrs.lastModifiedTime().toMillis(); |
|
927 return ((int)(t ^ (t >>> 32))) + file.hashCode(); |
|
928 } |
|
929 |
|
930 public boolean equals(Object obj) { |
|
931 if (obj instanceof Key) { |
|
932 Key key = (Key)obj; |
|
933 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { |
|
934 return false; |
|
935 } |
|
936 Object fk = attrs.fileKey(); |
|
937 if (fk != null) { |
|
938 return fk.equals(key.attrs.fileKey()); |
|
939 } else { |
|
940 return file.equals(key.file); |
|
941 } |
|
942 } |
|
943 return false; |
|
944 } |
|
945 } |
|
946 private static final HashMap<Key, Source> files = new HashMap<>(); |
|
947 |
|
948 |
|
949 public static Source get(File file, boolean toDelete) throws IOException { |
|
950 Key key = new Key(file, |
|
951 Files.readAttributes(file.toPath(), BasicFileAttributes.class)); |
|
952 Source src = null; |
|
953 synchronized (files) { |
|
954 src = files.get(key); |
|
955 if (src != null) { |
|
956 src.refs++; |
|
957 return src; |
|
958 } |
|
959 } |
|
960 src = new Source(key, toDelete); |
|
961 |
|
962 synchronized (files) { |
|
963 if (files.containsKey(key)) { // someone else put in first |
|
964 src.close(); // close the newly created one |
|
965 src = files.get(key); |
|
966 src.refs++; |
|
967 return src; |
|
968 } |
|
969 files.put(key, src); |
|
970 return src; |
|
971 } |
|
972 } |
|
973 |
|
974 private static void close(Source src) throws IOException { |
|
975 synchronized (files) { |
|
976 if (--src.refs == 0) { |
|
977 files.remove(src.key); |
|
978 src.close(); |
|
979 } |
|
980 } |
|
981 } |
|
982 |
|
983 private Source(Key key, boolean toDelete) throws IOException { |
|
984 this.key = key; |
|
985 if (toDelete) { |
|
986 if (isWindows) { |
|
987 this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess() |
|
988 .openAndDelete(key.file, "r"); |
|
989 } else { |
|
990 this.zfile = new RandomAccessFile(key.file, "r"); |
|
991 key.file.delete(); |
|
992 } |
|
993 } else { |
|
994 this.zfile = new RandomAccessFile(key.file, "r"); |
|
995 } |
|
996 try { |
|
997 initCEN(-1); |
|
998 byte[] buf = new byte[4]; |
|
999 readFullyAt(buf, 0, 4, 0); |
|
1000 this.startsWithLoc = (LOCSIG(buf) == LOCSIG); |
|
1001 } catch (IOException x) { |
|
1002 try { |
|
1003 this.zfile.close(); |
|
1004 } catch (IOException xx) {} |
|
1005 throw x; |
|
1006 } |
|
1007 } |
|
1008 |
|
1009 private void close() throws IOException { |
|
1010 zfile.close(); |
|
1011 zfile = null; |
|
1012 cen = null; |
|
1013 entries = null; |
|
1014 table = null; |
|
1015 metanames = null; |
|
1016 } |
|
1017 |
|
1018 private static final int BUF_SIZE = 8192; |
|
1019 private final int readFullyAt(byte[] buf, int off, int len, long pos) |
|
1020 throws IOException |
|
1021 { |
|
1022 synchronized(zfile) { |
|
1023 zfile.seek(pos); |
|
1024 int N = len; |
|
1025 while (N > 0) { |
|
1026 int n = Math.min(BUF_SIZE, N); |
|
1027 zfile.readFully(buf, off, n); |
|
1028 off += n; |
|
1029 N -= n; |
|
1030 } |
|
1031 return len; |
|
1032 } |
|
1033 } |
|
1034 |
|
1035 private final int readAt(byte[] buf, int off, int len, long pos) |
|
1036 throws IOException |
|
1037 { |
|
1038 synchronized(zfile) { |
|
1039 zfile.seek(pos); |
|
1040 return zfile.read(buf, off, len); |
|
1041 } |
|
1042 } |
|
1043 |
|
1044 private static final int hashN(byte[] a, int off, int len) { |
|
1045 int h = 1; |
|
1046 while (len-- > 0) { |
|
1047 h = 31 * h + a[off++]; |
|
1048 } |
|
1049 return h; |
|
1050 } |
|
1051 |
|
1052 private static final int hash_append(int hash, byte b) { |
|
1053 return hash * 31 + b; |
|
1054 } |
|
1055 |
|
1056 private static class End { |
|
1057 int centot; // 4 bytes |
|
1058 long cenlen; // 4 bytes |
|
1059 long cenoff; // 4 bytes |
|
1060 long endpos; // 4 bytes |
|
1061 } |
|
1062 |
|
1063 /* |
|
1064 * Searches for end of central directory (END) header. The contents of |
|
1065 * the END header will be read and placed in endbuf. Returns the file |
|
1066 * position of the END header, otherwise returns -1 if the END header |
|
1067 * was not found or an error occurred. |
|
1068 */ |
|
1069 private End findEND() throws IOException { |
|
1070 long ziplen = zfile.length(); |
|
1071 if (ziplen <= 0) |
|
1072 zerror("zip file is empty"); |
|
1073 End end = new End(); |
|
1074 byte[] buf = new byte[READBLOCKSZ]; |
|
1075 long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0; |
|
1076 long minPos = minHDR - (buf.length - ENDHDR); |
|
1077 for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR)) { |
|
1078 int off = 0; |
|
1079 if (pos < 0) { |
|
1080 // Pretend there are some NUL bytes before start of file |
|
1081 off = (int)-pos; |
|
1082 Arrays.fill(buf, 0, off, (byte)0); |
|
1083 } |
|
1084 int len = buf.length - off; |
|
1085 if (readFullyAt(buf, off, len, pos + off) != len ) { |
|
1086 zerror("zip END header not found"); |
|
1087 } |
|
1088 // Now scan the block backwards for END header signature |
|
1089 for (int i = buf.length - ENDHDR; i >= 0; i--) { |
|
1090 if (buf[i+0] == (byte)'P' && |
|
1091 buf[i+1] == (byte)'K' && |
|
1092 buf[i+2] == (byte)'\005' && |
|
1093 buf[i+3] == (byte)'\006') { |
|
1094 // Found ENDSIG header |
|
1095 byte[] endbuf = Arrays.copyOfRange(buf, i, i + ENDHDR); |
|
1096 end.centot = ENDTOT(endbuf); |
|
1097 end.cenlen = ENDSIZ(endbuf); |
|
1098 end.cenoff = ENDOFF(endbuf); |
|
1099 end.endpos = pos + i; |
|
1100 int comlen = ENDCOM(endbuf); |
|
1101 if (end.endpos + ENDHDR + comlen != ziplen) { |
|
1102 // ENDSIG matched, however the size of file comment in it does |
|
1103 // not match the real size. One "common" cause for this problem |
|
1104 // is some "extra" bytes are padded at the end of the zipfile. |
|
1105 // Let's do some extra verification, we don't care about the |
|
1106 // performance in this situation. |
|
1107 byte[] sbuf = new byte[4]; |
|
1108 long cenpos = end.endpos - end.cenlen; |
|
1109 long locpos = cenpos - end.cenoff; |
|
1110 if (cenpos < 0 || |
|
1111 locpos < 0 || |
|
1112 readFullyAt(sbuf, 0, sbuf.length, cenpos) != 4 || |
|
1113 GETSIG(sbuf) != CENSIG || |
|
1114 readFullyAt(sbuf, 0, sbuf.length, locpos) != 4 || |
|
1115 GETSIG(sbuf) != LOCSIG) { |
|
1116 continue; |
|
1117 } |
|
1118 } |
|
1119 if (comlen > 0) { // this zip file has comlen |
|
1120 comment = new byte[comlen]; |
|
1121 if (readFullyAt(comment, 0, comlen, end.endpos + ENDHDR) != comlen) { |
|
1122 zerror("zip comment read failed"); |
|
1123 } |
|
1124 } |
|
1125 if (end.cenlen == ZIP64_MAGICVAL || |
|
1126 end.cenoff == ZIP64_MAGICVAL || |
|
1127 end.centot == ZIP64_MAGICCOUNT) |
|
1128 { |
|
1129 // need to find the zip64 end; |
|
1130 try { |
|
1131 byte[] loc64 = new byte[ZIP64_LOCHDR]; |
|
1132 if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) |
|
1133 != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { |
|
1134 return end; |
|
1135 } |
|
1136 long end64pos = ZIP64_LOCOFF(loc64); |
|
1137 byte[] end64buf = new byte[ZIP64_ENDHDR]; |
|
1138 if (readFullyAt(end64buf, 0, end64buf.length, end64pos) |
|
1139 != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { |
|
1140 return end; |
|
1141 } |
|
1142 // end64 found, re-calcualte everything. |
|
1143 end.cenlen = ZIP64_ENDSIZ(end64buf); |
|
1144 end.cenoff = ZIP64_ENDOFF(end64buf); |
|
1145 end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g |
|
1146 end.endpos = end64pos; |
|
1147 } catch (IOException x) {} // no zip64 loc/end |
|
1148 } |
|
1149 return end; |
|
1150 } |
|
1151 } |
|
1152 } |
|
1153 zerror("zip END header not found"); |
|
1154 return null; //make compiler happy |
|
1155 } |
|
1156 |
|
1157 // Reads zip file central directory. |
|
1158 private void initCEN(int knownTotal) throws IOException { |
|
1159 if (knownTotal == -1) { |
|
1160 End end = findEND(); |
|
1161 if (end.endpos == 0) { |
|
1162 locpos = 0; |
|
1163 total = 0; |
|
1164 entries = new int[0]; |
|
1165 cen = null; |
|
1166 return; // only END header present |
|
1167 } |
|
1168 if (end.cenlen > end.endpos) |
|
1169 zerror("invalid END header (bad central directory size)"); |
|
1170 long cenpos = end.endpos - end.cenlen; // position of CEN table |
|
1171 // Get position of first local file (LOC) header, taking into |
|
1172 // account that there may be a stub prefixed to the zip file. |
|
1173 locpos = cenpos - end.cenoff; |
|
1174 if (locpos < 0) { |
|
1175 zerror("invalid END header (bad central directory offset)"); |
|
1176 } |
|
1177 // read in the CEN and END |
|
1178 cen = new byte[(int)(end.cenlen + ENDHDR)]; |
|
1179 if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { |
|
1180 zerror("read CEN tables failed"); |
|
1181 } |
|
1182 total = end.centot; |
|
1183 } else { |
|
1184 total = knownTotal; |
|
1185 } |
|
1186 // hash table for entries |
|
1187 entries = new int[total * 3]; |
|
1188 tablelen = ((total/2) | 1); // Odd -> fewer collisions |
|
1189 table = new int[tablelen]; |
|
1190 Arrays.fill(table, ZIP_ENDCHAIN); |
|
1191 int idx = 0; |
|
1192 int hash = 0; |
|
1193 int next = -1; |
|
1194 |
|
1195 // list for all meta entries |
|
1196 ArrayList<Integer> metanamesList = null; |
|
1197 |
|
1198 // Iterate through the entries in the central directory |
|
1199 int i = 0; |
|
1200 int hsh = 0; |
|
1201 int pos = 0; |
|
1202 int limit = cen.length - ENDHDR; |
|
1203 while (pos + CENHDR <= limit) { |
|
1204 if (i >= total) { |
|
1205 // This will only happen if the zip file has an incorrect |
|
1206 // ENDTOT field, which usually means it contains more than |
|
1207 // 65535 entries. |
|
1208 initCEN(countCENHeaders(cen, limit)); |
|
1209 return; |
|
1210 } |
|
1211 if (CENSIG(cen, pos) != CENSIG) |
|
1212 zerror("invalid CEN header (bad signature)"); |
|
1213 int method = CENHOW(cen, pos); |
|
1214 int nlen = CENNAM(cen, pos); |
|
1215 int elen = CENEXT(cen, pos); |
|
1216 int clen = CENCOM(cen, pos); |
|
1217 if ((CENFLG(cen, pos) & 1) != 0) |
|
1218 zerror("invalid CEN header (encrypted entry)"); |
|
1219 if (method != STORED && method != DEFLATED) |
|
1220 zerror("invalid CEN header (bad compression method: " + method + ")"); |
|
1221 if (pos + CENHDR + nlen > limit) |
|
1222 zerror("invalid CEN header (bad header size)"); |
|
1223 // Record the CEN offset and the name hash in our hash cell. |
|
1224 hash = hashN(cen, pos + CENHDR, nlen); |
|
1225 hsh = (hash & 0x7fffffff) % tablelen; |
|
1226 next = table[hsh]; |
|
1227 table[hsh] = idx; |
|
1228 idx = addEntry(idx, hash, next, pos); |
|
1229 // Adds name to metanames. |
|
1230 if (isMetaName(cen, pos + CENHDR, nlen)) { |
|
1231 if (metanamesList == null) |
|
1232 metanamesList = new ArrayList<>(4); |
|
1233 metanamesList.add(pos); |
|
1234 } |
|
1235 // skip ext and comment |
|
1236 pos += (CENHDR + nlen + elen + clen); |
|
1237 i++; |
|
1238 } |
|
1239 total = i; |
|
1240 if (metanamesList != null) { |
|
1241 metanames = new int[metanamesList.size()]; |
|
1242 for (int j = 0, len = metanames.length; j < len; j++) { |
|
1243 metanames[j] = metanamesList.get(j); |
|
1244 } |
|
1245 } |
|
1246 if (pos + ENDHDR != cen.length) { |
|
1247 zerror("invalid CEN header (bad header size)"); |
|
1248 } |
|
1249 } |
|
1250 |
|
1251 private static void zerror(String msg) throws ZipException { |
|
1252 throw new ZipException(msg); |
|
1253 } |
|
1254 |
|
1255 /* |
|
1256 * Returns the {@code pos} of the zip cen entry corresponding to the |
|
1257 * specified entry name, or -1 if not found. |
|
1258 */ |
|
1259 private int getEntryPos(byte[] name, boolean addSlash) { |
|
1260 if (total == 0) { |
|
1261 return -1; |
|
1262 } |
|
1263 int hsh = hashN(name, 0, name.length); |
|
1264 int idx = table[(hsh & 0x7fffffff) % tablelen]; |
|
1265 /* |
|
1266 * This while loop is an optimization where a double lookup |
|
1267 * for name and name+/ is being performed. The name char |
|
1268 * array has enough room at the end to try again with a |
|
1269 * slash appended if the first table lookup does not succeed. |
|
1270 */ |
|
1271 while(true) { |
|
1272 /* |
|
1273 * Search down the target hash chain for a entry whose |
|
1274 * 32 bit hash matches the hashed name. |
|
1275 */ |
|
1276 while (idx != ZIP_ENDCHAIN) { |
|
1277 if (getEntryHash(idx) == hsh) { |
|
1278 // The CEN name must match the specfied one |
|
1279 int pos = getEntryPos(idx); |
|
1280 if (name.length == CENNAM(cen, pos)) { |
|
1281 boolean matched = true; |
|
1282 int nameoff = pos + CENHDR; |
|
1283 for (int i = 0; i < name.length; i++) { |
|
1284 if (name[i] != cen[nameoff++]) { |
|
1285 matched = false; |
|
1286 break; |
|
1287 } |
|
1288 } |
|
1289 if (matched) { |
|
1290 return pos; |
|
1291 } |
|
1292 } |
|
1293 } |
|
1294 idx = getEntryNext(idx); |
|
1295 } |
|
1296 /* If not addSlash, or slash is already there, we are done */ |
|
1297 if (!addSlash || name.length == 0 || name[name.length - 1] == '/') { |
|
1298 return -1; |
|
1299 } |
|
1300 /* Add slash and try once more */ |
|
1301 name = Arrays.copyOf(name, name.length + 1); |
|
1302 name[name.length - 1] = '/'; |
|
1303 hsh = hash_append(hsh, (byte)'/'); |
|
1304 //idx = table[hsh % tablelen]; |
|
1305 idx = table[(hsh & 0x7fffffff) % tablelen]; |
|
1306 addSlash = false; |
|
1307 } |
|
1308 } |
|
1309 |
|
1310 /** |
|
1311 * Returns true if the bytes represent a non-directory name |
|
1312 * beginning with "META-INF/", disregarding ASCII case. |
|
1313 */ |
|
1314 private static boolean isMetaName(byte[] name, int off, int len) { |
|
1315 // Use the "oldest ASCII trick in the book" |
|
1316 return len > 9 // "META-INF/".length() |
|
1317 && name[off + len - 1] != '/' // non-directory |
|
1318 && (name[off++] | 0x20) == 'm' |
|
1319 && (name[off++] | 0x20) == 'e' |
|
1320 && (name[off++] | 0x20) == 't' |
|
1321 && (name[off++] | 0x20) == 'a' |
|
1322 && (name[off++] ) == '-' |
|
1323 && (name[off++] | 0x20) == 'i' |
|
1324 && (name[off++] | 0x20) == 'n' |
|
1325 && (name[off++] | 0x20) == 'f' |
|
1326 && (name[off] ) == '/'; |
|
1327 } |
|
1328 |
|
1329 /** |
|
1330 * Returns the number of CEN headers in a central directory. |
|
1331 * Will not throw, even if the zip file is corrupt. |
|
1332 * |
|
1333 * @param cen copy of the bytes in a zip file's central directory |
|
1334 * @param size number of bytes in central directory |
|
1335 */ |
|
1336 private static int countCENHeaders(byte[] cen, int size) { |
|
1337 int count = 0; |
|
1338 for (int p = 0; |
|
1339 p + CENHDR <= size; |
|
1340 p += CENHDR + CENNAM(cen, p) + CENEXT(cen, p) + CENCOM(cen, p)) |
|
1341 count++; |
|
1342 return count; |
|
1343 } |
|
1344 } |
|
1345 } |