/**************************************************************************
* Parts copyright (c) 2001 by Punch Telematix. All rights reserved. *
* Parts copyright (c) 2010 by Chris Gray, /k/ Embedded Java Solutions. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions*
* nor the names of other contributors may be used to endorse or promote*
* products derived from this software without specific prior written *
* permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package java.util.zip;
import java.io.InputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.ListIterator;
/**
** Basic InputStream for reading zip formatted streams.
** GENERAL NOTE:
** we use in.read(byte[] , int, int) to read bytes. In some occasions we
** might not get all the bytes we want (not yet available in the inputstream in)
** there might be some bytes left in buf. Whenever we need bytes we ask them from
** getBytes([BII) --> this method will take the remaining bytes from buf or else
** it will get them directly from in ...
*/
public class ZipInputStream extends InflaterInputStream implements ZipConstants {
private boolean closed;
private CRC32 crc = new CRC32();
private LinkedList entries = new LinkedList();
private ZipEntry currentEntry;
private boolean entryOpen;
private boolean preset;
private boolean inflating;
private boolean inCentDir;
private boolean allowDataDescriptor;
private long dataCount;
private int used;
private boolean zipFileStream;
private ZipStreamInfo zipStreamInfo;
public ZipInputStream(InputStream in) {
super(in, new Inflater(true));
if (in instanceof ZipByteArrayInputStream) {
setupZipByteArrayStream();
}
else if (in.markSupported()) {
try {
setupZipMarkableStream();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
protected ZipEntry createZipEntry(String zname) {
return new ZipEntry(zname);
}
public int available() throws IOException{
return (closed ? 0 : 1);
}
public void close() throws IOException {
if (!closed) {
if (entryOpen) {
closeEntry();
}
super.close();
closed=true;
currentEntry=null;
}
}
/**
** skip will only skip bytes within the current Entry ...
** call getNextEntry() to skip more data
**
*/
public long skip(long n) throws IOException {
return super.skip(n);
}
public ZipEntry getNextEntry() throws IOException {
if (zipFileStream){
return nextZipStreamEntry();
}
closeEntry();
readLFHeader();
if(inflating && entryOpen){
//if there is no entryOpen there is no need to preset the buf
presetBuf();
}
return currentEntry;
}
public void closeEntry() throws IOException {
if (!entryOpen) {
return;
}
if (zipFileStream) {
closeZipStreamEntry();
return;
}
// we discard all remaining data by calling read ...
byte [] data = new byte [1024];
while(/*dataCount > 0 &&*/ read(data,0,1024) != -1) {
}
used = len - inf.getRemaining();
//System.out.println("closeEntry(): after draining, used = " + used + " (len = " + len + ") inf remaining "+inf.getRemaining());
String corruption_type = null;
if (preset) {
if (inflating) {
if (currentEntry.getCrc() != crc.getValue()) {
corruption_type = "bad crc" + " (was " + crc.getValue() + ", expected " + currentEntry.getCrc() + ")";
}
else if (currentEntry.getSize() != inf.getTotalOut()) {
corruption_type = "bad decompressed length";
}
else if (currentEntry.getCompressedSize() != (inf.getTotalIn() + skipTillNextHeader() - inf.getRemaining())) {
corruption_type = "bad compressed length";
}
if (corruption_type != null) {
throw new ZipException("data was corrupted: " + corruption_type);
}
}
else {
if (currentEntry.getCrc() != crc.getValue()) {
corruption_type = "bad crc";
}
else if (dataCount != 0) {
corruption_type = "bad length";
}
if (corruption_type != null) {
throw new ZipException("data was corrupted: " + corruption_type);
}
}
}
else {
skipTillNextHeader();
if (getBytes(data,0,16) < 16) {
throw new ZipException("data was corrupted - EOF during header");
}
if (data[0] != dataDescS[0] || data[1] != dataDescS[1] || data[2] != dataDescS[2] || data[3] != dataDescS[3] || crc.getValue() != ZipFile.bytesToLong(data, 4)) {
throw new ZipException("corrupt dataDescriptor");
}
currentEntry.setCrc(crc.getValue());
currentEntry.setCompressedSize(ZipFile.bytesToLong(data, 8));
currentEntry.setSize(ZipFile.bytesToLong(data, 12));
}
entryOpen=false;
}
/**
** read will only read bytes in the current Entry ...
** call getNextEntry to read more bytes ...
**
*/
public int read(byte[] buff, int offset, int len) throws IOException {
if (zipFileStream) {
return zipStreamRead(buff, offset, len);
}
int read=-1;
if(entryOpen) {
if (inflating) {
read = super.read(buff, offset, len);
if (inf.needsDictionary()) {
throw new ZipException("needs dictionary");
}
dataCount -= read;
}
else {
if (dataCount > 0) {
len = (len > dataCount ? (int)dataCount : len);
read = getBytes(buff, offset, len);
dataCount -= read;
}
}
if (read != -1) {
crc.update(buff, offset, read);
}
}
return read;
}
/***
** private methods used for code with regular files.
**/
private void readLFHeader() throws IOException {
byte [] header = new byte [22];
int l = 0;
while (true) {
l = getBytes(header,l,22-l);
if (l == -1) {
// we reached the end of the stream ...
currentEntry=null;
break;
}
else {
// we read some bytes and we expect this to be a header ...
if (header[0] != 80 || header[1] != 75 ){
throw new ZipException("corrupt header found (not 80:75)");
}
if (header[2] == 3 && header[3] == 4) {
parseLocalFileHeader(header);
break;
}
else if (header[2] == 1 && header[3] == 2) {
parseFileHeader(header);
l = 0;
}
else if (header[2] == 5 && header[3] == 6) {
parseEndCD(header);
l = 0;
}
else if (allowDataDescriptor && header[2] == 7 && header[3] == 8) {
//lets hope we don't come here to often ...
allowDataDescriptor = false;
for(int i=4 ; i <16 ; i++){
if(header[i] != 0){
throw new ZipException("header data is wrong");
}
}
l = 6;
System.arraycopy(header,16,header,0,6);
}
else {
throw new ZipException("corrupt header found");
}
}
}
}
/**
** when we locate a FileHeader, it means we found the central directory ...
** the FileHeaders should have been preceeded by a LocalFileHeader.
** So we have a reference in our LinkedList to this Header. If we don't
** find one or data is inconsistant then we throw a ZipException.
*/
private void parseFileHeader(byte[] header) throws IOException {
// we have 22 bytes --> 4 are already checked Signature
// version made by and version needed --> skip ... (4 bytes)
// general purpose bytes (2) skip
byte [] buff = new byte [46];
System.arraycopy(header,0,buff,0,22);
// we read some more bytes since the header has 46 bytes ...
if ( getBytes(buff, 22, 24) < 24 ) {
throw new ZipException("bytes missing --> corrupt header");
}
long time = ZipFile.getDate(buff , 12);
long crc32 = ZipFile.bytesToLong(buff, 16);
long compSize = ZipFile.bytesToLong(buff, 20);
long size = ZipFile.bytesToLong(buff, 24);
int fnlen = (0x0ff&(char)buff[28]) + (0x0ff &(char)buff[29])*256;
byte [] fname = new byte[fnlen];
getBytes(fname,0, fnlen);
String name = new String(fname, 0, fnlen);
ListIterator li = entries.listIterator(0);
ZipEntry ze=null;
while (li.hasNext()) {
ze = (ZipEntry) li.next();
if (name.equals(ze.getName())) {
break;
}
}
if (ze == null) {
throw new ZipException("corrupt header");
}
if ( time != ze.getTime() || crc32 != ze.getCrc() || size != ze.getSize() || compSize != ze.getCompressedSize()) {
throw new ZipException("corrupt header");
}
fnlen = (0x0ff&(char)buff[30]) + (0x0ff &(char)buff[31])*256;
fname = new byte[fnlen];
if (fnlen == 0) {
ze.setExtra(null);
}
else {
getBytes(fname,0, fnlen);
ze.setExtra(fname);
}
fnlen = (0x0ff&(char)buff[32]) + (0x0ff &(char)buff[33])*256;
fname = new byte[fnlen];
if (fnlen == 0) {
ze.setComment(null);
}
else {
getBytes(fname,0, fnlen);
ze.setComment(new String(fname, 0, fnlen));
}
inCentDir=true;
// the rest of the bytes are not verified
}
private void parseLocalFileHeader(byte[] header) throws IOException {
// we have 22 bytes --> 4 are already checked Signature
// version needed --> ... (2 bytes)
// general purpose bytes (2)
if(inCentDir) {
throw new ZipException("stream is corrupted");
}
int mode = (0x0ff&(char)header[4]) + (0x0ff &(char)header[5])*256;
if (mode > 20) {
throw new ZipException("higher zip version needed");
}
mode = (0x08&(0x0ff&(char)header[6]));
// we read some more bytes since the header has 30 bytes ...
if ( getBytes(header, 0, 8) < 8 ) {
throw new ZipException("bytes missing --> corrupt header");
}
int fnlen = (0x0ff&(char)header[4]) + (0x0ff &(char)header[5])*256;
byte [] fname = new byte[fnlen];
getBytes(fname,0, fnlen);
String name = new String(fname, 0, fnlen);
ListIterator li = entries.listIterator(0);
ZipEntry ze;
while (li.hasNext()) {
ze = (ZipEntry) li.next();
if (name.equals(ze.getName())) {
throw new ZipException("file name already exist");
}
}
ze = createZipEntry(name);
if (mode != 8) {
preset = true;
ze.setCrc(ZipFile.bytesToLong(header, 14));
ze.setCompressedSize(ZipFile.bytesToLong(header, 18));
ze.setSize(ZipFile.bytesToLong(header, 0));
}
else {
preset = false;
}
mode = (0x0ff&(char)header[8]) + (0x0ff &(char)header[9])*256;
if (mode != 8 && mode != 0) {
throw new ZipException("stream is corrupted");
}
if (mode == 8) {
inflating=true;
}
else {
if (!preset) {
throw new ZipException("stream is corrupted");
}
inflating=false;
dataCount = ze.getSize();
}
inf.reset();
ze.setMethod(mode);
ze.setTime(ZipFile.getDate(header , 10));
fnlen = (0x0ff&(char)header[6]) + (0x0ff &(char)header[7])*256;
fname = new byte[fnlen];
if (fnlen == 0) {
ze.setExtra(null);
}
else {
getBytes(fname,0, fnlen);
ze.setExtra(fname);
}
entries.addLast(ze);
currentEntry=ze;
crc.reset();
if(preset && ze.getCompressedSize() == 0){
entryOpen=false;
allowDataDescriptor = true;
}
else {
entryOpen=true;
}
// the rest of the bytes are not verified
}
private void parseEndCD(byte[] header) throws IOException {
if (((0x0ff&(char)header[10]) + (0x0ff &(char)header[11])*256) != entries.size()) {
throw new ZipException("corrupt header");
}
int commentSize = ((0x0ff&(char)header[20]) + (0x0ff &(char)header[21])*256);
getBytes(new byte[commentSize],0,commentSize);
entries.clear();
inCentDir = false;
}
/**
** while reading through the stream some bytes are read and put into buf.
** if we need bytes we should check if there are unread bytes in buf
** getBytes will read bytes from buf or in (if necessary)
*/
private int getBytes(byte [] target, int offset, int length) throws IOException {
int rd = 0;
while (length > 0){
int get = len - used;
//System.out.println("DEBUG: getBytes get ="+get+", len = "+len+", used = "+used);
get = (get > length ? length : get);
if (get > 0) {
System.arraycopy(buf, used, target, offset, get);
used +=get;
//System.out.println("getBytes(" + target + ", " + offset + ", " + length + "): used now = " + used);
}
else {
get = in.read(target, offset, length);
if (get == -1) {
if (rd == 0){
rd--;
}
break;
}
}
length -= get;
rd +=get;
offset+=get;
}
return rd;
}
private void presetBuf(){
len -= used;
System.arraycopy(buf, used, buf, 0, len);
inf.setInput(buf,0,len);
}
private int skipTillNextHeader() throws IOException{
int skipped=0;
byte [] head = new byte[1];
getBytes(head,0,1);
//if (l < 1) System.out.println("getBytes(" + head + ", 0, 1) returned " + l);
while (head[0] != 80) {
skipped++;
getBytes(head,0,1);
//if (l < 1) System.out.println("getBytes(" + head + ", 0, 1) returned " + l);
}
used--;
//System.out.println("skipTillNextHeader(): used now = " + used);
buf[used] = head[0];
//System.out.println("skipTillNextHeader(): skipped = " +skipped);
return skipped;
}
/***
** Functions used for shortcutting ZipFile stream.
**/
private void setupZipByteArrayStream() {
byte[] bytes = ((ZipByteArrayInputStream)in).getBytes();
int size = bytes.length;
byte CDS3 = endCenDirS[3];
for (int p=size ; --p > -1 ; ) {
if (bytes[p] == CDS3) {
//this might be the last byte of the signature!
if (p >=3) {
if (bytes[p-1] == endCenDirS[2] && bytes[p-2] == endCenDirS[1] && bytes[p-3] == endCenDirS[0]) {
//we have found a signature ...
if(readEntries(bytes, size, p+1)){
this.zipFileStream = true;
return;
}
}
}
}
}
//System.out.println("ZipInputStream.setupZipByteArrayStream(): No valid zipfile found");
}
private void setupZipMarkableStream() throws IOException {
in.mark(Integer.MAX_VALUE);
LinkedList buffers = new LinkedList();
int bufsiz = 100000;
int totlen = 0;
byte CDS3 = endCenDirS[3];
while (true) {
try {
byte[] buf = new byte[bufsiz];
int l = in.read(buf);
if (l < 0) {
break;
}
if (l < bufsiz) {
byte[] newbuf = new byte[l];
System.arraycopy(buf, 0, newbuf, 0, l);
buf = newbuf;
}
buffers.add(buf);
totlen += l;
}
catch (IOException ioe) {
break;
}
}
int nbufs = buffers.size();
int count = 0;
buf = new byte[totlen];
while (count < totlen) {
byte[] nextbuf = (byte[])buffers.removeFirst();
System.arraycopy(nextbuf, 0, buf, count, nextbuf.length);
count += nextbuf.length;
}
int p = buf.length - 1;
while (--p >= 0) {
if (p >= 3 && buf[p] == CDS3) {
//this might be the last byte of the signature!
if (buf[p-1] == endCenDirS[2] && buf[p-2] == endCenDirS[1] && buf[p-3] == endCenDirS[0]) {
//we have found a signature ...
if(readEntries(buf, buf.length, p+1)){
zipFileStream = true;
break;
}
}
}
}
in.reset();
in.mark(0);
}
private boolean readEntries(byte [] bytes, int size, int pos) {
int nrE = size - pos;
nrE = (16 > nrE ? nrE : 16);
byte [] b = new byte [16];
System.arraycopy(bytes, pos, b,0, nrE);
//b should contain 16 bytes of the possible header
nrE = (0x0ff&(char)b[6]) + (0x0ff &(char)b[7])*256;
int stP = (int) ZipFile.bytesToLong(b, 12);
int length = (int) ZipFile.bytesToLong(b, 8);
if (nrE < 0 || (stP+length) > size || length < 0) {
//we encountered a fony header
return false;
}
zipStreamInfo = new ZipStreamInfo(nrE, stP, bytes);
return true;
}
private ZipEntry nextZipStreamEntry() throws IOException {
if (entryOpen) {
closeZipStreamEntry();
}
if (zipStreamInfo.nrEntries <= zipStreamInfo.currentEntry) {
return null;
}
byte[] b = zipStreamInfo.data;
int size = b.length;
int pos = zipStreamInfo.entryPointer;
if (pos + 46 > size) {
throw new ZipException("too few bytes for header");// bad Header ...
}
if (cenFileHeaderS[0] != b[pos] || cenFileHeaderS[1] != b[pos + 1]
|| cenFileHeaderS[2] != b[pos + 2] || cenFileHeaderS[3] != b[pos + 3]) {
throw new ZipException("bad header in central file directory");
}
// we have found a correct header ...
// lets skip useless bytes
pos += 28;
int len = (0x0ff & (char) b[pos]) + (0x0ff & (char) b[pos + 1]) * 256;
pos += 18;
if (pos + len > size) {
throw new ZipException("not enough bytes in header");
}
String name = new String(b, pos, len);
ZipEntry ze = createZipEntry(name);
pos -= 36;
int hlp = (0x0ff & (char) b[pos]) + (0x0ff & (char) b[pos + 1]) * 256;
if (hlp != 0 && hlp != 8) {
throw new ZipException("unknown store/zip method " + hlp);
}
ze.compressionMethod = hlp;
pos += 2;
ze.time = ZipFile.getDate(b, pos);
pos += 4;
ze.crc = ZipFile.bytesToLong(b, pos);
pos += 4;
ze.compressedSize = ZipFile.bytesToLong(b, pos);
pos += 4;
ze.size = ZipFile.bytesToLong(b, pos);
pos += 6;
hlp = (0x0ff & (char) b[pos]) + (0x0ff & (char) b[pos + 1]) * 256;
pos += 2;
int com = (0x0ff & (char) b[pos]) + (0x0ff & (char) b[pos + 1]) * 256;
if (hlp < 0 || com < 0) {
throw new ZipException("invalid header data");
}
pos += 10;
int localFHoffs = (int)ZipFile.bytesToLong(b, pos);
int localFHlen = (0x0ff & (char) b[localFHoffs + 26]) + (0x0ff & (char) b[localFHoffs + 27]) * 256;
// TODO: this should be the same as in global FH I guess
int localFHxlen = (0x0ff & (char) b[localFHoffs + 28]) + (0x0ff & (char) b[localFHoffs + 29]) * 256;
// apparently this can be different to the global FH
// WAS: ze.pointer = ZipFile.bytesToLong(b, pos) + len + hlp + com + 30;
ze.pointer = localFHoffs + localFHlen + localFHxlen + 30;
pos += 4 + len;
if (hlp > 0) {
byte[] extra = new byte[hlp];
System.arraycopy(b, pos, extra, 0, hlp);
ze.extra = extra;
pos += hlp;
}
if (com > 0) {
ze.comment = new String(b, pos, com);
pos += com;
}
zipStreamInfo.currentEntry++;
zipStreamInfo.entryPointer = pos;
currentEntry = ze;
entryOpen = false;
return ze;
}
private void closeZipStreamEntry() {
// TODO Add checks for zipfile consistency.
entryOpen = false;
currentEntry = null;
}
private int zipStreamRead(byte[] buff, int offset, int len) throws ZipException {
if (currentEntry == null) {
return -1;
}
if (!entryOpen) {
//TODO add integrety checks for zipfile.
//checkEntry();
if (currentEntry.compressionMethod != 0) {
inf.reset();
inf.setInput(zipStreamInfo.data, (int)currentEntry.pointer
, (int)currentEntry.compressedSize);
} else {
zipStreamInfo.have = (int) currentEntry.size;
}
entryOpen = true;
this.len = buf.length;
}
if (currentEntry.compressionMethod == 0) {
int have = zipStreamInfo.have;
if (have <= 0) {
return -1;
}
if (have < len) {
len = have;
}
System.arraycopy(zipStreamInfo.data, (int)currentEntry.pointer,
buff,offset, len);
currentEntry.pointer += len;
zipStreamInfo.have = have - len;
return len;
}
try {
int res = inf.inflate(buff, offset, len);
if (res <= 0) {
if (inf.finished()) {
return -1;
}
throw new ZipException("bad state");
}
return res;
} catch (DataFormatException e) {
ZipException zexc = new ZipException();
zexc.initCause(e);
throw zexc;
}
}
/*
private void checkEntry() throws ZipException {
int idx = (int) currentEntry.pointer;
byte[] bytes = zipStreamInfo.data;
if (bytes[idx++] != 80 || bytes[idx++] != 75
|| bytes[idx++] == 3 && bytes[idx++] == 4) {
throw new ZipException("corrupt header found (not 80:75)");
}
// TODO Auto-generated method stub
}
*/
}