001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 *
017 */
018package org.apache.commons.compress.archivers.zip;
019
020import java.io.BufferedInputStream;
021import java.io.ByteArrayInputStream;
022import java.io.Closeable;
023import java.io.EOFException;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.SequenceInputStream;
028import java.nio.ByteBuffer;
029import java.nio.channels.FileChannel;
030import java.nio.channels.SeekableByteChannel;
031import java.nio.file.Files;
032import java.nio.file.StandardOpenOption;
033import java.util.Arrays;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.Enumeration;
037import java.util.EnumSet;
038import java.util.HashMap;
039import java.util.LinkedList;
040import java.util.List;
041import java.util.Map;
042import java.util.zip.Inflater;
043import java.util.zip.ZipException;
044
045import org.apache.commons.compress.archivers.EntryStreamOffsets;
046import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
047import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
048import org.apache.commons.compress.utils.CountingInputStream;
049import org.apache.commons.compress.utils.IOUtils;
050import org.apache.commons.compress.utils.InputStreamStatistics;
051
052import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
053import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
054import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
055import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
056import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
057
058/**
059 * Replacement for <code>java.util.ZipFile</code>.
060 *
061 * <p>This class adds support for file name encodings other than UTF-8
062 * (which is required to work on ZIP files created by native zip tools
063 * and is able to skip a preamble like the one found in self
064 * extracting archives.  Furthermore it returns instances of
065 * <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code>
066 * instead of <code>java.util.zip.ZipEntry</code>.</p>
067 *
068 * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would
069 * have to reimplement all methods anyway.  Like
070 * <code>java.util.ZipFile</code>, it uses SeekableByteChannel under the
071 * covers and supports compressed and uncompressed entries.  As of
072 * Apache Commons Compress 1.3 it also transparently supports Zip64
073 * extensions and thus individual entries and archives larger than 4
074 * GB or with more than 65536 entries.</p>
075 *
076 * <p>The method signatures mimic the ones of
077 * <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
078 *
079 * <ul>
080 *   <li>There is no getName method.</li>
081 *   <li>entries has been renamed to getEntries.</li>
082 *   <li>getEntries and getEntry return
083 *   <code>org.apache.commons.compress.archivers.zip.ZipArchiveEntry</code>
084 *   instances.</li>
085 *   <li>close is allowed to throw IOException.</li>
086 * </ul>
087 *
088 */
089public class ZipFile implements Closeable {
090    private static final int HASH_SIZE = 509;
091    static final int NIBLET_MASK = 0x0f;
092    static final int BYTE_SHIFT = 8;
093    private static final int POS_0 = 0;
094    private static final int POS_1 = 1;
095    private static final int POS_2 = 2;
096    private static final int POS_3 = 3;
097    private static final byte[] ONE_ZERO_BYTE = new byte[1];
098
099    /**
100     * List of entries in the order they appear inside the central
101     * directory.
102     */
103    private final List<ZipArchiveEntry> entries =
104        new LinkedList<>();
105
106    /**
107     * Maps String to list of ZipArchiveEntrys, name -> actual entries.
108     */
109    private final Map<String, LinkedList<ZipArchiveEntry>> nameMap =
110        new HashMap<>(HASH_SIZE);
111
112    /**
113     * The encoding to use for file names and the file comment.
114     *
115     * <p>For a list of possible values see <a
116     * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
117     * Defaults to UTF-8.</p>
118     */
119    private final String encoding;
120
121    /**
122     * The zip encoding to use for file names and the file comment.
123     */
124    private final ZipEncoding zipEncoding;
125
126    /**
127     * File name of actual source.
128     */
129    private final String archiveName;
130
131    /**
132     * The actual data source.
133     */
134    private final SeekableByteChannel archive;
135
136    /**
137     * Whether to look for and use Unicode extra fields.
138     */
139    private final boolean useUnicodeExtraFields;
140
141    /**
142     * Whether the file is closed.
143     */
144    private volatile boolean closed = true;
145
146    /**
147     * Whether the zip archive is a splite zip archive
148     */
149    private final boolean isSplitZipArchive;
150
151    // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
152    private final byte[] dwordBuf = new byte[DWORD];
153    private final byte[] wordBuf = new byte[WORD];
154    private final byte[] cfhBuf = new byte[CFH_LEN];
155    private final byte[] shortBuf = new byte[SHORT];
156    private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
157    private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
158    private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
159    private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
160
161    /**
162     * Opens the given file for reading, assuming "UTF8" for file names.
163     *
164     * @param f the archive.
165     *
166     * @throws IOException if an error occurs while reading the file.
167     */
168    public ZipFile(final File f) throws IOException {
169        this(f, ZipEncodingHelper.UTF8);
170    }
171
172    /**
173     * Opens the given file for reading, assuming "UTF8".
174     *
175     * @param name name of the archive.
176     *
177     * @throws IOException if an error occurs while reading the file.
178     */
179    public ZipFile(final String name) throws IOException {
180        this(new File(name), ZipEncodingHelper.UTF8);
181    }
182
183    /**
184     * Opens the given file for reading, assuming the specified
185     * encoding for file names, scanning unicode extra fields.
186     *
187     * @param name name of the archive.
188     * @param encoding the encoding to use for file names, use null
189     * for the platform's default encoding
190     *
191     * @throws IOException if an error occurs while reading the file.
192     */
193    public ZipFile(final String name, final String encoding) throws IOException {
194        this(new File(name), encoding, true);
195    }
196
197    /**
198     * Opens the given file for reading, assuming the specified
199     * encoding for file names and scanning for unicode extra fields.
200     *
201     * @param f the archive.
202     * @param encoding the encoding to use for file names, use null
203     * for the platform's default encoding
204     *
205     * @throws IOException if an error occurs while reading the file.
206     */
207    public ZipFile(final File f, final String encoding) throws IOException {
208        this(f, encoding, true);
209    }
210
211    /**
212     * Opens the given file for reading, assuming the specified
213     * encoding for file names.
214     *
215     * @param f the archive.
216     * @param encoding the encoding to use for file names, use null
217     * for the platform's default encoding
218     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
219     * Extra Fields (if present) to set the file names.
220     *
221     * @throws IOException if an error occurs while reading the file.
222     */
223    public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields)
224        throws IOException {
225        this(f, encoding, useUnicodeExtraFields, false);
226    }
227
228    /**
229     * Opens the given file for reading, assuming the specified
230     * encoding for file names.
231     *
232     *
233     * <p>By default the central directory record and all local file headers of the archive will be read immediately
234     * which may take a considerable amount of time when the archive is big. The {@code ignoreLocalFileHeader} parameter
235     * can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header
236     * may contain information not present inside of the central directory which will not be available when the argument
237     * is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
238     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively. Also
239     * {@link #getRawInputStream} is always going to return {@code null} if {@code ignoreLocalFileHeader} is {@code
240     * true}.</p>
241     *
242     * @param f the archive.
243     * @param encoding the encoding to use for file names, use null
244     * for the platform's default encoding
245     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
246     * Extra Fields (if present) to set the file names.
247     * @param ignoreLocalFileHeader whether to ignore information
248     * stored inside the local file header (see the notes in this method's javadoc)
249     *
250     * @throws IOException if an error occurs while reading the file.
251     * @since 1.19
252     */
253    public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields,
254                   final boolean ignoreLocalFileHeader)
255        throws IOException {
256        this(Files.newByteChannel(f.toPath(), EnumSet.of(StandardOpenOption.READ)),
257             f.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
258    }
259
260    /**
261     * Opens the given channel for reading, assuming "UTF8" for file names.
262     *
263     * <p>{@link
264     * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
265     * allows you to read from an in-memory archive.</p>
266     *
267     * @param channel the archive.
268     *
269     * @throws IOException if an error occurs while reading the file.
270     * @since 1.13
271     */
272    public ZipFile(final SeekableByteChannel channel)
273            throws IOException {
274        this(channel, "unknown archive", ZipEncodingHelper.UTF8, true);
275    }
276
277    /**
278     * Opens the given channel for reading, assuming the specified
279     * encoding for file names.
280     *
281     * <p>{@link
282     * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
283     * allows you to read from an in-memory archive.</p>
284     *
285     * @param channel the archive.
286     * @param encoding the encoding to use for file names, use null
287     * for the platform's default encoding
288     *
289     * @throws IOException if an error occurs while reading the file.
290     * @since 1.13
291     */
292    public ZipFile(final SeekableByteChannel channel, final String encoding)
293        throws IOException {
294        this(channel, "unknown archive", encoding, true);
295    }
296
297    /**
298     * Opens the given channel for reading, assuming the specified
299     * encoding for file names.
300     *
301     * <p>{@link
302     * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
303     * allows you to read from an in-memory archive.</p>
304     *
305     * @param channel the archive.
306     * @param archiveName name of the archive, used for error messages only.
307     * @param encoding the encoding to use for file names, use null
308     * for the platform's default encoding
309     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
310     * Extra Fields (if present) to set the file names.
311     *
312     * @throws IOException if an error occurs while reading the file.
313     * @since 1.13
314     */
315    public ZipFile(final SeekableByteChannel channel, final String archiveName,
316                   final String encoding, final boolean useUnicodeExtraFields)
317        throws IOException {
318        this(channel, archiveName, encoding, useUnicodeExtraFields, false, false);
319    }
320
321    /**
322     * Opens the given channel for reading, assuming the specified
323     * encoding for file names.
324     *
325     * <p>{@link
326     * org.apache.commons.compress.utils.SeekableInMemoryByteChannel}
327     * allows you to read from an in-memory archive.</p>
328     *
329     * <p>By default the central directory record and all local file headers of the archive will be read immediately
330     * which may take a considerable amount of time when the archive is big. The {@code ignoreLocalFileHeader} parameter
331     * can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header
332     * may contain information not present inside of the central directory which will not be available when the argument
333     * is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
334     * ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively. Also
335     * {@link #getRawInputStream} is always going to return {@code null} if {@code ignoreLocalFileHeader} is {@code
336     * true}.</p>
337     *
338     * @param channel the archive.
339     * @param archiveName name of the archive, used for error messages only.
340     * @param encoding the encoding to use for file names, use null
341     * for the platform's default encoding
342     * @param useUnicodeExtraFields whether to use InfoZIP Unicode
343     * Extra Fields (if present) to set the file names.
344     * @param ignoreLocalFileHeader whether to ignore information
345     * stored inside the local file header (see the notes in this method's javadoc)
346     *
347     * @throws IOException if an error occurs while reading the file.
348     * @since 1.19
349     */
350    public ZipFile(final SeekableByteChannel channel, final String archiveName,
351                   final String encoding, final boolean useUnicodeExtraFields,
352                   final boolean ignoreLocalFileHeader)
353        throws IOException {
354        this(channel, archiveName, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader);
355    }
356
357    private ZipFile(final SeekableByteChannel channel, final String archiveName,
358                    final String encoding, final boolean useUnicodeExtraFields,
359                    final boolean closeOnError, final boolean ignoreLocalFileHeader)
360        throws IOException {
361        isSplitZipArchive = (channel instanceof ZipSplitReadOnlySeekableByteChannel);
362
363        this.archiveName = archiveName;
364        this.encoding = encoding;
365        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
366        this.useUnicodeExtraFields = useUnicodeExtraFields;
367        archive = channel;
368        boolean success = false;
369        try {
370            final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag =
371                populateFromCentralDirectory();
372            if (!ignoreLocalFileHeader) {
373                resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
374            }
375            fillNameMap();
376            success = true;
377        } finally {
378            closed = !success;
379            if (!success && closeOnError) {
380                IOUtils.closeQuietly(archive);
381            }
382        }
383    }
384
385    /**
386     * The encoding to use for file names and the file comment.
387     *
388     * @return null if using the platform's default character encoding.
389     */
390    public String getEncoding() {
391        return encoding;
392    }
393
394    /**
395     * Closes the archive.
396     * @throws IOException if an error occurs closing the archive.
397     */
398    @Override
399    public void close() throws IOException {
400        // this flag is only written here and read in finalize() which
401        // can never be run in parallel.
402        // no synchronization needed.
403        closed = true;
404
405        archive.close();
406    }
407
408    /**
409     * close a zipfile quietly; throw no io fault, do nothing
410     * on a null parameter
411     * @param zipfile file to close, can be null
412     */
413    public static void closeQuietly(final ZipFile zipfile) {
414        IOUtils.closeQuietly(zipfile);
415    }
416
417    /**
418     * Returns all entries.
419     *
420     * <p>Entries will be returned in the same order they appear
421     * within the archive's central directory.</p>
422     *
423     * @return all entries as {@link ZipArchiveEntry} instances
424     */
425    public Enumeration<ZipArchiveEntry> getEntries() {
426        return Collections.enumeration(entries);
427    }
428
429    /**
430     * Returns all entries in physical order.
431     *
432     * <p>Entries will be returned in the same order their contents
433     * appear within the archive.</p>
434     *
435     * @return all entries as {@link ZipArchiveEntry} instances
436     *
437     * @since 1.1
438     */
439    public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
440        final ZipArchiveEntry[] allEntries = entries.toArray(new ZipArchiveEntry[entries.size()]);
441        Arrays.sort(allEntries, offsetComparator);
442        return Collections.enumeration(Arrays.asList(allEntries));
443    }
444
445    /**
446     * Returns a named entry - or {@code null} if no entry by
447     * that name exists.
448     *
449     * <p>If multiple entries with the same name exist the first entry
450     * in the archive's central directory by that name is
451     * returned.</p>
452     *
453     * @param name name of the entry.
454     * @return the ZipArchiveEntry corresponding to the given name - or
455     * {@code null} if not present.
456     */
457    public ZipArchiveEntry getEntry(final String name) {
458        final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
459        return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
460    }
461
462    /**
463     * Returns all named entries in the same order they appear within
464     * the archive's central directory.
465     *
466     * @param name name of the entry.
467     * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the
468     * given name
469     * @since 1.6
470     */
471    public Iterable<ZipArchiveEntry> getEntries(final String name) {
472        final List<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
473        return entriesOfThatName != null ? entriesOfThatName
474            : Collections.<ZipArchiveEntry>emptyList();
475    }
476
477    /**
478     * Returns all named entries in the same order their contents
479     * appear within the archive.
480     *
481     * @param name name of the entry.
482     * @return the Iterable&lt;ZipArchiveEntry&gt; corresponding to the
483     * given name
484     * @since 1.6
485     */
486    public Iterable<ZipArchiveEntry> getEntriesInPhysicalOrder(final String name) {
487        ZipArchiveEntry[] entriesOfThatName = new ZipArchiveEntry[0];
488        if (nameMap.containsKey(name)) {
489            entriesOfThatName = nameMap.get(name).toArray(entriesOfThatName);
490            Arrays.sort(entriesOfThatName, offsetComparator);
491        }
492        return Arrays.asList(entriesOfThatName);
493    }
494
495    /**
496     * Whether this class is able to read the given entry.
497     *
498     * <p>May return false if it is set up to use encryption or a
499     * compression method that hasn't been implemented yet.</p>
500     * @since 1.1
501     * @param ze the entry
502     * @return whether this class is able to read the given entry.
503     */
504    public boolean canReadEntryData(final ZipArchiveEntry ze) {
505        return ZipUtil.canHandleEntryData(ze);
506    }
507
508    /**
509     * Expose the raw stream of the archive entry (compressed form).
510     *
511     * <p>This method does not relate to how/if we understand the payload in the
512     * stream, since we really only intend to move it on to somewhere else.</p>
513     *
514     * @param ze The entry to get the stream for
515     * @return The raw input stream containing (possibly) compressed data.
516     * @since 1.11
517     */
518    public InputStream getRawInputStream(final ZipArchiveEntry ze) {
519        if (!(ze instanceof Entry)) {
520            return null;
521        }
522        final long start = ze.getDataOffset();
523        if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
524            return null;
525        }
526        return createBoundedInputStream(start, ze.getCompressedSize());
527    }
528
529
530    /**
531     * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream.
532     * Compression and all other attributes will be as in this file.
533     * <p>This method transfers entries based on the central directory of the zip file.</p>
534     *
535     * @param target The zipArchiveOutputStream to write the entries to
536     * @param predicate A predicate that selects which entries to write
537     * @throws IOException on error
538     */
539    public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate)
540            throws IOException {
541        final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
542        while (src.hasMoreElements()) {
543            final ZipArchiveEntry entry = src.nextElement();
544            if (predicate.test( entry)) {
545                target.addRawArchiveEntry(entry, getRawInputStream(entry));
546            }
547        }
548    }
549
550    /**
551     * Returns an InputStream for reading the contents of the given entry.
552     *
553     * @param ze the entry to get the stream for.
554     * @return a stream to read the entry from. The returned stream
555     * implements {@link InputStreamStatistics}.
556     * @throws IOException if unable to create an input stream from the zipentry
557     */
558    public InputStream getInputStream(final ZipArchiveEntry ze)
559        throws IOException {
560        if (!(ze instanceof Entry)) {
561            return null;
562        }
563        // cast validity is checked just above
564        ZipUtil.checkRequestedFeatures(ze);
565        final long start = getDataOffset(ze);
566
567        // doesn't get closed if the method is not supported - which
568        // should never happen because of the checkRequestedFeatures
569        // call above
570        final InputStream is =
571            new BufferedInputStream(createBoundedInputStream(start, ze.getCompressedSize())); //NOSONAR
572        switch (ZipMethod.getMethodByCode(ze.getMethod())) {
573            case STORED:
574                return new StoredStatisticsStream(is);
575            case UNSHRINKING:
576                return new UnshrinkingInputStream(is);
577            case IMPLODING:
578                return new ExplodingInputStream(ze.getGeneralPurposeBit().getSlidingDictionarySize(),
579                        ze.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), is);
580            case DEFLATED:
581                final Inflater inflater = new Inflater(true);
582                // Inflater with nowrap=true has this odd contract for a zero padding
583                // byte following the data stream; this used to be zlib's requirement
584                // and has been fixed a long time ago, but the contract persists so
585                // we comply.
586                // https://docs.oracle.com/javase/7/docs/api/java/util/zip/Inflater.html#Inflater(boolean)
587                return new InflaterInputStreamWithStatistics(new SequenceInputStream(is, new ByteArrayInputStream(ONE_ZERO_BYTE)),
588                    inflater) {
589                    @Override
590                    public void close() throws IOException {
591                        try {
592                            super.close();
593                        } finally {
594                            inflater.end();
595                        }
596                    }
597                };
598            case BZIP2:
599                return new BZip2CompressorInputStream(is);
600            case ENHANCED_DEFLATED:
601                return new Deflate64CompressorInputStream(is);
602            case AES_ENCRYPTED:
603            case EXPANDING_LEVEL_1:
604            case EXPANDING_LEVEL_2:
605            case EXPANDING_LEVEL_3:
606            case EXPANDING_LEVEL_4:
607            case JPEG:
608            case LZMA:
609            case PKWARE_IMPLODING:
610            case PPMD:
611            case TOKENIZATION:
612            case UNKNOWN:
613            case WAVPACK:
614            case XZ:
615            default:
616                throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(ze.getMethod()), ze);
617        }
618    }
619
620    /**
621     * <p>
622     * Convenience method to return the entry's content as a String if isUnixSymlink()
623     * returns true for it, otherwise returns null.
624     * </p>
625     *
626     * <p>This method assumes the symbolic link's file name uses the
627     * same encoding that as been specified for this ZipFile.</p>
628     *
629     * @param entry ZipArchiveEntry object that represents the symbolic link
630     * @return entry's content as a String
631     * @throws IOException problem with content's input stream
632     * @since 1.5
633     */
634    public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException {
635        if (entry != null && entry.isUnixSymlink()) {
636            try (InputStream in = getInputStream(entry)) {
637                return zipEncoding.decode(IOUtils.toByteArray(in));
638            }
639        }
640        return null;
641    }
642
643    /**
644     * Ensures that the close method of this zipfile is called when
645     * there are no more references to it.
646     * @see #close()
647     */
648    @Override
649    protected void finalize() throws Throwable {
650        try {
651            if (!closed) {
652                System.err.println("Cleaning up unclosed ZipFile for archive "
653                                   + archiveName);
654                close();
655            }
656        } finally {
657            super.finalize();
658        }
659    }
660
661    /**
662     * Length of a "central directory" entry structure without file
663     * name, extra fields or comment.
664     */
665    private static final int CFH_LEN =
666        /* version made by                 */ SHORT
667        /* version needed to extract       */ + SHORT
668        /* general purpose bit flag        */ + SHORT
669        /* compression method              */ + SHORT
670        /* last mod file time              */ + SHORT
671        /* last mod file date              */ + SHORT
672        /* crc-32                          */ + WORD
673        /* compressed size                 */ + WORD
674        /* uncompressed size               */ + WORD
675        /* file name length                 */ + SHORT
676        /* extra field length              */ + SHORT
677        /* file comment length             */ + SHORT
678        /* disk number start               */ + SHORT
679        /* internal file attributes        */ + SHORT
680        /* external file attributes        */ + WORD
681        /* relative offset of local header */ + WORD;
682
683    private static final long CFH_SIG =
684        ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
685
686    /**
687     * Reads the central directory of the given archive and populates
688     * the internal tables with ZipArchiveEntry instances.
689     *
690     * <p>The ZipArchiveEntrys will know all data that can be obtained from
691     * the central directory alone, but not the data that requires the
692     * local file header or additional data to be read.</p>
693     *
694     * @return a map of zipentries that didn't have the language
695     * encoding flag set when read.
696     */
697    private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory()
698        throws IOException {
699        final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag =
700            new HashMap<>();
701
702        positionAtCentralDirectory();
703
704        wordBbuf.rewind();
705        IOUtils.readFully(archive, wordBbuf);
706        long sig = ZipLong.getValue(wordBuf);
707
708        if (sig != CFH_SIG && startsWithLocalFileHeader()) {
709            throw new IOException("Central directory is empty, can't expand"
710                                  + " corrupt archive.");
711        }
712
713        while (sig == CFH_SIG) {
714            readCentralDirectoryEntry(noUTF8Flag);
715            wordBbuf.rewind();
716            IOUtils.readFully(archive, wordBbuf);
717            sig = ZipLong.getValue(wordBuf);
718        }
719        return noUTF8Flag;
720    }
721
722    /**
723     * Reads an individual entry of the central directory, creats an
724     * ZipArchiveEntry from it and adds it to the global maps.
725     *
726     * @param noUTF8Flag map used to collect entries that don't have
727     * their UTF-8 flag set and whose name will be set by data read
728     * from the local file header later.  The current entry may be
729     * added to this map.
730     */
731    private void
732        readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag)
733        throws IOException {
734        cfhBbuf.rewind();
735        IOUtils.readFully(archive, cfhBbuf);
736        int off = 0;
737        final Entry ze = new Entry();
738
739        final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
740        off += SHORT;
741        ze.setVersionMadeBy(versionMadeBy);
742        ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
743
744        ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
745        off += SHORT; // version required
746
747        final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
748        final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
749        final ZipEncoding entryEncoding =
750            hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
751        if (hasUTF8Flag) {
752            ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
753        }
754        ze.setGeneralPurposeBit(gpFlag);
755        ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
756
757        off += SHORT;
758
759        //noinspection MagicConstant
760        ze.setMethod(ZipShort.getValue(cfhBuf, off));
761        off += SHORT;
762
763        final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
764        ze.setTime(time);
765        off += WORD;
766
767        ze.setCrc(ZipLong.getValue(cfhBuf, off));
768        off += WORD;
769
770        ze.setCompressedSize(ZipLong.getValue(cfhBuf, off));
771        off += WORD;
772
773        ze.setSize(ZipLong.getValue(cfhBuf, off));
774        off += WORD;
775
776        final int fileNameLen = ZipShort.getValue(cfhBuf, off);
777        off += SHORT;
778
779        final int extraLen = ZipShort.getValue(cfhBuf, off);
780        off += SHORT;
781
782        final int commentLen = ZipShort.getValue(cfhBuf, off);
783        off += SHORT;
784
785        ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
786        off += SHORT;
787
788        ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
789        off += SHORT;
790
791        ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
792        off += WORD;
793
794        final byte[] fileName = new byte[fileNameLen];
795        IOUtils.readFully(archive, ByteBuffer.wrap(fileName));
796        ze.setName(entryEncoding.decode(fileName), fileName);
797
798        // LFH offset,
799        ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off));
800        // data offset will be filled later
801        entries.add(ze);
802
803        final byte[] cdExtraData = new byte[extraLen];
804        IOUtils.readFully(archive, ByteBuffer.wrap(cdExtraData));
805        ze.setCentralDirectoryExtra(cdExtraData);
806
807        setSizesAndOffsetFromZip64Extra(ze);
808
809        final byte[] comment = new byte[commentLen];
810        IOUtils.readFully(archive, ByteBuffer.wrap(comment));
811        ze.setComment(entryEncoding.decode(comment));
812
813        if (!hasUTF8Flag && useUnicodeExtraFields) {
814            noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
815        }
816
817        ze.setStreamContiguous(true);
818    }
819
820    /**
821     * If the entry holds a Zip64 extended information extra field,
822     * read sizes from there if the entry's sizes are set to
823     * 0xFFFFFFFFF, do the same for the offset of the local file
824     * header.
825     *
826     * <p>Ensures the Zip64 extra either knows both compressed and
827     * uncompressed size or neither of both as the internal logic in
828     * ExtraFieldUtils forces the field to create local header data
829     * even if they are never used - and here a field with only one
830     * size would be invalid.</p>
831     */
832    private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze)
833        throws IOException {
834        final Zip64ExtendedInformationExtraField z64 =
835            (Zip64ExtendedInformationExtraField)
836            ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
837        if (z64 != null) {
838            final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
839            final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
840            final boolean hasRelativeHeaderOffset =
841                ze.getLocalHeaderOffset() == ZIP64_MAGIC;
842            final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT;
843            z64.reparseCentralDirectoryData(hasUncompressedSize,
844                                            hasCompressedSize,
845                                            hasRelativeHeaderOffset,
846                                            hasDiskStart);
847
848            if (hasUncompressedSize) {
849                ze.setSize(z64.getSize().getLongValue());
850            } else if (hasCompressedSize) {
851                z64.setSize(new ZipEightByteInteger(ze.getSize()));
852            }
853
854            if (hasCompressedSize) {
855                ze.setCompressedSize(z64.getCompressedSize().getLongValue());
856            } else if (hasUncompressedSize) {
857                z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
858            }
859
860            if (hasRelativeHeaderOffset) {
861                ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
862            }
863
864            if (hasDiskStart) {
865                ze.setDiskNumberStart(z64.getDiskStartNumber().getValue());
866            }
867        }
868    }
869
870    /**
871     * Length of the "End of central directory record" - which is
872     * supposed to be the last structure of the archive - without file
873     * comment.
874     */
875    static final int MIN_EOCD_SIZE =
876        /* end of central dir signature    */ WORD
877        /* number of this disk             */ + SHORT
878        /* number of the disk with the     */
879        /* start of the central directory  */ + SHORT
880        /* total number of entries in      */
881        /* the central dir on this disk    */ + SHORT
882        /* total number of entries in      */
883        /* the central dir                 */ + SHORT
884        /* size of the central directory   */ + WORD
885        /* offset of start of central      */
886        /* directory with respect to       */
887        /* the starting disk number        */ + WORD
888        /* zipfile comment length          */ + SHORT;
889
890    /**
891     * Maximum length of the "End of central directory record" with a
892     * file comment.
893     */
894    private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
895        /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
896
897    /**
898     * Offset of the field that holds the location of the first
899     * central directory entry inside the "End of central directory
900     * record" relative to the start of the "End of central directory
901     * record".
902     */
903    private static final int CFD_LOCATOR_OFFSET =
904        /* end of central dir signature    */ WORD
905        /* number of this disk             */ + SHORT
906        /* number of the disk with the     */
907        /* start of the central directory  */ + SHORT
908        /* total number of entries in      */
909        /* the central dir on this disk    */ + SHORT
910        /* total number of entries in      */
911        /* the central dir                 */ + SHORT
912        /* size of the central directory   */ + WORD;
913
914    /**
915     * Offset of the field that holds the disk number of the first
916     * central directory entry inside the "End of central directory
917     * record" relative to the start of the "End of central directory
918     * record".
919     */
920    private static final int CFD_DISK_OFFSET =
921            /* end of central dir signature    */ WORD
922            /* number of this disk             */ + SHORT;
923
924    /**
925     * Offset of the field that holds the location of the first
926     * central directory entry inside the "End of central directory
927     * record" relative to the "number of the disk with the start
928     * of the central directory".
929     */
930    private static final int CFD_LOCATOR_RELATIVE_OFFSET =
931            /* total number of entries in      */
932            /* the central dir on this disk    */ + SHORT
933            /* total number of entries in      */
934            /* the central dir                 */ + SHORT
935            /* size of the central directory   */ + WORD;
936
937    /**
938     * Length of the "Zip64 end of central directory locator" - which
939     * should be right in front of the "end of central directory
940     * record" if one is present at all.
941     */
942    private static final int ZIP64_EOCDL_LENGTH =
943        /* zip64 end of central dir locator sig */ WORD
944        /* number of the disk with the start    */
945        /* start of the zip64 end of            */
946        /* central directory                    */ + WORD
947        /* relative offset of the zip64         */
948        /* end of central directory record      */ + DWORD
949        /* total number of disks                */ + WORD;
950
951    /**
952     * Offset of the field that holds the location of the "Zip64 end
953     * of central directory record" inside the "Zip64 end of central
954     * directory locator" relative to the start of the "Zip64 end of
955     * central directory locator".
956     */
957    private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
958        /* zip64 end of central dir locator sig */ WORD
959        /* number of the disk with the start    */
960        /* start of the zip64 end of            */
961        /* central directory                    */ + WORD;
962
963    /**
964     * Offset of the field that holds the location of the first
965     * central directory entry inside the "Zip64 end of central
966     * directory record" relative to the start of the "Zip64 end of
967     * central directory record".
968     */
969    private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
970        /* zip64 end of central dir        */
971        /* signature                       */ WORD
972        /* size of zip64 end of central    */
973        /* directory record                */ + DWORD
974        /* version made by                 */ + SHORT
975        /* version needed to extract       */ + SHORT
976        /* number of this disk             */ + WORD
977        /* number of the disk with the     */
978        /* start of the central directory  */ + WORD
979        /* total number of entries in the  */
980        /* central directory on this disk  */ + DWORD
981        /* total number of entries in the  */
982        /* central directory               */ + DWORD
983        /* size of the central directory   */ + DWORD;
984
985    /**
986     * Offset of the field that holds the disk number of the first
987     * central directory entry inside the "Zip64 end of central
988     * directory record" relative to the start of the "Zip64 end of
989     * central directory record".
990     */
991    private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
992            /* zip64 end of central dir        */
993            /* signature                       */ WORD
994            /* size of zip64 end of central    */
995            /* directory record                */ + DWORD
996            /* version made by                 */ + SHORT
997            /* version needed to extract       */ + SHORT
998            /* number of this disk             */ + WORD;
999
1000    /**
1001     * Offset of the field that holds the location of the first
1002     * central directory entry inside the "Zip64 end of central
1003     * directory record" relative to the "number of the disk
1004     * with the start of the central directory".
1005     */
1006    private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
1007            /* total number of entries in the  */
1008            /* central directory on this disk  */ DWORD
1009            /* total number of entries in the  */
1010            /* central directory               */ + DWORD
1011            /* size of the central directory   */ + DWORD;
1012
1013    /**
1014     * Searches for either the &quot;Zip64 end of central directory
1015     * locator&quot; or the &quot;End of central dir record&quot;, parses
1016     * it and positions the stream at the first central directory
1017     * record.
1018     */
1019    private void positionAtCentralDirectory()
1020        throws IOException {
1021        positionAtEndOfCentralDirectoryRecord();
1022        boolean found = false;
1023        final boolean searchedForZip64EOCD =
1024            archive.position() > ZIP64_EOCDL_LENGTH;
1025        if (searchedForZip64EOCD) {
1026            archive.position(archive.position() - ZIP64_EOCDL_LENGTH);
1027            wordBbuf.rewind();
1028            IOUtils.readFully(archive, wordBbuf);
1029            found = Arrays.equals(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG,
1030                                  wordBuf);
1031        }
1032        if (!found) {
1033            // not a ZIP64 archive
1034            if (searchedForZip64EOCD) {
1035                skipBytes(ZIP64_EOCDL_LENGTH - WORD);
1036            }
1037            positionAtCentralDirectory32();
1038        } else {
1039            positionAtCentralDirectory64();
1040        }
1041    }
1042
1043    /**
1044     * Parses the &quot;Zip64 end of central directory locator&quot;,
1045     * finds the &quot;Zip64 end of central directory record&quot; using the
1046     * parsed information, parses that and positions the stream at the
1047     * first central directory record.
1048     *
1049     * Expects stream to be positioned right behind the &quot;Zip64
1050     * end of central directory locator&quot;'s signature.
1051     */
1052    private void positionAtCentralDirectory64()
1053        throws IOException {
1054        if (isSplitZipArchive) {
1055            wordBbuf.rewind();
1056            IOUtils.readFully(archive, wordBbuf);
1057            final long diskNumberOfEOCD = ZipLong.getValue(wordBuf);
1058
1059            dwordBbuf.rewind();
1060            IOUtils.readFully(archive, dwordBbuf);
1061            final long relativeOffsetOfEOCD = ZipEightByteInteger.getLongValue(dwordBuf);
1062            ((ZipSplitReadOnlySeekableByteChannel) archive)
1063                .position(diskNumberOfEOCD, relativeOffsetOfEOCD);
1064        } else {
1065            skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET
1066                    - WORD /* signature has already been read */);
1067            dwordBbuf.rewind();
1068            IOUtils.readFully(archive, dwordBbuf);
1069            archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
1070        }
1071
1072        wordBbuf.rewind();
1073        IOUtils.readFully(archive, wordBbuf);
1074        if (!Arrays.equals(wordBuf, ZipArchiveOutputStream.ZIP64_EOCD_SIG)) {
1075            throw new ZipException("Archive's ZIP64 end of central "
1076                                   + "directory locator is corrupt.");
1077        }
1078
1079        if (isSplitZipArchive) {
1080            skipBytes(ZIP64_EOCD_CFD_DISK_OFFSET
1081                    - WORD /* signature has already been read */);
1082            wordBbuf.rewind();
1083            IOUtils.readFully(archive, wordBbuf);
1084            final long diskNumberOfCFD = ZipLong.getValue(wordBuf);
1085
1086            skipBytes(ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET);
1087
1088            dwordBbuf.rewind();
1089            IOUtils.readFully(archive, dwordBbuf);
1090            final long relativeOffsetOfCFD = ZipEightByteInteger.getLongValue(dwordBuf);
1091            ((ZipSplitReadOnlySeekableByteChannel) archive)
1092                .position(diskNumberOfCFD, relativeOffsetOfCFD);
1093        } else {
1094            skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET
1095                    - WORD /* signature has already been read */);
1096            dwordBbuf.rewind();
1097            IOUtils.readFully(archive, dwordBbuf);
1098            archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
1099        }
1100    }
1101
1102    /**
1103     * Parses the &quot;End of central dir record&quot; and positions
1104     * the stream at the first central directory record.
1105     *
1106     * Expects stream to be positioned at the beginning of the
1107     * &quot;End of central dir record&quot;.
1108     */
1109    private void positionAtCentralDirectory32()
1110        throws IOException {
1111        if (isSplitZipArchive) {
1112            skipBytes(CFD_DISK_OFFSET);
1113            shortBbuf.rewind();
1114            IOUtils.readFully(archive, shortBbuf);
1115            final int diskNumberOfCFD = ZipShort.getValue(shortBuf);
1116
1117            skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
1118
1119            wordBbuf.rewind();
1120            IOUtils.readFully(archive, wordBbuf);
1121            final long relativeOffsetOfCFD = ZipLong.getValue(wordBuf);
1122            ((ZipSplitReadOnlySeekableByteChannel) archive)
1123                .position(diskNumberOfCFD, relativeOffsetOfCFD);
1124        } else {
1125            skipBytes(CFD_LOCATOR_OFFSET);
1126            wordBbuf.rewind();
1127            IOUtils.readFully(archive, wordBbuf);
1128            archive.position(ZipLong.getValue(wordBuf));
1129        }
1130    }
1131
1132    /**
1133     * Searches for the and positions the stream at the start of the
1134     * &quot;End of central dir record&quot;.
1135     */
1136    private void positionAtEndOfCentralDirectoryRecord()
1137        throws IOException {
1138        final boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE,
1139                                             ZipArchiveOutputStream.EOCD_SIG);
1140        if (!found) {
1141            throw new ZipException("Archive is not a ZIP archive");
1142        }
1143    }
1144
1145    /**
1146     * Searches the archive backwards from minDistance to maxDistance
1147     * for the given signature, positions the RandomaccessFile right
1148     * at the signature if it has been found.
1149     */
1150    private boolean tryToLocateSignature(final long minDistanceFromEnd,
1151                                         final long maxDistanceFromEnd,
1152                                         final byte[] sig) throws IOException {
1153        boolean found = false;
1154        long off = archive.size() - minDistanceFromEnd;
1155        final long stopSearching =
1156            Math.max(0L, archive.size() - maxDistanceFromEnd);
1157        if (off >= 0) {
1158            for (; off >= stopSearching; off--) {
1159                archive.position(off);
1160                try {
1161                    wordBbuf.rewind();
1162                    IOUtils.readFully(archive, wordBbuf);
1163                    wordBbuf.flip();
1164                } catch (EOFException ex) { // NOSONAR
1165                    break;
1166                }
1167                int curr = wordBbuf.get();
1168                if (curr == sig[POS_0]) {
1169                    curr = wordBbuf.get();
1170                    if (curr == sig[POS_1]) {
1171                        curr = wordBbuf.get();
1172                        if (curr == sig[POS_2]) {
1173                            curr = wordBbuf.get();
1174                            if (curr == sig[POS_3]) {
1175                                found = true;
1176                                break;
1177                            }
1178                        }
1179                    }
1180                }
1181            }
1182        }
1183        if (found) {
1184            archive.position(off);
1185        }
1186        return found;
1187    }
1188
1189    /**
1190     * Skips the given number of bytes or throws an EOFException if
1191     * skipping failed.
1192     */
1193    private void skipBytes(final int count) throws IOException {
1194        long currentPosition = archive.position();
1195        long newPosition = currentPosition + count;
1196        if (newPosition > archive.size()) {
1197            throw new EOFException();
1198        }
1199        archive.position(newPosition);
1200    }
1201
1202    /**
1203     * Number of bytes in local file header up to the &quot;length of
1204     * file name&quot; entry.
1205     */
1206    private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
1207        /* local file header signature     */ WORD
1208        /* version needed to extract       */ + SHORT
1209        /* general purpose bit flag        */ + SHORT
1210        /* compression method              */ + SHORT
1211        /* last mod file time              */ + SHORT
1212        /* last mod file date              */ + SHORT
1213        /* crc-32                          */ + WORD
1214        /* compressed size                 */ + WORD
1215        /* uncompressed size               */ + (long) WORD;
1216
1217    /**
1218     * Walks through all recorded entries and adds the data available
1219     * from the local file header.
1220     *
1221     * <p>Also records the offsets for the data to read from the
1222     * entries.</p>
1223     */
1224    private void resolveLocalFileHeaderData(final Map<ZipArchiveEntry, NameAndComment>
1225                                            entriesWithoutUTF8Flag)
1226        throws IOException {
1227        for (final ZipArchiveEntry zipArchiveEntry : entries) {
1228            // entries is filled in populateFromCentralDirectory and
1229            // never modified
1230            final Entry ze = (Entry) zipArchiveEntry;
1231            int[] lens = setDataOffset(ze);
1232            final int fileNameLen = lens[0];
1233            final int extraFieldLen = lens[1];
1234            skipBytes(fileNameLen);
1235            final byte[] localExtraData = new byte[extraFieldLen];
1236            IOUtils.readFully(archive, ByteBuffer.wrap(localExtraData));
1237            ze.setExtra(localExtraData);
1238
1239            if (entriesWithoutUTF8Flag.containsKey(ze)) {
1240                final NameAndComment nc = entriesWithoutUTF8Flag.get(ze);
1241                ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name,
1242                                                         nc.comment);
1243            }
1244        }
1245    }
1246
1247    private void fillNameMap() {
1248        for (final ZipArchiveEntry ze : entries) {
1249            // entries is filled in populateFromCentralDirectory and
1250            // never modified
1251            final String name = ze.getName();
1252            LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
1253            if (entriesOfThatName == null) {
1254                entriesOfThatName = new LinkedList<>();
1255                nameMap.put(name, entriesOfThatName);
1256            }
1257            entriesOfThatName.addLast(ze);
1258        }
1259    }
1260
1261    private int[] setDataOffset(ZipArchiveEntry ze) throws IOException {
1262        long offset = ze.getLocalHeaderOffset();
1263        if (isSplitZipArchive) {
1264            ((ZipSplitReadOnlySeekableByteChannel) archive)
1265                .position(ze.getDiskNumberStart(), offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
1266            // the offset should be updated to the global offset
1267            offset = archive.position() - LFH_OFFSET_FOR_FILENAME_LENGTH;
1268        } else {
1269            archive.position(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
1270        }
1271        wordBbuf.rewind();
1272        IOUtils.readFully(archive, wordBbuf);
1273        wordBbuf.flip();
1274        wordBbuf.get(shortBuf);
1275        final int fileNameLen = ZipShort.getValue(shortBuf);
1276        wordBbuf.get(shortBuf);
1277        final int extraFieldLen = ZipShort.getValue(shortBuf);
1278        ze.setDataOffset(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
1279                         + SHORT + SHORT + fileNameLen + extraFieldLen);
1280        return new int[] { fileNameLen, extraFieldLen };
1281    }
1282
1283    private long getDataOffset(ZipArchiveEntry ze) throws IOException {
1284        long s = ze.getDataOffset();
1285        if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
1286            setDataOffset(ze);
1287            return ze.getDataOffset();
1288        }
1289        return s;
1290    }
1291
1292    /**
1293     * Checks whether the archive starts with a LFH.  If it doesn't,
1294     * it may be an empty archive.
1295     */
1296    private boolean startsWithLocalFileHeader() throws IOException {
1297        archive.position(0);
1298        wordBbuf.rewind();
1299        IOUtils.readFully(archive, wordBbuf);
1300        return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
1301    }
1302
1303    /**
1304     * Creates new BoundedInputStream, according to implementation of
1305     * underlying archive channel.
1306     */
1307    private BoundedInputStream createBoundedInputStream(long start, long remaining) {
1308        return archive instanceof FileChannel ?
1309            new BoundedFileChannelInputStream(start, remaining) :
1310            new BoundedInputStream(start, remaining);
1311    }
1312
1313    /**
1314     * InputStream that delegates requests to the underlying
1315     * SeekableByteChannel, making sure that only bytes from a certain
1316     * range can be read.
1317     */
1318    private class BoundedInputStream extends InputStream {
1319        private ByteBuffer singleByteBuffer;
1320        private final long end;
1321        private long loc;
1322
1323        BoundedInputStream(final long start, final long remaining) {
1324            this.end = start+remaining;
1325            if (this.end < start) {
1326                // check for potential vulnerability due to overflow
1327                throw new IllegalArgumentException("Invalid length of stream at offset="+start+", length="+remaining);
1328            }
1329            loc = start;
1330        }
1331
1332        @Override
1333        public synchronized int read() throws IOException {
1334            if (loc >= end) {
1335                return -1;
1336            }
1337            if (singleByteBuffer == null) {
1338                singleByteBuffer = ByteBuffer.allocate(1);
1339            }
1340            else {
1341                singleByteBuffer.rewind();
1342            }
1343            int read = read(loc, singleByteBuffer);
1344            if (read < 0) {
1345                return read;
1346            }
1347            loc++;
1348            return singleByteBuffer.get() & 0xff;
1349        }
1350
1351        @Override
1352        public synchronized int read(final byte[] b, final int off, int len) throws IOException {
1353            if (len <= 0) {
1354                return 0;
1355            }
1356
1357            if (len > end-loc) {
1358                if (loc >= end) {
1359                    return -1;
1360                }
1361                len = (int)(end-loc);
1362            }
1363
1364            ByteBuffer buf;
1365            buf = ByteBuffer.wrap(b, off, len);
1366            int ret = read(loc, buf);
1367            if (ret > 0) {
1368                loc += ret;
1369                return ret;
1370            }
1371            return ret;
1372        }
1373
1374        protected int read(long pos, ByteBuffer buf) throws IOException {
1375            int read;
1376            synchronized (archive) {
1377                archive.position(pos);
1378                read = archive.read(buf);
1379            }
1380            buf.flip();
1381            return read;
1382        }
1383    }
1384
1385    /**
1386     * Lock-free implementation of BoundedInputStream. The
1387     * implementation uses positioned reads on the underlying archive
1388     * file channel and therefore performs significantly faster in
1389     * concurrent environment.
1390     */
1391    private class BoundedFileChannelInputStream extends BoundedInputStream {
1392        private final FileChannel archive;
1393
1394        BoundedFileChannelInputStream(final long start, final long remaining) {
1395            super(start, remaining);
1396            archive = (FileChannel)ZipFile.this.archive;
1397        }
1398
1399        @Override
1400        protected int read(long pos, ByteBuffer buf) throws IOException {
1401            int read = archive.read(buf, pos);
1402            buf.flip();
1403            return read;
1404        }
1405    }
1406
1407    private static final class NameAndComment {
1408        private final byte[] name;
1409        private final byte[] comment;
1410        private NameAndComment(final byte[] name, final byte[] comment) {
1411            this.name = name;
1412            this.comment = comment;
1413        }
1414    }
1415
1416    /**
1417     * Compares two ZipArchiveEntries based on their offset within the archive.
1418     *
1419     * <p>Won't return any meaningful results if one of the entries
1420     * isn't part of the archive at all.</p>
1421     *
1422     * @since 1.1
1423     */
1424    private final Comparator<ZipArchiveEntry> offsetComparator =
1425        new Comparator<ZipArchiveEntry>() {
1426        @Override
1427        public int compare(final ZipArchiveEntry e1, final ZipArchiveEntry e2) {
1428            if (e1 == e2) {
1429                return 0;
1430            }
1431
1432            final Entry ent1 = e1 instanceof Entry ? (Entry) e1 : null;
1433            final Entry ent2 = e2 instanceof Entry ? (Entry) e2 : null;
1434            if (ent1 == null) {
1435                return 1;
1436            }
1437            if (ent2 == null) {
1438                return -1;
1439            }
1440
1441            // disk number is prior to relative offset
1442            final long diskNumberStartVal = ent1.getDiskNumberStart() - ent2.getDiskNumberStart();
1443            if (diskNumberStartVal != 0) {
1444                return diskNumberStartVal < 0 ? -1 : +1;
1445            }
1446            final long val = (ent1.getLocalHeaderOffset()
1447                        - ent2.getLocalHeaderOffset());
1448            return val == 0 ? 0 : val < 0 ? -1 : +1;
1449        }
1450    };
1451
1452    /**
1453     * Extends ZipArchiveEntry to store the offset within the archive.
1454     */
1455    private static class Entry extends ZipArchiveEntry {
1456
1457        Entry() {
1458        }
1459
1460        @Override
1461        public int hashCode() {
1462            return 3 * super.hashCode()
1463                + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32);
1464        }
1465
1466        @Override
1467        public boolean equals(final Object other) {
1468            if (super.equals(other)) {
1469                // super.equals would return false if other were not an Entry
1470                final Entry otherEntry = (Entry) other;
1471                return getLocalHeaderOffset()
1472                        == otherEntry.getLocalHeaderOffset()
1473                    && super.getDataOffset()
1474                        == otherEntry.getDataOffset()
1475                    && super.getDiskNumberStart()
1476                        == otherEntry.getDiskNumberStart();
1477            }
1478            return false;
1479        }
1480    }
1481
1482    private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics {
1483        StoredStatisticsStream(InputStream in) {
1484            super(in);
1485        }
1486
1487        @Override
1488        public long getCompressedCount() {
1489            return super.getBytesRead();
1490        }
1491
1492        @Override
1493        public long getUncompressedCount() {
1494            return getCompressedCount();
1495        }
1496    }
1497}