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