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 getMaxCount() >= 0 && getCount() >= getMaxCount(); 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 = getMaxCount() >= 0 ? Math.min(len, getMaxCount() - 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 if (skip(Long.MAX_VALUE) < 0) { 481 throw new IllegalStateException("Can't read the remainder of the stream"); 482 } 483 484 final long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED ? getBytesInflated() : current.bytesRead; 485 486 // this is at most a single read() operation and can't 487 // exceed the range of int 488 final int diff = (int) (current.bytesReadFromStream - inB); 489 490 // Pushback any required bytes 491 if (diff > 0) { 492 pushback(buf.array(), buf.limit() - diff, diff); 493 current.bytesReadFromStream -= diff; 494 } 495 496 // Drain remainder of entry if not all data bytes were required 497 if (currentEntryHasOutstandingBytes()) { 498 drainCurrentEntryData(); 499 } 500 } 501 502 if (lastStoredEntry == null && current.hasDataDescriptor) { 503 readDataDescriptor(); 504 } 505 506 inf.reset(); 507 buf.clear().flip(); 508 current = null; 509 lastStoredEntry = null; 510 } 511 512 /** 513 * 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 514 * returns true. 515 * 516 * @return true, if current entry is determined to have outstanding bytes, false otherwise 517 */ 518 private boolean currentEntryHasOutstandingBytes() { 519 return current.bytesReadFromStream <= current.entry.getCompressedSize() && !current.hasDataDescriptor; 520 } 521 522 /** 523 * Read all data of the current entry from the underlying stream that hasn't been read, yet. 524 */ 525 private void drainCurrentEntryData() throws IOException { 526 long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream; 527 while (remaining > 0) { 528 final long n = in.read(buf.array(), 0, (int) Math.min(buf.capacity(), remaining)); 529 if (n < 0) { 530 throw new EOFException("Truncated ZIP entry: " + ArchiveUtils.sanitize(current.entry.getName())); 531 } 532 count(n); 533 remaining -= n; 534 } 535 } 536 537 private int fill() throws IOException { 538 if (closed) { 539 throw new IOException("The stream is closed"); 540 } 541 final int length = in.read(buf.array()); 542 if (length > 0) { 543 buf.limit(length); 544 count(buf.limit()); 545 inf.setInput(buf.array(), 0, buf.limit()); 546 } 547 return length; 548 } 549 550 /** 551 * Reads forward until the signature of the "End of central directory" record is found. 552 */ 553 private boolean findEocdRecord() throws IOException { 554 int currentByte = -1; 555 boolean skipReadCall = false; 556 while (skipReadCall || (currentByte = readOneByte()) > -1) { 557 skipReadCall = false; 558 if (!isFirstByteOfEocdSig(currentByte)) { 559 continue; 560 } 561 currentByte = readOneByte(); 562 if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) { 563 if (currentByte == -1) { 564 break; 565 } 566 skipReadCall = isFirstByteOfEocdSig(currentByte); 567 continue; 568 } 569 currentByte = readOneByte(); 570 if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) { 571 if (currentByte == -1) { 572 break; 573 } 574 skipReadCall = isFirstByteOfEocdSig(currentByte); 575 continue; 576 } 577 currentByte = readOneByte(); 578 if (currentByte == -1) { 579 break; 580 } 581 if (currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) { 582 return true; 583 } 584 skipReadCall = isFirstByteOfEocdSig(currentByte); 585 } 586 return false; 587 } 588 589 /** 590 * Gets the number of bytes Inflater has actually processed. 591 * <p> 592 * 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. 593 * </p> 594 * <p> 595 * 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 596 * 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 597 * number of bytes consumed must be smaller than (or equal to) the number of bytes read but not smaller by more than 2^32. 598 * </p> 599 */ 600 private long getBytesInflated() { 601 long inB = inf.getBytesRead(); 602 if (current.bytesReadFromStream >= TWO_EXP_32) { 603 while (inB + TWO_EXP_32 <= current.bytesReadFromStream) { 604 inB += TWO_EXP_32; 605 } 606 } 607 return inB; 608 } 609 610 /** 611 * @since 1.17 612 */ 613 @SuppressWarnings("resource") // checkInputStream() does not allocate. 614 @Override 615 public long getCompressedCount() { 616 final int method = current.entry.getMethod(); 617 if (method == ZipArchiveOutputStream.STORED) { 618 return current.bytesRead; 619 } 620 if (method == ZipArchiveOutputStream.DEFLATED) { 621 return getBytesInflated(); 622 } 623 if (method == ZipMethod.UNSHRINKING.getCode() || method == ZipMethod.IMPLODING.getCode() || method == ZipMethod.ENHANCED_DEFLATED.getCode() 624 || method == ZipMethod.BZIP2.getCode()) { 625 return ((InputStreamStatistics) current.checkInputStream()).getCompressedCount(); 626 } 627 return -1; 628 } 629 630 @Override 631 public ZipArchiveEntry getNextEntry() throws IOException { 632 return getNextZipEntry(); 633 } 634 635 /** 636 * Gets the next entry. 637 * 638 * @return the next entry. 639 * @throws IOException if an I/O error occurs. 640 * @deprecated Use {@link #getNextEntry()}. 641 */ 642 @Deprecated 643 public ZipArchiveEntry getNextZipEntry() throws IOException { 644 uncompressedCount = 0; 645 646 boolean firstEntry = true; 647 if (closed || hitCentralDirectory) { 648 return null; 649 } 650 if (current != null) { 651 closeEntry(); 652 firstEntry = false; 653 } 654 655 final long currentHeaderOffset = getBytesRead(); 656 try { 657 if (firstEntry) { 658 // split archives have a special signature before the 659 // first local file header - look for it and fail with 660 // the appropriate error message if this is a split 661 // archive. 662 if (!readFirstLocalFileHeader()) { 663 hitCentralDirectory = true; 664 skipRemainderOfArchive(); 665 return null; 666 } 667 } else { 668 readFully(lfhBuf); 669 } 670 } catch (final EOFException e) { // NOSONAR 671 return null; 672 } 673 674 final ZipLong sig = new ZipLong(lfhBuf); 675 if (!sig.equals(ZipLong.LFH_SIG)) { 676 if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG) || isApkSigningBlock(lfhBuf)) { 677 hitCentralDirectory = true; 678 skipRemainderOfArchive(); 679 return null; 680 } 681 throw new ZipException(String.format("Unexpected record signature: 0x%x", sig.getValue())); 682 } 683 684 int off = WORD; 685 current = new CurrentEntry(); 686 687 final int versionMadeBy = ZipShort.getValue(lfhBuf, off); 688 off += SHORT; 689 current.entry.setPlatform(versionMadeBy >> ZipFile.BYTE_SHIFT & ZipFile.NIBLET_MASK); 690 691 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfhBuf, off); 692 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); 693 final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding; 694 current.hasDataDescriptor = gpFlag.usesDataDescriptor(); 695 current.entry.setGeneralPurposeBit(gpFlag); 696 697 off += SHORT; 698 699 current.entry.setMethod(ZipShort.getValue(lfhBuf, off)); 700 off += SHORT; 701 702 final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfhBuf, off)); 703 current.entry.setTime(time); 704 off += WORD; 705 706 ZipLong size = null, cSize = null; 707 if (!current.hasDataDescriptor) { 708 current.entry.setCrc(ZipLong.getValue(lfhBuf, off)); 709 off += WORD; 710 711 cSize = new ZipLong(lfhBuf, off); 712 off += WORD; 713 714 size = new ZipLong(lfhBuf, off); 715 off += WORD; 716 } else { 717 off += 3 * WORD; 718 } 719 720 final int fileNameLen = ZipShort.getValue(lfhBuf, off); 721 722 off += SHORT; 723 724 final int extraLen = ZipShort.getValue(lfhBuf, off); 725 off += SHORT; // NOSONAR - assignment as documentation 726 727 final byte[] fileName = readRange(fileNameLen); 728 current.entry.setName(entryEncoding.decode(fileName), fileName); 729 if (hasUTF8Flag) { 730 current.entry.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG); 731 } 732 733 final byte[] extraData = readRange(extraLen); 734 try { 735 current.entry.setExtra(extraData); 736 } catch (final RuntimeException ex) { 737 final ZipException z = new ZipException("Invalid extra data in entry " + current.entry.getName()); 738 z.initCause(ex); 739 throw z; 740 } 741 742 if (!hasUTF8Flag && useUnicodeExtraFields) { 743 ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null); 744 } 745 746 processZip64Extra(size, cSize); 747 748 current.entry.setLocalHeaderOffset(currentHeaderOffset); 749 current.entry.setDataOffset(getBytesRead()); 750 current.entry.setStreamContiguous(true); 751 752 final ZipMethod m = ZipMethod.getMethodByCode(current.entry.getMethod()); 753 if (current.entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN) { 754 if (ZipUtil.canHandleEntryData(current.entry) && m != ZipMethod.STORED && m != ZipMethod.DEFLATED) { 755 final InputStream bis = new BoundCountInputStream(in, current.entry.getCompressedSize()); 756 switch (m) { 757 case UNSHRINKING: 758 current.inputStream = new UnshrinkingInputStream(bis); 759 break; 760 case IMPLODING: 761 try { 762 current.inputStream = new ExplodingInputStream(current.entry.getGeneralPurposeBit().getSlidingDictionarySize(), 763 current.entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), bis); 764 } catch (final IllegalArgumentException ex) { 765 throw new IOException("bad IMPLODE data", ex); 766 } 767 break; 768 case BZIP2: 769 current.inputStream = new BZip2CompressorInputStream(bis); 770 break; 771 case ENHANCED_DEFLATED: 772 current.inputStream = new Deflate64CompressorInputStream(bis); 773 break; 774 default: 775 // we should never get here as all supported methods have been covered 776 // will cause an error when read is invoked, don't throw an exception here so people can 777 // skip unsupported entries 778 break; 779 } 780 } 781 } else if (m == ZipMethod.ENHANCED_DEFLATED) { 782 current.inputStream = new Deflate64CompressorInputStream(in); 783 } 784 785 entriesRead++; 786 return current.entry; 787 } 788 789 /** 790 * Gets the uncompressed count. 791 * 792 * @since 1.17 793 */ 794 @Override 795 public long getUncompressedCount() { 796 return uncompressedCount; 797 } 798 799 /** 800 * Checks whether this might be an APK Signing Block. 801 * <p> 802 * 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 803 * the suspect length, skip ahead far enough, look for the signature and if we've found it, return true. 804 * </p> 805 * 806 * @param suspectLocalFileHeader the bytes read from the underlying stream in the expectation that they would hold the local file header of the next entry. 807 * @return true if this looks like an APK signing block 808 * @see <a href="https://source.android.com/security/apksigning/v2">https://source.android.com/security/apksigning/v2</a> 809 */ 810 private boolean isApkSigningBlock(final byte[] suspectLocalFileHeader) throws IOException { 811 // length of block excluding the size field itself 812 final BigInteger len = ZipEightByteInteger.getValue(suspectLocalFileHeader); 813 // LFH has already been read and all but the first eight bytes contain (part of) the APK signing block, 814 // also subtract 16 bytes in order to position us at the magic string 815 BigInteger toSkip = len.add(BigInteger.valueOf(DWORD - suspectLocalFileHeader.length - (long) APK_SIGNING_BLOCK_MAGIC.length)); 816 final byte[] magic = new byte[APK_SIGNING_BLOCK_MAGIC.length]; 817 818 try { 819 if (toSkip.signum() < 0) { 820 // suspectLocalFileHeader contains the start of suspect magic string 821 final int off = suspectLocalFileHeader.length + toSkip.intValue(); 822 // length was shorter than magic length 823 if (off < DWORD) { 824 return false; 825 } 826 final int bytesInBuffer = Math.abs(toSkip.intValue()); 827 System.arraycopy(suspectLocalFileHeader, off, magic, 0, Math.min(bytesInBuffer, magic.length)); 828 if (bytesInBuffer < magic.length) { 829 readFully(magic, bytesInBuffer); 830 } 831 } else { 832 while (toSkip.compareTo(LONG_MAX) > 0) { 833 realSkip(Long.MAX_VALUE); 834 toSkip = toSkip.add(LONG_MAX.negate()); 835 } 836 realSkip(toSkip.longValue()); 837 readFully(magic); 838 } 839 } catch (final EOFException ex) { // NOSONAR 840 // length was invalid 841 return false; 842 } 843 return Arrays.equals(magic, APK_SIGNING_BLOCK_MAGIC); 844 } 845 846 private boolean isFirstByteOfEocdSig(final int b) { 847 return b == ZipArchiveOutputStream.EOCD_SIG[0]; 848 } 849 850 /** 851 * 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. 852 */ 853 private void processZip64Extra(final ZipLong size, final ZipLong cSize) throws ZipException { 854 final ZipExtraField extra = current.entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); 855 if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) { 856 throw new ZipException("archive contains unparseable zip64 extra field"); 857 } 858 final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra; 859 current.usesZip64 = z64 != null; 860 if (!current.hasDataDescriptor) { 861 if (z64 != null // same as current.usesZip64 but avoids NPE warning 862 && (ZipLong.ZIP64_MAGIC.equals(cSize) || ZipLong.ZIP64_MAGIC.equals(size))) { 863 if (z64.getCompressedSize() == null || z64.getSize() == null) { 864 // avoid NPE if it's a corrupted ZIP archive 865 throw new ZipException("archive contains corrupted zip64 extra field"); 866 } 867 long s = z64.getCompressedSize().getLongValue(); 868 if (s < 0) { 869 throw new ZipException("broken archive, entry with negative compressed size"); 870 } 871 current.entry.setCompressedSize(s); 872 s = z64.getSize().getLongValue(); 873 if (s < 0) { 874 throw new ZipException("broken archive, entry with negative size"); 875 } 876 current.entry.setSize(s); 877 } else if (cSize != null && size != null) { 878 if (cSize.getValue() < 0) { 879 throw new ZipException("broken archive, entry with negative compressed size"); 880 } 881 current.entry.setCompressedSize(cSize.getValue()); 882 if (size.getValue() < 0) { 883 throw new ZipException("broken archive, entry with negative size"); 884 } 885 current.entry.setSize(size.getValue()); 886 } 887 } 888 } 889 890 private void pushback(final byte[] buf, final int offset, final int length) throws IOException { 891 if (offset < 0) { 892 // Instead of ArrayIndexOutOfBoundsException 893 throw new IOException(String.format("Negative offset %,d into buffer", offset)); 894 } 895 ((PushbackInputStream) in).unread(buf, offset, length); 896 pushedBackBytes(length); 897 } 898 899 @Override 900 public int read(final byte[] buffer, final int offset, final int length) throws IOException { 901 if (length == 0) { 902 return 0; 903 } 904 if (closed) { 905 throw new IOException("The stream is closed"); 906 } 907 908 if (current == null) { 909 return -1; 910 } 911 912 // avoid int overflow, check null buffer 913 if (offset > buffer.length || length < 0 || offset < 0 || buffer.length - offset < length) { 914 throw new ArrayIndexOutOfBoundsException(); 915 } 916 917 ZipUtil.checkRequestedFeatures(current.entry); 918 if (!supportsDataDescriptorFor(current.entry)) { 919 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.DATA_DESCRIPTOR, current.entry); 920 } 921 if (!supportsCompressedSizeFor(current.entry)) { 922 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.UNKNOWN_COMPRESSED_SIZE, current.entry); 923 } 924 925 final int read; 926 if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) { 927 read = readStored(buffer, offset, length); 928 } else if (current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED) { 929 read = readDeflated(buffer, offset, length); 930 } else if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode() || current.entry.getMethod() == ZipMethod.IMPLODING.getCode() 931 || current.entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() || current.entry.getMethod() == ZipMethod.BZIP2.getCode()) { 932 read = current.inputStream.read(buffer, offset, length); 933 } else { 934 throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(current.entry.getMethod()), current.entry); 935 } 936 937 if (read >= 0) { 938 current.crc.update(buffer, offset, read); 939 uncompressedCount += read; 940 } 941 942 return read; 943 } 944 945 private void readDataDescriptor() throws IOException { 946 readFully(wordBuf); 947 ZipLong val = new ZipLong(wordBuf); 948 if (ZipLong.DD_SIG.equals(val)) { 949 // data descriptor with signature, skip sig 950 readFully(wordBuf); 951 val = new ZipLong(wordBuf); 952 } 953 current.entry.setCrc(val.getValue()); 954 955 // if there is a ZIP64 extra field, sizes are eight bytes 956 // each, otherwise four bytes each. Unfortunately some 957 // implementations - namely Java7 - use eight bytes without 958 // using a ZIP64 extra field - 959 // https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588 960 961 // just read 16 bytes and check whether bytes nine to twelve 962 // look like one of the signatures of what could follow a data 963 // descriptor (ignoring archive decryption headers for now). 964 // If so, push back eight bytes and assume sizes are four 965 // bytes, otherwise sizes are eight bytes each. 966 readFully(twoDwordBuf); 967 final ZipLong potentialSig = new ZipLong(twoDwordBuf, DWORD); 968 if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) { 969 pushback(twoDwordBuf, DWORD, DWORD); 970 long size = ZipLong.getValue(twoDwordBuf); 971 if (size < 0) { 972 throw new ZipException("broken archive, entry with negative compressed size"); 973 } 974 current.entry.setCompressedSize(size); 975 size = ZipLong.getValue(twoDwordBuf, WORD); 976 if (size < 0) { 977 throw new ZipException("broken archive, entry with negative size"); 978 } 979 current.entry.setSize(size); 980 } else { 981 long size = ZipEightByteInteger.getLongValue(twoDwordBuf); 982 if (size < 0) { 983 throw new ZipException("broken archive, entry with negative compressed size"); 984 } 985 current.entry.setCompressedSize(size); 986 size = ZipEightByteInteger.getLongValue(twoDwordBuf, DWORD); 987 if (size < 0) { 988 throw new ZipException("broken archive, entry with negative size"); 989 } 990 current.entry.setSize(size); 991 } 992 } 993 994 /** 995 * Implements read for DEFLATED entries. 996 */ 997 private int readDeflated(final byte[] buffer, final int offset, final int length) throws IOException { 998 final int read = readFromInflater(buffer, offset, length); 999 if (read <= 0) { 1000 if (inf.finished()) { 1001 return -1; 1002 } 1003 if (inf.needsDictionary()) { 1004 throw new ZipException("This archive needs a preset dictionary" + " which is not supported by Commons" + " Compress."); 1005 } 1006 if (read == -1) { 1007 throw new IOException("Truncated ZIP file"); 1008 } 1009 } 1010 return read; 1011 } 1012 1013 /** 1014 * Fills the given array with the first local file header and deals with splitting/spanning markers that may prefix the first LFH. 1015 */ 1016 private boolean readFirstLocalFileHeader() throws IOException { 1017 // for empty archive, we may get only EOCD size: 1018 final byte[] header = new byte[Math.min(LFH_LEN, ZipFile.MIN_EOCD_SIZE)]; 1019 readFully(header); 1020 try { 1021 READ_LOOP: for (int i = 0; ; ) { 1022 for (int j = 0; i <= PREAMBLE_GARBAGE_MAX_SIZE - 4 && j <= header.length - 4; ++j, ++i) { 1023 final ZipLong sig = new ZipLong(header, j); 1024 if (sig.equals(ZipLong.LFH_SIG) || 1025 sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || 1026 sig.equals(ZipLong.DD_SIG)) { 1027 // regular archive containing at least one entry: 1028 System.arraycopy(header, j, header, 0, header.length - j); 1029 readFully(header, header.length - j); 1030 break READ_LOOP; 1031 } 1032 if (sig.equals(new ZipLong(ZipArchiveOutputStream.EOCD_SIG))) { 1033 // empty archive: 1034 pushback(header, j, header.length - j); 1035 return false; 1036 } 1037 } 1038 if (i >= PREAMBLE_GARBAGE_MAX_SIZE - 4) { 1039 throw new ZipException("Cannot find zip signature within the first " + PREAMBLE_GARBAGE_MAX_SIZE + " bytes"); 1040 } 1041 System.arraycopy(header, header.length - 3, header, 0, 3); 1042 readFully(header, 3); 1043 } 1044 System.arraycopy(header, 0, lfhBuf, 0, header.length); 1045 readFully(lfhBuf, header.length); 1046 } catch (final EOFException ex) { 1047 throw new ZipException("Cannot find zip signature within the file"); 1048 } 1049 final ZipLong sig = new ZipLong(lfhBuf); 1050 1051 if (!skipSplitSig && sig.equals(ZipLong.DD_SIG)) { 1052 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.SPLITTING); 1053 } 1054 1055 // the split ZIP signature(08074B50) should only be skipped when the skipSplitSig is set 1056 if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER) || sig.equals(ZipLong.DD_SIG)) { 1057 // Just skip over the marker. 1058 System.arraycopy(lfhBuf, 4, lfhBuf, 0, lfhBuf.length - 4); 1059 readFully(lfhBuf, lfhBuf.length - 4); 1060 } 1061 return true; 1062 } 1063 1064 /** 1065 * Potentially reads more bytes to fill the inflater's buffer and reads from it. 1066 */ 1067 private int readFromInflater(final byte[] buffer, final int offset, final int length) throws IOException { 1068 int read = 0; 1069 do { 1070 if (inf.needsInput()) { 1071 final int l = fill(); 1072 if (l > 0) { 1073 current.bytesReadFromStream += buf.limit(); 1074 } else if (l == -1) { 1075 return -1; 1076 } else { 1077 break; 1078 } 1079 } 1080 try { 1081 read = inf.inflate(buffer, offset, length); 1082 } catch (final DataFormatException e) { 1083 throw (IOException) new ZipException(e.getMessage()).initCause(e); 1084 } 1085 } while (read == 0 && inf.needsInput()); 1086 return read; 1087 } 1088 1089 private void readFully(final byte[] b) throws IOException { 1090 readFully(b, 0); 1091 } 1092 1093 private void readFully(final byte[] b, final int off) throws IOException { 1094 final int len = b.length - off; 1095 final int count = IOUtils.readFully(in, b, off, len); 1096 count(count); 1097 if (count < len) { 1098 throw new EOFException(); 1099 } 1100 } 1101 1102 // End of Central Directory Record 1103 // end of central dir signature WORD 1104 // number of this disk SHORT 1105 // number of the disk with the 1106 // start of the central directory SHORT 1107 // total number of entries in the 1108 // central directory on this disk SHORT 1109 // total number of entries in 1110 // the central directory SHORT 1111 // size of the central directory WORD 1112 // offset of start of central 1113 // directory with respect to 1114 // the starting disk number WORD 1115 // .ZIP file comment length SHORT 1116 // .ZIP file comment up to 64KB 1117 // 1118 1119 /** 1120 * Reads bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #read} would do. 1121 * 1122 * Also updates bytes-read counter. 1123 */ 1124 private int readOneByte() throws IOException { 1125 final int b = in.read(); 1126 if (b != -1) { 1127 count(1); 1128 } 1129 return b; 1130 } 1131 1132 private byte[] readRange(final int len) throws IOException { 1133 final byte[] ret = IOUtils.readRange(in, len); 1134 count(ret.length); 1135 if (ret.length < len) { 1136 throw new EOFException(); 1137 } 1138 return ret; 1139 } 1140 1141 /** 1142 * Implements read for STORED entries. 1143 */ 1144 private int readStored(final byte[] buffer, final int offset, final int length) throws IOException { 1145 1146 if (current.hasDataDescriptor) { 1147 if (lastStoredEntry == null) { 1148 readStoredEntry(); 1149 } 1150 return lastStoredEntry.read(buffer, offset, length); 1151 } 1152 1153 final long csize = current.entry.getSize(); 1154 if (current.bytesRead >= csize) { 1155 return -1; 1156 } 1157 1158 if (buf.position() >= buf.limit()) { 1159 buf.position(0); 1160 final int l = in.read(buf.array()); 1161 if (l == -1) { 1162 buf.limit(0); 1163 throw new IOException("Truncated ZIP file"); 1164 } 1165 buf.limit(l); 1166 1167 count(l); 1168 current.bytesReadFromStream += l; 1169 } 1170 1171 int toRead = Math.min(buf.remaining(), length); 1172 if (csize - current.bytesRead < toRead) { 1173 // if it is smaller than toRead then it fits into an int 1174 toRead = (int) (csize - current.bytesRead); 1175 } 1176 buf.get(buffer, offset, toRead); 1177 current.bytesRead += toRead; 1178 return toRead; 1179 } 1180 1181 /** 1182 * Caches a stored entry that uses the data descriptor. 1183 * <ul> 1184 * <li>Reads a stored entry until the signature of a local file header, central directory header or data descriptor has been found.</li> 1185 * <li>Stores all entry data in lastStoredEntry. 1186 * </p> 1187 * <li>Rewinds the stream to position at the data descriptor.</li> 1188 * <li>reads the data descriptor</li> 1189 * </ul> 1190 * <p> 1191 * 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 1192 * directory header. 1193 * </p> 1194 */ 1195 private void readStoredEntry() throws IOException { 1196 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 1197 int off = 0; 1198 boolean done = false; 1199 1200 // length of DD without signature 1201 final int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD; 1202 1203 while (!done) { 1204 final int r = in.read(buf.array(), off, ZipArchiveOutputStream.BUFFER_SIZE - off); 1205 if (r <= 0) { 1206 // read the whole archive without ever finding a 1207 // central directory 1208 throw new IOException("Truncated ZIP file"); 1209 } 1210 if (r + off < 4) { 1211 // buffer too small to check for a signature, loop 1212 off += r; 1213 continue; 1214 } 1215 1216 done = bufferContainsSignature(bos, off, r, ddLen); 1217 if (!done) { 1218 off = cacheBytesRead(bos, off, r, ddLen); 1219 } 1220 } 1221 if (current.entry.getCompressedSize() != current.entry.getSize()) { 1222 throw new ZipException("compressed and uncompressed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER); 1223 } 1224 final byte[] b = bos.toByteArray(); 1225 if (b.length != current.entry.getSize()) { 1226 throw new ZipException("actual and claimed size don't match" + USE_ZIPFILE_INSTEAD_OF_STREAM_DISCLAIMER); 1227 } 1228 lastStoredEntry = new ByteArrayInputStream(b); 1229 } 1230 1231 /** 1232 * Skips bytes by reading from the underlying stream rather than the (potentially inflating) archive stream - which {@link #skip} would do. 1233 * 1234 * Also updates bytes-read counter. 1235 */ 1236 private void realSkip(final long value) throws IOException { 1237 if (value >= 0) { 1238 long skipped = 0; 1239 while (skipped < value) { 1240 final long rem = value - skipped; 1241 final int x = in.read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length)); 1242 if (x == -1) { 1243 return; 1244 } 1245 count(x); 1246 skipped += x; 1247 } 1248 return; 1249 } 1250 throw new IllegalArgumentException(); 1251 } 1252 1253 /** 1254 * Currently unused. 1255 * 1256 * Sets the custom extra fields factory. 1257 * @param extraFieldSupport the lookup function based on extra field header id. 1258 * @return the archive. 1259 */ 1260 public ZipArchiveInputStream setExtraFieldSupport(final Function<ZipShort, ZipExtraField> extraFieldSupport) { 1261 // this.extraFieldSupport = extraFieldSupport; 1262 return this; 1263 } 1264 1265 /** 1266 * Skips over and discards value bytes of data from this input stream. 1267 * <p> 1268 * 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. 1269 * </p> 1270 * <p> 1271 * The actual number of bytes skipped is returned. 1272 * </p> 1273 * 1274 * @param value the number of bytes to be skipped. 1275 * @return the actual number of bytes skipped. 1276 * @throws IOException - if an I/O error occurs. 1277 * @throws IllegalArgumentException - if value is negative. 1278 */ 1279 @Override 1280 public long skip(final long value) throws IOException { 1281 if (value >= 0) { 1282 long skipped = 0; 1283 while (skipped < value) { 1284 final long rem = value - skipped; 1285 final int x = read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length)); 1286 if (x == -1) { 1287 return skipped; 1288 } 1289 skipped += x; 1290 } 1291 return skipped; 1292 } 1293 throw new IllegalArgumentException("Negative skip value"); 1294 } 1295 1296 /** 1297 * Reads the stream until it find the "End of central directory record" and consumes it as well. 1298 */ 1299 private void skipRemainderOfArchive() throws IOException { 1300 // skip over central directory. One LFH has been read too much 1301 // already. The calculation discounts file names and extra 1302 // data, so it will be too short. 1303 if (entriesRead > 0) { 1304 realSkip((long) entriesRead * CFH_LEN - LFH_LEN); 1305 } 1306 final boolean foundEocd = findEocdRecord(); 1307 if (foundEocd) { 1308 realSkip((long) ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* comment len */); 1309 readFully(shortBuf); 1310 // file comment 1311 final int commentLen = ZipShort.getValue(shortBuf); 1312 if (commentLen >= 0) { 1313 realSkip(commentLen); 1314 return; 1315 } 1316 } 1317 throw new IOException("Truncated ZIP file"); 1318 } 1319 1320 /** 1321 * Whether the compressed size for the entry is either known or not required by the compression method being used. 1322 */ 1323 private boolean supportsCompressedSizeFor(final ZipArchiveEntry entry) { 1324 return entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN || entry.getMethod() == ZipEntry.DEFLATED 1325 || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() 1326 || entry.getGeneralPurposeBit().usesDataDescriptor() && allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED; 1327 } 1328 1329 /** 1330 * Whether this entry requires a data descriptor this library can work with. 1331 * 1332 * @return true if allowStoredEntriesWithDataDescriptor is true, the entry doesn't require any data descriptor or the method is DEFLATED or 1333 * ENHANCED_DEFLATED. 1334 */ 1335 private boolean supportsDataDescriptorFor(final ZipArchiveEntry entry) { 1336 return !entry.getGeneralPurposeBit().usesDataDescriptor() || allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED 1337 || entry.getMethod() == ZipEntry.DEFLATED || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode(); 1338 } 1339}