001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.archivers.zip; 020 021import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 022import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 023import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 024import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC; 025 026import java.io.ByteArrayInputStream; 027import java.io.ByteArrayOutputStream; 028import java.io.EOFException; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.PushbackInputStream; 032import java.math.BigInteger; 033import java.nio.ByteBuffer; 034import java.nio.charset.StandardCharsets; 035import java.util.Arrays; 036import java.util.Objects; 037import java.util.function.Function; 038import java.util.zip.CRC32; 039import java.util.zip.DataFormatException; 040import java.util.zip.Inflater; 041import java.util.zip.ZipEntry; 042import java.util.zip.ZipException; 043 044import org.apache.commons.compress.archivers.ArchiveEntry; 045import org.apache.commons.compress.archivers.ArchiveInputStream; 046import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 047import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream; 048import org.apache.commons.compress.utils.ArchiveUtils; 049import org.apache.commons.compress.utils.IOUtils; 050import org.apache.commons.compress.utils.InputStreamStatistics; 051import org.apache.commons.io.input.BoundedInputStream; 052 053/** 054 * Implements an input stream that can read Zip archives. 055 * <p> 056 * As of Apache Commons Compress it transparently supports Zip64 extensions and thus individual entries and archives larger than 4 GB or with more than 65,536 057 * entries. 058 * </p> 059 * <p> 060 * The {@link ZipFile} class is preferred when reading from files as {@link ZipArchiveInputStream} is limited by not being able to read the central directory 061 * header before returning entries. In particular {@link ZipArchiveInputStream} 062 * </p> 063 * <ul> 064 * <li>may return entries that are not part of the central directory at all and shouldn't be considered part of the archive.</li> 065 * <li>may return several entries with the same name.</li> 066 * <li>will not return internal or external attributes.</li> 067 * <li>may return incomplete extra field data.</li> 068 * <li>may return unknown sizes and CRC values for entries until the next entry has been reached if the archive uses the data descriptor feature.</li> 069 * </ul> 070 * 071 * @see ZipFile 072 * @NotThreadSafe 073 */ 074public class ZipArchiveInputStream extends ArchiveInputStream<ZipArchiveEntry> implements InputStreamStatistics { 075 076 /** 077 * Input stream adapted from commons-io. 078 */ 079 private final class BoundCountInputStream extends BoundedInputStream { 080 081 // TODO Consider how to do this from a final class, an IO class, or basically without the current side-effect implementation. 082 083 /** 084 * Creates a new {@code BoundedInputStream} that wraps the given input stream and limits it to a certain size. 085 * 086 * @param in The wrapped input stream 087 * @param max The maximum number of bytes to return 088 */ 089 BoundCountInputStream(final InputStream in, final long max) { 090 super(in, max); 091 } 092 093 private boolean atMaxLength() { 094 return getMaxLength() >= 0 && getCount() >= getMaxLength(); 095 } 096 097 @Override 098 public int read() throws IOException { 099 if (atMaxLength()) { 100 return -1; 101 } 102 final int result = super.read(); 103 if (result != -1) { 104 readCount(1); 105 } 106 return result; 107 } 108 109 @Override 110 public int read(final byte[] b, final int off, final int len) throws IOException { 111 if (len == 0) { 112 return 0; 113 } 114 if (atMaxLength()) { 115 return -1; 116 } 117 final long maxRead = getMaxLength() >= 0 ? Math.min(len, getMaxLength() - getCount()) : len; 118 return readCount(super.read(b, off, (int) maxRead)); 119 } 120 121 private int readCount(final int bytesRead) { 122 if (bytesRead != -1) { 123 count(bytesRead); 124 current.bytesReadFromStream += bytesRead; 125 } 126 return bytesRead; 127 } 128 129 } 130 131 /** 132 * Structure collecting information for the entry that is currently being read. 133 */ 134 private static final class CurrentEntry { 135 136 /** 137 * Current ZIP entry. 138 */ 139 private final ZipArchiveEntry entry = new ZipArchiveEntry(); 140 141 /** 142 * Does the entry use a data descriptor? 143 */ 144 private boolean hasDataDescriptor; 145 146 /** 147 * Does the entry have a ZIP64 extended information extra field. 148 */ 149 private boolean usesZip64; 150 151 /** 152 * Number of bytes of entry content read by the client if the entry is STORED. 153 */ 154 private long bytesRead; 155 156 /** 157 * Number of bytes of entry content read from the stream. 158 * <p> 159 * This may be more than the actual entry's length as some stuff gets buffered up and needs to be pushed back when the end of the entry has been 160 * reached. 161 * </p> 162 */ 163 private long bytesReadFromStream; 164 165 /** 166 * The checksum calculated as the current entry is read. 167 */ 168 private final CRC32 crc = new CRC32(); 169 170 /** 171 * The input stream decompressing the data for shrunk and imploded entries. 172 */ 173 private InputStream inputStream; 174 175 @SuppressWarnings("unchecked") // Caller beware 176 private <T extends InputStream> T checkInputStream() { 177 return (T) Objects.requireNonNull(inputStream, "inputStream"); 178 } 179 } 180 181 public static final int PREAMBLE_GARBAGE_MAX_SIZE = 4096; 182 183 private static final int LFH_LEN = 30; 184 185 /* 186 * local file header signature WORD version needed to extract SHORT general purpose bit flag SHORT compression method SHORT last mod file time SHORT last 187 * mod file date SHORT crc-32 WORD compressed size WORD uncompressed size WORD file name length SHORT extra field length SHORT 188 */ 189 private static final int CFH_LEN = 46; 190 191 /* 192 * central file header signature WORD version made by SHORT version needed to extract SHORT general purpose bit flag SHORT compression method SHORT last mod 193 * file time SHORT last mod file date SHORT crc-32 WORD compressed size WORD uncompressed size WORD file name length SHORT extra field length SHORT file 194 * comment length SHORT disk number start SHORT internal file attributes SHORT external file attributes WORD relative offset of local header WORD 195 */ 196 private static final long TWO_EXP_32 = ZIP64_MAGIC + 1; 197 198 private static final String USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER = " while reading a stored entry using data descriptor. Either the archive is broken" 199 + " or it can not be read using ZipArchiveInputStream and you must use ZipFile." 200 + " A common cause for this is a ZIP archive containing a ZIP archive." 201 + " See https://commons.apache.org/proper/commons-compress/zip.html#ZipArchiveInputStream_vs_ZipFile"; 202 203 private static final byte[] LFH = ZipLong.LFH_SIG.getBytes(); 204 205 private static final byte[] CFH = ZipLong.CFH_SIG.getBytes(); 206 207 private static final byte[] DD = ZipLong.DD_SIG.getBytes(); 208 209 private static final byte[] APK_SIGNING_BLOCK_MAGIC = { 'A', 'P', 'K', ' ', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2', }; 210 211 private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); 212 213 private static boolean checksig(final byte[] expected, final byte[] signature) { 214 for (int i = 0; i < expected.length; i++) { 215 if (signature[i] != expected[i]) { 216 return false; 217 } 218 } 219 return true; 220 } 221 222 /** 223 * Checks if the signature matches what is expected for a ZIP file. Does not currently handle self-extracting ZIPs which may have arbitrary leading content. 224 * 225 * @param signature the bytes to check 226 * @param length the number of bytes to check 227 * @return true, if this stream is a ZIP archive stream, false otherwise 228 */ 229 public static boolean matches(final byte[] signature, final int length) { 230 if (length < ZipArchiveOutputStream.LFH_SIG.length) { 231 return false; 232 } 233 234 return checksig(ZipArchiveOutputStream.LFH_SIG, signature) // normal file 235 || checksig(ZipArchiveOutputStream.EOCD_SIG, signature) // empty zip 236 || checksig(ZipArchiveOutputStream.DD_SIG, signature) // split zip 237 || checksig(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER.getBytes(), signature); 238 } 239 240 /** The ZIP encoding to use for file names and the file comment. */ 241 private final ZipEncoding zipEncoding; 242 243 /** Whether to look for and use Unicode extra fields. */ 244 private final boolean useUnicodeExtraFields; 245 246 /** Inflater used for all deflated entries. */ 247 private final Inflater inf = new Inflater(true); 248 249 /** Buffer used to read from the wrapped stream. */ 250 private final ByteBuffer buf = ByteBuffer.allocate(ZipArchiveOutputStream.BUFFER_SIZE); 251 252 /** The entry that is currently being read. */ 253 private CurrentEntry current; 254 255 /** Whether the stream has been closed. */ 256 private boolean closed; 257 258 /** Whether the stream has reached the central directory - and thus found all entries. */ 259 private boolean hitCentralDirectory; 260 261 /** 262 * When reading a stored entry that uses the data descriptor this stream has to read the full entry and caches it. This is the cache. 263 */ 264 private ByteArrayInputStream lastStoredEntry; 265 266 /** 267 * Whether the stream will try to read STORED entries that use a data descriptor. Setting it to true means we will not stop reading an entry with the 268 * compressed size, instead we will stop reading an entry when a data descriptor is met (by finding the Data Descriptor Signature). This will completely 269 * break down in some cases - like JARs in WARs. 270 * <p> 271 * See also : https://issues.apache.org/jira/projects/COMPRESS/issues/COMPRESS-555 272 * https://github.com/apache/commons-compress/pull/137#issuecomment-690835644 273 * </p> 274 */ 275 private final boolean allowStoredEntriesWithDataDescriptor; 276 277 /** Count decompressed bytes for current entry */ 278 private long uncompressedCount; 279 280 /** Whether the stream will try to skip the ZIP split signature(08074B50) at the beginning **/ 281 private final boolean skipSplitSig; 282 283 /** Cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection). */ 284 private final byte[] lfhBuf = new byte[LFH_LEN]; 285 286 private final byte[] skipBuf = new byte[1024]; 287 288 private final byte[] shortBuf = new byte[SHORT]; 289 290 private final byte[] wordBuf = new byte[WORD]; 291 292 private final byte[] twoDwordBuf = new byte[2 * DWORD]; 293 294 private int entriesRead; 295 296 /** 297 * The factory for extra fields or null. 298 */ 299 private Function<ZipShort, ZipExtraField> extraFieldSupport; 300 301 /** 302 * Constructs an instance using UTF-8 encoding 303 * 304 * @param inputStream the stream to wrap 305 */ 306 public ZipArchiveInputStream(final InputStream inputStream) { 307 this(inputStream, StandardCharsets.UTF_8.name()); 308 } 309 310 /** 311 * Constructs an instance using the specified encoding 312 * 313 * @param inputStream the stream to wrap 314 * @param encoding the encoding to use for file names, use null for the platform's default encoding 315 * @since 1.5 316 */ 317 public ZipArchiveInputStream(final InputStream inputStream, final String encoding) { 318 this(inputStream, encoding, true); 319 } 320 321 /** 322 * Constructs an instance using the specified encoding 323 * 324 * @param inputStream the stream to wrap 325 * @param encoding the encoding to use for file names, use null for the platform's default encoding 326 * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names. 327 */ 328 public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields) { 329 this(inputStream, encoding, useUnicodeExtraFields, false); 330 } 331 332 /** 333 * Constructs an instance using the specified encoding 334 * 335 * @param inputStream the stream to wrap 336 * @param encoding the encoding to use for file names, use null for the platform's default encoding 337 * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names. 338 * @param allowStoredEntriesWithDataDescriptor whether the stream will try to read STORED entries that use a data descriptor 339 * @since 1.1 340 */ 341 public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields, 342 final boolean allowStoredEntriesWithDataDescriptor) { 343 this(inputStream, encoding, useUnicodeExtraFields, allowStoredEntriesWithDataDescriptor, false); 344 } 345 346 /** 347 * Constructs an instance using the specified encoding 348 * 349 * @param inputStream the stream to wrap 350 * @param encoding the encoding to use for file names, use null for the platform's default encoding 351 * @param useUnicodeExtraFields whether to use InfoZIP Unicode Extra Fields (if present) to set the file names. 352 * @param allowStoredEntriesWithDataDescriptor whether the stream will try to read STORED entries that use a data descriptor 353 * @param skipSplitSig Whether the stream will try to skip the zip split signature(08074B50) at the beginning. You will need to set 354 * this to true if you want to read a split archive. 355 * @since 1.20 356 */ 357 public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields, 358 final boolean allowStoredEntriesWithDataDescriptor, final boolean skipSplitSig) { 359 super(inputStream, encoding); 360 this.in = new PushbackInputStream(inputStream, buf.capacity()); 361 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 362 this.useUnicodeExtraFields = useUnicodeExtraFields; 363 this.allowStoredEntriesWithDataDescriptor = allowStoredEntriesWithDataDescriptor; 364 this.skipSplitSig = skipSplitSig; 365 // haven't read anything so far 366 buf.limit(0); 367 } 368 369 /** 370 * Checks whether the current buffer contains the signature of a "data descriptor", "local file header" or "central directory 371 * entry". 372 * <p> 373 * If it contains such a signature, reads the data descriptor and positions the stream right after the data descriptor. 374 * </p> 375 */ 376 private boolean bufferContainsSignature(final ByteArrayOutputStream bos, final int offset, final int lastRead, final int expectedDDLen) throws IOException { 377 378 boolean done = false; 379 for (int i = 0; !done && i < offset + lastRead - 4; i++) { 380 if (buf.array()[i] == LFH[0] && buf.array()[i + 1] == LFH[1]) { 381 int expectDDPos = i; 382 if (i >= expectedDDLen && buf.array()[i + 2] == LFH[2] && buf.array()[i + 3] == LFH[3] 383 || buf.array()[i + 2] == CFH[2] && buf.array()[i + 3] == CFH[3]) { 384 // found an LFH or CFH: 385 expectDDPos = i - expectedDDLen; 386 done = true; 387 } else if (buf.array()[i + 2] == DD[2] && buf.array()[i + 3] == DD[3]) { 388 // found DD: 389 done = true; 390 } 391 if (done) { 392 // * push back bytes read in excess as well as the data 393 // descriptor 394 // * copy the remaining bytes to cache 395 // * read data descriptor 396 pushback(buf.array(), expectDDPos, offset + lastRead - expectDDPos); 397 bos.write(buf.array(), 0, expectDDPos); 398 readDataDescriptor(); 399 } 400 } 401 } 402 return done; 403 } 404 405 /** 406 * If the last read bytes could hold a data descriptor and an incomplete signature then save the last bytes to the front of the buffer and cache everything 407 * in front of the potential data descriptor into the given ByteArrayOutputStream. 408 * <p> 409 * Data descriptor plus incomplete signature (3 bytes in the worst case) can be 20 bytes max. 410 * </p> 411 */ 412 private int cacheBytesRead(final ByteArrayOutputStream bos, int offset, final int lastRead, final int expectedDDLen) { 413 final int cacheable = offset + lastRead - expectedDDLen - 3; 414 if (cacheable > 0) { 415 bos.write(buf.array(), 0, cacheable); 416 System.arraycopy(buf.array(), cacheable, buf.array(), 0, expectedDDLen + 3); 417 offset = expectedDDLen + 3; 418 } else { 419 offset += lastRead; 420 } 421 return offset; 422 } 423 424 /** 425 * Whether this class is able to read the given entry. 426 * <p> 427 * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet. 428 * </p> 429 * 430 * @since 1.1 431 */ 432 @Override 433 public boolean canReadEntryData(final ArchiveEntry ae) { 434 if (ae instanceof ZipArchiveEntry) { 435 final ZipArchiveEntry ze = (ZipArchiveEntry) ae; 436 return ZipUtil.canHandleEntryData(ze) && supportsDataDescriptorFor(ze) && supportsCompressedSizeFor(ze); 437 } 438 return false; 439 } 440 441 @Override 442 public void close() throws IOException { 443 if (!closed) { 444 closed = true; 445 try { 446 in.close(); 447 } finally { 448 inf.end(); 449 } 450 } 451 } 452 453 /** 454 * Closes the current ZIP archive entry and positions the underlying stream to the beginning of the next entry. All per-entry variables and data structures 455 * are cleared. 456 * <p> 457 * If the compressed size of this entry is included in the entry header, then any outstanding bytes are simply skipped from the underlying stream without 458 * uncompressing them. This allows an entry to be safely closed even if the compression method is unsupported. 459 * </p> 460 * <p> 461 * In case we don't know the compressed size of this entry or have already buffered too much data from the underlying stream to support uncompression, then 462 * the uncompression process is completed and the end position of the stream is adjusted based on the result of that process. 463 * </p> 464 * 465 * @throws IOException if an error occurs 466 */ 467 private void closeEntry() throws IOException { 468 if (closed) { 469 throw new IOException("The stream is closed"); 470 } 471 if (current == null) { 472 return; 473 } 474 475 // Ensure all entry bytes are read 476 if (currentEntryHasOutstandingBytes()) { 477 drainCurrentEntryData(); 478 } else { 479 // this is guaranteed to exhaust the stream 480 skip(Long.MAX_VALUE); // NOSONAR 481 482 final long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED ? getBytesInflated() : current.bytesRead; 483 484 // this is at most a single read() operation and can't 485 // exceed the range of int 486 final int diff = (int) (current.bytesReadFromStream - inB); 487 488 // Pushback any required bytes 489 if (diff > 0) { 490 pushback(buf.array(), buf.limit() - diff, diff); 491 current.bytesReadFromStream -= diff; 492 } 493 494 // Drain remainder of entry if not all data bytes were required 495 if (currentEntryHasOutstandingBytes()) { 496 drainCurrentEntryData(); 497 } 498 } 499 500 if (lastStoredEntry == null && current.hasDataDescriptor) { 501 readDataDescriptor(); 502 } 503 504 inf.reset(); 505 buf.clear().flip(); 506 current = null; 507 lastStoredEntry = null; 508 } 509 510 /** 511 * If the compressed size of the current entry is included in the entry header and there are any outstanding bytes in the underlying stream, then this 512 * returns true. 513 * 514 * @return true, if current entry is determined to have outstanding bytes, false otherwise 515 */ 516 private boolean currentEntryHasOutstandingBytes() { 517 return current.bytesReadFromStream <= current.entry.getCompressedSize() && !current.hasDataDescriptor; 518 } 519 520 /** 521 * Read all data of the current entry from the underlying stream that hasn't been read, yet. 522 */ 523 private void drainCurrentEntryData() throws IOException { 524 long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream; 525 while (remaining > 0) { 526 final long n = in.read(buf.array(), 0, (int) Math.min(buf.capacity(), remaining)); 527 if (n < 0) { 528 throw new EOFException("Truncated ZIP entry: " + ArchiveUtils.sanitize(current.entry.getName())); 529 } 530 count(n); 531 remaining -= n; 532 } 533 } 534 535 private int fill() throws IOException { 536 if (closed) { 537 throw new IOException("The stream is closed"); 538 } 539 final int length = in.read(buf.array()); 540 if (length > 0) { 541 buf.limit(length); 542 count(buf.limit()); 543 inf.setInput(buf.array(), 0, buf.limit()); 544 } 545 return length; 546 } 547 548 /** 549 * Reads forward until the signature of the "End of central directory" record is found. 550 */ 551 private boolean findEocdRecord() throws IOException { 552 int currentByte = -1; 553 boolean skipReadCall = false; 554 while (skipReadCall || (currentByte = readOneByte()) > -1) { 555 skipReadCall = false; 556 if (!isFirstByteOfEocdSig(currentByte)) { 557 continue; 558 } 559 currentByte = readOneByte(); 560 if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) { 561 if (currentByte == -1) { 562 break; 563 } 564 skipReadCall = isFirstByteOfEocdSig(currentByte); 565 continue; 566 } 567 currentByte = readOneByte(); 568 if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) { 569 if (currentByte == -1) { 570 break; 571 } 572 skipReadCall = isFirstByteOfEocdSig(currentByte); 573 continue; 574 } 575 currentByte = readOneByte(); 576 if (currentByte == -1) { 577 break; 578 } 579 if (currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) { 580 return true; 581 } 582 skipReadCall = isFirstByteOfEocdSig(currentByte); 583 } 584 return false; 585 } 586 587 /** 588 * Gets the number of bytes Inflater has actually processed. 589 * <p> 590 * for Java < Java7 the getBytes* methods in Inflater/Deflater seem to return unsigned ints rather than longs that start over with 0 at 2^32. 591 * </p> 592 * <p> 593 * The stream knows how many bytes it has read, but not how many the Inflater actually consumed - it should be between the total number of bytes read for 594 * the entry and the total number minus the last read operation. Here we just try to make the value close enough to the bytes we've read by assuming the 595 * number of bytes consumed must be smaller than (or equal to) the number of bytes read but not smaller by more than 2^32. 596 * </p> 597 */ 598 private long getBytesInflated() { 599 long inB = inf.getBytesRead(); 600 if (current.bytesReadFromStream >= TWO_EXP_32) { 601 while (inB + TWO_EXP_32 <= current.bytesReadFromStream) { 602 inB += TWO_EXP_32; 603 } 604 } 605 return inB; 606 } 607 608 /** 609 * @since 1.17 610 */ 611 @SuppressWarnings("resource") // checkInputStream() does not allocate. 612 @Override 613 public long getCompressedCount() { 614 final int method = current.entry.getMethod(); 615 if (method == ZipArchiveOutputStream.STORED) { 616 return current.bytesRead; 617 } 618 if (method == ZipArchiveOutputStream.DEFLATED) { 619 return getBytesInflated(); 620 } 621 if (method == ZipMethod.UNSHRINKING.getCode() || method == ZipMethod.IMPLODING.getCode() || method == ZipMethod.ENHANCED_DEFLATED.getCode() 622 || method == ZipMethod.BZIP2.getCode()) { 623 return ((InputStreamStatistics) current.checkInputStream()).getCompressedCount(); 624 } 625 return -1; 626 } 627 628 @Override 629 public ZipArchiveEntry getNextEntry() throws IOException { 630 return getNextZipEntry(); 631 } 632 633 /** 634 * Gets the next entry. 635 * 636 * @return the next entry. 637 * @throws IOException if an I/O error occurs. 638 * @deprecated Use {@link #getNextEntry()}. 639 */ 640 @Deprecated 641 public ZipArchiveEntry getNextZipEntry() throws IOException { 642 uncompressedCount = 0; 643 644 boolean firstEntry = true; 645 if (closed || hitCentralDirectory) { 646 return null; 647 } 648 if (current != null) { 649 closeEntry(); 650 firstEntry = false; 651 } 652 653 final long currentHeaderOffset = getBytesRead(); 654 try { 655 if (firstEntry) { 656 // split archives have a special signature before the 657 // first local file header - look for it and fail with 658 // the appropriate error message if this is a split 659 // archive. 660 if (!readFirstLocalFileHeader()) { 661 hitCentralDirectory = true; 662 skipRemainderOfArchive(true); 663 return null; 664 } 665 } else { 666 readFully(lfhBuf); 667 } 668 } catch (final EOFException e) { // NOSONAR 669 return null; 670 } 671 672 final ZipLong sig = new ZipLong(lfhBuf); 673 if (!sig.equals(ZipLong.LFH_SIG)) { 674 if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) || isApkSigningBlock(lfhBuf)) { 675 hitCentralDirectory = true; 676 skipRemainderOfArchive(false); 677 return null; 678 } 679 throw new ZipException(String.format("Unexpected record signature: 0x%x", sig.getValue())); 680 } 681 682 int off = WORD; 683 current = new CurrentEntry(); 684 685 final int versionMadeBy = ZipShort.getValue(lfhBuf, off); 686 off += SHORT; 687 current.entry.setPlatform(versionMadeBy >> ZipFile.BYTE_SHIFT & ZipFile.NIBLET_MASK); 688 689 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfhBuf, off); 690 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); 691 final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding; 692 current.hasDataDescriptor = gpFlag.usesDataDescriptor(); 693 current.entry.setGeneralPurposeBit(gpFlag); 694 695 off += SHORT; 696 697 current.entry.setMethod(ZipShort.getValue(lfhBuf, off)); 698 off += SHORT; 699 700 final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfhBuf, off)); 701 current.entry.setTime(time); 702 off += WORD; 703 704 ZipLong size = null, cSize = null; 705 if (!current.hasDataDescriptor) { 706 current.entry.setCrc(ZipLong.getValue(lfhBuf, off)); 707 off += WORD; 708 709 cSize = new ZipLong(lfhBuf, off); 710 off += WORD; 711 712 size = new ZipLong(lfhBuf, off); 713 off += WORD; 714 } else { 715 off += 3 * WORD; 716 } 717 718 final int fileNameLen = ZipShort.getValue(lfhBuf, off); 719 720 off += SHORT; 721 722 final int extraLen = ZipShort.getValue(lfhBuf, off); 723 off += SHORT; // NOSONAR - assignment as documentation 724 725 final byte[] fileName = readRange(fileNameLen); 726 current.entry.setName(entryEncoding.decode(fileName), fileName); 727 if (hasUTF8Flag) { 728 current.entry.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG); 729 } 730 731 final byte[] extraData = readRange(extraLen); 732 try { 733 current.entry.setExtra(extraData); 734 } catch (final RuntimeException ex) { 735 final ZipException z = new ZipException("Invalid extra data in entry " + current.entry.getName()); 736 z.initCause(ex); 737 throw z; 738 } 739 740 if (!hasUTF8Flag && useUnicodeExtraFields) { 741 ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null); 742 } 743 744 processZip64Extra(size, cSize); 745 746 current.entry.setLocalHeaderOffset(currentHeaderOffset); 747 current.entry.setDataOffset(getBytesRead()); 748 current.entry.setStreamContiguous(true); 749 750 final ZipMethod m = ZipMethod.getMethodByCode(current.entry.getMethod()); 751 if (current.entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN) { 752 if (ZipUtil.canHandleEntryData(current.entry) && m != ZipMethod.STORED && m != ZipMethod.DEFLATED) { 753 final InputStream bis = new BoundCountInputStream(in, current.entry.getCompressedSize()); 754 switch (m) { 755 case UNSHRINKING: 756 current.inputStream = new UnshrinkingInputStream(bis); 757 break; 758 case IMPLODING: 759 try { 760 current.inputStream = new ExplodingInputStream(current.entry.getGeneralPurposeBit().getSlidingDictionarySize(), 761 current.entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), bis); 762 } catch (final IllegalArgumentException ex) { 763 throw new IOException("bad IMPLODE data", ex); 764 } 765 break; 766 case BZIP2: 767 current.inputStream = new BZip2CompressorInputStream(bis); 768 break; 769 case ENHANCED_DEFLATED: 770 current.inputStream = new Deflate64CompressorInputStream(bis); 771 break; 772 default: 773 // we should never get here as all supported methods have been covered 774 // will cause an error when read is invoked, don't throw an exception here so people can 775 // skip unsupported entries 776 break; 777 } 778 } 779 } else if (m == ZipMethod.ENHANCED_DEFLATED) { 780 current.inputStream = new Deflate64CompressorInputStream(in); 781 } 782 783 entriesRead++; 784 return current.entry; 785 } 786 787 /** 788 * Gets the uncompressed count. 789 * 790 * @since 1.17 791 */ 792 @Override 793 public long getUncompressedCount() { 794 return uncompressedCount; 795 } 796 797 /** 798 * Checks whether this might be an APK Signing Block. 799 * <p> 800 * Unfortunately the APK signing block does not start with some kind of signature, it rather ends with one. It starts with a length, so what we do is parse 801 * the suspect length, skip ahead far enough, look for the signature and if we've found it, return true. 802 * </p> 803 * 804 * @param suspectLocalFileHeader the bytes read from the underlying stream in the expectation that they would hold the local file header of the next entry. 805 * @return true if this looks like an APK signing block 806 * @see <a href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a> 807 */ 808 private boolean isApkSigningBlock(final byte[] suspectLocalFileHeader) throws IOException { 809 // length of block excluding the size field itself 810 final BigInteger len = ZipEightByteInteger.getValue(suspectLocalFileHeader); 811 // LFH has already been read and all but the first eight bytes contain (part of) the APK signing block, 812 // also subtract 16 bytes in order to position us at the magic string 813 BigInteger toSkip = len.add(BigInteger.valueOf(DWORD - suspectLocalFileHeader.length - (long) APK_SIGNING_BLOCK_MAGIC.length)); 814 final byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length]; 815 816 try { 817 if (toSkip.signum() < 0) { 818 // suspectLocalFileHeader contains the start of suspect magic string 819 final int off = suspectLocalFileHeader.length + toSkip.intValue(); 820 // length was shorter than magic length 821 if (off < DWORD) { 822 return false; 823 } 824 final int bytesInBuffer = Math.abs(toSkip.intValue()); 825 System.arraycopy(suspectLocalFileHeader, off, magic, 0, Math.min(bytesInBuffer, magic.length)); 826 if (bytesInBuffer < magic.length) { 827 readFully(magic, bytesInBuffer); 828 } 829 } else { 830 while (toSkip.compareTo(LONG_MAX) > 0) { 831 realSkip(Long.MAX_VALUE); 832 toSkip = toSkip.add(LONG_MAX.negate()); 833 } 834 realSkip(toSkip.longValue()); 835 readFully(magic); 836 } 837 } catch (final EOFException ex) { // NOSONAR 838 // length was invalid 839 return false; 840 } 841 return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC); 842 } 843 844 private boolean isFirstByteOfEocdSig(final int b) { 845 return b == ZipArchiveOutputStream.EOCD_SIG[0]; 846 } 847 848 /** 849 * Records whether a Zip64 extra is present and sets the size information from it if sizes are 0xFFFFFFFF and the entry doesn't use a data descriptor. 850 */ 851 private void processZip64Extra(final ZipLong size, final ZipLong cSize) throws ZipException { 852 final ZipExtraField extra = current.entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); 853 if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) { 854 throw new ZipException("archive contains unparseable zip64 extra field"); 855 } 856 final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra; 857 current.usesZip64 = z64 != null; 858 if (!current.hasDataDescriptor) { 859 if (z64 != null // same as current.usesZip64 but avoids NPE warning 860 && (ZipLong.ZIP64_MAGIC.equals(cSize) || ZipLong.ZIP64_MAGIC.equals(size))) { 861 if (z64.getCompressedSize() == null || z64.getSize() == null) { 862 // avoid NPE if it's a corrupted ZIP archive 863 throw new ZipException("archive contains corrupted zip64 extra field"); 864 } 865 long s = z64.getCompressedSize().getLongValue(); 866 if (s < 0) { 867 throw new ZipException("broken archive, entry with negative compressed size"); 868 } 869 current.entry.setCompressedSize(s); 870 s = z64.getSize().getLongValue(); 871 if (s < 0) { 872 throw new ZipException("broken archive, entry with negative size"); 873 } 874 current.entry.setSize(s); 875 } else if (cSize != null && size != null) { 876 if (cSize.getValue() < 0) { 877 throw new ZipException("broken archive, entry with negative compressed size"); 878 } 879 current.entry.setCompressedSize(cSize.getValue()); 880 if (size.getValue() < 0) { 881 throw new ZipException("broken archive, entry with negative size"); 882 } 883 current.entry.setSize(size.getValue()); 884 } 885 } 886 } 887 888 private void pushback(final byte[] buf, final int offset, final int length) throws IOException { 889 if (offset < 0) { 890 // Instead of ArrayIndexOutOfBoundsException 891 throw new IOException(String.format("Negative offset %,d into buffer", offset)); 892 } 893 ((PushbackInputStream) in).unread(buf, offset, length); 894 pushedBackBytes(length); 895 } 896 897 @Override 898 public int read(final byte[] buffer, final int offset, final int length) throws IOException { 899 if (length == 0) { 900 return 0; 901 } 902 if (closed) { 903 throw new IOException("The stream is closed"); 904 } 905 906 if (current == null) { 907 return -1; 908 } 909 910 // avoid int overflow, check null buffer 911 if (offset > buffer.length || length < 0 || offset < 0 || buffer.length - offset < length) { 912 throw new ArrayIndexOutOfBoundsException(); 913 } 914 915 ZipUtil.checkRequestedFeatures(current.entry); 916 if (!supportsDataDescriptorFor(current.entry)) { 917 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.DATA_DESCRIPTOR, current.entry); 918 } 919 if (!supportsCompressedSizeFor(current.entry)) { 920 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.UNKNOWN_COMPRESSED_SIZE, current.entry); 921 } 922 923 final int read; 924 if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) { 925 read = readStored(buffer, offset, length); 926 } else if (current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED) { 927 read = readDeflated(buffer, offset, length); 928 } else if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode() || current.entry.getMethod() == ZipMethod.IMPLODING.getCode() 929 || current.entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() || current.entry.getMethod() == ZipMethod.BZIP2.getCode()) { 930 read = current.inputStream.read(buffer, offset, length); 931 } else { 932 throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(current.entry.getMethod()), current.entry); 933 } 934 935 if (read >= 0) { 936 current.crc.update(buffer, offset, read); 937 uncompressedCount += read; 938 } 939 940 return read; 941 } 942 943 private void readDataDescriptor() throws IOException { 944 readFully(wordBuf); 945 ZipLong val = new ZipLong(wordBuf); 946 if (ZipLong.DD_SIG.equals(val)) { 947 // data descriptor with signature, skip sig 948 readFully(wordBuf); 949 val = new ZipLong(wordBuf); 950 } 951 current.entry.setCrc(val.getValue()); 952 953 // if there is a ZIP64 extra field, sizes are eight bytes 954 // each, otherwise four bytes each. Unfortunately some 955 // implementations - namely Java7 - use eight bytes without 956 // using a ZIP64 extra field - 957 // https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588 958 959 // just read 16 bytes and check whether bytes nine to twelve 960 // look like one of the signatures of what could follow a data 961 // descriptor (ignoring archive decryption headers for now). 962 // If so, push back eight bytes and assume sizes are four 963 // bytes, otherwise sizes are eight bytes each. 964 readFully(twoDwordBuf); 965 final ZipLong potentialSig = new ZipLong(twoDwordBuf, DWORD); 966 if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) { 967 pushback(twoDwordBuf, DWORD, DWORD); 968 long size = ZipLong.getValue(twoDwordBuf); 969 if (size < 0) { 970 throw new ZipException("broken archive, entry with negative compressed size"); 971 } 972 current.entry.setCompressedSize(size); 973 size = ZipLong.getValue(twoDwordBuf, WORD); 974 if (size < 0) { 975 throw new ZipException("broken archive, entry with negative size"); 976 } 977 current.entry.setSize(size); 978 } else { 979 long size = ZipEightByteInteger.getLongValue(twoDwordBuf); 980 if (size < 0) { 981 throw new ZipException("broken archive, entry with negative compressed size"); 982 } 983 current.entry.setCompressedSize(size); 984 size = ZipEightByteInteger.getLongValue(twoDwordBuf, DWORD); 985 if (size < 0) { 986 throw new ZipException("broken archive, entry with negative size"); 987 } 988 current.entry.setSize(size); 989 } 990 } 991 992 /** 993 * Implementation of read for DEFLATED entries. 994 */ 995 private int readDeflated(final byte[] buffer, final int offset, final int length) throws IOException { 996 final int read = readFromInflater(buffer, offset, length); 997 if (read <= 0) { 998 if (inf.finished()) { 999 return -1; 1000 } 1001 if (inf.needsDictionary()) { 1002 throw new ZipException("This archive needs a preset dictionary" + " which is not supported by Commons" + " Compress."); 1003 } 1004 if (read == -1) { 1005 throw new IOException("Truncated ZIP file"); 1006 } 1007 } 1008 return read; 1009 } 1010 1011 /** 1012 * Fills the given array with the first local file header and deals with splitting/spanning markers that may prefix the first LFH. 1013 */ 1014 private boolean readFirstLocalFileHeader() throws IOException { 1015 // for empty archive, we may get only EOCD size: 1016 final byte[] header = new byte[Math.min(LFH_LEN, ZipFile.MIN_EOCD_SIZE)]; 1017 readFully(header); 1018 try { 1019 READ_LOOP: for (int i = 0; ; ) { 1020 for (int j = 0; i <= PREAMBLE_GARBAGE_MAX_SIZE - 4 && j <= header.length - 4; ++j, ++i) { 1021 final ZipLong sig = new ZipLong(header, j); 1022 if ( 1023 sig.equals(ZipLong.LFH_SIG) || 1024 sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || 1025 sig.equals(ZipLong.DD_SIG)) { 1026 // regular archive containing at least one entry: 1027 System.arraycopy(header, j, header, 0, header.length - j); 1028 readFully(header, header.length - j); 1029 break READ_LOOP; 1030 } 1031 if ( 1032 sig.equals(new ZipLong(ZipArchiveOutputStream.EOCD_SIG)) 1033 ) { 1034 // empty archive: 1035 pushback(header, j, header.length - j); 1036 return false; 1037 } 1038 } 1039 if (i >= PREAMBLE_GARBAGE_MAX_SIZE - 4) { 1040 throw new ZipException("Cannot find zip signature within the first " + PREAMBLE_GARBAGE_MAX_SIZE + " bytes"); 1041 } 1042 System.arraycopy(header, header.length - 3, header, 0, 3); 1043 readFully(header, 3); 1044 } 1045 System.arraycopy(header, 0, lfhBuf, 0, header.length); 1046 readFully(lfhBuf, header.length); 1047 } catch (final EOFException ex) { 1048 throw new ZipException("Cannot find zip signature within the file"); 1049 } 1050 final ZipLong sig = new ZipLong(lfhBuf); 1051 1052 if (!skipSplitSig && sig.equals(ZipLong.DD_SIG)) { 1053 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.SPLITTING); 1054 } 1055 1056 // the split ZIP signature(08074B50) should only be skipped when the skipSplitSig is set 1057 if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || sig.equals(ZipLong.DD_SIG)) { 1058 // Just skip over the marker. 1059 System.arraycopy(lfhBuf, 4, lfhBuf, 0, lfhBuf.length - 4); 1060 readFully(lfhBuf, lfhBuf.length - 4); 1061 } 1062 return true; 1063 } 1064 1065 /** 1066 * Potentially reads more bytes to fill the inflater's buffer and reads from it. 1067 */ 1068 private int readFromInflater(final byte[] buffer, final int offset, final int length) throws IOException { 1069 int read = 0; 1070 do { 1071 if (inf.needsInput()) { 1072 final int l = fill(); 1073 if (l > 0) { 1074 current.bytesReadFromStream += buf.limit(); 1075 } else if (l == -1) { 1076 return -1; 1077 } else { 1078 break; 1079 } 1080 } 1081 try { 1082 read = inf.inflate(buffer, offset, length); 1083 } catch (final DataFormatException e) { 1084 throw (IOException) new ZipException(e.getMessage()).initCause(e); 1085 } 1086 } while (read == 0 && inf.needsInput()); 1087 return read; 1088 } 1089 1090 private void readFully(final byte[] b) throws IOException { 1091 readFully(b, 0); 1092 } 1093 1094 private void readFully(final byte[] b, final int off) throws IOException { 1095 final int len = b.length - off; 1096 final int count = IOUtils.readFully(in, b, off, len); 1097 count(count); 1098 if (count < len) { 1099 throw new EOFException(); 1100 } 1101 } 1102 1103 // End of Central Directory Record 1104 // end of central dir signature WORD 1105 // number of this disk SHORT 1106 // number of the disk with the 1107 // start of the central directory SHORT 1108 // total number of entries in the 1109 // central directory on this disk SHORT 1110 // total number of entries in 1111 // the central directory SHORT 1112 // size of the central directory WORD 1113 // offset of start of central 1114 // directory with respect to 1115 // the starting disk number WORD 1116 // .ZIP file comment length SHORT 1117 // .ZIP file comment up to 64KB 1118 // 1119 1120 /** 1121 * Reads bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #read} would do. 1122 * 1123 * Also updates bytes-read counter. 1124 */ 1125 private int readOneByte() throws IOException { 1126 final int b = in.read(); 1127 if (b != -1) { 1128 count(1); 1129 } 1130 return b; 1131 } 1132 1133 private byte[] readRange(final int len) throws IOException { 1134 final byte[] ret = IOUtils.readRange(in, len); 1135 count(ret.length); 1136 if (ret.length < len) { 1137 throw new EOFException(); 1138 } 1139 return ret; 1140 } 1141 1142 /** 1143 * Implementation of read for STORED entries. 1144 */ 1145 private int readStored(final byte[] buffer, final int offset, final int length) throws IOException { 1146 1147 if (current.hasDataDescriptor) { 1148 if (lastStoredEntry == null) { 1149 readStoredEntry(); 1150 } 1151 return lastStoredEntry.read(buffer, offset, length); 1152 } 1153 1154 final long csize = current.entry.getSize(); 1155 if (current.bytesRead >= csize) { 1156 return -1; 1157 } 1158 1159 if (buf.position() >= buf.limit()) { 1160 buf.position(0); 1161 final int l = in.read(buf.array()); 1162 if (l == -1) { 1163 buf.limit(0); 1164 throw new IOException("Truncated ZIP file"); 1165 } 1166 buf.limit(l); 1167 1168 count(l); 1169 current.bytesReadFromStream += l; 1170 } 1171 1172 int toRead = Math.min(buf.remaining(), length); 1173 if (csize - current.bytesRead < toRead) { 1174 // if it is smaller than toRead then it fits into an int 1175 toRead = (int) (csize - current.bytesRead); 1176 } 1177 buf.get(buffer, offset, toRead); 1178 current.bytesRead += toRead; 1179 return toRead; 1180 } 1181 1182 /** 1183 * Caches a stored entry that uses the data descriptor. 1184 * <ul> 1185 * <li>Reads a stored entry until the signature of a local file header, central directory header or data descriptor has been found.</li> 1186 * <li>Stores all entry data in lastStoredEntry. 1187 * </p> 1188 * <li>Rewinds the stream to position at the data descriptor.</li> 1189 * <li>reads the data descriptor</li> 1190 * </ul> 1191 * <p> 1192 * After calling this method the entry should know its size, the entry's data is cached and the stream is positioned at the next local file or central 1193 * directory header. 1194 * </p> 1195 */ 1196 private void readStoredEntry() throws IOException { 1197 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 1198 int off = 0; 1199 boolean done = false; 1200 1201 // length of DD without signature 1202 final int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD; 1203 1204 while (!done) { 1205 final int r = in.read(buf.array(), off, ZipArchiveOutputStream.BUFFER_SIZE - off); 1206 if (r <= 0) { 1207 // read the whole archive without ever finding a 1208 // central directory 1209 throw new IOException("Truncated ZIP file"); 1210 } 1211 if (r + off < 4) { 1212 // buffer too small to check for a signature, loop 1213 off += r; 1214 continue; 1215 } 1216 1217 done = bufferContainsSignature(bos, off, r, ddLen); 1218 if (!done) { 1219 off = cacheBytesRead(bos, off, r, ddLen); 1220 } 1221 } 1222 if (current.entry.getCompressedSize() != current.entry.getSize()) { 1223 throw new ZipException("compressed and uncompressed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER); 1224 } 1225 final byte[] b = bos.toByteArray(); 1226 if (b.length != current.entry.getSize()) { 1227 throw new ZipException("actual and claimed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER); 1228 } 1229 lastStoredEntry = new ByteArrayInputStream(b); 1230 } 1231 1232 /** 1233 * Skips bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #skip} would do. 1234 * 1235 * Also updates bytes-read counter. 1236 */ 1237 private void realSkip(final long value) throws IOException { 1238 if (value >= 0) { 1239 long skipped = 0; 1240 while (skipped < value) { 1241 final long rem = value - skipped; 1242 final int x = in.read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length)); 1243 if (x == -1) { 1244 return; 1245 } 1246 count(x); 1247 skipped += x; 1248 } 1249 return; 1250 } 1251 throw new IllegalArgumentException(); 1252 } 1253 1254 /** 1255 * Enable custom extra fields factory. 1256 * @param extraFieldSupport the lookup function based on extra field header id. 1257 * @return the archive. 1258 */ 1259 public ZipArchiveInputStream setExtraFieldSupport(final Function<ZipShort, ZipExtraField> extraFieldSupport) { 1260 this.extraFieldSupport = extraFieldSupport; 1261 return this; 1262 } 1263 1264 /** 1265 * Skips over and discards value bytes of data from this input stream. 1266 * <p> 1267 * This implementation may end up skipping over some smaller number of bytes, possibly 0, if and only if it reaches the end of the underlying stream. 1268 * </p> 1269 * <p> 1270 * The actual number of bytes skipped is returned. 1271 * </p> 1272 * 1273 * @param value the number of bytes to be skipped. 1274 * @return the actual number of bytes skipped. 1275 * @throws IOException - if an I/O error occurs. 1276 * @throws IllegalArgumentException - if value is negative. 1277 */ 1278 @Override 1279 public long skip(final long value) throws IOException { 1280 if (value >= 0) { 1281 long skipped = 0; 1282 while (skipped < value) { 1283 final long rem = value - skipped; 1284 final int x = read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length)); 1285 if (x == -1) { 1286 return skipped; 1287 } 1288 skipped += x; 1289 } 1290 return skipped; 1291 } 1292 throw new IllegalArgumentException(); 1293 } 1294 1295 /** 1296 * Reads the stream until it find the "End of central directory record" and consumes it as well. 1297 */ 1298 private void skipRemainderOfArchive(final boolean read) throws IOException { 1299 // skip over central directory. One LFH has been read too much 1300 // already. The calculation discounts file names and extra 1301 // data, so it will be too short. 1302 if (entriesRead > 0) { 1303 realSkip((long) entriesRead * CFH_LEN - LFH_LEN); 1304 } 1305 final boolean foundEocd = findEocdRecord(); 1306 if (foundEocd) { 1307 realSkip((long) ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* comment len */); 1308 readFully(shortBuf); 1309 // file comment 1310 final int commentLen = ZipShort.getValue(shortBuf); 1311 if (commentLen >= 0) { 1312 realSkip(commentLen); 1313 return; 1314 } 1315 } 1316 throw new IOException("Truncated ZIP file"); 1317 } 1318 1319 /** 1320 * Whether the compressed size for the entry is either known or not required by the compression method being used. 1321 */ 1322 private boolean supportsCompressedSizeFor(final ZipArchiveEntry entry) { 1323 return entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN || entry.getMethod() == ZipEntry.DEFLATED 1324 || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() 1325 || entry.getGeneralPurposeBit().usesDataDescriptor() && allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED; 1326 } 1327 1328 /** 1329 * Whether this entry requires a data descriptor this library can work with. 1330 * 1331 * @return true if allowStoredEntriesWithDataDescriptor is true, the entry doesn't require any data descriptor or the method is DEFLATED or 1332 * ENHANCED_DEFLATED. 1333 */ 1334 private boolean supportsDataDescriptorFor(final ZipArchiveEntry entry) { 1335 return !entry.getGeneralPurposeBit().usesDataDescriptor() || allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED 1336 || entry.getMethod() == ZipEntry.DEFLATED || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode(); 1337 } 1338}