/* * 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 compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/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 java.util.zip; import dalvik.system.CloseGuard; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteOrder; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import libcore.io.BufferIterator; import libcore.io.HeapBufferIterator; import libcore.io.Streams; /** * This class provides random read access to a ZIP-archive file. *
* While {@code ZipInputStream} provides stream based read access to a * ZIP-archive, this class implements more efficient (file based) access * and makes use of the central directory within a ZIP-archive. *
* Use {@code ZipOutputStream} if you want to create an archive. *
* A temporary ZIP file can be marked for automatic deletion upon closing it.
*
* @see ZipEntry
* @see ZipOutputStream
*/
public class ZipFile implements ZipConstants {
/**
* General Purpose Bit Flags, Bit 3.
* If this bit is set, the fields crc-32, compressed
* size and uncompressed size are set to zero in the
* local header. The correct values are put in the
* data descriptor immediately following the compressed
* data. (Note: PKZIP version 2.04g for DOS only
* recognizes this bit for method 8 compression, newer
* versions of PKZIP recognize this bit for any
* compression method.)
*/
static final int GPBF_DATA_DESCRIPTOR_FLAG = 1 << 3;
/**
* General Purpose Bit Flags, Bit 11.
* Language encoding flag (EFS). If this bit is set,
* the filename and comment fields for this file
* must be encoded using UTF-8.
*/
static final int GPBF_UTF8_FLAG = 1 << 11;
/**
* Open ZIP file for read.
*/
public static final int OPEN_READ = 1;
/**
* Delete ZIP file when closed.
*/
public static final int OPEN_DELETE = 4;
private final String fileName;
private File fileToDeleteOnClose;
private RandomAccessFile mRaf;
private final LinkedHashMap The central directory can be followed by a variable-length comment
* field, so we have to scan through it backwards. The comment is at
* most 64K, plus we have 18 bytes for the end-of-central-dir stuff
* itself, plus apparently sometimes people throw random junk on the end
* just for the fun of it.
*
* This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that everybody handles
* it though, so we're in good company if this fails.
*/
private void readCentralDir() throws IOException {
/*
* Scan back, looking for the End Of Central Directory field. If
* the archive doesn't have a comment, we'll hit it on the first
* try.
*
* No need to synchronize mRaf here -- we only do this when we
* first open the Zip file.
*/
long scanOffset = mRaf.length() - ENDHDR;
if (scanOffset < 0) {
throw new ZipException("too short to be Zip");
}
long stopOffset = scanOffset - 65536;
if (stopOffset < 0) {
stopOffset = 0;
}
final int ENDHEADERMAGIC = 0x06054b50;
while (true) {
mRaf.seek(scanOffset);
if (Integer.reverseBytes(mRaf.readInt()) == ENDHEADERMAGIC) {
break;
}
scanOffset--;
if (scanOffset < stopOffset) {
throw new ZipException("EOCD not found; not a Zip archive?");
}
}
// Read the End Of Central Directory. We could use ENDHDR instead of the magic number 18,
// but we don't actually need all the header.
byte[] eocd = new byte[18];
mRaf.readFully(eocd);
// Pull out the information we need.
BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
short diskNumber = it.readShort();
short diskWithCentralDir = it.readShort();
short numEntries = it.readShort();
short totalNumEntries = it.readShort();
it.skip(4); // Ignore centralDirSize.
int centralDirOffset = it.readInt();
if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
throw new ZipException("spanned archives not supported");
}
// Seek to the first CDE and read all entries.
RAFStream rafs = new RAFStream(mRaf, centralDirOffset);
BufferedInputStream bin = new BufferedInputStream(rafs, 4096);
byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
for (int i = 0; i < numEntries; ++i) {
ZipEntry newEntry = new ZipEntry(hdrBuf, bin);
mEntries.put(newEntry.getName(), newEntry);
}
}
/**
* Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
* among all streams returned by getInputStream(), so we have to synchronize
* access to it. (We can optimize this by adding buffering here to reduce
* collisions.)
*
* We could support mark/reset, but we don't currently need them.
*/
static class RAFStream extends InputStream {
RandomAccessFile mSharedRaf;
long mOffset;
long mLength;
public RAFStream(RandomAccessFile raf, long pos) throws IOException {
mSharedRaf = raf;
mOffset = pos;
mLength = raf.length();
}
@Override public int available() throws IOException {
return (mOffset < mLength ? 1 : 0);
}
@Override public int read() throws IOException {
return Streams.readSingleByte(this);
}
@Override public int read(byte[] b, int off, int len) throws IOException {
synchronized (mSharedRaf) {
mSharedRaf.seek(mOffset);
if (len > mLength - mOffset) {
len = (int) (mLength - mOffset);
}
int count = mSharedRaf.read(b, off, len);
if (count > 0) {
mOffset += count;
return count;
} else {
return -1;
}
}
}
@Override
public long skip(long byteCount) throws IOException {
if (byteCount > mLength - mOffset) {
byteCount = mLength - mOffset;
}
mOffset += byteCount;
return byteCount;
}
}
static class ZipInflaterInputStream extends InflaterInputStream {
ZipEntry entry;
long bytesRead = 0;
public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
super(is, inf, bsize);
this.entry = entry;
}
@Override
public int read(byte[] buffer, int off, int nbytes) throws IOException {
int i = super.read(buffer, off, nbytes);
if (i != -1) {
bytesRead += i;
}
return i;
}
@Override
public int available() throws IOException {
if (closed) {
// Our superclass will throw an exception, but there's a jtreg test that
// explicitly checks that the InputStream returned from ZipFile.getInputStream
// returns 0 even when closed.
return 0;
}
return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
}
}
}