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