blob: d99b414b3f8b3e9d0d89c28214e747099a755c20 [file] [log] [blame]
/*
* 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 java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.charset.Charsets;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* An instance of {@code ZipEntry} represents an entry within a <i>ZIP-archive</i>.
* An entry has attributes such as name (= path) or the size of its data. While
* an entry identifies data stored in an archive, it does not hold the data
* itself. For example when reading a <i>ZIP-file</i> you will first retrieve
* all its entries in a collection and then read the data for a specific entry
* through an input stream.
*
* @see ZipFile
* @see ZipOutputStream
*/
public class ZipEntry implements ZipConstants, Cloneable {
String name, comment;
long compressedSize = -1, crc = -1, size = -1;
int compressionMethod = -1, time = -1, modDate = -1;
byte[] extra;
int nameLen = -1;
long mLocalHeaderRelOffset = -1;
/**
* Zip entry state: Deflated.
*/
public static final int DEFLATED = 8;
/**
* Zip entry state: Stored.
*/
public static final int STORED = 0;
/**
* Constructs a new {@code ZipEntry} with the specified name.
*
* @param name
* the name of the ZIP entry.
* @throws IllegalArgumentException
* if the name length is outside the range (> 0xFFFF).
*/
public ZipEntry(String name) {
if (name == null) {
throw new NullPointerException();
}
if (name.length() > 0xFFFF) {
throw new IllegalArgumentException();
}
this.name = name;
}
/**
* Gets the comment for this {@code ZipEntry}.
*
* @return the comment for this {@code ZipEntry}, or {@code null} if there
* is no comment. If we're reading an archive with
* {@code ZipInputStream} the comment is not available.
*/
public String getComment() {
return comment;
}
/**
* Gets the compressed size of this {@code ZipEntry}.
*
* @return the compressed size, or -1 if the compressed size has not been
* set.
*/
public long getCompressedSize() {
return compressedSize;
}
/**
* Gets the checksum for this {@code ZipEntry}.
*
* @return the checksum, or -1 if the checksum has not been set.
*/
public long getCrc() {
return crc;
}
/**
* Gets the extra information for this {@code ZipEntry}.
*
* @return a byte array containing the extra information, or {@code null} if
* there is none.
*/
public byte[] getExtra() {
return extra;
}
/**
* Gets the compression method for this {@code ZipEntry}.
*
* @return the compression method, either {@code DEFLATED}, {@code STORED}
* or -1 if the compression method has not been set.
*/
public int getMethod() {
return compressionMethod;
}
/**
* Gets the name of this {@code ZipEntry}.
*
* @return the entry name.
*/
public String getName() {
return name;
}
/**
* Gets the uncompressed size of this {@code ZipEntry}.
*
* @return the uncompressed size, or {@code -1} if the size has not been
* set.
*/
public long getSize() {
return size;
}
/**
* Gets the last modification time of this {@code ZipEntry}.
*
* @return the last modification time as the number of milliseconds since
* Jan. 1, 1970.
*/
public long getTime() {
if (time != -1) {
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
(time & 0x1f) << 1);
return cal.getTime().getTime();
}
return -1;
}
/**
* Determine whether or not this {@code ZipEntry} is a directory.
*
* @return {@code true} when this {@code ZipEntry} is a directory, {@code
* false} otherwise.
*/
public boolean isDirectory() {
return name.charAt(name.length() - 1) == '/';
}
/**
* Sets the comment for this {@code ZipEntry}.
*
* @param string
* the comment for this entry.
*/
public void setComment(String string) {
if (string == null || string.length() <= 0xFFFF) {
comment = string;
} else {
throw new IllegalArgumentException();
}
}
/**
* Sets the compressed size for this {@code ZipEntry}.
*
* @param value
* the compressed size (in bytes).
*/
public void setCompressedSize(long value) {
compressedSize = value;
}
/**
* Sets the checksum for this {@code ZipEntry}.
*
* @param value
* the checksum for this entry.
* @throws IllegalArgumentException
* if {@code value} is < 0 or > 0xFFFFFFFFL.
*/
public void setCrc(long value) {
if (value >= 0 && value <= 0xFFFFFFFFL) {
crc = value;
} else {
throw new IllegalArgumentException();
}
}
/**
* Sets the extra information for this {@code ZipEntry}.
*
* @param data
* a byte array containing the extra information.
* @throws IllegalArgumentException
* when the length of data is greater than 0xFFFF bytes.
*/
public void setExtra(byte[] data) {
if (data == null || data.length <= 0xFFFF) {
extra = data;
} else {
throw new IllegalArgumentException();
}
}
/**
* Sets the compression method for this {@code ZipEntry}.
*
* @param value
* the compression method, either {@code DEFLATED} or {@code
* STORED}.
* @throws IllegalArgumentException
* when value is not {@code DEFLATED} or {@code STORED}.
*/
public void setMethod(int value) {
if (value != STORED && value != DEFLATED) {
throw new IllegalArgumentException();
}
compressionMethod = value;
}
/**
* Sets the uncompressed size of this {@code ZipEntry}.
*
* @param value
* the uncompressed size for this entry.
* @throws IllegalArgumentException
* if {@code value} < 0 or {@code value} > 0xFFFFFFFFL.
*/
public void setSize(long value) {
if (value >= 0 && value <= 0xFFFFFFFFL) {
size = value;
} else {
throw new IllegalArgumentException();
}
}
/**
* Sets the modification time of this {@code ZipEntry}.
*
* @param value
* the modification time as the number of milliseconds since Jan.
* 1, 1970.
*/
public void setTime(long value) {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date(value));
int year = cal.get(Calendar.YEAR);
if (year < 1980) {
modDate = 0x21;
time = 0;
} else {
modDate = cal.get(Calendar.DATE);
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
time = cal.get(Calendar.SECOND) >> 1;
time = (cal.get(Calendar.MINUTE) << 5) | time;
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
}
}
/**
* Returns the string representation of this {@code ZipEntry}.
*
* @return the string representation of this {@code ZipEntry}.
*/
@Override
public String toString() {
return name;
}
/**
* Constructs a new {@code ZipEntry} using the values obtained from {@code
* ze}.
*
* @param ze
* the {@code ZipEntry} from which to obtain values.
*/
public ZipEntry(ZipEntry ze) {
name = ze.name;
comment = ze.comment;
time = ze.time;
size = ze.size;
compressedSize = ze.compressedSize;
crc = ze.crc;
compressionMethod = ze.compressionMethod;
modDate = ze.modDate;
extra = ze.extra;
nameLen = ze.nameLen;
mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset;
}
/**
* Returns a shallow copy of this entry.
*
* @return a copy of this entry.
*/
@Override
public Object clone() {
return new ZipEntry(this);
}
/**
* Returns the hash code for this {@code ZipEntry}.
*
* @return the hash code of the entry.
*/
@Override
public int hashCode() {
return name.hashCode();
}
/*
* Internal constructor. Creates a new ZipEntry by reading the
* Central Directory Entry from "in", which must be positioned at
* the CDE signature.
*
* On exit, "in" will be positioned at the start of the next entry.
*/
ZipEntry(LittleEndianReader ler, InputStream in) throws IOException {
/*
* We're seeing performance issues when we call readShortLE and
* readIntLE, so we're going to read the entire header at once
* and then parse the results out without using any function calls.
* Uglier, but should be much faster.
*
* Note that some lines look a bit different, because the corresponding
* fields or locals are long and so we need to do & 0xffffffffl to avoid
* problems induced by sign extension.
*/
byte[] hdrBuf = ler.hdrBuf;
myReadFully(in, hdrBuf);
long sig = (hdrBuf[0] & 0xff) | ((hdrBuf[1] & 0xff) << 8) |
((hdrBuf[2] & 0xff) << 16) | ((hdrBuf[3] << 24) & 0xffffffffL);
if (sig != CENSIG) {
throw new ZipException("Central Directory Entry not found");
}
compressionMethod = (hdrBuf[10] & 0xff) | ((hdrBuf[11] & 0xff) << 8);
time = (hdrBuf[12] & 0xff) | ((hdrBuf[13] & 0xff) << 8);
modDate = (hdrBuf[14] & 0xff) | ((hdrBuf[15] & 0xff) << 8);
crc = (hdrBuf[16] & 0xff) | ((hdrBuf[17] & 0xff) << 8)
| ((hdrBuf[18] & 0xff) << 16)
| ((hdrBuf[19] << 24) & 0xffffffffL);
compressedSize = (hdrBuf[20] & 0xff) | ((hdrBuf[21] & 0xff) << 8)
| ((hdrBuf[22] & 0xff) << 16)
| ((hdrBuf[23] << 24) & 0xffffffffL);
size = (hdrBuf[24] & 0xff) | ((hdrBuf[25] & 0xff) << 8)
| ((hdrBuf[26] & 0xff) << 16)
| ((hdrBuf[27] << 24) & 0xffffffffL);
nameLen = (hdrBuf[28] & 0xff) | ((hdrBuf[29] & 0xff) << 8);
int extraLen = (hdrBuf[30] & 0xff) | ((hdrBuf[31] & 0xff) << 8);
int commentLen = (hdrBuf[32] & 0xff) | ((hdrBuf[33] & 0xff) << 8);
mLocalHeaderRelOffset = (hdrBuf[42] & 0xff) | ((hdrBuf[43] & 0xff) << 8)
| ((hdrBuf[44] & 0xff) << 16)
| ((hdrBuf[45] << 24) & 0xffffffffL);
byte[] nameBytes = new byte[nameLen];
myReadFully(in, nameBytes);
byte[] commentBytes = null;
if (commentLen > 0) {
commentBytes = new byte[commentLen];
myReadFully(in, commentBytes);
}
if (extraLen > 0) {
extra = new byte[extraLen];
myReadFully(in, extra);
}
// The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is
// actually IBM-437.)
name = new String(nameBytes, 0, nameBytes.length, Charsets.UTF_8);
if (commentBytes != null) {
comment = new String(commentBytes, 0, commentBytes.length, Charsets.UTF_8);
} else {
comment = null;
}
}
private void myReadFully(InputStream in, byte[] b) throws IOException {
int len = b.length;
int off = 0;
while (len > 0) {
int count = in.read(b, off, len);
if (count <= 0) {
throw new EOFException();
}
off += count;
len -= count;
}
}
/*
* Read a four-byte int in little-endian order.
*/
static long readIntLE(RandomAccessFile raf) throws IOException {
int b0 = raf.read();
int b1 = raf.read();
int b2 = raf.read();
int b3 = raf.read();
if (b3 < 0) {
throw new EOFException("in ZipEntry.readIntLE(RandomAccessFile)");
}
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); // ATTENTION: DOES SIGN EXTENSION: IS THIS WANTED?
}
static class LittleEndianReader {
private byte[] b = new byte[4];
byte[] hdrBuf = new byte[CENHDR];
/*
* Read a two-byte short in little-endian order.
*/
int readShortLE(InputStream in) throws IOException {
if (in.read(b, 0, 2) == 2) {
return (b[0] & 0XFF) | ((b[1] & 0XFF) << 8);
} else {
throw new EOFException("in ZipEntry.readShortLE(InputStream)");
}
}
/*
* Read a four-byte int in little-endian order.
*/
long readIntLE(InputStream in) throws IOException {
if (in.read(b, 0, 4) == 4) {
return ( ((b[0] & 0XFF))
| ((b[1] & 0XFF) << 8)
| ((b[2] & 0XFF) << 16)
| ((b[3] & 0XFF) << 24))
& 0XFFFFFFFFL; // Here for sure NO sign extension is wanted.
} else {
throw new EOFException("in ZipEntry.readIntLE(InputStream)");
}
}
}
}