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