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