All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.apache.commons.compress.archivers.zip.ZipArchiveInputStream Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in com.fitburpliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.fitbur/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.zip;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;

import com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.ArchiveEntry;
import com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.ArchiveInputStream;

import static com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.zip.ZipConstants.DWORD;
import static com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.zip.ZipConstants.SHORT;
import static com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.zip.ZipConstants.WORD;
import static com.fitbur.apache.com.fitburmons.com.fitburpress.archivers.zip.ZipConstants.ZIP64_MAGIC;

/**
 * Implements an input stream that can read Zip archives.
 *
 * 

Note that {@link ZipArchiveEntry#getSize()} may return -1 if the * DEFLATE algorithm is used, as the size information is not available * from the header.

* *

The {@link ZipFile} class is preferred when reading from files.

* *

As of Apache Commons Compress it transparently supports Zip64 * extensions and thus individual entries and archives larger than 4 * GB or with more than 65536 entries.

* * @see ZipFile * @NotThreadSafe */ public class ZipArchiveInputStream extends ArchiveInputStream { /** * The zip encoding to use for filenames and the file com.fitburment. */ private final ZipEncoding zipEncoding; /** * Whether to look for and use Unicode extra fields. */ private final boolean useUnicodeExtraFields; /** * Wrapped stream, will always be a PushbackInputStream. */ private final InputStream in; /** * Inflater used for all com.fitburflated entries. */ private final Inflater inf = new Inflater(true); /** * Calculates checkusms for all entries. */ private final CRC32 crc = new CRC32(); /** * Buffer used to read from the wrapped stream. */ private final Buffer buf = new Buffer(); /** * The entry that is currently being read. */ private CurrentEntry current = null; /** * Whether the stream has been closed. */ private boolean closed = false; /** * Whether the stream has reached the central directory - and thus * found all entries. */ private boolean hitCentralDirectory = false; /** * When reading a stored entry that uses the data com.fitburscriptor this * stream has to read the full entry and caches it. This is the * cache. */ private ByteArrayInputStream lastStoredEntry = null; /** * Whether the stream will try to read STORED entries that use a * data com.fitburscriptor. */ private boolean allowStoredEntriesWithDataDescriptor = false; private static final int LFH_LEN = 30; /* local file header signature 4 bytes (0x04034b50) version needed to extract 2 bytes general purpose bit flag 2 bytes com.fitburpression method 2 bytes last mod file time 2 bytes last mod file date 2 bytes crc-32 4 bytes com.fitburpressed size 4 bytes uncompressed size 4 bytes file name length 2 bytes extra field length 2 bytes */ private static final int CFH_LEN = 46; /* central file header signature 4 bytes (0x02014b50) version made by 2 bytes version needed to extract 2 bytes general purpose bit flag 2 bytes com.fitburpression method 2 bytes last mod file time 2 bytes last mod file date 2 bytes crc-32 4 bytes com.fitburpressed size 4 bytes uncompressed size 4 bytes file name length 2 bytes extra field length 2 bytes file com.fitburment length 2 bytes disk number start 2 bytes internal file attributes 2 bytes external file attributes 4 bytes relative offset of local header 4 bytes */ private static final long TWO_EXP_32 = ZIP64_MAGIC + 1; // cached buffers - must only be used locally in the class (COMPRESS-172 - reduce garbage collection) private final byte[] LFH_BUF = new byte[LFH_LEN]; private final byte[] SKIP_BUF = new byte[1024]; private final byte[] SHORT_BUF = new byte[SHORT]; private final byte[] WORD_BUF = new byte[WORD]; private final byte[] TWO_DWORD_BUF = new byte[2 * DWORD]; private int entriesRead = 0; public ZipArchiveInputStream(InputStream inputStream) { this(inputStream, ZipEncodingHelper.UTF8); } /** * @param encoding the encoding to use for file names, use null * for the platform's com.fitburfault encoding * @since 1.5 */ public ZipArchiveInputStream(InputStream inputStream, String encoding) { this(inputStream, encoding, true); } /** * @param encoding the encoding to use for file names, use null * for the platform's com.fitburfault encoding * @param useUnicodeExtraFields whether to use InfoZIP Unicode * Extra Fields (if present) to set the file names. */ public ZipArchiveInputStream(InputStream inputStream, String encoding, boolean useUnicodeExtraFields) { this(inputStream, encoding, useUnicodeExtraFields, false); } /** * @param encoding the encoding to use for file names, use null * for the platform's com.fitburfault encoding * @param useUnicodeExtraFields whether to use InfoZIP Unicode * Extra Fields (if present) to set the file names. * @param allowStoredEntriesWithDataDescriptor whether the stream * will try to read STORED entries that use a data com.fitburscriptor * @since 1.1 */ public ZipArchiveInputStream(InputStream inputStream, String encoding, boolean useUnicodeExtraFields, boolean allowStoredEntriesWithDataDescriptor) { zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); this.useUnicodeExtraFields = useUnicodeExtraFields; in = new PushbackInputStream(inputStream, buf.buf.length); this.allowStoredEntriesWithDataDescriptor = allowStoredEntriesWithDataDescriptor; } public ZipArchiveEntry getNextZipEntry() throws IOException { boolean firstEntry = true; if (closed || hitCentralDirectory) { return null; } if (current != null) { closeEntry(); firstEntry = false; } try { if (firstEntry) { // split archives have a special signature before the // first local file header - look for it and fail with // the appropriate error message if this is a split // archive. readFirstLocalFileHeader(LFH_BUF); } else { readFully(LFH_BUF); } } catch (EOFException e) { return null; } ZipLong sig = new ZipLong(LFH_BUF); if (sig.equals(ZipLong.CFH_SIG) || sig.equals(ZipLong.AED_SIG)) { hitCentralDirectory = true; skipRemainderOfArchive(); } if (!sig.equals(ZipLong.LFH_SIG)) { return null; } int off = WORD; current = new CurrentEntry(); int versionMadeBy = ZipShort.getValue(LFH_BUF, off); off += SHORT; current.entry.setPlatform((versionMadeBy >> ZipFile.BYTE_SHIFT) & ZipFile.NIBLET_MASK); final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(LFH_BUF, off); final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; current.hasDataDescriptor = gpFlag.usesDataDescriptor(); current.entry.setGeneralPurposeBit(gpFlag); off += SHORT; current.entry.setMethod(ZipShort.getValue(LFH_BUF, off)); off += SHORT; long time = ZipUtil.dosToJavaTime(ZipLong.getValue(LFH_BUF, off)); current.entry.setTime(time); off += WORD; ZipLong size = null, cSize = null; if (!current.hasDataDescriptor) { current.entry.setCrc(ZipLong.getValue(LFH_BUF, off)); off += WORD; cSize = new ZipLong(LFH_BUF, off); off += WORD; size = new ZipLong(LFH_BUF, off); off += WORD; } else { off += 3 * WORD; } int fileNameLen = ZipShort.getValue(LFH_BUF, off); off += SHORT; int extraLen = ZipShort.getValue(LFH_BUF, off); off += SHORT; byte[] fileName = new byte[fileNameLen]; readFully(fileName); current.entry.setName(entryEncoding.com.fitburcode(fileName), fileName); byte[] extraData = new byte[extraLen]; readFully(extraData); current.entry.setExtra(extraData); if (!hasUTF8Flag && useUnicodeExtraFields) { ZipUtil.setNameAndCommentFromExtraFields(current.entry, fileName, null); } processZip64Extra(size, cSize); entriesRead++; return current.entry; } /** * Fills the given array with the first local file header and * com.fitburals with splitting/spanning markers that may prefix the first * LFH. */ private void readFirstLocalFileHeader(byte[] lfh) throws IOException { readFully(lfh); ZipLong sig = new ZipLong(lfh); if (sig.equals(ZipLong.DD_SIG)) { throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException .Feature.SPLITTING); } if (sig.equals(ZipLong.SINGLE_SEGMENT_SPLIT_MARKER)) { // The archive is not really split as only one segment was // needed in the end. Just skip over the marker. byte[] missedLfhBytes = new byte[4]; readFully(missedLfhBytes); System.arraycopy(lfh, 4, lfh, 0, LFH_LEN - 4); System.arraycopy(missedLfhBytes, 0, lfh, LFH_LEN - 4, 4); } } /** * Records whether a Zip64 extra is present and sets the size * information from it if sizes are 0xFFFFFFFF and the entry * doesn't use a data com.fitburscriptor. */ private void processZip64Extra(ZipLong size, ZipLong cSize) { Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) current.entry.getExtraField(Zip64ExtendedInformationExtraField .HEADER_ID); current.usesZip64 = z64 != null; if (!current.hasDataDescriptor) { if (current.usesZip64 && (cSize.equals(ZipLong.ZIP64_MAGIC) || size.equals(ZipLong.ZIP64_MAGIC)) ) { current.entry.setCompressedSize(z64.getCompressedSize() // z64 cannot be null here .getLongValue()); current.entry.setSize(z64.getSize().getLongValue()); } else { current.entry.setCompressedSize(cSize.getValue()); current.entry.setSize(size.getValue()); } } } /** {@inheritDoc} */ @Override public ArchiveEntry getNextEntry() throws IOException { return getNextZipEntry(); } /** * Whether this class is able to read the given entry. * *

May return false if it is set up to use encryption or a * com.fitburpression method that hasn't been implemented yet.

* @since 1.1 */ @Override public boolean canReadEntryData(ArchiveEntry ae) { if (ae instanceof ZipArchiveEntry) { ZipArchiveEntry ze = (ZipArchiveEntry) ae; return ZipUtil.canHandleEntryData(ze) && supportsDataDescriptorFor(ze); } return false; } @Override public int read(byte[] buffer, int start, int length) throws IOException { if (closed) { throw new IOException("The stream is closed"); } if (inf.finished() || current == null) { return -1; } // avoid int overflow, check null buffer if (start <= buffer.length && length >= 0 && start >= 0 && buffer.length - start >= length) { ZipUtil.checkRequestedFeatures(current.entry); if (!supportsDataDescriptorFor(current.entry)) { throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException .Feature .DATA_DESCRIPTOR, current.entry); } if (current.entry.getMethod() == ZipArchiveOutputStream.STORED) { return readStored(buffer, start, length); } return readDeflated(buffer, start, length); } throw new ArrayIndexOutOfBoundsException(); } /** * Implementation of read for STORED entries. */ private int readStored(byte[] buffer, int start, int length) throws IOException { if (current.hasDataDescriptor) { if (lastStoredEntry == null) { readStoredEntry(); } return lastStoredEntry.read(buffer, start, length); } long csize = current.entry.getSize(); if (current.bytesRead >= csize) { return -1; } if (buf.offsetInBuffer >= buf.lengthOfLastRead) { buf.offsetInBuffer = 0; if ((buf.lengthOfLastRead = in.read(buf.buf)) == -1) { return -1; } count(buf.lengthOfLastRead); current.bytesReadFromStream += buf.lengthOfLastRead; } int availableBytesInBuffer = buf.lengthOfLastRead - buf.offsetInBuffer; int toRead = Math.min(availableBytesInBuffer, length); if ((csize - current.bytesRead) < toRead) { // if it is smaller than toRead then it fits into an int toRead = (int) (csize - current.bytesRead); } System.arraycopy(buf.buf, buf.offsetInBuffer, buffer, start, toRead); buf.offsetInBuffer += toRead; current.bytesRead += toRead; crc.update(buffer, start, toRead); return toRead; } /** * Implementation of read for DEFLATED entries. */ private int readDeflated(byte[] buffer, int start, int length) throws IOException { int read = readFromInflater(buffer, start, length); if (read == 0) { if (inf.finished()) { return -1; } else if (inf.needsDictionary()) { throw new ZipException("This archive needs a preset dictionary" + " which is not supported by Commons" + " Compress."); } else if (buf.lengthOfLastRead == -1) { throw new IOException("Truncated ZIP file"); } } crc.update(buffer, start, read); return read; } /** * Potentially reads more bytes to fill the inflater's buffer and * reads from it. */ private int readFromInflater(byte[] buffer, int start, int length) throws IOException { int read = 0; do { if (inf.needsInput()) { fill(); if (buf.lengthOfLastRead > 0) { current.bytesReadFromStream += buf.lengthOfLastRead; } else { break; } } try { read = inf.inflate(buffer, start, length); } catch (DataFormatException e) { throw new ZipException(e.getMessage()); } } while (read == 0 && inf.needsInput()); return read; } @Override public void close() throws IOException { if (!closed) { closed = true; in.close(); inf.end(); } } /** * Skips over and discards value bytes of data from this input * stream. * *

This implementation may end up skipping over some smaller * number of bytes, possibly 0, if and only if it reaches the end * of the underlying stream.

* *

The actual number of bytes skipped is returned.

* * @param value the number of bytes to be skipped. * @return the actual number of bytes skipped. * @throws IOException - if an I/O error occurs. * @throws IllegalArgumentException - if value is negative. */ @Override public long skip(long value) throws IOException { if (value >= 0) { long skipped = 0; while (skipped < value) { long rem = value - skipped; int x = read(SKIP_BUF, 0, (int) (SKIP_BUF.length > rem ? rem : SKIP_BUF.length)); if (x == -1) { return skipped; } skipped += x; } return skipped; } throw new IllegalArgumentException(); } /** * Checks if the signature matches what is expected for a zip file. * Does not currently handle self-extracting zips which may have arbitrary * leading content. * * @param signature * the bytes to check * @param length * the number of bytes to check * @return true, if this stream is a zip archive stream, false otherwise */ public static boolean matches(byte[] signature, int length) { if (length < ZipArchiveOutputStream.LFH_SIG.length) { return false; } return checksig(signature, ZipArchiveOutputStream.LFH_SIG) // normal file || checksig(signature, ZipArchiveOutputStream.EOCD_SIG) // empty zip || checksig(signature, ZipArchiveOutputStream.DD_SIG) // split zip || checksig(signature, ZipLong.SINGLE_SEGMENT_SPLIT_MARKER.getBytes()); } private static boolean checksig(byte[] signature, byte[] expected){ for (int i = 0; i < expected.length; i++) { if (signature[i] != expected[i]) { return false; } } return true; } /** * Closes the current ZIP archive entry and positions the underlying * stream to the beginning of the next entry. All per-entry variables * and data structures are cleared. *

* If the com.fitburpressed size of this entry is included in the entry header, * then any outstanding bytes are simply skipped from the underlying * stream without uncompressing them. This allows an entry to be safely * closed even if the com.fitburpression method is unsupported. *

* In case we don't know the com.fitburpressed size of this entry or have * already buffered too much data from the underlying stream to support * uncompression, then the uncompression process is com.fitburpleted and the * end position of the stream is adjusted based on the result of that * process. * * @throws IOException if an error occurs */ private void closeEntry() throws IOException { if (closed) { throw new IOException("The stream is closed"); } if (current == null) { return; } // Ensure all entry bytes are read if (current.bytesReadFromStream <= current.entry.getCompressedSize() && !current.hasDataDescriptor) { drainCurrentEntryData(); } else { skip(Long.MAX_VALUE); long inB = current.entry.getMethod() == ZipArchiveOutputStream.DEFLATED ? getBytesInflated() : current.bytesRead; // this is at most a single read() operation and can't // exceed the range of int int diff = (int) (current.bytesReadFromStream - inB); // Pushback any required bytes if (diff > 0) { pushback(buf.buf, buf.lengthOfLastRead - diff, diff); } } if (lastStoredEntry == null && current.hasDataDescriptor) { readDataDescriptor(); } inf.reset(); buf.reset(); crc.reset(); current = null; lastStoredEntry = null; } /** * Read all data of the current entry from the underlying stream * that hasn't been read, yet. */ private void drainCurrentEntryData() throws IOException { long remaining = current.entry.getCompressedSize() - current.bytesReadFromStream; while (remaining > 0) { long n = in.read(buf.buf, 0, (int) Math.min(buf.buf.length, remaining)); if (n < 0) { throw new EOFException( "Truncated ZIP entry: " + current.entry.getName()); } else { count(n); remaining -= n; } } } /** * Get the number of bytes Inflater has actually processed. * *

for Java < Java7 the getBytes* methods in * Inflater/Deflater seem to return unsigned ints rather than * longs that start over with 0 at 2^32.

* *

The stream knows how many bytes it has read, but not how * many the Inflater actually consumed - it should be between the * total number of bytes read for the entry and the total number * minus the last read operation. Here we just try to make the * value close enough to the bytes we've read by assuming the * number of bytes consumed must be smaller than (or equal to) the * number of bytes read but not smaller by more than 2^32.

*/ private long getBytesInflated() { long inB = inf.getBytesRead(); if (current.bytesReadFromStream >= TWO_EXP_32) { while (inB + TWO_EXP_32 <= current.bytesReadFromStream) { inB += TWO_EXP_32; } } return inB; } private void fill() throws IOException { if (closed) { throw new IOException("The stream is closed"); } if ((buf.lengthOfLastRead = in.read(buf.buf)) > 0) { count(buf.lengthOfLastRead); inf.setInput(buf.buf, 0, buf.lengthOfLastRead); } } private void readFully(byte[] b) throws IOException { int count = 0, x = 0; while (count != b.length) { count += x = in.read(b, count, b.length - count); if (x == -1) { throw new EOFException(); } count(x); } } private void readDataDescriptor() throws IOException { readFully(WORD_BUF); ZipLong val = new ZipLong(WORD_BUF); if (ZipLong.DD_SIG.equals(val)) { // data com.fitburscriptor with signature, skip sig readFully(WORD_BUF); val = new ZipLong(WORD_BUF); } current.entry.setCrc(val.getValue()); // if there is a ZIP64 extra field, sizes are eight bytes // each, otherwise four bytes each. Unfortunately some // implementations - namely Java7 - use eight bytes without // using a ZIP64 extra field - // http://bugs.sun.com.fitbur/bugdatabase/view_bug.do?bug_id=7073588 // just read 16 bytes and check whether bytes nine to twelve // look like one of the signatures of what could follow a data // com.fitburscriptor (ignoring archive com.fitburcryption headers for now). // If so, push back eight bytes and assume sizes are four // bytes, otherwise sizes are eight bytes each. readFully(TWO_DWORD_BUF); ZipLong potentialSig = new ZipLong(TWO_DWORD_BUF, DWORD); if (potentialSig.equals(ZipLong.CFH_SIG) || potentialSig.equals(ZipLong.LFH_SIG)) { pushback(TWO_DWORD_BUF, DWORD, DWORD); current.entry.setCompressedSize(ZipLong.getValue(TWO_DWORD_BUF)); current.entry.setSize(ZipLong.getValue(TWO_DWORD_BUF, WORD)); } else { current.entry .setCompressedSize(ZipEightByteInteger .getLongValue(TWO_DWORD_BUF)); current.entry.setSize(ZipEightByteInteger .getLongValue(TWO_DWORD_BUF, DWORD)); } } /** * Whether this entry requires a data com.fitburscriptor this library can work with. * * @return true if allowStoredEntriesWithDataDescriptor is true, * the entry doesn't require any data com.fitburscriptor or the method is * DEFLATED. */ private boolean supportsDataDescriptorFor(ZipArchiveEntry entry) { return allowStoredEntriesWithDataDescriptor || !entry.getGeneralPurposeBit().usesDataDescriptor() || entry.getMethod() == ZipEntry.DEFLATED; } /** * Caches a stored entry that uses the data com.fitburscriptor. * *
    *
  • Reads a stored entry until the signature of a local file * header, central directory header or data com.fitburscriptor has been * found.
  • *
  • Stores all entry data in lastStoredEntry.

    *
  • Rewinds the stream to position at the data * com.fitburscriptor.
  • *
  • reads the data com.fitburscriptor
  • *
* *

After calling this method the entry should know its size, * the entry's data is cached and the stream is positioned at the * next local file or central directory header.

*/ private void readStoredEntry() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); int off = 0; boolean done = false; // length of DD without signature int ddLen = current.usesZip64 ? WORD + 2 * DWORD : 3 * WORD; while (!done) { int r = in.read(buf.buf, off, ZipArchiveOutputStream.BUFFER_SIZE - off); if (r <= 0) { // read the whole archive without ever finding a // central directory throw new IOException("Truncated ZIP file"); } if (r + off < 4) { // buf is too small to check for a signature, loop off += r; continue; } done = bufferContainsSignature(bos, off, r, ddLen); if (!done) { off = cacheBytesRead(bos, off, r, ddLen); } } byte[] b = bos.toByteArray(); lastStoredEntry = new ByteArrayInputStream(b); } private static final byte[] LFH = ZipLong.LFH_SIG.getBytes(); private static final byte[] CFH = ZipLong.CFH_SIG.getBytes(); private static final byte[] DD = ZipLong.DD_SIG.getBytes(); /** * Checks whether the current buffer contains the signature of a * "data com.fitburcsriptor", "local file header" or * "central directory entry". * *

If it contains such a signature, reads the data com.fitburscriptor * and positions the stream right after the data com.fitburscriptor.

*/ private boolean bufferContainsSignature(ByteArrayOutputStream bos, int offset, int lastRead, int expectedDDLen) throws IOException { boolean done = false; int readTooMuch = 0; for (int i = 0; !done && i < lastRead - 4; i++) { if (buf.buf[i] == LFH[0] && buf.buf[i + 1] == LFH[1]) { if ((buf.buf[i + 2] == LFH[2] && buf.buf[i + 3] == LFH[3]) || (buf.buf[i] == CFH[2] && buf.buf[i + 3] == CFH[3])) { // found a LFH or CFH: readTooMuch = offset + lastRead - i - expectedDDLen; done = true; } else if (buf.buf[i + 2] == DD[2] && buf.buf[i + 3] == DD[3]) { // found DD: readTooMuch = offset + lastRead - i; done = true; } if (done) { // * push back bytes read in excess as well as the data // com.fitburscriptor // * copy the remaining bytes to cache // * read data com.fitburscriptor pushback(buf.buf, offset + lastRead - readTooMuch, readTooMuch); bos.write(buf.buf, 0, i); readDataDescriptor(); } } } return done; } /** * If the last read bytes could hold a data com.fitburscriptor and an * incomplete signature then save the last bytes to the front of * the buffer and cache everything in front of the potential data * com.fitburscriptor into the given ByteArrayOutputStream. * *

Data com.fitburscriptor plus incomplete signature (3 bytes in the * worst case) can be 20 bytes max.

*/ private int cacheBytesRead(ByteArrayOutputStream bos, int offset, int lastRead, int expecteDDLen) { final int cacheable = offset + lastRead - expecteDDLen - 3; if (cacheable > 0) { bos.write(buf.buf, 0, cacheable); System.arraycopy(buf.buf, cacheable, buf.buf, 0, expecteDDLen + 3); offset = expecteDDLen + 3; } else { offset += lastRead; } return offset; } private void pushback(byte[] buf, int offset, int length) throws IOException { ((PushbackInputStream) in).unread(buf, offset, length); pushedBackBytes(length); } // End of Central Directory Record // end of central dir signature 4 bytes (0x06054b50) // number of this disk 2 bytes // number of the disk with the // start of the central directory 2 bytes // total number of entries in the // central directory on this disk 2 bytes // total number of entries in // the central directory 2 bytes // size of the central directory 4 bytes // offset of start of central // directory with respect to // the starting disk number 4 bytes // .ZIP file com.fitburment length 2 bytes // .ZIP file com.fitburment (variable size) // /** * Reads the stream until it find the "End of central directory * record" and consumes it as well. */ private void skipRemainderOfArchive() throws IOException { // skip over central directory. One LFH has been read too much // already. The calculation discounts file names and extra // data so it will be too short. realSkip(entriesRead * CFH_LEN - LFH_LEN); findEocdRecord(); realSkip(ZipFile.MIN_EOCD_SIZE - WORD /* signature */ - SHORT /* com.fitburment len */); readFully(SHORT_BUF); // file com.fitburment realSkip(ZipShort.getValue(SHORT_BUF)); } /** * Reads forward until the signature of the "End of central * directory" recod is found. */ private void findEocdRecord() throws IOException { int currentByte = -1; boolean skipReadCall = false; while (skipReadCall || (currentByte = readOneByte()) > -1) { skipReadCall = false; if (!isFirstByteOfEocdSig(currentByte)) { continue; } currentByte = readOneByte(); if (currentByte != ZipArchiveOutputStream.EOCD_SIG[1]) { if (currentByte == -1) { break; } skipReadCall = isFirstByteOfEocdSig(currentByte); continue; } currentByte = readOneByte(); if (currentByte != ZipArchiveOutputStream.EOCD_SIG[2]) { if (currentByte == -1) { break; } skipReadCall = isFirstByteOfEocdSig(currentByte); continue; } currentByte = readOneByte(); if (currentByte == -1 || currentByte == ZipArchiveOutputStream.EOCD_SIG[3]) { break; } skipReadCall = isFirstByteOfEocdSig(currentByte); } } /** * Skips bytes by reading from the underlying stream rather than * the (potentially inflating) archive stream - which {@link * #skip} would do. * * Also updates bytes-read counter. */ private void realSkip(long value) throws IOException { if (value >= 0) { long skipped = 0; while (skipped < value) { long rem = value - skipped; int x = in.read(SKIP_BUF, 0, (int) (SKIP_BUF.length > rem ? rem : SKIP_BUF.length)); if (x == -1) { return; } count(x); skipped += x; } return; } throw new IllegalArgumentException(); } /** * Reads bytes by reading from the underlying stream rather than * the (potentially inflating) archive stream - which {@link * #read} would do. * * Also updates bytes-read counter. */ private int readOneByte() throws IOException { int b = in.read(); if (b != -1) { count(1); } return b; } private boolean isFirstByteOfEocdSig(int b) { return b == ZipArchiveOutputStream.EOCD_SIG[0]; } /** * Structure collecting information for the entry that is * currently being read. */ private static final class CurrentEntry { /** * Current ZIP entry. */ private final ZipArchiveEntry entry = new ZipArchiveEntry(); /** * Does the entry use a data com.fitburscriptor? */ private boolean hasDataDescriptor; /** * Does the entry have a ZIP64 extended information extra field. */ private boolean usesZip64; /** * Number of bytes of entry content read by the client if the * entry is STORED. */ private long bytesRead; /** * Number of bytes of entry content read so from the stream. * *

This may be more than the actual entry's length as some * stuff gets buffered up and needs to be pushed back when the * end of the entry has been reached.

*/ private long bytesReadFromStream; } /** * Contains a temporary buffer used to read from the wrapped * stream together with some information needed for internal * housekeeping. */ private static final class Buffer { /** * Buffer used as temporary buffer when reading from the stream. */ private final byte[] buf = new byte[ZipArchiveOutputStream.BUFFER_SIZE]; /** * {@link #buf buf} may contain data the client hasnt read, yet, * this is the first byte that hasn't been read so far. */ private int offsetInBuffer = 0; /** * Number of bytes read from the wrapped stream into {@link #buf * buf} with the last read operation. */ private int lengthOfLastRead = 0; /** * Reset internal housekeeping. */ private void reset() { offsetInBuffer = lengthOfLastRead = 0; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy