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 java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.EOFException;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.PushbackInputStream;
027import java.nio.ByteBuffer;
028import java.util.zip.CRC32;
029import java.util.zip.DataFormatException;
030import java.util.zip.Inflater;
031import java.util.zip.ZipEntry;
032import java.util.zip.ZipException;
033
034import org.apache.commons.compress.archivers.ArchiveEntry;
035import org.apache.commons.compress.archivers.ArchiveInputStream;
036import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
037import org.apache.commons.compress.utils.ArchiveUtils;
038import org.apache.commons.compress.utils.IOUtils;
039
040import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
041import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
042import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
043import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
044
045/**
046 * Implements an input stream that can read Zip archives.
047 *
048 * <p>As of Apache Commons Compress it transparently supports Zip64
049 * extensions and thus individual entries and archives larger than 4
050 * GB or with more than 65536 entries.</p>
051 *
052 * <p>The {@link ZipFile} class is preferred when reading from files
053 * as {@link ZipArchiveInputStream} is limited by not being able to
054 * read the central directory header before returning entries.  In
055 * particular {@link ZipArchiveInputStream}</p>
056 *
057 * <ul>
058 *
059 *  <li>may return entries that are not part of the central directory
060 *  at all and shouldn't be considered part of the archive.</li>
061 *
062 *  <li>may return several entries with the same name.</li>
063 *
064 *  <li>will not return internal or external attributes.</li>
065 *
066 *  <li>may return incomplete extra field data.</li>
067 *
068 *  <li>may return unknown sizes and CRC values for entries until the
069 *  next entry has been reached if the archive uses the data
070 *  descriptor feature.</li>
071 *
072 * </ul>
073 *
074 * @see ZipFile
075 * @NotThreadSafe
076 */
077public class ZipArchiveInputStream extends ArchiveInputStream {
078
079    /** The zip encoding to use for filenames and the file comment. */
080    private final ZipEncoding zipEncoding;
081
082    // the provided encoding (for unit tests)
083    final String encoding;
084
085    /** Whether to look for and use Unicode extra fields. */
086    private final boolean useUnicodeExtraFields;
087
088    /** Wrapped stream, will always be a PushbackInputStream. */
089    private final InputStream in;
090
091    /** Inflater used for all deflated entries. */
092    private final Inflater inf = new Inflater(true);
093
094    /** Buffer used to read from the wrapped stream. */
095    private final ByteBuffer buf = ByteBuffer.allocate(ZipArchiveOutputStream.BUFFER_SIZE);
096
097    /** The entry that is currently being read. */
098    private CurrentEntry current = null;
099
100    /** Whether the stream has been closed. */
101    private boolean closed = false;
102
103    /** Whether the stream has reached the central directory - and thus found all entries. */
104    private boolean hitCentralDirectory = false;
105
106    /**
107     * When reading a stored entry that uses the data descriptor this
108     * stream has to read the full entry and caches it.  This is the
109     * cache.
110     */
111    private ByteArrayInputStream lastStoredEntry = null;
112
113    /** Whether the stream will try to read STORED entries that use a data descriptor. */
114    private boolean allowStoredEntriesWithDataDescriptor = false;
115
116    private static final int LFH_LEN = 30;
117    /*
118      local file header signature     WORD
119      version needed to extract       SHORT
120      general purpose bit flag        SHORT
121      compression method              SHORT
122      last mod file time              SHORT
123      last mod file date              SHORT
124      crc-32                          WORD
125      compressed size                 WORD
126      uncompressed size               WORD
127      file name length                SHORT
128      extra field length              SHORT
129    */
130
131    private static final int CFH_LEN = 46;
132    /*
133        central file header signature   WORD
134        version made by                 SHORT
135        version needed to extract       SHORT
136        general purpose bit flag        SHORT
137        compression method              SHORT
138        last mod file time              SHORT
139        last mod file date              SHORT
140        crc-32                          WORD
141        compressed size                 WORD
142        uncompressed size               WORD
143        file name length                SHORT
144        extra field length              SHORT
145        file comment length             SHORT
146        disk number start               SHORT
147        internal file attributes        SHORT
148        external file attributes        WORD
149        relative offset of local header WORD
150    */
151
152    private static final long TWO_EXP_32 = ZIP64_MAGIC + 1;
153
154    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
155    private final byte[] lfhBuf = new byte[LFH_LEN];
156    private final byte[] skipBuf = new byte[1024];
157    private final byte[] shortBuf = new byte[SHORT];
158    private final byte[] wordBuf = new byte[WORD];
159    private final byte[] twoDwordBuf = new byte[2 * DWORD];
160
161    private int entriesRead = 0;
162
163    /**
164     * Create an instance using UTF-8 encoding
165     * @param inputStream the stream to wrap
166     */
167    public ZipArchiveInputStream(final InputStream inputStream) {
168        this(inputStream, ZipEncodingHelper.UTF8);
169    }
170
171    /**
172     * Create an instance using the specified encoding
173     * @param inputStream the stream to wrap
174     * @param encoding the encoding to use for file names, use null
175     * for the platform's default encoding
176     * @since 1.5
177     */
178    public ZipArchiveInputStream(final InputStream inputStream, final String encoding) {
179        this(inputStream, encoding, true);
180    }
181
182    /**
183     * Create an instance using the specified encoding
184     * @param inputStream the stream to wrap
185     * @param encoding the encoding to use for file names, use null
186     * for the platform's default encoding
187     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
188     * Extra Fields (if present) to set the file names.
189     */
190    public ZipArchiveInputStream(final InputStream inputStream, final String encoding, final boolean useUnicodeExtraFields) {
191        this(inputStream, encoding, useUnicodeExtraFields, false);
192    }
193
194    /**
195     * Create an instance using the specified encoding
196     * @param inputStream the stream to wrap
197     * @param encoding the encoding to use for file names, use null
198     * for the platform's default encoding
199     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
200     * Extra Fields (if present) to set the file names.
201     * @param allowStoredEntriesWithDataDescriptor whether the stream
202     * will try to read STORED entries that use a data descriptor
203     * @since 1.1
204     */
205    public ZipArchiveInputStream(final InputStream inputStream,
206                                 final String encoding,
207                                 final boolean useUnicodeExtraFields,
208                                 final boolean allowStoredEntriesWithDataDescriptor) {
209        this.encoding = encoding;
210        zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
211        this.useUnicodeExtraFields = useUnicodeExtraFields;
212        in = new PushbackInputStream(inputStream, buf.capacity());
213        this.allowStoredEntriesWithDataDescriptor =
214            allowStoredEntriesWithDataDescriptor;
215        // haven't read anything so far
216        buf.limit(0);
217    }
218
219    public ZipArchiveEntry getNextZipEntry() throws IOException {
220        boolean firstEntry = true;
221        if (closed || hitCentralDirectory) {
222            return null;
223        }
224        if (current != null) {
225            closeEntry();
226            firstEntry = false;
227        }
228
229        try {
230            if (firstEntry) {
231                // split archives have a special signature before the
232                // first local file header - look for it and fail with
233                // the appropriate error message if this is a split
234                // archive.
235                readFirstLocalFileHeader(lfhBuf);
236            } else {
237                readFully(lfhBuf);
238            }
239        } catch (final EOFException e) {
240            return null;
241        }
242
243        final ZipLong sig = new ZipLong(lfhBuf);
244        if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG)) {
245            hitCentralDirectory = true;
246            skipRemainderOfArchive();
247            return null;
248        }
249        if (!sig.equals(ZipLong.LFH_SIG)) {
250            throw new ZipException(String.format("Unexpected record signature: 0X%X", sig.getValue()));
251        }
252
253        int off = WORD;
254        current = new CurrentEntry();
255
256        final int versionMadeBy = ZipShort.getValue(lfhBuf, off);
257        off += SHORT;
258        current.entry.setPlatform((versionMadeBy >> ZipFile.BYTE_SHIFT) & ZipFile.NIBLET_MASK);
259
260        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(lfhBuf, off);
261        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
262        final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
263        current.hasDataDescriptor = gpFlag.usesDataDescriptor();
264        current.entry.setGeneralPurposeBit(gpFlag);
265
266        off += SHORT;
267
268        current.entry.setMethod(ZipShort.getValue(lfhBuf, off));
269        off += SHORT;
270
271        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(lfhBuf, off));
272        current.entry.setTime(time);
273        off += WORD;
274
275        ZipLong size = null, cSize = null;
276        if (!current.hasDataDescriptor) {
277            current.entry.setCrc(ZipLong.getValue(lfhBuf, off));
278            off += WORD;
279
280            cSize = new ZipLong(lfhBuf, off);
281            off += WORD;
282
283            size = new ZipLong(lfhBuf, off);
284            off += WORD;
285        } else {
286            off += 3 * WORD;
287        }
288
289        final int fileNameLen = ZipShort.getValue(lfhBuf, off);
290
291        off += SHORT;
292
293        final int extraLen = ZipShort.getValue(lfhBuf, off);
294        off += SHORT;
295
296        final byte[] fileName = new byte[fileNameLen];
297        readFully(fileName);
298        current.entry.setName(entryEncoding.decode(fileName), fileName);
299
300        final byte[] extraData = new byte[extraLen];
301        readFully(extraData);
302        current.entry.setExtra(extraData);
303
304        if (!hasUTF8Flag && useUnicodeExtraFields) {
305            ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null);
306        }
307
308        processZip64Extra(size, cSize);
309
310        if (current.entry.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN) {
311            if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode()) {
312                current.in = new UnshrinkingInputStream(new BoundedInputStream(in, current.entry.getCompressedSize()));
313            } else if (current.entry.getMethod() == ZipMethod.IMPLODING.getCode()) {
314                current.in = new ExplodingInputStream(
315                        current.entry.getGeneralPurposeBit().getSlidingDictionarySize(),
316                        current.entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(),
317                        new BoundedInputStream(in, current.entry.getCompressedSize()));
318            } else if (current.entry.getMethod() == ZipMethod.BZIP2.getCode()) {
319                current.in = new BZip2CompressorInputStream(new BoundedInputStream(in, current.entry.getCompressedSize()));
320            }
321        }
322        
323        entriesRead++;
324        return current.entry;
325    }
326
327    /**
328     * Fills the given array with the first local file header and
329     * deals with splitting/spanning markers that may prefix the first
330     * LFH.
331     */
332    private void readFirstLocalFileHeader(final byte[] lfh) throws IOException {
333        readFully(lfh);
334        final ZipLong sig = new ZipLong(lfh);
335        if (sig.equals(ZipLong.DD_SIG)) {
336            throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.SPLITTING);
337        }
338
339        if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER)) {
340            // The archive is not really split as only one segment was
341            // needed in the end.  Just skip over the marker.
342            final byte[] missedLfhBytes = new byte[4];
343            readFully(missedLfhBytes);
344            System.arraycopy(lfh, 4, lfh, 0, LFH_LEN - 4);
345            System.arraycopy(missedLfhBytes, 0, lfh, LFH_LEN - 4, 4);
346        }
347    }
348
349    /**
350     * Records whether a Zip64 extra is present and sets the size
351     * information from it if sizes are 0xFFFFFFFF and the entry
352     * doesn't use a data descriptor.
353     */
354    private void processZip64Extra(final ZipLong size, final ZipLong cSize) {
355        final Zip64ExtendedInformationExtraField z64 =
356            (Zip64ExtendedInformationExtraField) 
357            current.entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
358        current.usesZip64 = z64 != null;
359        if (!current.hasDataDescriptor) {
360            if (z64 != null // same as current.usesZip64 but avoids NPE warning
361                    && (cSize.equals(ZipLong.ZIP64_MAGIC) || size.equals(ZipLong.ZIP64_MAGIC)) ) {
362                current.entry.setCompressedSize(z64.getCompressedSize().getLongValue());
363                current.entry.setSize(z64.getSize().getLongValue());
364            } else {
365                current.entry.setCompressedSize(cSize.getValue());
366                current.entry.setSize(size.getValue());
367            }
368        }
369    }
370
371    @Override
372    public ArchiveEntry getNextEntry() throws IOException {
373        return getNextZipEntry();
374    }
375
376    /**
377     * Whether this class is able to read the given entry.
378     *
379     * <p>May return false if it is set up to use encryption or a
380     * compression method that hasn't been implemented yet.</p>
381     * @since 1.1
382     */
383    @Override
384    public boolean canReadEntryData(final ArchiveEntry ae) {
385        if (ae instanceof ZipArchiveEntry) {
386            final ZipArchiveEntry ze = (ZipArchiveEntry) ae;
387            return ZipUtil.canHandleEntryData(ze)
388                && supportsDataDescriptorFor(ze);
389
390        }
391        return false;
392    }
393
394    @Override
395    public int read(final byte[] buffer, final int offset, final int length) throws IOException {
396        if (closed) {
397            throw new IOException("The stream is closed");
398        }
399
400        if (current == null) {
401            return -1;
402        }
403
404        // avoid int overflow, check null buffer
405        if (offset > buffer.length || length < 0 || offset < 0 || buffer.length - offset < length) {
406            throw new ArrayIndexOutOfBoundsException();
407        }
408        
409        ZipUtil.checkRequestedFeatures(current.entry);
410        if (!supportsDataDescriptorFor(current.entry)) {
411            throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.DATA_DESCRIPTOR,
412                    current.entry);
413        }
414
415        int read;
416        if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) {
417            read = readStored(buffer, offset, length);
418        } else if (current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED) {
419            read = readDeflated(buffer, offset, length);
420        } else if (current.entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
421                || current.entry.getMethod() == ZipMethod.IMPLODING.getCode()
422                || current.entry.getMethod() == ZipMethod.BZIP2.getCode()) {
423            read = current.in.read(buffer, offset, length);
424        } else {
425            throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(current.entry.getMethod()),
426                    current.entry);
427        }
428        
429        if (read >= 0) {
430            current.crc.update(buffer, offset, read);
431        }
432        
433        return read;
434    }
435
436    /**
437     * Implementation of read for STORED entries.
438     */
439    private int readStored(final byte[] buffer, final int offset, final int length) throws IOException {
440
441        if (current.hasDataDescriptor) {
442            if (lastStoredEntry == null) {
443                readStoredEntry();
444            }
445            return lastStoredEntry.read(buffer, offset, length);
446        }
447
448        final long csize = current.entry.getSize();
449        if (current.bytesRead >= csize) {
450            return -1;
451        }
452
453        if (buf.position() >= buf.limit()) {
454            buf.position(0);
455            final int l = in.read(buf.array());
456            if (l == -1) {
457                return -1;
458            }
459            buf.limit(l);
460
461            count(l);
462            current.bytesReadFromStream += l;
463        }
464
465        int toRead = Math.min(buf.remaining(), length);
466        if ((csize - current.bytesRead) < toRead) {
467            // if it is smaller than toRead then it fits into an int
468            toRead = (int) (csize - current.bytesRead);
469        }
470        buf.get(buffer, offset, toRead);
471        current.bytesRead += toRead;
472        return toRead;
473    }
474
475    /**
476     * Implementation of read for DEFLATED entries.
477     */
478    private int readDeflated(final byte[] buffer, final int offset, final int length) throws IOException {
479        final int read = readFromInflater(buffer, offset, length);
480        if (read <= 0) {
481            if (inf.finished()) {
482                return -1;
483            } else if (inf.needsDictionary()) {
484                throw new ZipException("This archive needs a preset dictionary"
485                                       + " which is not supported by Commons"
486                                       + " Compress.");
487            } else if (read == -1) {
488                throw new IOException("Truncated ZIP file");
489            }
490        }
491        return read;
492    }
493
494    /**
495     * Potentially reads more bytes to fill the inflater's buffer and
496     * reads from it.
497     */
498    private int readFromInflater(final byte[] buffer, final int offset, final int length) throws IOException {
499        int read = 0;
500        do {
501            if (inf.needsInput()) {
502                final int l = fill();
503                if (l > 0) {
504                    current.bytesReadFromStream += buf.limit();
505                } else if (l == -1) {
506                    return -1;
507                } else {
508                    break;
509                }
510            }
511            try {
512                read = inf.inflate(buffer, offset, length);
513            } catch (final DataFormatException e) {
514                throw (IOException) new ZipException(e.getMessage()).initCause(e);
515            }
516        } while (read == 0 && inf.needsInput());
517        return read;
518    }
519
520    @Override
521    public void close() throws IOException {
522        if (!closed) {
523            closed = true;
524            try {
525                in.close();
526            } finally {
527                inf.end();
528            }
529        }
530    }
531
532    /**
533     * Skips over and discards value bytes of data from this input
534     * stream.
535     *
536     * <p>This implementation may end up skipping over some smaller
537     * number of bytes, possibly 0, if and only if it reaches the end
538     * of the underlying stream.</p>
539     *
540     * <p>The actual number of bytes skipped is returned.</p>
541     *
542     * @param value the number of bytes to be skipped.
543     * @return the actual number of bytes skipped.
544     * @throws IOException - if an I/O error occurs.
545     * @throws IllegalArgumentException - if value is negative.
546     */
547    @Override
548    public long skip(final long value) throws IOException {
549        if (value >= 0) {
550            long skipped = 0;
551            while (skipped < value) {
552                final long rem = value - skipped;
553                final int x = read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
554                if (x == -1) {
555                    return skipped;
556                }
557                skipped += x;
558            }
559            return skipped;
560        }
561        throw new IllegalArgumentException();
562    }
563
564    /**
565     * Checks if the signature matches what is expected for a zip file.
566     * Does not currently handle self-extracting zips which may have arbitrary
567     * leading content.
568     *
569     * @param signature the bytes to check
570     * @param length    the number of bytes to check
571     * @return true, if this stream is a zip archive stream, false otherwise
572     */
573    public static boolean matches(final byte[] signature, final int length) {
574        if (length < ZipArchiveOutputStream.LFH_SIG.length) {
575            return false;
576        }
577
578        return checksig(signature, ZipArchiveOutputStream.LFH_SIG) // normal file
579            || checksig(signature, ZipArchiveOutputStream.EOCD_SIG) // empty zip
580            || checksig(signature, ZipArchiveOutputStream.DD_SIG) // split zip
581            || checksig(signature, ZipLong.SINGLE_SEGMENT_SPLIT_MARKER.getBytes());
582    }
583
584    private static boolean checksig(final byte[] signature, final byte[] expected) {
585        for (int i = 0; i < expected.length; i++) {
586            if (signature[i] != expected[i]) {
587                return false;
588            }
589        }
590        return true;
591    }
592
593    /**
594     * Closes the current ZIP archive entry and positions the underlying
595     * stream to the beginning of the next entry. All per-entry variables
596     * and data structures are cleared.
597     * <p>
598     * If the compressed size of this entry is included in the entry header,
599     * then any outstanding bytes are simply skipped from the underlying
600     * stream without uncompressing them. This allows an entry to be safely
601     * closed even if the compression method is unsupported.
602     * <p>
603     * In case we don't know the compressed size of this entry or have
604     * already buffered too much data from the underlying stream to support
605     * uncompression, then the uncompression process is completed and the
606     * end position of the stream is adjusted based on the result of that
607     * process.
608     *
609     * @throws IOException if an error occurs
610     */
611    private void closeEntry() throws IOException {
612        if (closed) {
613            throw new IOException("The stream is closed");
614        }
615        if (current == null) {
616            return;
617        }
618
619        // Ensure all entry bytes are read
620        if (currentEntryHasOutstandingBytes()) {
621            drainCurrentEntryData();
622        } else {
623            // this is guaranteed to exhaust the stream
624            skip(Long.MAX_VALUE); //NOSONAR
625
626            final long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED
627                       ? getBytesInflated() : current.bytesRead;
628
629            // this is at most a single read() operation and can't
630            // exceed the range of int
631            final int diff = (int) (current.bytesReadFromStream - inB);
632
633            // Pushback any required bytes
634            if (diff > 0) {
635                pushback(buf.array(), buf.limit() - diff, diff);
636                current.bytesReadFromStream -= diff;
637            }
638
639            // Drain remainder of entry if not all data bytes were required
640            if (currentEntryHasOutstandingBytes()) {
641                drainCurrentEntryData();
642            }
643        }
644
645        if (lastStoredEntry == null && current.hasDataDescriptor) {
646            readDataDescriptor();
647        }
648
649        inf.reset();
650        buf.clear().flip();
651        current = null;
652        lastStoredEntry = null;
653    }
654
655    /**
656     * If the compressed size of the current entry is included in the entry header
657     * and there are any outstanding bytes in the underlying stream, then
658     * this returns true.
659     *
660     * @return true, if current entry is determined to have outstanding bytes, false otherwise
661     */
662    private boolean currentEntryHasOutstandingBytes() {
663        return current.bytesReadFromStream <= current.entry.getCompressedSize()
664                && !current.hasDataDescriptor;
665    }
666
667    /**
668     * Read all data of the current entry from the underlying stream
669     * that hasn't been read, yet.
670     */
671    private void drainCurrentEntryData() throws IOException {
672        long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream;
673        while (remaining > 0) {
674            final long n = in.read(buf.array(), 0, (int) Math.min(buf.capacity(), remaining));
675            if (n < 0) {
676                throw new EOFException("Truncated ZIP entry: "
677                                       + ArchiveUtils.sanitize(current.entry.getName()));
678            }
679            count(n);
680            remaining -= n;
681        }
682    }
683
684    /**
685     * Get the number of bytes Inflater has actually processed.
686     *
687     * <p>for Java &lt; Java7 the getBytes* methods in
688     * Inflater/Deflater seem to return unsigned ints rather than
689     * longs that start over with 0 at 2^32.</p>
690     *
691     * <p>The stream knows how many bytes it has read, but not how
692     * many the Inflater actually consumed - it should be between the
693     * total number of bytes read for the entry and the total number
694     * minus the last read operation.  Here we just try to make the
695     * value close enough to the bytes we've read by assuming the
696     * number of bytes consumed must be smaller than (or equal to) the
697     * number of bytes read but not smaller by more than 2^32.</p>
698     */
699    private long getBytesInflated() {
700        long inB = inf.getBytesRead();
701        if (current.bytesReadFromStream >= TWO_EXP_32) {
702            while (inB + TWO_EXP_32 <= current.bytesReadFromStream) {
703                inB += TWO_EXP_32;
704            }
705        }
706        return inB;
707    }
708
709    private int fill() throws IOException {
710        if (closed) {
711            throw new IOException("The stream is closed");
712        }
713        final int length = in.read(buf.array());
714        if (length > 0) {
715            buf.limit(length);
716            count(buf.limit());
717            inf.setInput(buf.array(), 0, buf.limit());
718        }
719        return length;
720    }
721
722    private void readFully(final byte[] b) throws IOException {
723        final int count = IOUtils.readFully(in, b);
724        count(count);
725        if (count < b.length) {
726            throw new EOFException();
727        }
728    }
729
730    private void readDataDescriptor() throws IOException {
731        readFully(wordBuf);
732        ZipLong val = new ZipLong(wordBuf);
733        if (ZipLong.DD_SIG.equals(val)) {
734            // data descriptor with signature, skip sig
735            readFully(wordBuf);
736            val = new ZipLong(wordBuf);
737        }
738        current.entry.setCrc(val.getValue());
739
740        // if there is a ZIP64 extra field, sizes are eight bytes
741        // each, otherwise four bytes each.  Unfortunately some
742        // implementations - namely Java7 - use eight bytes without
743        // using a ZIP64 extra field -
744        // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588
745
746        // just read 16 bytes and check whether bytes nine to twelve
747        // look like one of the signatures of what could follow a data
748        // descriptor (ignoring archive decryption headers for now).
749        // If so, push back eight bytes and assume sizes are four
750        // bytes, otherwise sizes are eight bytes each.
751        readFully(twoDwordBuf);
752        final ZipLong potentialSig = new ZipLong(twoDwordBuf, DWORD);
753        if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) {
754            pushback(twoDwordBuf, DWORD, DWORD);
755            current.entry.setCompressedSize(ZipLong.getValue(twoDwordBuf));
756            current.entry.setSize(ZipLong.getValue(twoDwordBuf, WORD));
757        } else {
758            current.entry.setCompressedSize(ZipEightByteInteger.getLongValue(twoDwordBuf));
759            current.entry.setSize(ZipEightByteInteger.getLongValue(twoDwordBuf, DWORD));
760        }
761    }
762
763    /**
764     * Whether this entry requires a data descriptor this library can work with.
765     *
766     * @return true if allowStoredEntriesWithDataDescriptor is true,
767     * the entry doesn't require any data descriptor or the method is
768     * DEFLATED.
769     */
770    private boolean supportsDataDescriptorFor(final ZipArchiveEntry entry) {
771        return !entry.getGeneralPurposeBit().usesDataDescriptor()
772
773                || (allowStoredEntriesWithDataDescriptor && entry.getMethod() == ZipEntry.STORED)
774                || entry.getMethod() == ZipEntry.DEFLATED;
775    }
776
777    /**
778     * Caches a stored entry that uses the data descriptor.
779     *
780     * <ul>
781     *   <li>Reads a stored entry until the signature of a local file
782     *     header, central directory header or data descriptor has been
783     *     found.</li>
784     *   <li>Stores all entry data in lastStoredEntry.</p>
785     *   <li>Rewinds the stream to position at the data
786     *     descriptor.</li>
787     *   <li>reads the data descriptor</li>
788     * </ul>
789     *
790     * <p>After calling this method the entry should know its size,
791     * the entry's data is cached and the stream is positioned at the
792     * next local file or central directory header.</p>
793     */
794    private void readStoredEntry() throws IOException {
795        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
796        int off = 0;
797        boolean done = false;
798
799        // length of DD without signature
800        final int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD;
801
802        while (!done) {
803            final int r = in.read(buf.array(), off, ZipArchiveOutputStream.BUFFER_SIZE - off);
804            if (r <= 0) {
805                // read the whole archive without ever finding a
806                // central directory
807                throw new IOException("Truncated ZIP file");
808            }
809            if (r + off < 4) {
810                // buffer too small to check for a signature, loop
811                off += r;
812                continue;
813            }
814
815            done = bufferContainsSignature(bos, off, r, ddLen);
816            if (!done) {
817                off = cacheBytesRead(bos, off, r, ddLen);
818            }
819        }
820
821        final byte[] b = bos.toByteArray();
822        lastStoredEntry = new ByteArrayInputStream(b);
823    }
824
825    private static final byte[] LFH = ZipLong.LFH_SIG.getBytes();
826    private static final byte[] CFH = ZipLong.CFH_SIG.getBytes();
827    private static final byte[] DD = ZipLong.DD_SIG.getBytes();
828
829    /**
830     * Checks whether the current buffer contains the signature of a
831     * &quot;data descriptor&quot;, &quot;local file header&quot; or
832     * &quot;central directory entry&quot;.
833     *
834     * <p>If it contains such a signature, reads the data descriptor
835     * and positions the stream right after the data descriptor.</p>
836     */
837    private boolean bufferContainsSignature(final ByteArrayOutputStream bos, final int offset, final int lastRead, final int expectedDDLen)
838            throws IOException {
839
840        boolean done = false;
841        int readTooMuch = 0;
842        for (int i = 0; !done && i < lastRead - 4; i++) {
843            if (buf.array()[i] == LFH[0] && buf.array()[i + 1] == LFH[1]) {
844                if ((buf.array()[i + 2] == LFH[2] && buf.array()[i + 3] == LFH[3])
845                    || (buf.array()[i] == CFH[2] && buf.array()[i + 3] == CFH[3])) {
846                    // found a LFH or CFH:
847                    readTooMuch = offset + lastRead - i - expectedDDLen;
848                    done = true;
849                }
850                else if (buf.array()[i + 2] == DD[2] && buf.array()[i + 3] == DD[3]) {
851                    // found DD:
852                    readTooMuch = offset + lastRead - i;
853                    done = true;
854                }
855                if (done) {
856                    // * push back bytes read in excess as well as the data
857                    //   descriptor
858                    // * copy the remaining bytes to cache
859                    // * read data descriptor
860                    pushback(buf.array(), offset + lastRead - readTooMuch, readTooMuch);
861                    bos.write(buf.array(), 0, i);
862                    readDataDescriptor();
863                }
864            }
865        }
866        return done;
867    }
868
869    /**
870     * If the last read bytes could hold a data descriptor and an
871     * incomplete signature then save the last bytes to the front of
872     * the buffer and cache everything in front of the potential data
873     * descriptor into the given ByteArrayOutputStream.
874     *
875     * <p>Data descriptor plus incomplete signature (3 bytes in the
876     * worst case) can be 20 bytes max.</p>
877     */
878    private int cacheBytesRead(final ByteArrayOutputStream bos, int offset, final int lastRead, final int expecteDDLen) {
879        final int cacheable = offset + lastRead - expecteDDLen - 3;
880        if (cacheable > 0) {
881            bos.write(buf.array(), 0, cacheable);
882            System.arraycopy(buf.array(), cacheable, buf.array(), 0, expecteDDLen + 3);
883            offset = expecteDDLen + 3;
884        } else {
885            offset += lastRead;
886        }
887        return offset;
888    }
889
890    private void pushback(final byte[] buf, final int offset, final int length) throws IOException {
891        ((PushbackInputStream) in).unread(buf, offset, length);
892        pushedBackBytes(length);
893    }
894
895    // End of Central Directory Record
896    //   end of central dir signature    WORD
897    //   number of this disk             SHORT
898    //   number of the disk with the
899    //   start of the central directory  SHORT
900    //   total number of entries in the
901    //   central directory on this disk  SHORT
902    //   total number of entries in
903    //   the central directory           SHORT
904    //   size of the central directory   WORD
905    //   offset of start of central
906    //   directory with respect to
907    //   the starting disk number        WORD
908    //   .ZIP file comment length        SHORT
909    //   .ZIP file comment               up to 64KB
910    //
911
912    /**
913     * Reads the stream until it find the "End of central directory
914     * record" and consumes it as well.
915     */
916    private void skipRemainderOfArchive() throws IOException {
917        // skip over central directory. One LFH has been read too much
918        // already.  The calculation discounts file names and extra
919        // data so it will be too short.
920        realSkip((long) entriesRead * CFH_LEN - LFH_LEN);
921        findEocdRecord();
922        realSkip((long) ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* comment len */);
923        readFully(shortBuf);
924        // file comment
925        realSkip(ZipShort.getValue(shortBuf));
926    }
927
928    /**
929     * Reads forward until the signature of the &quot;End of central
930     * directory&quot; record is found.
931     */
932    private void findEocdRecord() throws IOException {
933        int currentByte = -1;
934        boolean skipReadCall = false;
935        while (skipReadCall || (currentByte = readOneByte()) > -1) {
936            skipReadCall = false;
937            if (!isFirstByteOfEocdSig(currentByte)) {
938                continue;
939            }
940            currentByte = readOneByte();
941            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) {
942                if (currentByte == -1) {
943                    break;
944                }
945                skipReadCall = isFirstByteOfEocdSig(currentByte);
946                continue;
947            }
948            currentByte = readOneByte();
949            if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) {
950                if (currentByte == -1) {
951                    break;
952                }
953                skipReadCall = isFirstByteOfEocdSig(currentByte);
954                continue;
955            }
956            currentByte = readOneByte();
957            if (currentByte == -1
958                || currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) {
959                break;
960            }
961            skipReadCall = isFirstByteOfEocdSig(currentByte);
962        }
963    }
964
965    /**
966     * Skips bytes by reading from the underlying stream rather than
967     * the (potentially inflating) archive stream - which {@link
968     * #skip} would do.
969     *
970     * Also updates bytes-read counter.
971     */
972    private void realSkip(final long value) throws IOException {
973        if (value >= 0) {
974            long skipped = 0;
975            while (skipped < value) {
976                final long rem = value - skipped;
977                final int x = in.read(skipBuf, 0, (int) (skipBuf.length > rem ? rem : skipBuf.length));
978                if (x == -1) {
979                    return;
980                }
981                count(x);
982                skipped += x;
983            }
984            return;
985        }
986        throw new IllegalArgumentException();
987    }
988
989    /**
990     * Reads bytes by reading from the underlying stream rather than
991     * the (potentially inflating) archive stream - which {@link #read} would do.
992     *
993     * Also updates bytes-read counter.
994     */
995    private int readOneByte() throws IOException {
996        final int b = in.read();
997        if (b != -1) {
998            count(1);
999        }
1000        return b;
1001    }
1002
1003    private boolean isFirstByteOfEocdSig(final int b) {
1004        return b == ZipArchiveOutputStream.EOCD_SIG[0];
1005    }
1006
1007    /**
1008     * Structure collecting information for the entry that is
1009     * currently being read.
1010     */
1011    private static final class CurrentEntry {
1012
1013        /**
1014         * Current ZIP entry.
1015         */
1016        private final ZipArchiveEntry entry = new ZipArchiveEntry();
1017
1018        /**
1019         * Does the entry use a data descriptor?
1020         */
1021        private boolean hasDataDescriptor;
1022
1023        /**
1024         * Does the entry have a ZIP64 extended information extra field.
1025         */
1026        private boolean usesZip64;
1027
1028        /**
1029         * Number of bytes of entry content read by the client if the
1030         * entry is STORED.
1031         */
1032        private long bytesRead;
1033
1034        /**
1035         * Number of bytes of entry content read so from the stream.
1036         *
1037         * <p>This may be more than the actual entry's length as some
1038         * stuff gets buffered up and needs to be pushed back when the
1039         * end of the entry has been reached.</p>
1040         */
1041        private long bytesReadFromStream;
1042
1043        /**
1044         * The checksum calculated as the current entry is read.
1045         */
1046        private final CRC32 crc = new CRC32();
1047
1048        /**
1049         * The input stream decompressing the data for shrunk and imploded entries.
1050         */
1051        private InputStream in;
1052    }
1053
1054    /**
1055     * Bounded input stream adapted from commons-io
1056     */
1057    private class BoundedInputStream extends InputStream {
1058
1059        /** the wrapped input stream */
1060        private final InputStream in;
1061
1062        /** the max length to provide */
1063        private final long max;
1064
1065        /** the number of bytes already returned */
1066        private long pos = 0;
1067    
1068        /**
1069         * Creates a new <code>BoundedInputStream</code> that wraps the given input
1070         * stream and limits it to a certain size.
1071         *
1072         * @param in The wrapped input stream
1073         * @param size The maximum number of bytes to return
1074         */
1075        public BoundedInputStream(final InputStream in, final long size) {
1076            this.max = size;
1077            this.in = in;
1078        }
1079
1080        @Override
1081        public int read() throws IOException {
1082            if (max >= 0 && pos >= max) {
1083                return -1;
1084            }
1085            final int result = in.read();
1086            pos++;
1087            count(1);
1088            current.bytesReadFromStream++;
1089            return result;
1090        }
1091
1092        @Override
1093        public int read(final byte[] b) throws IOException {
1094            return this.read(b, 0, b.length);
1095        }
1096
1097        @Override
1098        public int read(final byte[] b, final int off, final int len) throws IOException {
1099            if (max >= 0 && pos >= max) {
1100                return -1;
1101            }
1102            final long maxRead = max >= 0 ? Math.min(len, max - pos) : len;
1103            final int bytesRead = in.read(b, off, (int) maxRead);
1104
1105            if (bytesRead == -1) {
1106                return -1;
1107            }
1108
1109            pos += bytesRead;
1110            count(bytesRead);
1111            current.bytesReadFromStream += bytesRead;
1112            return bytesRead;
1113        }
1114
1115        @Override
1116        public long skip(final long n) throws IOException {
1117            final long toSkip = max >= 0 ? Math.min(n, max - pos) : n;
1118            final long skippedBytes = in.skip(toSkip);
1119            pos += skippedBytes;
1120            return skippedBytes;
1121        }
1122    
1123        @Override
1124        public int available() throws IOException {
1125            if (max >= 0 && pos >= max) {
1126                return 0;
1127            }
1128            return in.available();
1129        }
1130    }
1131}