001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018package org.apache.commons.compress.archivers.zip; 019 020import java.io.BufferedInputStream; 021import java.io.ByteArrayInputStream; 022import java.io.Closeable; 023import java.io.EOFException; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.SequenceInputStream; 028import java.nio.ByteBuffer; 029import java.nio.channels.FileChannel; 030import java.nio.channels.SeekableByteChannel; 031import java.nio.file.Files; 032import java.nio.file.StandardOpenOption; 033import java.util.Arrays; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.Enumeration; 037import java.util.EnumSet; 038import java.util.HashMap; 039import java.util.LinkedList; 040import java.util.List; 041import java.util.Map; 042import java.util.zip.Inflater; 043import java.util.zip.ZipException; 044 045import org.apache.commons.compress.archivers.EntryStreamOffsets; 046import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 047import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream; 048import org.apache.commons.compress.utils.CountingInputStream; 049import org.apache.commons.compress.utils.IOUtils; 050import org.apache.commons.compress.utils.InputStreamStatistics; 051 052import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 053import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 054import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 055import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC; 056import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT; 057 058/** 059 * Replacement for <code>java.util.ZipFile</code>. 060 * 061 * <p>This class adds support for file name encodings other than UTF-8 062 * (which is required to work on ZIP files created by native zip tools 063 * and is able to skip a preamble like the one found in self 064 * extracting archives. Furthermore it returns instances of 065 * <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code> 066 * instead of <code>java.util.zip.ZipEntry</code>.</p> 067 * 068 * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would 069 * have to reimplement all methods anyway. Like 070 * <code>java.util.ZipFile</code>, it uses SeekableByteChannel under the 071 * covers and supports compressed and uncompressed entries. As of 072 * Apache Commons Compress 1.3 it also transparently supports Zip64 073 * extensions and thus individual entries and archives larger than 4 074 * GB or with more than 65536 entries.</p> 075 * 076 * <p>The method signatures mimic the ones of 077 * <code>java.util.zip.ZipFile</code>, with a couple of exceptions: 078 * 079 * <ul> 080 * <li>There is no getName method.</li> 081 * <li>entries has been renamed to getEntries.</li> 082 * <li>getEntries and getEntry return 083 * <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code> 084 * instances.</li> 085 * <li>close is allowed to throw IOException.</li> 086 * </ul> 087 * 088 */ 089public class ZipFile implements Closeable { 090 private static final int HASH_SIZE = 509; 091 static final int NIBLET_MASK = 0x0f; 092 static final int BYTE_SHIFT = 8; 093 private static final int POS_0 = 0; 094 private static final int POS_1 = 1; 095 private static final int POS_2 = 2; 096 private static final int POS_3 = 3; 097 private static final byte[] ONE_ZERO_BYTE = new byte[1]; 098 099 /** 100 * List of entries in the order they appear inside the central 101 * directory. 102 */ 103 private final List<ZipArchiveEntry> entries = 104 new LinkedList<>(); 105 106 /** 107 * Maps String to list of ZipArchiveEntrys, name -> actual entries. 108 */ 109 private final Map<String, LinkedList<ZipArchiveEntry>> nameMap = 110 new HashMap<>(HASH_SIZE); 111 112 /** 113 * The encoding to use for file names and the file comment. 114 * 115 * <p>For a list of possible values see <a 116 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. 117 * Defaults to UTF-8.</p> 118 */ 119 private final String encoding; 120 121 /** 122 * The zip encoding to use for file names and the file comment. 123 */ 124 private final ZipEncoding zipEncoding; 125 126 /** 127 * File name of actual source. 128 */ 129 private final String archiveName; 130 131 /** 132 * The actual data source. 133 */ 134 private final SeekableByteChannel archive; 135 136 /** 137 * Whether to look for and use Unicode extra fields. 138 */ 139 private final boolean useUnicodeExtraFields; 140 141 /** 142 * Whether the file is closed. 143 */ 144 private volatile boolean closed = true; 145 146 /** 147 * Whether the zip archive is a splite zip archive 148 */ 149 private final boolean isSplitZipArchive; 150 151 // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection) 152 private final byte[] dwordBuf = new byte[DWORD]; 153 private final byte[] wordBuf = new byte[WORD]; 154 private final byte[] cfhBuf = new byte[CFH_LEN]; 155 private final byte[] shortBuf = new byte[SHORT]; 156 private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf); 157 private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf); 158 private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf); 159 private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf); 160 161 /** 162 * Opens the given file for reading, assuming "UTF8" for file names. 163 * 164 * @param f the archive. 165 * 166 * @throws IOException if an error occurs while reading the file. 167 */ 168 public ZipFile(final File f) throws IOException { 169 this(f, ZipEncodingHelper.UTF8); 170 } 171 172 /** 173 * Opens the given file for reading, assuming "UTF8". 174 * 175 * @param name name of the archive. 176 * 177 * @throws IOException if an error occurs while reading the file. 178 */ 179 public ZipFile(final String name) throws IOException { 180 this(new File(name), ZipEncodingHelper.UTF8); 181 } 182 183 /** 184 * Opens the given file for reading, assuming the specified 185 * encoding for file names, scanning unicode extra fields. 186 * 187 * @param name name of the archive. 188 * @param encoding the encoding to use for file names, use null 189 * for the platform's default encoding 190 * 191 * @throws IOException if an error occurs while reading the file. 192 */ 193 public ZipFile(final String name, final String encoding) throws IOException { 194 this(new File(name), encoding, true); 195 } 196 197 /** 198 * Opens the given file for reading, assuming the specified 199 * encoding for file names and scanning for unicode extra fields. 200 * 201 * @param f the archive. 202 * @param encoding the encoding to use for file names, use null 203 * for the platform's default encoding 204 * 205 * @throws IOException if an error occurs while reading the file. 206 */ 207 public ZipFile(final File f, final String encoding) throws IOException { 208 this(f, encoding, true); 209 } 210 211 /** 212 * Opens the given file for reading, assuming the specified 213 * encoding for file names. 214 * 215 * @param f the archive. 216 * @param encoding the encoding to use for file names, use null 217 * for the platform's default encoding 218 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 219 * Extra Fields (if present) to set the file names. 220 * 221 * @throws IOException if an error occurs while reading the file. 222 */ 223 public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields) 224 throws IOException { 225 this(f, encoding, useUnicodeExtraFields, false); 226 } 227 228 /** 229 * Opens the given file for reading, assuming the specified 230 * encoding for file names. 231 * 232 * 233 * <p>By default the central directory record and all local file headers of the archive will be read immediately 234 * which may take a considerable amount of time when the archive is big. The {@code ignoreLocalFileHeader} parameter 235 * can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header 236 * may contain information not present inside of the central directory which will not be available when the argument 237 * is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code 238 * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively. Also 239 * {@link #getRawInputStream} is always going to return {@code null} if {@code ignoreLocalFileHeader} is {@code 240 * true}.</p> 241 * 242 * @param f the archive. 243 * @param encoding the encoding to use for file names, use null 244 * for the platform's default encoding 245 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 246 * Extra Fields (if present) to set the file names. 247 * @param ignoreLocalFileHeader whether to ignore information 248 * stored inside the local file header (see the notes in this method's javadoc) 249 * 250 * @throws IOException if an error occurs while reading the file. 251 * @since 1.19 252 */ 253 public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields, 254 final boolean ignoreLocalFileHeader) 255 throws IOException { 256 this(Files.newByteChannel(f.toPath(), EnumSet.of(StandardOpenOption.READ)), 257 f.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader); 258 } 259 260 /** 261 * Opens the given channel for reading, assuming "UTF8" for file names. 262 * 263 * <p>{@link 264 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 265 * allows you to read from an in-memory archive.</p> 266 * 267 * @param channel the archive. 268 * 269 * @throws IOException if an error occurs while reading the file. 270 * @since 1.13 271 */ 272 public ZipFile(final SeekableByteChannel channel) 273 throws IOException { 274 this(channel, "unknown archive", ZipEncodingHelper.UTF8, true); 275 } 276 277 /** 278 * Opens the given channel for reading, assuming the specified 279 * encoding for file names. 280 * 281 * <p>{@link 282 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 283 * allows you to read from an in-memory archive.</p> 284 * 285 * @param channel the archive. 286 * @param encoding the encoding to use for file names, use null 287 * for the platform's default encoding 288 * 289 * @throws IOException if an error occurs while reading the file. 290 * @since 1.13 291 */ 292 public ZipFile(final SeekableByteChannel channel, final String encoding) 293 throws IOException { 294 this(channel, "unknown archive", encoding, true); 295 } 296 297 /** 298 * Opens the given channel for reading, assuming the specified 299 * encoding for file names. 300 * 301 * <p>{@link 302 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 303 * allows you to read from an in-memory archive.</p> 304 * 305 * @param channel the archive. 306 * @param archiveName name of the archive, used for error messages only. 307 * @param encoding the encoding to use for file names, use null 308 * for the platform's default encoding 309 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 310 * Extra Fields (if present) to set the file names. 311 * 312 * @throws IOException if an error occurs while reading the file. 313 * @since 1.13 314 */ 315 public ZipFile(final SeekableByteChannel channel, final String archiveName, 316 final String encoding, final boolean useUnicodeExtraFields) 317 throws IOException { 318 this(channel, archiveName, encoding, useUnicodeExtraFields, false, false); 319 } 320 321 /** 322 * Opens the given channel for reading, assuming the specified 323 * encoding for file names. 324 * 325 * <p>{@link 326 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 327 * allows you to read from an in-memory archive.</p> 328 * 329 * <p>By default the central directory record and all local file headers of the archive will be read immediately 330 * which may take a considerable amount of time when the archive is big. The {@code ignoreLocalFileHeader} parameter 331 * can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header 332 * may contain information not present inside of the central directory which will not be available when the argument 333 * is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code 334 * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively. Also 335 * {@link #getRawInputStream} is always going to return {@code null} if {@code ignoreLocalFileHeader} is {@code 336 * true}.</p> 337 * 338 * @param channel the archive. 339 * @param archiveName name of the archive, used for error messages only. 340 * @param encoding the encoding to use for file names, use null 341 * for the platform's default encoding 342 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 343 * Extra Fields (if present) to set the file names. 344 * @param ignoreLocalFileHeader whether to ignore information 345 * stored inside the local file header (see the notes in this method's javadoc) 346 * 347 * @throws IOException if an error occurs while reading the file. 348 * @since 1.19 349 */ 350 public ZipFile(final SeekableByteChannel channel, final String archiveName, 351 final String encoding, final boolean useUnicodeExtraFields, 352 final boolean ignoreLocalFileHeader) 353 throws IOException { 354 this(channel, archiveName, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader); 355 } 356 357 private ZipFile(final SeekableByteChannel channel, final String archiveName, 358 final String encoding, final boolean useUnicodeExtraFields, 359 final boolean closeOnError, final boolean ignoreLocalFileHeader) 360 throws IOException { 361 isSplitZipArchive = (channel instanceof ZipSplitReadOnlySeekableByteChannel); 362 363 this.archiveName = archiveName; 364 this.encoding = encoding; 365 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 366 this.useUnicodeExtraFields = useUnicodeExtraFields; 367 archive = channel; 368 boolean success = false; 369 try { 370 final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag = 371 populateFromCentralDirectory(); 372 if (!ignoreLocalFileHeader) { 373 resolveLocalFileHeaderData(entriesWithoutUTF8Flag); 374 } 375 fillNameMap(); 376 success = true; 377 } finally { 378 closed = !success; 379 if (!success && closeOnError) { 380 IOUtils.closeQuietly(archive); 381 } 382 } 383 } 384 385 /** 386 * The encoding to use for file names and the file comment. 387 * 388 * @return null if using the platform's default character encoding. 389 */ 390 public String getEncoding() { 391 return encoding; 392 } 393 394 /** 395 * Closes the archive. 396 * @throws IOException if an error occurs closing the archive. 397 */ 398 @Override 399 public void close() throws IOException { 400 // this flag is only written here and read in finalize() which 401 // can never be run in parallel. 402 // no synchronization needed. 403 closed = true; 404 405 archive.close(); 406 } 407 408 /** 409 * close a zipfile quietly; throw no io fault, do nothing 410 * on a null parameter 411 * @param zipfile file to close, can be null 412 */ 413 public static void closeQuietly(final ZipFile zipfile) { 414 IOUtils.closeQuietly(zipfile); 415 } 416 417 /** 418 * Returns all entries. 419 * 420 * <p>Entries will be returned in the same order they appear 421 * within the archive's central directory.</p> 422 * 423 * @return all entries as {@link ZipArchiveEntry} instances 424 */ 425 public Enumeration<ZipArchiveEntry> getEntries() { 426 return Collections.enumeration(entries); 427 } 428 429 /** 430 * Returns all entries in physical order. 431 * 432 * <p>Entries will be returned in the same order their contents 433 * appear within the archive.</p> 434 * 435 * @return all entries as {@link ZipArchiveEntry} instances 436 * 437 * @since 1.1 438 */ 439 public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() { 440 final ZipArchiveEntry[] allEntries = entries.toArray(new ZipArchiveEntry[entries.size()]); 441 Arrays.sort(allEntries, offsetComparator); 442 return Collections.enumeration(Arrays.asList(allEntries)); 443 } 444 445 /** 446 * Returns a named entry - or {@code null} if no entry by 447 * that name exists. 448 * 449 * <p>If multiple entries with the same name exist the first entry 450 * in the archive's central directory by that name is 451 * returned.</p> 452 * 453 * @param name name of the entry. 454 * @return the ZipArchiveEntry corresponding to the given name - or 455 * {@code null} if not present. 456 */ 457 public ZipArchiveEntry getEntry(final String name) { 458 final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name); 459 return entriesOfThatName != null ? entriesOfThatName.getFirst() : null; 460 } 461 462 /** 463 * Returns all named entries in the same order they appear within 464 * the archive's central directory. 465 * 466 * @param name name of the entry. 467 * @return the Iterable<ZipArchiveEntry> corresponding to the 468 * given name 469 * @since 1.6 470 */ 471 public Iterable<ZipArchiveEntry> getEntries(final String name) { 472 final List<ZipArchiveEntry> entriesOfThatName = nameMap.get(name); 473 return entriesOfThatName != null ? entriesOfThatName 474 : Collections.<ZipArchiveEntry>emptyList(); 475 } 476 477 /** 478 * Returns all named entries in the same order their contents 479 * appear within the archive. 480 * 481 * @param name name of the entry. 482 * @return the Iterable<ZipArchiveEntry> corresponding to the 483 * given name 484 * @since 1.6 485 */ 486 public Iterable<ZipArchiveEntry> getEntriesInPhysicalOrder(final String name) { 487 ZipArchiveEntry[] entriesOfThatName = new ZipArchiveEntry[0]; 488 if (nameMap.containsKey(name)) { 489 entriesOfThatName = nameMap.get(name).toArray(entriesOfThatName); 490 Arrays.sort(entriesOfThatName, offsetComparator); 491 } 492 return Arrays.asList(entriesOfThatName); 493 } 494 495 /** 496 * Whether this class is able to read the given entry. 497 * 498 * <p>May return false if it is set up to use encryption or a 499 * compression method that hasn't been implemented yet.</p> 500 * @since 1.1 501 * @param ze the entry 502 * @return whether this class is able to read the given entry. 503 */ 504 public boolean canReadEntryData(final ZipArchiveEntry ze) { 505 return ZipUtil.canHandleEntryData(ze); 506 } 507 508 /** 509 * Expose the raw stream of the archive entry (compressed form). 510 * 511 * <p>This method does not relate to how/if we understand the payload in the 512 * stream, since we really only intend to move it on to somewhere else.</p> 513 * 514 * @param ze The entry to get the stream for 515 * @return The raw input stream containing (possibly) compressed data. 516 * @since 1.11 517 */ 518 public InputStream getRawInputStream(final ZipArchiveEntry ze) { 519 if (!(ze instanceof Entry)) { 520 return null; 521 } 522 final long start = ze.getDataOffset(); 523 if (start == EntryStreamOffsets.OFFSET_UNKNOWN) { 524 return null; 525 } 526 return createBoundedInputStream(start, ze.getCompressedSize()); 527 } 528 529 530 /** 531 * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream. 532 * Compression and all other attributes will be as in this file. 533 * <p>This method transfers entries based on the central directory of the zip file.</p> 534 * 535 * @param target The zipArchiveOutputStream to write the entries to 536 * @param predicate A predicate that selects which entries to write 537 * @throws IOException on error 538 */ 539 public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate) 540 throws IOException { 541 final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder(); 542 while (src.hasMoreElements()) { 543 final ZipArchiveEntry entry = src.nextElement(); 544 if (predicate.test( entry)) { 545 target.addRawArchiveEntry(entry, getRawInputStream(entry)); 546 } 547 } 548 } 549 550 /** 551 * Returns an InputStream for reading the contents of the given entry. 552 * 553 * @param ze the entry to get the stream for. 554 * @return a stream to read the entry from. The returned stream 555 * implements {@link InputStreamStatistics}. 556 * @throws IOException if unable to create an input stream from the zipentry 557 */ 558 public InputStream getInputStream(final ZipArchiveEntry ze) 559 throws IOException { 560 if (!(ze instanceof Entry)) { 561 return null; 562 } 563 // cast validity is checked just above 564 ZipUtil.checkRequestedFeatures(ze); 565 final long start = getDataOffset(ze); 566 567 // doesn't get closed if the method is not supported - which 568 // should never happen because of the checkRequestedFeatures 569 // call above 570 final InputStream is = 571 new BufferedInputStream(createBoundedInputStream(start, ze.getCompressedSize())); //NOSONAR 572 switch (ZipMethod.getMethodByCode(ze.getMethod())) { 573 case STORED: 574 return new StoredStatisticsStream(is); 575 case UNSHRINKING: 576 return new UnshrinkingInputStream(is); 577 case IMPLODING: 578 return new ExplodingInputStream(ze.getGeneralPurposeBit().getSlidingDictionarySize(), 579 ze.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), is); 580 case DEFLATED: 581 final Inflater inflater = new Inflater(true); 582 // Inflater with nowrap=true has this odd contract for a zero padding 583 // byte following the data stream; this used to be zlib's requirement 584 // and has been fixed a long time ago, but the contract persists so 585 // we comply. 586 // https://docs.oracle.com/javase/7/docs/api/java/util/zip/Inflater.html#Inflater(boolean) 587 return new InflaterInputStreamWithStatistics(new SequenceInputStream(is, new ByteArrayInputStream(ONE_ZERO_BYTE)), 588 inflater) { 589 @Override 590 public void close() throws IOException { 591 try { 592 super.close(); 593 } finally { 594 inflater.end(); 595 } 596 } 597 }; 598 case BZIP2: 599 return new BZip2CompressorInputStream(is); 600 case ENHANCED_DEFLATED: 601 return new Deflate64CompressorInputStream(is); 602 case AES_ENCRYPTED: 603 case EXPANDING_LEVEL_1: 604 case EXPANDING_LEVEL_2: 605 case EXPANDING_LEVEL_3: 606 case EXPANDING_LEVEL_4: 607 case JPEG: 608 case LZMA: 609 case PKWARE_IMPLODING: 610 case PPMD: 611 case TOKENIZATION: 612 case UNKNOWN: 613 case WAVPACK: 614 case XZ: 615 default: 616 throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(ze.getMethod()), ze); 617 } 618 } 619 620 /** 621 * <p> 622 * Convenience method to return the entry's content as a String if isUnixSymlink() 623 * returns true for it, otherwise returns null. 624 * </p> 625 * 626 * <p>This method assumes the symbolic link's file name uses the 627 * same encoding that as been specified for this ZipFile.</p> 628 * 629 * @param entry ZipArchiveEntry object that represents the symbolic link 630 * @return entry's content as a String 631 * @throws IOException problem with content's input stream 632 * @since 1.5 633 */ 634 public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException { 635 if (entry != null && entry.isUnixSymlink()) { 636 try (InputStream in = getInputStream(entry)) { 637 return zipEncoding.decode(IOUtils.toByteArray(in)); 638 } 639 } 640 return null; 641 } 642 643 /** 644 * Ensures that the close method of this zipfile is called when 645 * there are no more references to it. 646 * @see #close() 647 */ 648 @Override 649 protected void finalize() throws Throwable { 650 try { 651 if (!closed) { 652 System.err.println("Cleaning up unclosed ZipFile for archive " 653 + archiveName); 654 close(); 655 } 656 } finally { 657 super.finalize(); 658 } 659 } 660 661 /** 662 * Length of a "central directory" entry structure without file 663 * name, extra fields or comment. 664 */ 665 private static final int CFH_LEN = 666 /* version made by */ SHORT 667 /* version needed to extract */ + SHORT 668 /* general purpose bit flag */ + SHORT 669 /* compression method */ + SHORT 670 /* last mod file time */ + SHORT 671 /* last mod file date */ + SHORT 672 /* crc-32 */ + WORD 673 /* compressed size */ + WORD 674 /* uncompressed size */ + WORD 675 /* file name length */ + SHORT 676 /* extra field length */ + SHORT 677 /* file comment length */ + SHORT 678 /* disk number start */ + SHORT 679 /* internal file attributes */ + SHORT 680 /* external file attributes */ + WORD 681 /* relative offset of local header */ + WORD; 682 683 private static final long CFH_SIG = 684 ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG); 685 686 /** 687 * Reads the central directory of the given archive and populates 688 * the internal tables with ZipArchiveEntry instances. 689 * 690 * <p>The ZipArchiveEntrys will know all data that can be obtained from 691 * the central directory alone, but not the data that requires the 692 * local file header or additional data to be read.</p> 693 * 694 * @return a map of zipentries that didn't have the language 695 * encoding flag set when read. 696 */ 697 private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory() 698 throws IOException { 699 final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag = 700 new HashMap<>(); 701 702 positionAtCentralDirectory(); 703 704 wordBbuf.rewind(); 705 IOUtils.readFully(archive, wordBbuf); 706 long sig = ZipLong.getValue(wordBuf); 707 708 if (sig != CFH_SIG && startsWithLocalFileHeader()) { 709 throw new IOException("Central directory is empty, can't expand" 710 + " corrupt archive."); 711 } 712 713 while (sig == CFH_SIG) { 714 readCentralDirectoryEntry(noUTF8Flag); 715 wordBbuf.rewind(); 716 IOUtils.readFully(archive, wordBbuf); 717 sig = ZipLong.getValue(wordBuf); 718 } 719 return noUTF8Flag; 720 } 721 722 /** 723 * Reads an individual entry of the central directory, creats an 724 * ZipArchiveEntry from it and adds it to the global maps. 725 * 726 * @param noUTF8Flag map used to collect entries that don't have 727 * their UTF-8 flag set and whose name will be set by data read 728 * from the local file header later. The current entry may be 729 * added to this map. 730 */ 731 private void 732 readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag) 733 throws IOException { 734 cfhBbuf.rewind(); 735 IOUtils.readFully(archive, cfhBbuf); 736 int off = 0; 737 final Entry ze = new Entry(); 738 739 final int versionMadeBy = ZipShort.getValue(cfhBuf, off); 740 off += SHORT; 741 ze.setVersionMadeBy(versionMadeBy); 742 ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); 743 744 ze.setVersionRequired(ZipShort.getValue(cfhBuf, off)); 745 off += SHORT; // version required 746 747 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off); 748 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); 749 final ZipEncoding entryEncoding = 750 hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; 751 if (hasUTF8Flag) { 752 ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG); 753 } 754 ze.setGeneralPurposeBit(gpFlag); 755 ze.setRawFlag(ZipShort.getValue(cfhBuf, off)); 756 757 off += SHORT; 758 759 //noinspection MagicConstant 760 ze.setMethod(ZipShort.getValue(cfhBuf, off)); 761 off += SHORT; 762 763 final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off)); 764 ze.setTime(time); 765 off += WORD; 766 767 ze.setCrc(ZipLong.getValue(cfhBuf, off)); 768 off += WORD; 769 770 ze.setCompressedSize(ZipLong.getValue(cfhBuf, off)); 771 off += WORD; 772 773 ze.setSize(ZipLong.getValue(cfhBuf, off)); 774 off += WORD; 775 776 final int fileNameLen = ZipShort.getValue(cfhBuf, off); 777 off += SHORT; 778 779 final int extraLen = ZipShort.getValue(cfhBuf, off); 780 off += SHORT; 781 782 final int commentLen = ZipShort.getValue(cfhBuf, off); 783 off += SHORT; 784 785 ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off)); 786 off += SHORT; 787 788 ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off)); 789 off += SHORT; 790 791 ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off)); 792 off += WORD; 793 794 final byte[] fileName = new byte[fileNameLen]; 795 IOUtils.readFully(archive, ByteBuffer.wrap(fileName)); 796 ze.setName(entryEncoding.decode(fileName), fileName); 797 798 // LFH offset, 799 ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off)); 800 // data offset will be filled later 801 entries.add(ze); 802 803 final byte[] cdExtraData = new byte[extraLen]; 804 IOUtils.readFully(archive, ByteBuffer.wrap(cdExtraData)); 805 ze.setCentralDirectoryExtra(cdExtraData); 806 807 setSizesAndOffsetFromZip64Extra(ze); 808 809 final byte[] comment = new byte[commentLen]; 810 IOUtils.readFully(archive, ByteBuffer.wrap(comment)); 811 ze.setComment(entryEncoding.decode(comment)); 812 813 if (!hasUTF8Flag && useUnicodeExtraFields) { 814 noUTF8Flag.put(ze, new NameAndComment(fileName, comment)); 815 } 816 817 ze.setStreamContiguous(true); 818 } 819 820 /** 821 * If the entry holds a Zip64 extended information extra field, 822 * read sizes from there if the entry's sizes are set to 823 * 0xFFFFFFFFF, do the same for the offset of the local file 824 * header. 825 * 826 * <p>Ensures the Zip64 extra either knows both compressed and 827 * uncompressed size or neither of both as the internal logic in 828 * ExtraFieldUtils forces the field to create local header data 829 * even if they are never used - and here a field with only one 830 * size would be invalid.</p> 831 */ 832 private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze) 833 throws IOException { 834 final Zip64ExtendedInformationExtraField z64 = 835 (Zip64ExtendedInformationExtraField) 836 ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); 837 if (z64 != null) { 838 final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC; 839 final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC; 840 final boolean hasRelativeHeaderOffset = 841 ze.getLocalHeaderOffset() == ZIP64_MAGIC; 842 final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT; 843 z64.reparseCentralDirectoryData(hasUncompressedSize, 844 hasCompressedSize, 845 hasRelativeHeaderOffset, 846 hasDiskStart); 847 848 if (hasUncompressedSize) { 849 ze.setSize(z64.getSize().getLongValue()); 850 } else if (hasCompressedSize) { 851 z64.setSize(new ZipEightByteInteger(ze.getSize())); 852 } 853 854 if (hasCompressedSize) { 855 ze.setCompressedSize(z64.getCompressedSize().getLongValue()); 856 } else if (hasUncompressedSize) { 857 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); 858 } 859 860 if (hasRelativeHeaderOffset) { 861 ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue()); 862 } 863 864 if (hasDiskStart) { 865 ze.setDiskNumberStart(z64.getDiskStartNumber().getValue()); 866 } 867 } 868 } 869 870 /** 871 * Length of the "End of central directory record" - which is 872 * supposed to be the last structure of the archive - without file 873 * comment. 874 */ 875 static final int MIN_EOCD_SIZE = 876 /* end of central dir signature */ WORD 877 /* number of this disk */ + SHORT 878 /* number of the disk with the */ 879 /* start of the central directory */ + SHORT 880 /* total number of entries in */ 881 /* the central dir on this disk */ + SHORT 882 /* total number of entries in */ 883 /* the central dir */ + SHORT 884 /* size of the central directory */ + WORD 885 /* offset of start of central */ 886 /* directory with respect to */ 887 /* the starting disk number */ + WORD 888 /* zipfile comment length */ + SHORT; 889 890 /** 891 * Maximum length of the "End of central directory record" with a 892 * file comment. 893 */ 894 private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE 895 /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT; 896 897 /** 898 * Offset of the field that holds the location of the first 899 * central directory entry inside the "End of central directory 900 * record" relative to the start of the "End of central directory 901 * record". 902 */ 903 private static final int CFD_LOCATOR_OFFSET = 904 /* end of central dir signature */ WORD 905 /* number of this disk */ + SHORT 906 /* number of the disk with the */ 907 /* start of the central directory */ + SHORT 908 /* total number of entries in */ 909 /* the central dir on this disk */ + SHORT 910 /* total number of entries in */ 911 /* the central dir */ + SHORT 912 /* size of the central directory */ + WORD; 913 914 /** 915 * Offset of the field that holds the disk number of the first 916 * central directory entry inside the "End of central directory 917 * record" relative to the start of the "End of central directory 918 * record". 919 */ 920 private static final int CFD_DISK_OFFSET = 921 /* end of central dir signature */ WORD 922 /* number of this disk */ + SHORT; 923 924 /** 925 * Offset of the field that holds the location of the first 926 * central directory entry inside the "End of central directory 927 * record" relative to the "number of the disk with the start 928 * of the central directory". 929 */ 930 private static final int CFD_LOCATOR_RELATIVE_OFFSET = 931 /* total number of entries in */ 932 /* the central dir on this disk */ + SHORT 933 /* total number of entries in */ 934 /* the central dir */ + SHORT 935 /* size of the central directory */ + WORD; 936 937 /** 938 * Length of the "Zip64 end of central directory locator" - which 939 * should be right in front of the "end of central directory 940 * record" if one is present at all. 941 */ 942 private static final int ZIP64_EOCDL_LENGTH = 943 /* zip64 end of central dir locator sig */ WORD 944 /* number of the disk with the start */ 945 /* start of the zip64 end of */ 946 /* central directory */ + WORD 947 /* relative offset of the zip64 */ 948 /* end of central directory record */ + DWORD 949 /* total number of disks */ + WORD; 950 951 /** 952 * Offset of the field that holds the location of the "Zip64 end 953 * of central directory record" inside the "Zip64 end of central 954 * directory locator" relative to the start of the "Zip64 end of 955 * central directory locator". 956 */ 957 private static final int ZIP64_EOCDL_LOCATOR_OFFSET = 958 /* zip64 end of central dir locator sig */ WORD 959 /* number of the disk with the start */ 960 /* start of the zip64 end of */ 961 /* central directory */ + WORD; 962 963 /** 964 * Offset of the field that holds the location of the first 965 * central directory entry inside the "Zip64 end of central 966 * directory record" relative to the start of the "Zip64 end of 967 * central directory record". 968 */ 969 private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET = 970 /* zip64 end of central dir */ 971 /* signature */ WORD 972 /* size of zip64 end of central */ 973 /* directory record */ + DWORD 974 /* version made by */ + SHORT 975 /* version needed to extract */ + SHORT 976 /* number of this disk */ + WORD 977 /* number of the disk with the */ 978 /* start of the central directory */ + WORD 979 /* total number of entries in the */ 980 /* central directory on this disk */ + DWORD 981 /* total number of entries in the */ 982 /* central directory */ + DWORD 983 /* size of the central directory */ + DWORD; 984 985 /** 986 * Offset of the field that holds the disk number of the first 987 * central directory entry inside the "Zip64 end of central 988 * directory record" relative to the start of the "Zip64 end of 989 * central directory record". 990 */ 991 private static final int ZIP64_EOCD_CFD_DISK_OFFSET = 992 /* zip64 end of central dir */ 993 /* signature */ WORD 994 /* size of zip64 end of central */ 995 /* directory record */ + DWORD 996 /* version made by */ + SHORT 997 /* version needed to extract */ + SHORT 998 /* number of this disk */ + WORD; 999 1000 /** 1001 * Offset of the field that holds the location of the first 1002 * central directory entry inside the "Zip64 end of central 1003 * directory record" relative to the "number of the disk 1004 * with the start of the central directory". 1005 */ 1006 private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET = 1007 /* total number of entries in the */ 1008 /* central directory on this disk */ DWORD 1009 /* total number of entries in the */ 1010 /* central directory */ + DWORD 1011 /* size of the central directory */ + DWORD; 1012 1013 /** 1014 * Searches for either the "Zip64 end of central directory 1015 * locator" or the "End of central dir record", parses 1016 * it and positions the stream at the first central directory 1017 * record. 1018 */ 1019 private void positionAtCentralDirectory() 1020 throws IOException { 1021 positionAtEndOfCentralDirectoryRecord(); 1022 boolean found = false; 1023 final boolean searchedForZip64EOCD = 1024 archive.position() > ZIP64_EOCDL_LENGTH; 1025 if (searchedForZip64EOCD) { 1026 archive.position(archive.position() - ZIP64_EOCDL_LENGTH); 1027 wordBbuf.rewind(); 1028 IOUtils.readFully(archive, wordBbuf); 1029 found = Arrays.equals(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG, 1030 wordBuf); 1031 } 1032 if (!found) { 1033 // not a ZIP64 archive 1034 if (searchedForZip64EOCD) { 1035 skipBytes(ZIP64_EOCDL_LENGTH - WORD); 1036 } 1037 positionAtCentralDirectory32(); 1038 } else { 1039 positionAtCentralDirectory64(); 1040 } 1041 } 1042 1043 /** 1044 * Parses the "Zip64 end of central directory locator", 1045 * finds the "Zip64 end of central directory record" using the 1046 * parsed information, parses that and positions the stream at the 1047 * first central directory record. 1048 * 1049 * Expects stream to be positioned right behind the "Zip64 1050 * end of central directory locator"'s signature. 1051 */ 1052 private void positionAtCentralDirectory64() 1053 throws IOException { 1054 if (isSplitZipArchive) { 1055 wordBbuf.rewind(); 1056 IOUtils.readFully(archive, wordBbuf); 1057 final long diskNumberOfEOCD = ZipLong.getValue(wordBuf); 1058 1059 dwordBbuf.rewind(); 1060 IOUtils.readFully(archive, dwordBbuf); 1061 final long relativeOffsetOfEOCD = ZipEightByteInteger.getLongValue(dwordBuf); 1062 ((ZipSplitReadOnlySeekableByteChannel) archive) 1063 .position(diskNumberOfEOCD, relativeOffsetOfEOCD); 1064 } else { 1065 skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET 1066 - WORD /* signature has already been read */); 1067 dwordBbuf.rewind(); 1068 IOUtils.readFully(archive, dwordBbuf); 1069 archive.position(ZipEightByteInteger.getLongValue(dwordBuf)); 1070 } 1071 1072 wordBbuf.rewind(); 1073 IOUtils.readFully(archive, wordBbuf); 1074 if (!Arrays.equals(wordBuf, ZipArchiveOutputStream.ZIP64_EOCD_SIG)) { 1075 throw new ZipException("Archive's ZIP64 end of central " 1076 + "directory locator is corrupt."); 1077 } 1078 1079 if (isSplitZipArchive) { 1080 skipBytes(ZIP64_EOCD_CFD_DISK_OFFSET 1081 - WORD /* signature has already been read */); 1082 wordBbuf.rewind(); 1083 IOUtils.readFully(archive, wordBbuf); 1084 final long diskNumberOfCFD = ZipLong.getValue(wordBuf); 1085 1086 skipBytes(ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET); 1087 1088 dwordBbuf.rewind(); 1089 IOUtils.readFully(archive, dwordBbuf); 1090 final long relativeOffsetOfCFD = ZipEightByteInteger.getLongValue(dwordBuf); 1091 ((ZipSplitReadOnlySeekableByteChannel) archive) 1092 .position(diskNumberOfCFD, relativeOffsetOfCFD); 1093 } else { 1094 skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET 1095 - WORD /* signature has already been read */); 1096 dwordBbuf.rewind(); 1097 IOUtils.readFully(archive, dwordBbuf); 1098 archive.position(ZipEightByteInteger.getLongValue(dwordBuf)); 1099 } 1100 } 1101 1102 /** 1103 * Parses the "End of central dir record" and positions 1104 * the stream at the first central directory record. 1105 * 1106 * Expects stream to be positioned at the beginning of the 1107 * "End of central dir record". 1108 */ 1109 private void positionAtCentralDirectory32() 1110 throws IOException { 1111 if (isSplitZipArchive) { 1112 skipBytes(CFD_DISK_OFFSET); 1113 shortBbuf.rewind(); 1114 IOUtils.readFully(archive, shortBbuf); 1115 final int diskNumberOfCFD = ZipShort.getValue(shortBuf); 1116 1117 skipBytes(CFD_LOCATOR_RELATIVE_OFFSET); 1118 1119 wordBbuf.rewind(); 1120 IOUtils.readFully(archive, wordBbuf); 1121 final long relativeOffsetOfCFD = ZipLong.getValue(wordBuf); 1122 ((ZipSplitReadOnlySeekableByteChannel) archive) 1123 .position(diskNumberOfCFD, relativeOffsetOfCFD); 1124 } else { 1125 skipBytes(CFD_LOCATOR_OFFSET); 1126 wordBbuf.rewind(); 1127 IOUtils.readFully(archive, wordBbuf); 1128 archive.position(ZipLong.getValue(wordBuf)); 1129 } 1130 } 1131 1132 /** 1133 * Searches for the and positions the stream at the start of the 1134 * "End of central dir record". 1135 */ 1136 private void positionAtEndOfCentralDirectoryRecord() 1137 throws IOException { 1138 final boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE, 1139 ZipArchiveOutputStream.EOCD_SIG); 1140 if (!found) { 1141 throw new ZipException("Archive is not a ZIP archive"); 1142 } 1143 } 1144 1145 /** 1146 * Searches the archive backwards from minDistance to maxDistance 1147 * for the given signature, positions the RandomaccessFile right 1148 * at the signature if it has been found. 1149 */ 1150 private boolean tryToLocateSignature(final long minDistanceFromEnd, 1151 final long maxDistanceFromEnd, 1152 final byte[] sig) throws IOException { 1153 boolean found = false; 1154 long off = archive.size() - minDistanceFromEnd; 1155 final long stopSearching = 1156 Math.max(0L, archive.size() - maxDistanceFromEnd); 1157 if (off >= 0) { 1158 for (; off >= stopSearching; off--) { 1159 archive.position(off); 1160 try { 1161 wordBbuf.rewind(); 1162 IOUtils.readFully(archive, wordBbuf); 1163 wordBbuf.flip(); 1164 } catch (EOFException ex) { // NOSONAR 1165 break; 1166 } 1167 int curr = wordBbuf.get(); 1168 if (curr == sig[POS_0]) { 1169 curr = wordBbuf.get(); 1170 if (curr == sig[POS_1]) { 1171 curr = wordBbuf.get(); 1172 if (curr == sig[POS_2]) { 1173 curr = wordBbuf.get(); 1174 if (curr == sig[POS_3]) { 1175 found = true; 1176 break; 1177 } 1178 } 1179 } 1180 } 1181 } 1182 } 1183 if (found) { 1184 archive.position(off); 1185 } 1186 return found; 1187 } 1188 1189 /** 1190 * Skips the given number of bytes or throws an EOFException if 1191 * skipping failed. 1192 */ 1193 private void skipBytes(final int count) throws IOException { 1194 long currentPosition = archive.position(); 1195 long newPosition = currentPosition + count; 1196 if (newPosition > archive.size()) { 1197 throw new EOFException(); 1198 } 1199 archive.position(newPosition); 1200 } 1201 1202 /** 1203 * Number of bytes in local file header up to the "length of 1204 * file name" entry. 1205 */ 1206 private static final long LFH_OFFSET_FOR_FILENAME_LENGTH = 1207 /* local file header signature */ WORD 1208 /* version needed to extract */ + SHORT 1209 /* general purpose bit flag */ + SHORT 1210 /* compression method */ + SHORT 1211 /* last mod file time */ + SHORT 1212 /* last mod file date */ + SHORT 1213 /* crc-32 */ + WORD 1214 /* compressed size */ + WORD 1215 /* uncompressed size */ + (long) WORD; 1216 1217 /** 1218 * Walks through all recorded entries and adds the data available 1219 * from the local file header. 1220 * 1221 * <p>Also records the offsets for the data to read from the 1222 * entries.</p> 1223 */ 1224 private void resolveLocalFileHeaderData(final Map<ZipArchiveEntry, NameAndComment> 1225 entriesWithoutUTF8Flag) 1226 throws IOException { 1227 for (final ZipArchiveEntry zipArchiveEntry : entries) { 1228 // entries is filled in populateFromCentralDirectory and 1229 // never modified 1230 final Entry ze = (Entry) zipArchiveEntry; 1231 int[] lens = setDataOffset(ze); 1232 final int fileNameLen = lens[0]; 1233 final int extraFieldLen = lens[1]; 1234 skipBytes(fileNameLen); 1235 final byte[] localExtraData = new byte[extraFieldLen]; 1236 IOUtils.readFully(archive, ByteBuffer.wrap(localExtraData)); 1237 ze.setExtra(localExtraData); 1238 1239 if (entriesWithoutUTF8Flag.containsKey(ze)) { 1240 final NameAndComment nc = entriesWithoutUTF8Flag.get(ze); 1241 ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, 1242 nc.comment); 1243 } 1244 } 1245 } 1246 1247 private void fillNameMap() { 1248 for (final ZipArchiveEntry ze : entries) { 1249 // entries is filled in populateFromCentralDirectory and 1250 // never modified 1251 final String name = ze.getName(); 1252 LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name); 1253 if (entriesOfThatName == null) { 1254 entriesOfThatName = new LinkedList<>(); 1255 nameMap.put(name, entriesOfThatName); 1256 } 1257 entriesOfThatName.addLast(ze); 1258 } 1259 } 1260 1261 private int[] setDataOffset(ZipArchiveEntry ze) throws IOException { 1262 long offset = ze.getLocalHeaderOffset(); 1263 if (isSplitZipArchive) { 1264 ((ZipSplitReadOnlySeekableByteChannel) archive) 1265 .position(ze.getDiskNumberStart(), offset + LFH_OFFSET_FOR_FILENAME_LENGTH); 1266 // the offset should be updated to the global offset 1267 offset = archive.position() - LFH_OFFSET_FOR_FILENAME_LENGTH; 1268 } else { 1269 archive.position(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); 1270 } 1271 wordBbuf.rewind(); 1272 IOUtils.readFully(archive, wordBbuf); 1273 wordBbuf.flip(); 1274 wordBbuf.get(shortBuf); 1275 final int fileNameLen = ZipShort.getValue(shortBuf); 1276 wordBbuf.get(shortBuf); 1277 final int extraFieldLen = ZipShort.getValue(shortBuf); 1278 ze.setDataOffset(offset + LFH_OFFSET_FOR_FILENAME_LENGTH 1279 + SHORT + SHORT + fileNameLen + extraFieldLen); 1280 return new int[] { fileNameLen, extraFieldLen }; 1281 } 1282 1283 private long getDataOffset(ZipArchiveEntry ze) throws IOException { 1284 long s = ze.getDataOffset(); 1285 if (s == EntryStreamOffsets.OFFSET_UNKNOWN) { 1286 setDataOffset(ze); 1287 return ze.getDataOffset(); 1288 } 1289 return s; 1290 } 1291 1292 /** 1293 * Checks whether the archive starts with a LFH. If it doesn't, 1294 * it may be an empty archive. 1295 */ 1296 private boolean startsWithLocalFileHeader() throws IOException { 1297 archive.position(0); 1298 wordBbuf.rewind(); 1299 IOUtils.readFully(archive, wordBbuf); 1300 return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG); 1301 } 1302 1303 /** 1304 * Creates new BoundedInputStream, according to implementation of 1305 * underlying archive channel. 1306 */ 1307 private BoundedInputStream createBoundedInputStream(long start, long remaining) { 1308 return archive instanceof FileChannel ? 1309 new BoundedFileChannelInputStream(start, remaining) : 1310 new BoundedInputStream(start, remaining); 1311 } 1312 1313 /** 1314 * InputStream that delegates requests to the underlying 1315 * SeekableByteChannel, making sure that only bytes from a certain 1316 * range can be read. 1317 */ 1318 private class BoundedInputStream extends InputStream { 1319 private ByteBuffer singleByteBuffer; 1320 private final long end; 1321 private long loc; 1322 1323 BoundedInputStream(final long start, final long remaining) { 1324 this.end = start+remaining; 1325 if (this.end < start) { 1326 // check for potential vulnerability due to overflow 1327 throw new IllegalArgumentException("Invalid length of stream at offset="+start+", length="+remaining); 1328 } 1329 loc = start; 1330 } 1331 1332 @Override 1333 public synchronized int read() throws IOException { 1334 if (loc >= end) { 1335 return -1; 1336 } 1337 if (singleByteBuffer == null) { 1338 singleByteBuffer = ByteBuffer.allocate(1); 1339 } 1340 else { 1341 singleByteBuffer.rewind(); 1342 } 1343 int read = read(loc, singleByteBuffer); 1344 if (read < 0) { 1345 return read; 1346 } 1347 loc++; 1348 return singleByteBuffer.get() & 0xff; 1349 } 1350 1351 @Override 1352 public synchronized int read(final byte[] b, final int off, int len) throws IOException { 1353 if (len <= 0) { 1354 return 0; 1355 } 1356 1357 if (len > end-loc) { 1358 if (loc >= end) { 1359 return -1; 1360 } 1361 len = (int)(end-loc); 1362 } 1363 1364 ByteBuffer buf; 1365 buf = ByteBuffer.wrap(b, off, len); 1366 int ret = read(loc, buf); 1367 if (ret > 0) { 1368 loc += ret; 1369 return ret; 1370 } 1371 return ret; 1372 } 1373 1374 protected int read(long pos, ByteBuffer buf) throws IOException { 1375 int read; 1376 synchronized (archive) { 1377 archive.position(pos); 1378 read = archive.read(buf); 1379 } 1380 buf.flip(); 1381 return read; 1382 } 1383 } 1384 1385 /** 1386 * Lock-free implementation of BoundedInputStream. The 1387 * implementation uses positioned reads on the underlying archive 1388 * file channel and therefore performs significantly faster in 1389 * concurrent environment. 1390 */ 1391 private class BoundedFileChannelInputStream extends BoundedInputStream { 1392 private final FileChannel archive; 1393 1394 BoundedFileChannelInputStream(final long start, final long remaining) { 1395 super(start, remaining); 1396 archive = (FileChannel)ZipFile.this.archive; 1397 } 1398 1399 @Override 1400 protected int read(long pos, ByteBuffer buf) throws IOException { 1401 int read = archive.read(buf, pos); 1402 buf.flip(); 1403 return read; 1404 } 1405 } 1406 1407 private static final class NameAndComment { 1408 private final byte[] name; 1409 private final byte[] comment; 1410 private NameAndComment(final byte[] name, final byte[] comment) { 1411 this.name = name; 1412 this.comment = comment; 1413 } 1414 } 1415 1416 /** 1417 * Compares two ZipArchiveEntries based on their offset within the archive. 1418 * 1419 * <p>Won't return any meaningful results if one of the entries 1420 * isn't part of the archive at all.</p> 1421 * 1422 * @since 1.1 1423 */ 1424 private final Comparator<ZipArchiveEntry> offsetComparator = 1425 new Comparator<ZipArchiveEntry>() { 1426 @Override 1427 public int compare(final ZipArchiveEntry e1, final ZipArchiveEntry e2) { 1428 if (e1 == e2) { 1429 return 0; 1430 } 1431 1432 final Entry ent1 = e1 instanceof Entry ? (Entry) e1 : null; 1433 final Entry ent2 = e2 instanceof Entry ? (Entry) e2 : null; 1434 if (ent1 == null) { 1435 return 1; 1436 } 1437 if (ent2 == null) { 1438 return -1; 1439 } 1440 1441 // disk number is prior to relative offset 1442 final long diskNumberStartVal = ent1.getDiskNumberStart() - ent2.getDiskNumberStart(); 1443 if (diskNumberStartVal != 0) { 1444 return diskNumberStartVal < 0 ? -1 : +1; 1445 } 1446 final long val = (ent1.getLocalHeaderOffset() 1447 - ent2.getLocalHeaderOffset()); 1448 return val == 0 ? 0 : val < 0 ? -1 : +1; 1449 } 1450 }; 1451 1452 /** 1453 * Extends ZipArchiveEntry to store the offset within the archive. 1454 */ 1455 private static class Entry extends ZipArchiveEntry { 1456 1457 Entry() { 1458 } 1459 1460 @Override 1461 public int hashCode() { 1462 return 3 * super.hashCode() 1463 + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32); 1464 } 1465 1466 @Override 1467 public boolean equals(final Object other) { 1468 if (super.equals(other)) { 1469 // super.equals would return false if other were not an Entry 1470 final Entry otherEntry = (Entry) other; 1471 return getLocalHeaderOffset() 1472 == otherEntry.getLocalHeaderOffset() 1473 && super.getDataOffset() 1474 == otherEntry.getDataOffset() 1475 && super.getDiskNumberStart() 1476 == otherEntry.getDiskNumberStart(); 1477 } 1478 return false; 1479 } 1480 } 1481 1482 private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics { 1483 StoredStatisticsStream(InputStream in) { 1484 super(in); 1485 } 1486 1487 @Override 1488 public long getCompressedCount() { 1489 return super.getBytesRead(); 1490 } 1491 1492 @Override 1493 public long getUncompressedCount() { 1494 return getCompressedCount(); 1495 } 1496 } 1497}