You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/12/08 01:56:31 UTC
[commons-compress] branch master updated: Javadoc, sort methods
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git
The following commit(s) were added to refs/heads/master by this push:
new 5c2b2d20 Javadoc, sort methods
5c2b2d20 is described below
commit 5c2b2d2092e8f2f43e526ac31eb2067a18024fb8
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Wed Dec 7 20:56:26 2022 -0500
Javadoc, sort methods
---
.../commons/compress/archivers/zip/ZipFile.java | 1719 ++++++++++----------
1 file changed, 859 insertions(+), 860 deletions(-)
diff --git a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
index 3caf12f3..14e949b6 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/ZipFile.java
@@ -87,133 +87,377 @@ import org.apache.commons.compress.utils.InputStreamStatistics;
* instances.</li>
* <li>close is allowed to throw IOException.</li>
* </ul>
- *
*/
public class ZipFile implements Closeable {
+ /**
+ * Lock-free implementation of BoundedInputStream. The
+ * implementation uses positioned reads on the underlying archive
+ * file channel and therefore performs significantly faster in
+ * concurrent environment.
+ */
+ private class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
+ private final FileChannel archive;
+
+ BoundedFileChannelInputStream(final long start, final long remaining) {
+ super(start, remaining);
+ archive = (FileChannel) ZipFile.this.archive;
+ }
+
+ @Override
+ protected int read(final long pos, final ByteBuffer buf) throws IOException {
+ final int read = archive.read(buf, pos);
+ buf.flip();
+ return read;
+ }
+ }
+ /**
+ * Extends ZipArchiveEntry to store the offset within the archive.
+ */
+ private static class Entry extends ZipArchiveEntry {
+
+ Entry() {
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (super.equals(other)) {
+ // super.equals would return false if other were not an Entry
+ final Entry otherEntry = (Entry) other;
+ return getLocalHeaderOffset()
+ == otherEntry.getLocalHeaderOffset()
+ && super.getDataOffset()
+ == otherEntry.getDataOffset()
+ && super.getDiskNumberStart()
+ == otherEntry.getDiskNumberStart();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 3 * super.hashCode()
+ + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32);
+ }
+ }
+ private static final class NameAndComment {
+ private final byte[] name;
+ private final byte[] comment;
+ private NameAndComment(final byte[] name, final byte[] comment) {
+ this.name = name;
+ this.comment = comment;
+ }
+ }
+ private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics {
+ StoredStatisticsStream(final InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public long getCompressedCount() {
+ return super.getBytesRead();
+ }
+
+ @Override
+ public long getUncompressedCount() {
+ return getCompressedCount();
+ }
+ }
private static final int HASH_SIZE = 509;
static final int NIBLET_MASK = 0x0f;
static final int BYTE_SHIFT = 8;
private static final int POS_0 = 0;
+
private static final int POS_1 = 1;
+
private static final int POS_2 = 2;
+
private static final int POS_3 = 3;
+
private static final byte[] ONE_ZERO_BYTE = new byte[1];
/**
- * List of entries in the order they appear inside the central
- * directory.
+ * Length of a "central directory" entry structure without file
+ * name, extra fields or comment.
*/
- private final List<ZipArchiveEntry> entries =
- new LinkedList<>();
+ private static final int CFH_LEN =
+ /* version made by */ SHORT
+ /* version needed to extract */ + SHORT
+ /* general purpose bit flag */ + SHORT
+ /* compression method */ + SHORT
+ /* last mod file time */ + SHORT
+ /* last mod file date */ + SHORT
+ /* crc-32 */ + WORD
+ /* compressed size */ + WORD
+ /* uncompressed size */ + WORD
+ /* file name length */ + SHORT
+ /* extra field length */ + SHORT
+ /* file comment length */ + SHORT
+ /* disk number start */ + SHORT
+ /* internal file attributes */ + SHORT
+ /* external file attributes */ + WORD
+ /* relative offset of local header */ + WORD;
- /**
- * Maps String to list of ZipArchiveEntrys, name -> actual entries.
- */
- private final Map<String, LinkedList<ZipArchiveEntry>> nameMap =
- new HashMap<>(HASH_SIZE);
+ private static final long CFH_SIG =
+ ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
/**
- * The encoding to use for file names and the file comment.
- *
- * <p>For a list of possible values see <a
- * 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>.
- * Defaults to UTF-8.</p>
+ * Length of the "End of central directory record" - which is
+ * supposed to be the last structure of the archive - without file
+ * comment.
*/
- private final String encoding;
+ static final int MIN_EOCD_SIZE =
+ /* end of central dir signature */ WORD
+ /* number of this disk */ + SHORT
+ /* number of the disk with the */
+ /* start of the central directory */ + SHORT
+ /* total number of entries in */
+ /* the central dir on this disk */ + SHORT
+ /* total number of entries in */
+ /* the central dir */ + SHORT
+ /* size of the central directory */ + WORD
+ /* offset of start of central */
+ /* directory with respect to */
+ /* the starting disk number */ + WORD
+ /* zipfile comment length */ + SHORT;
/**
- * The zip encoding to use for file names and the file comment.
+ * Maximum length of the "End of central directory record" with a
+ * file comment.
*/
- private final ZipEncoding zipEncoding;
+ private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
+ /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
/**
- * File name of actual source.
+ * Offset of the field that holds the location of the length of
+ * the central directory inside the "End of central directory
+ * record" relative to the start of the "End of central directory
+ * record".
*/
- private final String archiveName;
+ private static final int CFD_LENGTH_OFFSET =
+ /* end of central dir signature */ WORD
+ /* number of this disk */ + SHORT
+ /* number of the disk with the */
+ /* start of the central directory */ + SHORT
+ /* total number of entries in */
+ /* the central dir on this disk */ + SHORT
+ /* total number of entries in */
+ /* the central dir */ + SHORT;
/**
- * The actual data source.
+ * Offset of the field that holds the disk number of the first
+ * central directory entry inside the "End of central directory
+ * record" relative to the start of the "End of central directory
+ * record".
*/
- private final SeekableByteChannel archive;
-
+ private static final int CFD_DISK_OFFSET =
+ /* end of central dir signature */ WORD
+ /* number of this disk */ + SHORT;
/**
- * Whether to look for and use Unicode extra fields.
+ * Offset of the field that holds the location of the first
+ * central directory entry inside the "End of central directory
+ * record" relative to the "number of the disk with the start
+ * of the central directory".
*/
- private final boolean useUnicodeExtraFields;
-
+ private static final int CFD_LOCATOR_RELATIVE_OFFSET =
+ /* total number of entries in */
+ /* the central dir on this disk */ + SHORT
+ /* total number of entries in */
+ /* the central dir */ + SHORT
+ /* size of the central directory */ + WORD;
/**
- * Whether the file is closed.
+ * Length of the "Zip64 end of central directory locator" - which
+ * should be right in front of the "end of central directory
+ * record" if one is present at all.
*/
- private volatile boolean closed = true;
-
+ private static final int ZIP64_EOCDL_LENGTH =
+ /* zip64 end of central dir locator sig */ WORD
+ /* number of the disk with the start */
+ /* start of the zip64 end of */
+ /* central directory */ + WORD
+ /* relative offset of the zip64 */
+ /* end of central directory record */ + DWORD
+ /* total number of disks */ + WORD;
/**
- * Whether the zip archive is a split zip archive
+ * Offset of the field that holds the location of the "Zip64 end
+ * of central directory record" inside the "Zip64 end of central
+ * directory locator" relative to the start of the "Zip64 end of
+ * central directory locator".
*/
- private final boolean isSplitZipArchive;
-
- // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
- private final byte[] dwordBuf = new byte[DWORD];
- private final byte[] wordBuf = new byte[WORD];
- private final byte[] cfhBuf = new byte[CFH_LEN];
- private final byte[] shortBuf = new byte[SHORT];
- private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
- private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
- private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
- private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
-
- private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;
- private long centralDirectoryStartOffset;
- private long firstLocalFileHeaderOffset = 0L;
-
+ private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
+ /* zip64 end of central dir locator sig */ WORD
+ /* number of the disk with the start */
+ /* start of the zip64 end of */
+ /* central directory */ + WORD;
/**
- * Opens the given file for reading, assuming "UTF8" for file names.
- *
- * @param f the archive.
- *
- * @throws IOException if an error occurs while reading the file.
+ * Offset of the field that holds the location of the first
+ * central directory entry inside the "Zip64 end of central
+ * directory record" relative to the start of the "Zip64 end of
+ * central directory record".
*/
- public ZipFile(final File f) throws IOException {
- this(f, ZipEncodingHelper.UTF8);
- }
-
+ private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
+ /* zip64 end of central dir */
+ /* signature */ WORD
+ /* size of zip64 end of central */
+ /* directory record */ + DWORD
+ /* version made by */ + SHORT
+ /* version needed to extract */ + SHORT
+ /* number of this disk */ + WORD
+ /* number of the disk with the */
+ /* start of the central directory */ + WORD
+ /* total number of entries in the */
+ /* central directory on this disk */ + DWORD
+ /* total number of entries in the */
+ /* central directory */ + DWORD
+ /* size of the central directory */ + DWORD;
/**
- * Opens the given path for reading, assuming "UTF8" for file names.
- * @param path path to the archive.
- * @throws IOException if an error occurs while reading the file.
- * @since 1.22
+ * Offset of the field that holds the disk number of the first
+ * central directory entry inside the "Zip64 end of central
+ * directory record" relative to the start of the "Zip64 end of
+ * central directory record".
*/
- public ZipFile(final Path path) throws IOException {
- this(path, ZipEncodingHelper.UTF8);
- }
-
- /**
- * Opens the given file for reading, assuming "UTF8".
+ private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
+ /* zip64 end of central dir */
+ /* signature */ WORD
+ /* size of zip64 end of central */
+ /* directory record */ + DWORD
+ /* version made by */ + SHORT
+ /* version needed to extract */ + SHORT
+ /* number of this disk */ + WORD;
+ /**
+ * Offset of the field that holds the location of the first
+ * central directory entry inside the "Zip64 end of central
+ * directory record" relative to the "number of the disk
+ * with the start of the central directory".
+ */
+ private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
+ /* total number of entries in the */
+ /* central directory on this disk */ DWORD
+ /* total number of entries in the */
+ /* central directory */ + DWORD
+ /* size of the central directory */ + DWORD;
+ /**
+ * Number of bytes in local file header up to the "length of
+ * file name" entry.
+ */
+ private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
+ /* local file header signature */ WORD
+ /* version needed to extract */ + SHORT
+ /* general purpose bit flag */ + SHORT
+ /* compression method */ + SHORT
+ /* last mod file time */ + SHORT
+ /* last mod file date */ + SHORT
+ /* crc-32 */ + WORD
+ /* compressed size */ + WORD
+ /* uncompressed size */ + (long) WORD;
+
+ /**
+ * close a zipfile quietly; throw no io fault, do nothing
+ * on a null parameter
+ * @param zipfile file to close, can be null
+ */
+ public static void closeQuietly(final ZipFile zipfile) {
+ IOUtils.closeQuietly(zipfile);
+ }
+ /**
+ * List of entries in the order they appear inside the central
+ * directory.
+ */
+ private final List<ZipArchiveEntry> entries =
+ new LinkedList<>();
+ /**
+ * Maps String to list of ZipArchiveEntrys, name -> actual entries.
+ */
+ private final Map<String, LinkedList<ZipArchiveEntry>> nameMap =
+ new HashMap<>(HASH_SIZE);
+
+ /**
+ * The encoding to use for file names and the file comment.
*
- * @param name name of the archive.
+ * <p>For a list of possible values see <a
+ * 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>.
+ * Defaults to UTF-8.</p>
+ */
+ private final String encoding;
+
+ /**
+ * The zip encoding to use for file names and the file comment.
+ */
+ private final ZipEncoding zipEncoding;
+
+ /**
+ * File name of actual source.
+ */
+ private final String archiveName;
+
+ /**
+ * The actual data source.
+ */
+ private final SeekableByteChannel archive;
+
+ /**
+ * Whether to look for and use Unicode extra fields.
+ */
+ private final boolean useUnicodeExtraFields;
+
+ /**
+ * Whether the file is closed.
+ */
+ private volatile boolean closed = true;
+
+ /**
+ * Whether the zip archive is a split zip archive
+ */
+ private final boolean isSplitZipArchive;
+
+ // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection)
+ private final byte[] dwordBuf = new byte[DWORD];
+
+ private final byte[] wordBuf = new byte[WORD];
+
+ private final byte[] cfhBuf = new byte[CFH_LEN];
+
+ private final byte[] shortBuf = new byte[SHORT];
+
+ private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
+
+ private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
+
+ private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
+
+ private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
+
+ private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;
+
+ private long centralDirectoryStartOffset;
+
+ private long firstLocalFileHeaderOffset;
+
+ /**
+ * Compares two ZipArchiveEntries based on their offset within the archive.
*
- * @throws IOException if an error occurs while reading the file.
+ * <p>Won't return any meaningful results if one of the entries
+ * isn't part of the archive at all.</p>
+ *
+ * @since 1.1
*/
- public ZipFile(final String name) throws IOException {
- this(new File(name).toPath(), ZipEncodingHelper.UTF8);
- }
+ private final Comparator<ZipArchiveEntry> offsetComparator =
+ Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
+ .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
/**
- * Opens the given file for reading, assuming the specified
- * encoding for file names, scanning unicode extra fields.
+ * Opens the given file for reading, assuming "UTF8" for file names.
*
- * @param name name of the archive.
- * @param encoding the encoding to use for file names, use null
- * for the platform's default encoding
+ * @param f the archive.
*
* @throws IOException if an error occurs while reading the file.
*/
- public ZipFile(final String name, final String encoding) throws IOException {
- this(new File(name).toPath(), encoding, true);
+ public ZipFile(final File f) throws IOException {
+ this(f, ZipEncodingHelper.UTF8);
}
/**
* Opens the given file for reading, assuming the specified
- * encoding for file names and scanning for unicode extra fields.
+ * encoding for file names and scanning for Unicode extra fields.
*
* @param f the archive.
* @param encoding the encoding to use for file names, use null
@@ -225,19 +469,6 @@ public class ZipFile implements Closeable {
this(f.toPath(), encoding, true);
}
- /**
- * Opens the given path for reading, assuming the specified
- * encoding for file names and scanning for unicode extra fields.
- * @param path path to the archive.
- * @param encoding the encoding to use for file names, use null
- * for the platform's default encoding
- * @throws IOException if an error occurs while reading the file.
- * @since 1.22
- */
- public ZipFile(final Path path, final String encoding) throws IOException {
- this(path, encoding, true);
- }
-
/**
* Opens the given file for reading, assuming the specified
* encoding for file names.
@@ -255,27 +486,10 @@ public class ZipFile implements Closeable {
this(f.toPath(), encoding, useUnicodeExtraFields, false);
}
- /**
- * Opens the given path for reading, assuming the specified
- * encoding for file names.
- * @param path path to the archive.
- * @param encoding the encoding to use for file names, use null
- * for the platform's default encoding
- * @param useUnicodeExtraFields whether to use InfoZIP Unicode
- * Extra Fields (if present) to set the file names.
- * @throws IOException if an error occurs while reading the file.
- * @since 1.22
- */
- public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields)
- throws IOException {
- this(path, encoding, useUnicodeExtraFields, false);
- }
-
/**
* Opens the given file for reading, assuming the specified
* encoding for file names.
*
- *
* <p>By default the central directory record and all local file headers of the archive will be read immediately
* which may take a considerable amount of time when the archive is big. The {@code ignoreLocalFileHeader} parameter
* can be set to {@code true} which restricts parsing to the central directory. Unfortunately the local file header
@@ -301,6 +515,49 @@ public class ZipFile implements Closeable {
f.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
}
+ /**
+ * Opens the given path for reading, assuming "UTF8" for file names.
+ *
+ * @param path path to the archive.
+ * @throws IOException if an error occurs while reading the file.
+ * @since 1.22
+ */
+ public ZipFile(final Path path) throws IOException {
+ this(path, ZipEncodingHelper.UTF8);
+ }
+
+ /**
+ * Opens the given path for reading, assuming the specified
+ * encoding for file names and scanning for Unicode extra fields.
+ *
+ * @param path path to the archive.
+ * @param encoding the encoding to use for file names, use null
+ * for the platform's default encoding
+ * @throws IOException if an error occurs while reading the file.
+ * @since 1.22
+ */
+ public ZipFile(final Path path, final String encoding) throws IOException {
+ this(path, encoding, true);
+ }
+
+
+ /**
+ * Opens the given path for reading, assuming the specified
+ * encoding for file names.
+ *
+ * @param path path to the archive.
+ * @param encoding the encoding to use for file names, use null
+ * for the platform's default encoding
+ * @param useUnicodeExtraFields whether to use InfoZIP Unicode
+ * Extra Fields (if present) to set the file names.
+ * @throws IOException if an error occurs while reading the file.
+ * @since 1.22
+ */
+ public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields)
+ throws IOException {
+ this(path, encoding, useUnicodeExtraFields, false);
+ }
+
/**
* Opens the given path for reading, assuming the specified
* encoding for file names.
@@ -310,6 +567,7 @@ public class ZipFile implements Closeable {
* may contain information not present inside of the central directory which will not be available when the argument
* is set to {@code true}. This includes the content of the Unicode extra field, so setting {@code
* ignoreLocalFileHeader} to {@code true} means {@code useUnicodeExtraFields} will be ignored effectively.</p>
+ *
* @param path path to the archive.
* @param encoding the encoding to use for file names, use null
* for the platform's default encoding
@@ -454,12 +712,41 @@ public class ZipFile implements Closeable {
}
/**
- * The encoding to use for file names and the file comment.
+ * Opens the given file for reading, assuming "UTF8".
*
- * @return null if using the platform's default character encoding.
+ * @param name name of the archive.
+ *
+ * @throws IOException if an error occurs while reading the file.
*/
- public String getEncoding() {
- return encoding;
+ public ZipFile(final String name) throws IOException {
+ this(new File(name).toPath(), ZipEncodingHelper.UTF8);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the specified
+ * encoding for file names, scanning unicode extra fields.
+ *
+ * @param name name of the archive.
+ * @param encoding the encoding to use for file names, use null
+ * for the platform's default encoding
+ *
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(final String name, final String encoding) throws IOException {
+ this(new File(name).toPath(), encoding, true);
+ }
+
+ /**
+ * Whether this class is able to read the given entry.
+ *
+ * <p>May return false if it is set up to use encryption or a
+ * compression method that hasn't been implemented yet.</p>
+ * @since 1.1
+ * @param ze the entry
+ * @return whether this class is able to read the given entry.
+ */
+ public boolean canReadEntryData(final ZipArchiveEntry ze) {
+ return ZipUtil.canHandleEntryData(ze);
}
/**
@@ -477,61 +764,111 @@ public class ZipFile implements Closeable {
}
/**
- * close a zipfile quietly; throw no io fault, do nothing
- * on a null parameter
- * @param zipfile file to close, can be null
- */
- public static void closeQuietly(final ZipFile zipfile) {
- IOUtils.closeQuietly(zipfile);
- }
+ * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream.
+ * Compression and all other attributes will be as in this file.
+ * <p>This method transfers entries based on the central directory of the zip file.</p>
+ *
+ * @param target The zipArchiveOutputStream to write the entries to
+ * @param predicate A predicate that selects which entries to write
+ * @throws IOException on error
+ */
+ public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate)
+ throws IOException {
+ final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
+ while (src.hasMoreElements()) {
+ final ZipArchiveEntry entry = src.nextElement();
+ if (predicate.test( entry)) {
+ target.addRawArchiveEntry(entry, getRawInputStream(entry));
+ }
+ }
+ }
/**
- * Returns all entries.
- *
- * <p>Entries will be returned in the same order they appear
- * within the archive's central directory.</p>
- *
- * @return all entries as {@link ZipArchiveEntry} instances
+ * Creates new BoundedInputStream, according to implementation of
+ * underlying archive channel.
*/
- public Enumeration<ZipArchiveEntry> getEntries() {
- return Collections.enumeration(entries);
+ private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
+ if (start < 0 || remaining < 0 || start + remaining < start) {
+ throw new IllegalArgumentException("Corrupted archive, stream boundaries"
+ + " are out of range");
+ }
+ return archive instanceof FileChannel ?
+ new BoundedFileChannelInputStream(start, remaining) :
+ new BoundedSeekableByteChannelInputStream(start, remaining, archive);
+ }
+
+ private void fillNameMap() {
+ entries.forEach(ze -> {
+ // entries is filled in populateFromCentralDirectory and
+ // never modified
+ final String name = ze.getName();
+ LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
+ entriesOfThatName.addLast(ze);
+ });
}
/**
- * Returns all entries in physical order.
- *
- * <p>Entries will be returned in the same order their contents
- * appear within the archive.</p>
+ * Ensures that the close method of this zipfile is called when
+ * there are no more references to it.
+ * @see #close()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (!closed) {
+ System.err.println("Cleaning up unclosed ZipFile for archive "
+ + archiveName);
+ close();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Gets an InputStream for reading the content before the first local file header.
*
- * @return all entries as {@link ZipArchiveEntry} instances
+ * @return null if there is no content before the first local file header.
+ * Otherwise returns a stream to read the content before the first local file header.
+ * @since 1.23
+ */
+ public InputStream getContentBeforeFirstLocalFileHeader() {
+ return firstLocalFileHeaderOffset == 0
+ ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
+ }
+
+ private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
+ final long s = ze.getDataOffset();
+ if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
+ setDataOffset(ze);
+ return ze.getDataOffset();
+ }
+ return s;
+ }
+
+ /**
+ * Gets the encoding to use for file names and the file comment.
*
- * @since 1.1
+ * @return null if using the platform's default character encoding.
*/
- public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
- final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
- Arrays.sort(allEntries, offsetComparator);
- return Collections.enumeration(Arrays.asList(allEntries));
+ public String getEncoding() {
+ return encoding;
}
/**
- * Returns a named entry - or {@code null} if no entry by
- * that name exists.
+ * Gets all entries.
*
- * <p>If multiple entries with the same name exist the first entry
- * in the archive's central directory by that name is
- * returned.</p>
+ * <p>Entries will be returned in the same order they appear
+ * within the archive's central directory.</p>
*
- * @param name name of the entry.
- * @return the ZipArchiveEntry corresponding to the given name - or
- * {@code null} if not present.
+ * @return all entries as {@link ZipArchiveEntry} instances
*/
- public ZipArchiveEntry getEntry(final String name) {
- final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
- return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
+ public Enumeration<ZipArchiveEntry> getEntries() {
+ return Collections.enumeration(entries);
}
/**
- * Returns all named entries in the same order they appear within
+ * Gets all named entries in the same order they appear within
* the archive's central directory.
*
* @param name name of the entry.
@@ -546,7 +883,23 @@ public class ZipFile implements Closeable {
}
/**
- * Returns all named entries in the same order their contents
+ * Gets all entries in physical order.
+ *
+ * <p>Entries will be returned in the same order their contents
+ * appear within the archive.</p>
+ *
+ * @return all entries as {@link ZipArchiveEntry} instances
+ *
+ * @since 1.1
+ */
+ public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
+ final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
+ Arrays.sort(allEntries, offsetComparator);
+ return Collections.enumeration(Arrays.asList(allEntries));
+ }
+
+ /**
+ * Gets all named entries in the same order their contents
* appear within the archive.
*
* @param name name of the entry.
@@ -565,69 +918,34 @@ public class ZipFile implements Closeable {
}
/**
- * Whether this class is able to read the given entry.
- *
- * <p>May return false if it is set up to use encryption or a
- * compression method that hasn't been implemented yet.</p>
- * @since 1.1
- * @param ze the entry
- * @return whether this class is able to read the given entry.
- */
- public boolean canReadEntryData(final ZipArchiveEntry ze) {
- return ZipUtil.canHandleEntryData(ze);
- }
-
- /**
- * Expose the raw stream of the archive entry (compressed form).
- *
- * <p>This method does not relate to how/if we understand the payload in the
- * stream, since we really only intend to move it on to somewhere else.</p>
+ * Gets a named entry or {@code null} if no entry by
+ * that name exists.
*
- * <p>Since version 1.22, this method will make an attempt to read the entry's data
- * stream offset, even if the {@code ignoreLocalFileHeader} parameter was {@code true}
- * in the constructor. An IOException can also be thrown from the body of the method
- * if this lookup fails for some reason.</p>
+ * <p>If multiple entries with the same name exist the first entry
+ * in the archive's central directory by that name is
+ * returned.</p>
*
- * @param ze The entry to get the stream for
- * @return The raw input stream containing (possibly) compressed data.
- * @since 1.11
- * @throws IOException if there is a problem reading data offset (added in version 1.22).
+ * @param name name of the entry.
+ * @return the ZipArchiveEntry corresponding to the given name - or
+ * {@code null} if not present.
*/
- public InputStream getRawInputStream(final ZipArchiveEntry ze) throws IOException {
- if (!(ze instanceof Entry)) {
- return null;
- }
-
- final long start = getDataOffset(ze);
- if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
- return null;
- }
- return createBoundedInputStream(start, ze.getCompressedSize());
+ public ZipArchiveEntry getEntry(final String name) {
+ final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.get(name);
+ return entriesOfThatName != null ? entriesOfThatName.getFirst() : null;
}
-
/**
- * Transfer selected entries from this zipfile to a given #ZipArchiveOutputStream.
- * Compression and all other attributes will be as in this file.
- * <p>This method transfers entries based on the central directory of the zip file.</p>
+ * Gets the offset of the first local file header in the file.
*
- * @param target The zipArchiveOutputStream to write the entries to
- * @param predicate A predicate that selects which entries to write
- * @throws IOException on error
+ * @return the length of the content before the first local file header
+ * @since 1.23
*/
- public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate)
- throws IOException {
- final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
- while (src.hasMoreElements()) {
- final ZipArchiveEntry entry = src.nextElement();
- if (predicate.test( entry)) {
- target.addRawArchiveEntry(entry, getRawInputStream(entry));
- }
- }
+ public long getFirstLocalFileHeaderOffset() {
+ return firstLocalFileHeaderOffset;
}
/**
- * Returns an InputStream for reading the contents of the given entry.
+ * Gets an InputStream for reading the contents of the given entry.
*
* @param ze the entry to get the stream for.
* @return a stream to read the entry from. The returned stream
@@ -699,11 +1017,36 @@ public class ZipFile implements Closeable {
}
/**
- * <p>
- * Convenience method to return the entry's content as a String if isUnixSymlink()
- * returns true for it, otherwise returns null.
- * </p>
+ * Gets the raw stream of the archive entry (compressed form).
+ *
+ * <p>This method does not relate to how/if we understand the payload in the
+ * stream, since we really only intend to move it on to somewhere else.</p>
*
+ * <p>Since version 1.22, this method will make an attempt to read the entry's data
+ * stream offset, even if the {@code ignoreLocalFileHeader} parameter was {@code true}
+ * in the constructor. An IOException can also be thrown from the body of the method
+ * if this lookup fails for some reason.</p>
+ *
+ * @param ze The entry to get the stream for
+ * @return The raw input stream containing (possibly) compressed data.
+ * @since 1.11
+ * @throws IOException if there is a problem reading data offset (added in version 1.22).
+ */
+ public InputStream getRawInputStream(final ZipArchiveEntry ze) throws IOException {
+ if (!(ze instanceof Entry)) {
+ return null;
+ }
+
+ final long start = getDataOffset(ze);
+ if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
+ return null;
+ }
+ return createBoundedInputStream(start, ze.getCompressedSize());
+ }
+
+ /**
+ * Gets the entry's content as a String if isUnixSymlink()
+ * returns true for it, otherwise returns null.
* <p>This method assumes the symbolic link's file name uses the
* same encoding that as been specified for this ZipFile.</p>
*
@@ -722,85 +1065,20 @@ public class ZipFile implements Closeable {
}
/**
- * Offset of the first local file header in the file.
+ * Reads the central directory of the given archive and populates
+ * the internal tables with ZipArchiveEntry instances.
*
- * @return the length of the content before the first local file header
- * @since 1.23
- */
- public long getFirstLocalFileHeaderOffset() {
- return firstLocalFileHeaderOffset;
- }
-
- /**
- * Returns an InputStream for reading the content before the first local file header.
+ * <p>The ZipArchiveEntrys will know all data that can be obtained from
+ * the central directory alone, but not the data that requires the
+ * local file header or additional data to be read.</p>
*
- * @return null if there is no content before the first local file header.
- * Otherwise returns a stream to read the content before the first local file header.
- * @since 1.23
+ * @return a map of zipentries that didn't have the language
+ * encoding flag set when read.
*/
- public InputStream getContentBeforeFirstLocalFileHeader() {
- return firstLocalFileHeaderOffset == 0
- ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
- }
-
- /**
- * Ensures that the close method of this zipfile is called when
- * there are no more references to it.
- * @see #close()
- */
- @Override
- protected void finalize() throws Throwable {
- try {
- if (!closed) {
- System.err.println("Cleaning up unclosed ZipFile for archive "
- + archiveName);
- close();
- }
- } finally {
- super.finalize();
- }
- }
-
- /**
- * Length of a "central directory" entry structure without file
- * name, extra fields or comment.
- */
- private static final int CFH_LEN =
- /* version made by */ SHORT
- /* version needed to extract */ + SHORT
- /* general purpose bit flag */ + SHORT
- /* compression method */ + SHORT
- /* last mod file time */ + SHORT
- /* last mod file date */ + SHORT
- /* crc-32 */ + WORD
- /* compressed size */ + WORD
- /* uncompressed size */ + WORD
- /* file name length */ + SHORT
- /* extra field length */ + SHORT
- /* file comment length */ + SHORT
- /* disk number start */ + SHORT
- /* internal file attributes */ + SHORT
- /* external file attributes */ + WORD
- /* relative offset of local header */ + WORD;
-
- private static final long CFH_SIG =
- ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
-
- /**
- * Reads the central directory of the given archive and populates
- * the internal tables with ZipArchiveEntry instances.
- *
- * <p>The ZipArchiveEntrys will know all data that can be obtained from
- * the central directory alone, but not the data that requires the
- * local file header or additional data to be read.</p>
- *
- * @return a map of zipentries that didn't have the language
- * encoding flag set when read.
- */
- private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory()
- throws IOException {
- final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag =
- new HashMap<>();
+ private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory()
+ throws IOException {
+ final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag =
+ new HashMap<>();
positionAtCentralDirectory();
centralDirectoryStartOffset = archive.position();
@@ -809,371 +1087,19 @@ public class ZipFile implements Closeable {
IOUtils.readFully(archive, wordBbuf);
long sig = ZipLong.getValue(wordBuf);
- if (sig != CFH_SIG && startsWithLocalFileHeader()) {
- throw new IOException("Central directory is empty, can't expand"
- + " corrupt archive.");
- }
-
- while (sig == CFH_SIG) {
- readCentralDirectoryEntry(noUTF8Flag);
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- sig = ZipLong.getValue(wordBuf);
- }
- return noUTF8Flag;
- }
-
- /**
- * Reads an individual entry of the central directory, creats an
- * ZipArchiveEntry from it and adds it to the global maps.
- *
- * @param noUTF8Flag map used to collect entries that don't have
- * their UTF-8 flag set and whose name will be set by data read
- * from the local file header later. The current entry may be
- * added to this map.
- */
- private void
- readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag)
- throws IOException {
- cfhBbuf.rewind();
- IOUtils.readFully(archive, cfhBbuf);
- int off = 0;
- final Entry ze = new Entry();
-
- final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
- off += SHORT;
- ze.setVersionMadeBy(versionMadeBy);
- ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
-
- ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
- off += SHORT; // version required
-
- final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
- final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
- final ZipEncoding entryEncoding =
- hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
- if (hasUTF8Flag) {
- ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
- }
- ze.setGeneralPurposeBit(gpFlag);
- ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
-
- off += SHORT;
-
- //noinspection MagicConstant
- ze.setMethod(ZipShort.getValue(cfhBuf, off));
- off += SHORT;
-
- final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
- ze.setTime(time);
- off += WORD;
-
- ze.setCrc(ZipLong.getValue(cfhBuf, off));
- off += WORD;
-
- long size = ZipLong.getValue(cfhBuf, off);
- if (size < 0) {
- throw new IOException("broken archive, entry with negative compressed size");
- }
- ze.setCompressedSize(size);
- off += WORD;
-
- size = ZipLong.getValue(cfhBuf, off);
- if (size < 0) {
- throw new IOException("broken archive, entry with negative size");
- }
- ze.setSize(size);
- off += WORD;
-
- final int fileNameLen = ZipShort.getValue(cfhBuf, off);
- off += SHORT;
- if (fileNameLen < 0) {
- throw new IOException("broken archive, entry with negative fileNameLen");
- }
-
- final int extraLen = ZipShort.getValue(cfhBuf, off);
- off += SHORT;
- if (extraLen < 0) {
- throw new IOException("broken archive, entry with negative extraLen");
- }
-
- final int commentLen = ZipShort.getValue(cfhBuf, off);
- off += SHORT;
- if (commentLen < 0) {
- throw new IOException("broken archive, entry with negative commentLen");
- }
-
- ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
- off += SHORT;
-
- ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
- off += SHORT;
-
- ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
- off += WORD;
-
- final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
- if (fileName.length < fileNameLen) {
- throw new EOFException();
- }
- ze.setName(entryEncoding.decode(fileName), fileName);
-
- // LFH offset,
- ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
- // data offset will be filled later
- entries.add(ze);
-
- final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
- if (cdExtraData.length < extraLen) {
- throw new EOFException();
- }
- try {
- ze.setCentralDirectoryExtra(cdExtraData);
- } catch (RuntimeException ex) {
- final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
- z.initCause(ex);
- throw z;
- }
-
- setSizesAndOffsetFromZip64Extra(ze);
- sanityCheckLFHOffset(ze);
-
- final byte[] comment = IOUtils.readRange(archive, commentLen);
- if (comment.length < commentLen) {
- throw new EOFException();
- }
- ze.setComment(entryEncoding.decode(comment));
-
- if (!hasUTF8Flag && useUnicodeExtraFields) {
- noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
- }
-
- ze.setStreamContiguous(true);
- }
-
- private void sanityCheckLFHOffset(final ZipArchiveEntry ze) throws IOException {
- if (ze.getDiskNumberStart() < 0) {
- throw new IOException("broken archive, entry with negative disk number");
- }
- if (ze.getLocalHeaderOffset() < 0) {
- throw new IOException("broken archive, entry with negative local file header offset");
- }
- if (isSplitZipArchive) {
- if (ze.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
- throw new IOException("local file header for " + ze.getName() + " starts on a later disk than central directory");
- }
- if (ze.getDiskNumberStart() == centralDirectoryStartDiskNumber
- && ze.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
- throw new IOException("local file header for " + ze.getName() + " starts after central directory");
- }
- } else if (ze.getLocalHeaderOffset() > centralDirectoryStartOffset) {
- throw new IOException("local file header for " + ze.getName() + " starts after central directory");
- }
- }
-
- /**
- * If the entry holds a Zip64 extended information extra field,
- * read sizes from there if the entry's sizes are set to
- * 0xFFFFFFFFF, do the same for the offset of the local file
- * header.
- *
- * <p>Ensures the Zip64 extra either knows both compressed and
- * uncompressed size or neither of both as the internal logic in
- * ExtraFieldUtils forces the field to create local header data
- * even if they are never used - and here a field with only one
- * size would be invalid.</p>
- */
- private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze)
- throws IOException {
- final ZipExtraField extra =
- ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
- if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
- throw new ZipException("archive contains unparseable zip64 extra field");
- }
- final Zip64ExtendedInformationExtraField z64 =
- (Zip64ExtendedInformationExtraField) extra;
- if (z64 != null) {
- final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
- final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
- final boolean hasRelativeHeaderOffset =
- ze.getLocalHeaderOffset() == ZIP64_MAGIC;
- final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT;
- z64.reparseCentralDirectoryData(hasUncompressedSize,
- hasCompressedSize,
- hasRelativeHeaderOffset,
- hasDiskStart);
-
- if (hasUncompressedSize) {
- final long size = z64.getSize().getLongValue();
- if (size < 0) {
- throw new IOException("broken archive, entry with negative size");
- }
- ze.setSize(size);
- } else if (hasCompressedSize) {
- z64.setSize(new ZipEightByteInteger(ze.getSize()));
- }
-
- if (hasCompressedSize) {
- final long size = z64.getCompressedSize().getLongValue();
- if (size < 0) {
- throw new IOException("broken archive, entry with negative compressed size");
- }
- ze.setCompressedSize(size);
- } else if (hasUncompressedSize) {
- z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
- }
-
- if (hasRelativeHeaderOffset) {
- ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
- }
-
- if (hasDiskStart) {
- ze.setDiskNumberStart(z64.getDiskStartNumber().getValue());
- }
- }
- }
-
- /**
- * Length of the "End of central directory record" - which is
- * supposed to be the last structure of the archive - without file
- * comment.
- */
- static final int MIN_EOCD_SIZE =
- /* end of central dir signature */ WORD
- /* number of this disk */ + SHORT
- /* number of the disk with the */
- /* start of the central directory */ + SHORT
- /* total number of entries in */
- /* the central dir on this disk */ + SHORT
- /* total number of entries in */
- /* the central dir */ + SHORT
- /* size of the central directory */ + WORD
- /* offset of start of central */
- /* directory with respect to */
- /* the starting disk number */ + WORD
- /* zipfile comment length */ + SHORT;
-
- /**
- * Maximum length of the "End of central directory record" with a
- * file comment.
- */
- private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
- /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT;
-
- /**
- * Offset of the field that holds the location of the length of
- * the central directory inside the "End of central directory
- * record" relative to the start of the "End of central directory
- * record".
- */
- private static final int CFD_LENGTH_OFFSET =
- /* end of central dir signature */ WORD
- /* number of this disk */ + SHORT
- /* number of the disk with the */
- /* start of the central directory */ + SHORT
- /* total number of entries in */
- /* the central dir on this disk */ + SHORT
- /* total number of entries in */
- /* the central dir */ + SHORT;
-
- /**
- * Offset of the field that holds the disk number of the first
- * central directory entry inside the "End of central directory
- * record" relative to the start of the "End of central directory
- * record".
- */
- private static final int CFD_DISK_OFFSET =
- /* end of central dir signature */ WORD
- /* number of this disk */ + SHORT;
-
- /**
- * Offset of the field that holds the location of the first
- * central directory entry inside the "End of central directory
- * record" relative to the "number of the disk with the start
- * of the central directory".
- */
- private static final int CFD_LOCATOR_RELATIVE_OFFSET =
- /* total number of entries in */
- /* the central dir on this disk */ + SHORT
- /* total number of entries in */
- /* the central dir */ + SHORT
- /* size of the central directory */ + WORD;
-
- /**
- * Length of the "Zip64 end of central directory locator" - which
- * should be right in front of the "end of central directory
- * record" if one is present at all.
- */
- private static final int ZIP64_EOCDL_LENGTH =
- /* zip64 end of central dir locator sig */ WORD
- /* number of the disk with the start */
- /* start of the zip64 end of */
- /* central directory */ + WORD
- /* relative offset of the zip64 */
- /* end of central directory record */ + DWORD
- /* total number of disks */ + WORD;
-
- /**
- * Offset of the field that holds the location of the "Zip64 end
- * of central directory record" inside the "Zip64 end of central
- * directory locator" relative to the start of the "Zip64 end of
- * central directory locator".
- */
- private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
- /* zip64 end of central dir locator sig */ WORD
- /* number of the disk with the start */
- /* start of the zip64 end of */
- /* central directory */ + WORD;
-
- /**
- * Offset of the field that holds the location of the first
- * central directory entry inside the "Zip64 end of central
- * directory record" relative to the start of the "Zip64 end of
- * central directory record".
- */
- private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
- /* zip64 end of central dir */
- /* signature */ WORD
- /* size of zip64 end of central */
- /* directory record */ + DWORD
- /* version made by */ + SHORT
- /* version needed to extract */ + SHORT
- /* number of this disk */ + WORD
- /* number of the disk with the */
- /* start of the central directory */ + WORD
- /* total number of entries in the */
- /* central directory on this disk */ + DWORD
- /* total number of entries in the */
- /* central directory */ + DWORD
- /* size of the central directory */ + DWORD;
-
- /**
- * Offset of the field that holds the disk number of the first
- * central directory entry inside the "Zip64 end of central
- * directory record" relative to the start of the "Zip64 end of
- * central directory record".
- */
- private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
- /* zip64 end of central dir */
- /* signature */ WORD
- /* size of zip64 end of central */
- /* directory record */ + DWORD
- /* version made by */ + SHORT
- /* version needed to extract */ + SHORT
- /* number of this disk */ + WORD;
-
- /**
- * Offset of the field that holds the location of the first
- * central directory entry inside the "Zip64 end of central
- * directory record" relative to the "number of the disk
- * with the start of the central directory".
- */
- private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
- /* total number of entries in the */
- /* central directory on this disk */ DWORD
- /* total number of entries in the */
- /* central directory */ + DWORD
- /* size of the central directory */ + DWORD;
+ if (sig != CFH_SIG && startsWithLocalFileHeader()) {
+ throw new IOException("Central directory is empty, can't expand"
+ + " corrupt archive.");
+ }
+
+ while (sig == CFH_SIG) {
+ readCentralDirectoryEntry(noUTF8Flag);
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ sig = ZipLong.getValue(wordBuf);
+ }
+ return noUTF8Flag;
+ }
/**
* Searches for either the "Zip64 end of central directory
@@ -1205,6 +1131,47 @@ public class ZipFile implements Closeable {
}
}
+ /**
+ * Parses the "End of central dir record" and positions
+ * the stream at the first central directory record.
+ *
+ * Expects stream to be positioned at the beginning of the
+ * "End of central dir record".
+ */
+ private void positionAtCentralDirectory32()
+ throws IOException {
+ long endOfCentralDirectoryRecordOffset = archive.position();
+ if (isSplitZipArchive) {
+ skipBytes(CFD_DISK_OFFSET);
+ shortBbuf.rewind();
+ IOUtils.readFully(archive, shortBbuf);
+ centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
+
+ skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
+
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
+ ((ZipSplitReadOnlySeekableByteChannel) archive)
+ .position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
+ } else {
+ skipBytes(CFD_LENGTH_OFFSET);
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ long centralDirectoryLength = ZipLong.getValue(wordBuf);
+
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ centralDirectoryStartDiskNumber = 0;
+ centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
+
+ firstLocalFileHeaderOffset = Long.max(
+ endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset,
+ 0L);
+ archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
+ }
+ }
+
/**
* Parses the "Zip64 end of central directory locator",
* finds the "Zip64 end of central directory record" using the
@@ -1266,47 +1233,6 @@ public class ZipFile implements Closeable {
}
}
- /**
- * Parses the "End of central dir record" and positions
- * the stream at the first central directory record.
- *
- * Expects stream to be positioned at the beginning of the
- * "End of central dir record".
- */
- private void positionAtCentralDirectory32()
- throws IOException {
- long endOfCentralDirectoryRecordOffset = archive.position();
- if (isSplitZipArchive) {
- skipBytes(CFD_DISK_OFFSET);
- shortBbuf.rewind();
- IOUtils.readFully(archive, shortBbuf);
- centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
-
- skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
-
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
- ((ZipSplitReadOnlySeekableByteChannel) archive)
- .position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
- } else {
- skipBytes(CFD_LENGTH_OFFSET);
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- long centralDirectoryLength = ZipLong.getValue(wordBuf);
-
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- centralDirectoryStartDiskNumber = 0;
- centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
-
- firstLocalFileHeaderOffset = Long.max(
- endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset,
- 0L);
- archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
- }
- }
-
/**
* Searches for the and positions the stream at the start of the
* "End of central dir record".
@@ -1321,76 +1247,132 @@ public class ZipFile implements Closeable {
}
/**
- * Searches the archive backwards from minDistance to maxDistance
- * for the given signature, positions the RandomaccessFile right
- * at the signature if it has been found.
+ * Reads an individual entry of the central directory, creats an
+ * ZipArchiveEntry from it and adds it to the global maps.
+ *
+ * @param noUTF8Flag map used to collect entries that don't have
+ * their UTF-8 flag set and whose name will be set by data read
+ * from the local file header later. The current entry may be
+ * added to this map.
*/
- private boolean tryToLocateSignature(final long minDistanceFromEnd,
- final long maxDistanceFromEnd,
- final byte[] sig) throws IOException {
- boolean found = false;
- long off = archive.size() - minDistanceFromEnd;
- final long stopSearching =
- Math.max(0L, archive.size() - maxDistanceFromEnd);
- if (off >= 0) {
- for (; off >= stopSearching; off--) {
- archive.position(off);
- try {
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- wordBbuf.flip();
- } catch (final EOFException ex) { // NOSONAR
- break;
- }
- int curr = wordBbuf.get();
- if (curr == sig[POS_0]) {
- curr = wordBbuf.get();
- if (curr == sig[POS_1]) {
- curr = wordBbuf.get();
- if (curr == sig[POS_2]) {
- curr = wordBbuf.get();
- if (curr == sig[POS_3]) {
- found = true;
- break;
- }
- }
- }
- }
- }
+ private void
+ readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag)
+ throws IOException {
+ cfhBbuf.rewind();
+ IOUtils.readFully(archive, cfhBbuf);
+ int off = 0;
+ final Entry ze = new Entry();
+
+ final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
+ off += SHORT;
+ ze.setVersionMadeBy(versionMadeBy);
+ ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
+
+ ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
+ off += SHORT; // version required
+
+ final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
+ final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
+ final ZipEncoding entryEncoding =
+ hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
+ if (hasUTF8Flag) {
+ ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
}
- if (found) {
- archive.position(off);
+ ze.setGeneralPurposeBit(gpFlag);
+ ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
+
+ off += SHORT;
+
+ //noinspection MagicConstant
+ ze.setMethod(ZipShort.getValue(cfhBuf, off));
+ off += SHORT;
+
+ final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
+ ze.setTime(time);
+ off += WORD;
+
+ ze.setCrc(ZipLong.getValue(cfhBuf, off));
+ off += WORD;
+
+ long size = ZipLong.getValue(cfhBuf, off);
+ if (size < 0) {
+ throw new IOException("broken archive, entry with negative compressed size");
}
- return found;
- }
+ ze.setCompressedSize(size);
+ off += WORD;
- /**
- * Skips the given number of bytes or throws an EOFException if
- * skipping failed.
- */
- private void skipBytes(final int count) throws IOException {
- final long currentPosition = archive.position();
- final long newPosition = currentPosition + count;
- if (newPosition > archive.size()) {
+ size = ZipLong.getValue(cfhBuf, off);
+ if (size < 0) {
+ throw new IOException("broken archive, entry with negative size");
+ }
+ ze.setSize(size);
+ off += WORD;
+
+ final int fileNameLen = ZipShort.getValue(cfhBuf, off);
+ off += SHORT;
+ if (fileNameLen < 0) {
+ throw new IOException("broken archive, entry with negative fileNameLen");
+ }
+
+ final int extraLen = ZipShort.getValue(cfhBuf, off);
+ off += SHORT;
+ if (extraLen < 0) {
+ throw new IOException("broken archive, entry with negative extraLen");
+ }
+
+ final int commentLen = ZipShort.getValue(cfhBuf, off);
+ off += SHORT;
+ if (commentLen < 0) {
+ throw new IOException("broken archive, entry with negative commentLen");
+ }
+
+ ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
+ off += SHORT;
+
+ ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
+ off += SHORT;
+
+ ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
+ off += WORD;
+
+ final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
+ if (fileName.length < fileNameLen) {
+ throw new EOFException();
+ }
+ ze.setName(entryEncoding.decode(fileName), fileName);
+
+ // LFH offset,
+ ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
+ // data offset will be filled later
+ entries.add(ze);
+
+ final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
+ if (cdExtraData.length < extraLen) {
+ throw new EOFException();
+ }
+ try {
+ ze.setCentralDirectoryExtra(cdExtraData);
+ } catch (RuntimeException ex) {
+ final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
+ z.initCause(ex);
+ throw z;
+ }
+
+ setSizesAndOffsetFromZip64Extra(ze);
+ sanityCheckLFHOffset(ze);
+
+ final byte[] comment = IOUtils.readRange(archive, commentLen);
+ if (comment.length < commentLen) {
throw new EOFException();
}
- archive.position(newPosition);
- }
+ ze.setComment(entryEncoding.decode(comment));
+
+ if (!hasUTF8Flag && useUnicodeExtraFields) {
+ noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
+ }
- /**
- * Number of bytes in local file header up to the "length of
- * file name" entry.
- */
- private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
- /* local file header signature */ WORD
- /* version needed to extract */ + SHORT
- /* general purpose bit flag */ + SHORT
- /* compression method */ + SHORT
- /* last mod file time */ + SHORT
- /* last mod file date */ + SHORT
- /* crc-32 */ + WORD
- /* compressed size */ + WORD
- /* uncompressed size */ + (long) WORD;
+ ze.setStreamContiguous(true);
+ }
/**
* Walks through all recorded entries and adds the data available
@@ -1430,14 +1412,24 @@ public class ZipFile implements Closeable {
}
}
- private void fillNameMap() {
- entries.forEach(ze -> {
- // entries is filled in populateFromCentralDirectory and
- // never modified
- final String name = ze.getName();
- LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
- entriesOfThatName.addLast(ze);
- });
+ private void sanityCheckLFHOffset(final ZipArchiveEntry ze) throws IOException {
+ if (ze.getDiskNumberStart() < 0) {
+ throw new IOException("broken archive, entry with negative disk number");
+ }
+ if (ze.getLocalHeaderOffset() < 0) {
+ throw new IOException("broken archive, entry with negative local file header offset");
+ }
+ if (isSplitZipArchive) {
+ if (ze.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
+ throw new IOException("local file header for " + ze.getName() + " starts on a later disk than central directory");
+ }
+ if (ze.getDiskNumberStart() == centralDirectoryStartDiskNumber
+ && ze.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
+ throw new IOException("local file header for " + ze.getName() + " starts after central directory");
+ }
+ } else if (ze.getLocalHeaderOffset() > centralDirectoryStartOffset) {
+ throw new IOException("local file header for " + ze.getName() + " starts after central directory");
+ }
}
private int[] setDataOffset(final ZipArchiveEntry ze) throws IOException {
@@ -1465,126 +1457,133 @@ public class ZipFile implements Closeable {
return new int[] { fileNameLen, extraFieldLen };
}
- private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
- final long s = ze.getDataOffset();
- if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
- setDataOffset(ze);
- return ze.getDataOffset();
- }
- return s;
- }
-
- /**
- * Checks whether the archive starts with a LFH. If it doesn't,
- * it may be an empty archive.
- */
- private boolean startsWithLocalFileHeader() throws IOException {
- archive.position(firstLocalFileHeaderOffset);
- wordBbuf.rewind();
- IOUtils.readFully(archive, wordBbuf);
- return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
- }
-
/**
- * Creates new BoundedInputStream, according to implementation of
- * underlying archive channel.
+ * If the entry holds a Zip64 extended information extra field,
+ * read sizes from there if the entry's sizes are set to
+ * 0xFFFFFFFFF, do the same for the offset of the local file
+ * header.
+ *
+ * <p>Ensures the Zip64 extra either knows both compressed and
+ * uncompressed size or neither of both as the internal logic in
+ * ExtraFieldUtils forces the field to create local header data
+ * even if they are never used - and here a field with only one
+ * size would be invalid.</p>
*/
- private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
- if (start < 0 || remaining < 0 || start + remaining < start) {
- throw new IllegalArgumentException("Corrupted archive, stream boundaries"
- + " are out of range");
+ private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry ze)
+ throws IOException {
+ final ZipExtraField extra =
+ ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
+ if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
+ throw new ZipException("archive contains unparseable zip64 extra field");
}
- return archive instanceof FileChannel ?
- new BoundedFileChannelInputStream(start, remaining) :
- new BoundedSeekableByteChannelInputStream(start, remaining, archive);
- }
+ final Zip64ExtendedInformationExtraField z64 =
+ (Zip64ExtendedInformationExtraField) extra;
+ if (z64 != null) {
+ final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC;
+ final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC;
+ final boolean hasRelativeHeaderOffset =
+ ze.getLocalHeaderOffset() == ZIP64_MAGIC;
+ final boolean hasDiskStart = ze.getDiskNumberStart() == ZIP64_MAGIC_SHORT;
+ z64.reparseCentralDirectoryData(hasUncompressedSize,
+ hasCompressedSize,
+ hasRelativeHeaderOffset,
+ hasDiskStart);
- /**
- * Lock-free implementation of BoundedInputStream. The
- * implementation uses positioned reads on the underlying archive
- * file channel and therefore performs significantly faster in
- * concurrent environment.
- */
- private class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
- private final FileChannel archive;
+ if (hasUncompressedSize) {
+ final long size = z64.getSize().getLongValue();
+ if (size < 0) {
+ throw new IOException("broken archive, entry with negative size");
+ }
+ ze.setSize(size);
+ } else if (hasCompressedSize) {
+ z64.setSize(new ZipEightByteInteger(ze.getSize()));
+ }
- BoundedFileChannelInputStream(final long start, final long remaining) {
- super(start, remaining);
- archive = (FileChannel) ZipFile.this.archive;
- }
+ if (hasCompressedSize) {
+ final long size = z64.getCompressedSize().getLongValue();
+ if (size < 0) {
+ throw new IOException("broken archive, entry with negative compressed size");
+ }
+ ze.setCompressedSize(size);
+ } else if (hasUncompressedSize) {
+ z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
+ }
- @Override
- protected int read(final long pos, final ByteBuffer buf) throws IOException {
- final int read = archive.read(buf, pos);
- buf.flip();
- return read;
+ if (hasRelativeHeaderOffset) {
+ ze.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
+ }
+
+ if (hasDiskStart) {
+ ze.setDiskNumberStart(z64.getDiskStartNumber().getValue());
+ }
}
}
- private static final class NameAndComment {
- private final byte[] name;
- private final byte[] comment;
- private NameAndComment(final byte[] name, final byte[] comment) {
- this.name = name;
- this.comment = comment;
+ /**
+ * Skips the given number of bytes or throws an EOFException if
+ * skipping failed.
+ */
+ private void skipBytes(final int count) throws IOException {
+ final long currentPosition = archive.position();
+ final long newPosition = currentPosition + count;
+ if (newPosition > archive.size()) {
+ throw new EOFException();
}
+ archive.position(newPosition);
}
/**
- * Compares two ZipArchiveEntries based on their offset within the archive.
- *
- * <p>Won't return any meaningful results if one of the entries
- * isn't part of the archive at all.</p>
- *
- * @since 1.1
+ * Checks whether the archive starts with a LFH. If it doesn't,
+ * it may be an empty archive.
*/
- private final Comparator<ZipArchiveEntry> offsetComparator =
- Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
- .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
+ private boolean startsWithLocalFileHeader() throws IOException {
+ archive.position(firstLocalFileHeaderOffset);
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
+ }
/**
- * Extends ZipArchiveEntry to store the offset within the archive.
+ * Searches the archive backwards from minDistance to maxDistance
+ * for the given signature, positions the RandomaccessFile right
+ * at the signature if it has been found.
*/
- private static class Entry extends ZipArchiveEntry {
-
- Entry() {
- }
-
- @Override
- public int hashCode() {
- return 3 * super.hashCode()
- + (int) getLocalHeaderOffset()+(int)(getLocalHeaderOffset()>>32);
- }
-
- @Override
- public boolean equals(final Object other) {
- if (super.equals(other)) {
- // super.equals would return false if other were not an Entry
- final Entry otherEntry = (Entry) other;
- return getLocalHeaderOffset()
- == otherEntry.getLocalHeaderOffset()
- && super.getDataOffset()
- == otherEntry.getDataOffset()
- && super.getDiskNumberStart()
- == otherEntry.getDiskNumberStart();
+ private boolean tryToLocateSignature(final long minDistanceFromEnd,
+ final long maxDistanceFromEnd,
+ final byte[] sig) throws IOException {
+ boolean found = false;
+ long off = archive.size() - minDistanceFromEnd;
+ final long stopSearching =
+ Math.max(0L, archive.size() - maxDistanceFromEnd);
+ if (off >= 0) {
+ for (; off >= stopSearching; off--) {
+ archive.position(off);
+ try {
+ wordBbuf.rewind();
+ IOUtils.readFully(archive, wordBbuf);
+ wordBbuf.flip();
+ } catch (final EOFException ex) { // NOSONAR
+ break;
+ }
+ int curr = wordBbuf.get();
+ if (curr == sig[POS_0]) {
+ curr = wordBbuf.get();
+ if (curr == sig[POS_1]) {
+ curr = wordBbuf.get();
+ if (curr == sig[POS_2]) {
+ curr = wordBbuf.get();
+ if (curr == sig[POS_3]) {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
}
- return false;
- }
- }
-
- private static class StoredStatisticsStream extends CountingInputStream implements InputStreamStatistics {
- StoredStatisticsStream(final InputStream in) {
- super(in);
- }
-
- @Override
- public long getCompressedCount() {
- return super.getBytesRead();
}
-
- @Override
- public long getUncompressedCount() {
- return getCompressedCount();
+ if (found) {
+ archive.position(off);
}
+ return found;
}
}