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