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 */ 019 package org.apache.commons.compress.archivers.zip; 020 021 import java.io.ByteArrayInputStream; 022 import java.io.ByteArrayOutputStream; 023 import java.io.EOFException; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.PushbackInputStream; 027 import java.util.zip.CRC32; 028 import java.util.zip.DataFormatException; 029 import java.util.zip.Inflater; 030 import java.util.zip.ZipException; 031 032 import org.apache.commons.compress.archivers.ArchiveEntry; 033 import org.apache.commons.compress.archivers.ArchiveInputStream; 034 035 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 036 import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 037 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 038 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC; 039 040 /** 041 * Implements an input stream that can read Zip archives. 042 * 043 * <p>Note that {@link ZipArchiveEntry#getSize()} may return -1 if the 044 * DEFLATE algorithm is used, as the size information is not available 045 * from the header.</p> 046 * 047 * <p>The {@link ZipFile} class is preferred when reading from files.</p> 048 * 049 * <p>As of Apache Commons Compress it transparently supports Zip64 050 * extensions and thus individual entries and archives larger than 4 051 * GB or with more than 65536 entries.</p> 052 * 053 * @see ZipFile 054 * @NotThreadSafe 055 */ 056 public class ZipArchiveInputStream extends ArchiveInputStream { 057 058 /** 059 * The zip encoding to use for filenames and the file comment. 060 */ 061 private final ZipEncoding zipEncoding; 062 063 /** 064 * Whether to look for and use Unicode extra fields. 065 */ 066 private final boolean useUnicodeExtraFields; 067 068 /** 069 * Wrapped stream, will always be a PushbackInputStream. 070 */ 071 private final InputStream in; 072 073 /** 074 * Inflater used for all deflated entries. 075 */ 076 private final Inflater inf = new Inflater(true); 077 078 /** 079 * Calculates checkusms for all entries. 080 */ 081 private final CRC32 crc = new CRC32(); 082 083 /** 084 * Buffer used to read from the wrapped stream. 085 */ 086 private final Buffer buf = new Buffer(); 087 /** 088 * The entry that is currently being read. 089 */ 090 private CurrentEntry current = null; 091 /** 092 * Whether the stream has been closed. 093 */ 094 private boolean closed = false; 095 /** 096 * Whether the stream has reached the central directory - and thus 097 * found all entries. 098 */ 099 private boolean hitCentralDirectory = false; 100 /** 101 * When reading a stored entry that uses the data descriptor this 102 * stream has to read the full entry and caches it. This is the 103 * cache. 104 */ 105 private ByteArrayInputStream lastStoredEntry = null; 106 107 /** 108 * Whether the stream will try to read STORED entries that use a 109 * data descriptor. 110 */ 111 private boolean allowStoredEntriesWithDataDescriptor = false; 112 113 private static final int LFH_LEN = 30; 114 /* 115 local file header signature 4 bytes (0x04034b50) 116 version needed to extract 2 bytes 117 general purpose bit flag 2 bytes 118 compression method 2 bytes 119 last mod file time 2 bytes 120 last mod file date 2 bytes 121 crc-32 4 bytes 122 compressed size 4 bytes 123 uncompressed size 4 bytes 124 file name length 2 bytes 125 extra field length 2 bytes 126 */ 127 128 private static final long TWO_EXP_32 = ZIP64_MAGIC + 1; 129 130 public ZipArchiveInputStream(InputStream inputStream) { 131 this(inputStream, ZipEncodingHelper.UTF8, true); 132 } 133 134 /** 135 * @param encoding the encoding to use for file names, use null 136 * for the platform's default encoding 137 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 138 * Extra Fields (if present) to set the file names. 139 */ 140 public ZipArchiveInputStream(InputStream inputStream, 141 String encoding, 142 boolean useUnicodeExtraFields) { 143 this(inputStream, encoding, useUnicodeExtraFields, false); 144 } 145 146 /** 147 * @param encoding the encoding to use for file names, use null 148 * for the platform's default encoding 149 * @param useUnicodeExtraFields whether to use InfoZIP Unicode 150 * Extra Fields (if present) to set the file names. 151 * @param allowStoredEntriesWithDataDescriptor whether the stream 152 * will try to read STORED entries that use a data descriptor 153 * @since Apache Commons Compress 1.1 154 */ 155 public ZipArchiveInputStream(InputStream inputStream, 156 String encoding, 157 boolean useUnicodeExtraFields, 158 boolean allowStoredEntriesWithDataDescriptor) { 159 zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 160 this.useUnicodeExtraFields = useUnicodeExtraFields; 161 in = new PushbackInputStream(inputStream, buf.buf.length); 162 this.allowStoredEntriesWithDataDescriptor = 163 allowStoredEntriesWithDataDescriptor; 164 } 165 166 public ZipArchiveEntry getNextZipEntry() throws IOException { 167 if (closed || hitCentralDirectory) { 168 return null; 169 } 170 if (current != null) { 171 closeEntry(); 172 } 173 byte[] lfh = new byte[LFH_LEN]; 174 try { 175 readFully(lfh); 176 } catch (EOFException e) { 177 return null; 178 } 179 ZipLong sig = new ZipLong(lfh); 180 if (sig.equals(ZipLong.CFH_SIG)) { 181 hitCentralDirectory = true; 182 return null; 183 } 184 if (!sig.equals(ZipLong.LFH_SIG)) { 185 return null; 186 } 187 188 int off = WORD; 189 current = new CurrentEntry(); 190 191 int versionMadeBy = ZipShort.getValue(lfh, off); 192 off += SHORT; 193 current.entry.setPlatform((versionMadeBy >> ZipFile.BYTE_SHIFT) 194 & ZipFile.NIBLET_MASK); 195 196 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfh, off); 197 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); 198 final ZipEncoding entryEncoding = 199 hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; 200 current.hasDataDescriptor = gpFlag.usesDataDescriptor(); 201 current.entry.setGeneralPurposeBit(gpFlag); 202 203 off += SHORT; 204 205 current.entry.setMethod(ZipShort.getValue(lfh, off)); 206 off += SHORT; 207 208 long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfh, off)); 209 current.entry.setTime(time); 210 off += WORD; 211 212 ZipLong size = null, cSize = null; 213 if (!current.hasDataDescriptor) { 214 current.entry.setCrc(ZipLong.getValue(lfh, off)); 215 off += WORD; 216 217 cSize = new ZipLong(lfh, off); 218 off += WORD; 219 220 size = new ZipLong(lfh, off); 221 off += WORD; 222 } else { 223 off += 3 * WORD; 224 } 225 226 int fileNameLen = ZipShort.getValue(lfh, off); 227 228 off += SHORT; 229 230 int extraLen = ZipShort.getValue(lfh, off); 231 off += SHORT; 232 233 byte[] fileName = new byte[fileNameLen]; 234 readFully(fileName); 235 current.entry.setName(entryEncoding.decode(fileName), fileName); 236 237 byte[] extraData = new byte[extraLen]; 238 readFully(extraData); 239 current.entry.setExtra(extraData); 240 241 if (!hasUTF8Flag && useUnicodeExtraFields) { 242 ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, 243 null); 244 } 245 246 processZip64Extra(size, cSize); 247 return current.entry; 248 } 249 250 /** 251 * Records whether a Zip64 extra is present and sets the size 252 * information from it if sizes are 0xFFFFFFFF and the entry 253 * doesn't use a data descriptor. 254 */ 255 private void processZip64Extra(ZipLong size, ZipLong cSize) { 256 Zip64ExtendedInformationExtraField z64 = 257 (Zip64ExtendedInformationExtraField) 258 current.entry.getExtraField(Zip64ExtendedInformationExtraField 259 .HEADER_ID); 260 current.usesZip64 = z64 != null; 261 if (!current.hasDataDescriptor) { 262 if (current.usesZip64 && (cSize.equals(ZipLong.ZIP64_MAGIC) 263 || size.equals(ZipLong.ZIP64_MAGIC)) 264 ) { 265 current.entry.setCompressedSize(z64.getCompressedSize() // z64 cannot be null here 266 .getLongValue()); 267 current.entry.setSize(z64.getSize().getLongValue()); 268 } else { 269 current.entry.setCompressedSize(cSize.getValue()); 270 current.entry.setSize(size.getValue()); 271 } 272 } 273 } 274 275 /** {@inheritDoc} */ 276 @Override 277 public ArchiveEntry getNextEntry() throws IOException { 278 return getNextZipEntry(); 279 } 280 281 /** 282 * Whether this class is able to read the given entry. 283 * 284 * <p>May return false if it is set up to use encryption or a 285 * compression method that hasn't been implemented yet.</p> 286 * @since Apache Commons Compress 1.1 287 */ 288 @Override 289 public boolean canReadEntryData(ArchiveEntry ae) { 290 if (ae instanceof ZipArchiveEntry) { 291 ZipArchiveEntry ze = (ZipArchiveEntry) ae; 292 return ZipUtil.canHandleEntryData(ze) 293 && supportsDataDescriptorFor(ze); 294 295 } 296 return false; 297 } 298 299 @Override 300 public int read(byte[] buffer, int start, int length) throws IOException { 301 if (closed) { 302 throw new IOException("The stream is closed"); 303 } 304 if (inf.finished() || current == null) { 305 return -1; 306 } 307 308 // avoid int overflow, check null buffer 309 if (start <= buffer.length && length >= 0 && start >= 0 310 && buffer.length - start >= length) { 311 ZipUtil.checkRequestedFeatures(current.entry); 312 if (!supportsDataDescriptorFor(current.entry)) { 313 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException 314 .Feature 315 .DATA_DESCRIPTOR, 316 current.entry); 317 } 318 319 if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) { 320 return readStored(buffer, start, length); 321 } 322 return readDeflated(buffer, start, length); 323 } 324 throw new ArrayIndexOutOfBoundsException(); 325 } 326 327 /** 328 * Implementation of read for STORED entries. 329 */ 330 private int readStored(byte[] buffer, int start, int length) 331 throws IOException { 332 333 if (current.hasDataDescriptor) { 334 if (lastStoredEntry == null) { 335 readStoredEntry(); 336 } 337 return lastStoredEntry.read(buffer, start, length); 338 } 339 340 long csize = current.entry.getSize(); 341 if (current.bytesRead >= csize) { 342 return -1; 343 } 344 345 if (buf.offsetInBuffer >= buf.lengthOfLastRead) { 346 buf.offsetInBuffer = 0; 347 if ((buf.lengthOfLastRead = in.read(buf.buf)) == -1) { 348 return -1; 349 } 350 count(buf.lengthOfLastRead); 351 current.bytesReadFromStream += buf.lengthOfLastRead; 352 } 353 354 int toRead = length > buf.lengthOfLastRead 355 ? buf.lengthOfLastRead - buf.offsetInBuffer 356 : length; 357 if ((csize - current.bytesRead) < toRead) { 358 // if it is smaller than toRead then it fits into an int 359 toRead = (int) (csize - current.bytesRead); 360 } 361 System.arraycopy(buf.buf, buf.offsetInBuffer, buffer, start, toRead); 362 buf.offsetInBuffer += toRead; 363 current.bytesRead += toRead; 364 crc.update(buffer, start, toRead); 365 return toRead; 366 } 367 368 /** 369 * Implementation of read for DEFLATED entries. 370 */ 371 private int readDeflated(byte[] buffer, int start, int length) 372 throws IOException { 373 if (inf.needsInput()) { 374 fill(); 375 if (buf.lengthOfLastRead > 0) { 376 current.bytesReadFromStream += buf.lengthOfLastRead; 377 } 378 } 379 int read = 0; 380 try { 381 read = inf.inflate(buffer, start, length); 382 } catch (DataFormatException e) { 383 throw new ZipException(e.getMessage()); 384 } 385 if (read == 0) { 386 if (inf.finished()) { 387 return -1; 388 } else if (buf.lengthOfLastRead == -1) { 389 throw new IOException("Truncated ZIP file"); 390 } 391 } 392 crc.update(buffer, start, read); 393 return read; 394 } 395 396 @Override 397 public void close() throws IOException { 398 if (!closed) { 399 closed = true; 400 in.close(); 401 inf.end(); 402 } 403 } 404 405 /** 406 * Skips over and discards value bytes of data from this input 407 * stream. 408 * 409 * <p>This implementation may end up skipping over some smaller 410 * number of bytes, possibly 0, if and only if it reaches the end 411 * of the underlying stream.</p> 412 * 413 * <p>The actual number of bytes skipped is returned.</p> 414 * 415 * @param value the number of bytes to be skipped. 416 * @return the actual number of bytes skipped. 417 * @throws IOException - if an I/O error occurs. 418 * @throws IllegalArgumentException - if value is negative. 419 */ 420 @Override 421 public long skip(long value) throws IOException { 422 if (value >= 0) { 423 long skipped = 0; 424 byte[] b = new byte[1024]; 425 while (skipped < value) { 426 long rem = value - skipped; 427 int x = read(b, 0, (int) (b.length > rem ? rem : b.length)); 428 if (x == -1) { 429 return skipped; 430 } 431 skipped += x; 432 } 433 return skipped; 434 } 435 throw new IllegalArgumentException(); 436 } 437 438 /** 439 * Checks if the signature matches what is expected for a zip file. 440 * Does not currently handle self-extracting zips which may have arbitrary 441 * leading content. 442 * 443 * @param signature 444 * the bytes to check 445 * @param length 446 * the number of bytes to check 447 * @return true, if this stream is a zip archive stream, false otherwise 448 */ 449 public static boolean matches(byte[] signature, int length) { 450 if (length < ZipArchiveOutputStream.LFH_SIG.length) { 451 return false; 452 } 453 454 return checksig(signature, ZipArchiveOutputStream.LFH_SIG) // normal file 455 || checksig(signature, ZipArchiveOutputStream.EOCD_SIG); // empty zip 456 } 457 458 private static boolean checksig(byte[] signature, byte[] expected){ 459 for (int i = 0; i < expected.length; i++) { 460 if (signature[i] != expected[i]) { 461 return false; 462 } 463 } 464 return true; 465 } 466 467 /** 468 * Closes the current ZIP archive entry and positions the underlying 469 * stream to the beginning of the next entry. All per-entry variables 470 * and data structures are cleared. 471 * <p> 472 * If the compressed size of this entry is included in the entry header, 473 * then any outstanding bytes are simply skipped from the underlying 474 * stream without uncompressing them. This allows an entry to be safely 475 * closed even if the compression method is unsupported. 476 * <p> 477 * In case we don't know the compressed size of this entry or have 478 * already buffered too much data from the underlying stream to support 479 * uncompression, then the uncompression process is completed and the 480 * end position of the stream is adjusted based on the result of that 481 * process. 482 * 483 * @throws IOException if an error occurs 484 */ 485 private void closeEntry() throws IOException { 486 if (closed) { 487 throw new IOException("The stream is closed"); 488 } 489 if (current == null) { 490 return; 491 } 492 493 // Ensure all entry bytes are read 494 if (current.bytesReadFromStream <= current.entry.getCompressedSize() 495 && !current.hasDataDescriptor) { 496 drainCurrentEntryData(); 497 } else { 498 skip(Long.MAX_VALUE); 499 500 long inB = 501 current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED 502 ? getBytesInflated() : current.bytesRead; 503 504 // this is at most a single read() operation and can't 505 // exceed the range of int 506 int diff = (int) (current.bytesReadFromStream - inB); 507 508 // Pushback any required bytes 509 if (diff > 0) { 510 pushback(buf.buf, buf.lengthOfLastRead - diff, diff); 511 } 512 } 513 514 if (lastStoredEntry == null && current.hasDataDescriptor) { 515 readDataDescriptor(); 516 } 517 518 inf.reset(); 519 buf.reset(); 520 crc.reset(); 521 current = null; 522 lastStoredEntry = null; 523 } 524 525 /** 526 * Read all data of the current entry from the underlying stream 527 * that hasn't been read, yet. 528 */ 529 private void drainCurrentEntryData() throws IOException { 530 long remaining = current.entry.getCompressedSize() 531 - current.bytesReadFromStream; 532 while (remaining > 0) { 533 long n = in.read(buf.buf, 0, (int) Math.min(buf.buf.length, 534 remaining)); 535 if (n < 0) { 536 throw new EOFException( 537 "Truncated ZIP entry: " + current.entry.getName()); 538 } else { 539 count(n); 540 remaining -= n; 541 } 542 } 543 } 544 545 /** 546 * Get the number of bytes Inflater has actually processed. 547 * 548 * <p>for Java < Java7 the getBytes* methods in 549 * Inflater/Deflater seem to return unsigned ints rather than 550 * longs that start over with 0 at 2^32.</p> 551 * 552 * <p>The stream knows how many bytes it has read, but not how 553 * many the Inflater actually consumed - it should be between the 554 * total number of bytes read for the entry and the total number 555 * minus the last read operation. Here we just try to make the 556 * value close enough to the bytes we've read by assuming the 557 * number of bytes consumed must be smaller than (or equal to) the 558 * number of bytes read but not smaller by more than 2^32.</p> 559 */ 560 private long getBytesInflated() { 561 long inB = inf.getBytesRead(); 562 if (current.bytesReadFromStream >= TWO_EXP_32) { 563 while (inB + TWO_EXP_32 <= current.bytesReadFromStream) { 564 inB += TWO_EXP_32; 565 } 566 } 567 return inB; 568 } 569 570 private void fill() throws IOException { 571 if (closed) { 572 throw new IOException("The stream is closed"); 573 } 574 if ((buf.lengthOfLastRead = in.read(buf.buf)) > 0) { 575 count(buf.lengthOfLastRead); 576 inf.setInput(buf.buf, 0, buf.lengthOfLastRead); 577 } 578 } 579 580 private void readFully(byte[] b) throws IOException { 581 int count = 0, x = 0; 582 while (count != b.length) { 583 count += x = in.read(b, count, b.length - count); 584 if (x == -1) { 585 throw new EOFException(); 586 } 587 count(x); 588 } 589 } 590 591 private void readDataDescriptor() throws IOException { 592 byte[] b = new byte[WORD]; 593 readFully(b); 594 ZipLong val = new ZipLong(b); 595 if (ZipLong.DD_SIG.equals(val)) { 596 // data descriptor with signature, skip sig 597 readFully(b); 598 val = new ZipLong(b); 599 } 600 current.entry.setCrc(val.getValue()); 601 602 // if there is a ZIP64 extra field, sizes are eight bytes 603 // each, otherwise four bytes each. Unfortunately some 604 // implementations - namely Java7 - use eight bytes without 605 // using a ZIP64 extra field - 606 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588 607 608 // just read 16 bytes and check whether bytes nine to twelve 609 // look like one of the signatures of what could follow a data 610 // descriptor (ignoring archive decryption headers for now). 611 // If so, push back eight bytes and assume sizes are four 612 // bytes, otherwise sizes are eight bytes each. 613 b = new byte[2 * DWORD]; 614 readFully(b); 615 ZipLong potentialSig = new ZipLong(b, DWORD); 616 if (potentialSig.equals(ZipLong.CFH_SIG) 617 || potentialSig.equals(ZipLong.LFH_SIG)) { 618 pushback(b, DWORD, DWORD); 619 current.entry.setCompressedSize(ZipLong.getValue(b)); 620 current.entry.setSize(ZipLong.getValue(b, WORD)); 621 } else { 622 current.entry 623 .setCompressedSize(ZipEightByteInteger.getLongValue(b)); 624 current.entry.setSize(ZipEightByteInteger.getLongValue(b, DWORD)); 625 } 626 } 627 628 /** 629 * Whether this entry requires a data descriptor this library can work with. 630 * 631 * @return true if allowStoredEntriesWithDataDescriptor is true, 632 * the entry doesn't require any data descriptor or the method is 633 * DEFLATED. 634 */ 635 private boolean supportsDataDescriptorFor(ZipArchiveEntry entry) { 636 return allowStoredEntriesWithDataDescriptor || 637 !entry.getGeneralPurposeBit().usesDataDescriptor() 638 || entry.getMethod() == ZipArchiveEntry.DEFLATED; 639 } 640 641 /** 642 * Caches a stored entry that uses the data descriptor. 643 * 644 * <ul> 645 * <li>Reads a stored entry until the signature of a local file 646 * header, central directory header or data descriptor has been 647 * found.</li> 648 * <li>Stores all entry data in lastStoredEntry.</p> 649 * <li>Rewinds the stream to position at the data 650 * descriptor.</li> 651 * <li>reads the data descriptor</li> 652 * </ul> 653 * 654 * <p>After calling this method the entry should know its size, 655 * the entry's data is cached and the stream is positioned at the 656 * next local file or central directory header.</p> 657 */ 658 private void readStoredEntry() throws IOException { 659 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 660 int off = 0; 661 boolean done = false; 662 663 // length of DD without signature 664 int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD; 665 666 while (!done) { 667 int r = in.read(buf.buf, off, 668 ZipArchiveOutputStream.BUFFER_SIZE - off); 669 if (r <= 0) { 670 // read the whole archive without ever finding a 671 // central directory 672 throw new IOException("Truncated ZIP file"); 673 } 674 if (r + off < 4) { 675 // buf is too small to check for a signature, loop 676 off += r; 677 continue; 678 } 679 680 done = bufferContainsSignature(bos, off, r, ddLen); 681 if (!done) { 682 off = cacheBytesRead(bos, off, r, ddLen); 683 } 684 } 685 686 byte[] b = bos.toByteArray(); 687 lastStoredEntry = new ByteArrayInputStream(b); 688 } 689 690 private static final byte[] LFH = ZipLong.LFH_SIG.getBytes(); 691 private static final byte[] CFH = ZipLong.CFH_SIG.getBytes(); 692 private static final byte[] DD = ZipLong.DD_SIG.getBytes(); 693 694 /** 695 * Checks whether the current buffer contains the signature of a 696 * "data decsriptor", "local file header" or 697 * "central directory entry". 698 * 699 * <p>If it contains such a signature, reads the data descriptor 700 * and positions the stream right after the data descriptor.</p> 701 */ 702 private boolean bufferContainsSignature(ByteArrayOutputStream bos, 703 int offset, int lastRead, 704 int expectedDDLen) 705 throws IOException { 706 boolean done = false; 707 int readTooMuch = 0; 708 for (int i = 0; !done && i < lastRead - 4; i++) { 709 if (buf.buf[i] == LFH[0] && buf.buf[i + 1] == LFH[1]) { 710 if ((buf.buf[i + 2] == LFH[2] && buf.buf[i + 3] == LFH[3]) 711 || (buf.buf[i] == CFH[2] && buf.buf[i + 3] == CFH[3])) { 712 // found a LFH or CFH: 713 readTooMuch = offset + lastRead - i - expectedDDLen; 714 done = true; 715 } 716 else if (buf.buf[i + 2] == DD[2] && buf.buf[i + 3] == DD[3]) { 717 // found DD: 718 readTooMuch = offset + lastRead - i; 719 done = true; 720 } 721 if (done) { 722 // * push back bytes read in excess as well as the data 723 // descriptor 724 // * copy the remaining bytes to cache 725 // * read data descriptor 726 pushback(buf.buf, offset + lastRead - readTooMuch, 727 readTooMuch); 728 bos.write(buf.buf, 0, i); 729 readDataDescriptor(); 730 } 731 } 732 } 733 return done; 734 } 735 736 /** 737 * If the last read bytes could hold a data descriptor and an 738 * incomplete signature then save the last bytes to the front of 739 * the buffer and cache everything in front of the potential data 740 * descriptor into the given ByteArrayOutputStream. 741 * 742 * <p>Data descriptor plus incomplete signature (3 bytes in the 743 * worst case) can be 20 bytes max.</p> 744 */ 745 private int cacheBytesRead(ByteArrayOutputStream bos, int offset, 746 int lastRead, int expecteDDLen) { 747 final int cacheable = offset + lastRead - expecteDDLen - 3; 748 if (cacheable > 0) { 749 bos.write(buf.buf, 0, cacheable); 750 System.arraycopy(buf.buf, cacheable, buf.buf, 0, 751 expecteDDLen + 3); 752 offset = expecteDDLen + 3; 753 } else { 754 offset += lastRead; 755 } 756 return offset; 757 } 758 759 private void pushback(byte[] buf, int offset, int length) 760 throws IOException { 761 ((PushbackInputStream) in).unread(buf, offset, length); 762 pushedBackBytes(length); 763 } 764 765 /** 766 * Structure collecting information for the entry that is 767 * currently being read. 768 */ 769 private static final class CurrentEntry { 770 /** 771 * Current ZIP entry. 772 */ 773 private final ZipArchiveEntry entry = new ZipArchiveEntry(); 774 /** 775 * Does the entry use a data descriptor? 776 */ 777 private boolean hasDataDescriptor; 778 /** 779 * Does the entry have a ZIP64 extended information extra field. 780 */ 781 private boolean usesZip64; 782 /** 783 * Number of bytes of entry content read by the client if the 784 * entry is STORED. 785 */ 786 private long bytesRead; 787 /** 788 * Number of bytes of entry content read so from the stream. 789 * 790 * <p>This may be more than the actual entry's length as some 791 * stuff gets buffered up and needs to be pushed back when the 792 * end of the entry has been reached.</p> 793 */ 794 private long bytesReadFromStream; 795 } 796 797 /** 798 * Contains a temporary buffer used to read from the wrapped 799 * stream together with some information needed for internal 800 * housekeeping. 801 */ 802 private static final class Buffer { 803 /** 804 * Buffer used as temporary buffer when reading from the stream. 805 */ 806 private final byte[] buf = new byte[ZipArchiveOutputStream.BUFFER_SIZE]; 807 /** 808 * {@link #buf buf} may contain data the client hasnt read, yet, 809 * this is the first byte that hasn't been read so far. 810 */ 811 private int offsetInBuffer = 0; 812 /** 813 * Number of bytes read from the wrapped stream into {@link #buf 814 * buf} with the last read operation. 815 */ 816 private int lengthOfLastRead = 0; 817 /** 818 * Reset internal housekeeping. 819 */ 820 private void reset() { 821 offsetInBuffer = lengthOfLastRead = 0; 822 } 823 } 824 }