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