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