/**************************************************************************
* Copyright (c) 2001 by Punch Telematix. 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 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 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. *
**************************************************************************/
/**
* $Id: ZipOutputStream.java,v 1.1.1.1 2004/07/12 14:07:47 cvs Exp $
*/
package java.util.zip;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.util.Date;
/**
** the format of a Zip stream looks like:
** [local file Header + file data + data_descriptor] ...
** [file header] ... end of central dir record.
**
** for each local file (ZipEntry we push we write a file header at the end of the stream)
** ...
*/
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
public static final int DEFLATED = 8;
public static final int STORED = 0;
private boolean finished=false;
private int level = DEFLATED;
private int method = DEFLATED;
/**
** will be use to store de central dir data
*/
private ByteArrayOutputStream centralDirRecord = new ByteArrayOutputStream();
private String comment=null;
private long byteCount=0;
private int NrOfEntries=0;
/**
** we use a fixed time for each entry and keep the bytes ...
*/
private byte [] timeBytes;
private boolean entryOpen=false;
private byte [] currentFileHeader=null;
private ZipEntry currentEntry=null;
private boolean preset;
private boolean deflating;
private long dataCount;
private LinkedList names = new LinkedList();
private CRC32 crc = new CRC32();
public ZipOutputStream(OutputStream out) {
super(out, new Deflater(8, true));
timeBytes = new byte[4];
longToDosTime(System.currentTimeMillis(),timeBytes,0);
}
public void close() throws IOException {
finish();
out.close();
}
public void finish() throws IOException {
if (!finished){
if(entryOpen) {
closeEntry();
}
//write central dir record
//write file headers
out.write(centralDirRecord.toByteArray());
byte [] endCD = (byte[])EndOfCDRecord.clone();
endCD[8] = (byte)NrOfEntries;
endCD[9] = ((byte)(NrOfEntries>>>8));
endCD[10] = endCD[8];
endCD[11] = endCD[9];
putBytes(centralDirRecord.size(), 12, endCD);
putBytes((int)byteCount, 16, endCD);
if (comment != null) {
int l = comment.length();
endCD[20] = (byte)l;
endCD[21] = ((byte)(l>>>8));
out.write(endCD,0,22);
out.write(comment.getBytes());
}
else {
out.write(endCD,0,22);
}
finished = true;
}
}
public void closeEntry() throws IOException {
if (finished) {
throw new IOException();
}
if (deflating) {
def.finish();
deflate();
}
if (!preset) {
//Descriptor is needed
byte [] trailer = new byte [16];
System.arraycopy(dataDescS, 0, trailer, 0, 4); //data descriptor Signature
int bytes = (int)crc.getValue();
currentEntry.setCrc(crc.getValue());
putBytes(bytes, 4, trailer);
putBytes(bytes, 16, currentFileHeader);
bytes = def.getTotalOut();
currentEntry.setCompressedSize(bytes);
putBytes(bytes, 8, trailer);
putBytes(bytes, 20, currentFileHeader);
bytes = def.getTotalIn();
currentEntry.setSize(bytes);
putBytes(bytes, 12, trailer);
putBytes(bytes, 24, currentFileHeader);
out.write(trailer, 0, 16);
}
else {
if (deflating) {
if (currentEntry.getCrc() != crc.getValue()
|| (int)currentEntry.getSize() != def.getTotalIn()
|| (int)currentEntry.getCompressedSize() != def.getTotalOut()) {
throw new ZipException("problem when deflating " + currentEntry + ": current entry does not match def");
}
}
else {
if (currentEntry.getCrc() != crc.getValue()) {
throw new ZipException("problem when storing " + currentEntry + ": CRC of data is " + currentEntry.getCrc() + ", should be " + crc.getValue());
}
if ((int)currentEntry.getSize() != dataCount
|| (int)currentEntry.getCompressedSize() != dataCount) {
throw new ZipException("problem when storing " + currentEntry + ": sizes do not match : getSize() returns " + currentEntry.getSize() + ", getCompressedSize() returns " + currentEntry.getCompressedSize() + ", dataCount is " + dataCount);
}
}
}
byte [] extra = currentEntry.getExtra();
if (extra != null) {
int i = extra.length;
currentFileHeader[30] = (byte)i;
currentFileHeader[31] = ((byte)(i>>>8));
}
String com = currentEntry.getComment();
if (com != null) {
int i = com.length();
currentFileHeader[32] = (byte)i;
currentFileHeader[33] = ((byte)(i>>>8));
}
centralDirRecord.write(currentFileHeader, 0, 46);
centralDirRecord.write(currentEntry.getName().getBytes());
if (extra != null) {
centralDirRecord.write(extra);
}
if (com != null) {
centralDirRecord.write(com.getBytes());
}
byteCount += (preset ? 0 : 16) + currentEntry.getCompressedSize();
entryOpen=false;
def.reset(); // reseting the deflater ... (releasing memory)
}
/**
** Prepares the stream to write the data belonging to the ZipEntry passed to this method
** DO NOT CHANGE THE ZIPENTRY AFTER PASSING IT TO THIS METHOD, INCONSISTANT DATA WILL BE WRITTEN OUT !
*/
public void putNextEntry(ZipEntry ze) throws IOException {
if (finished) {
throw new IOException();
}
if(entryOpen) {
closeEntry();
}
String s = ze.getName();
if (names.contains(s)) {
throw new ZipException("stream already contains "+s);
}
names.addLast(s);
currentEntry = ze;
byte [] header = new byte [30];
currentFileHeader = (byte[])fileHeader.clone();
System.arraycopy(locFileHeaderS, 0, header, 0, 4); //local File Header Signature
header[4] = (byte) 20; // version needed to extract (byte one): 20 --> 2.0
header[5] = (byte) 0; // version needed to extract (byte two): Os indication
//general purpose bits (2 bytes) --> CHsize 8
//--> see CRC, un- and compressed size
//compression method (2 bytes) --> CHsize 10
int i = ze.getMethod();
i = (i == -1 ? method : i);
header[8] = (byte)i;
currentFileHeader[10] = (byte)i;
def.setLevel(level);
if (i == DEFLATED) {
deflating = true;
}
else {
deflating = false;
dataCount=0;
}
//date and time fields (2 bytes each) --> MS-Dos format ... --> CHsize 14
if (ze.getTime() == -1) {
System.arraycopy(timeBytes, 0, header, 10, 4);
System.arraycopy(timeBytes, 0, currentFileHeader, 12, 4);
}
else {
longToDosTime(ze.getTime(), header, 10);
System.arraycopy(header,10, currentFileHeader, 12, 4);
}
//CRC-32, compressed size and uncompressed size is only set all three are defined in the entry.
//CRC-32 (4 bytes) --> CHsize 18
//compressed size (4 bytes) --> CHsize 22
//uncompressed size (4 bytes) --> CHsize 26
if(ze.getCrc() == -1l || ze.getSize() == -1l || ze.getCompressedSize()== -1l) {
if (!deflating) {
throw new ZipException("CRC, size and compressed size should be set when storing");
}
preset = false;
header[6] = (byte)8;
currentFileHeader[8] = (byte)8;
}
else {
int bytes = (int)ze.getCrc();
putBytes(bytes, 14 , header);
putBytes(bytes, 16 , currentFileHeader);
bytes = (int)ze.getCompressedSize();
putBytes(bytes, 18 , header);
putBytes(bytes, 20 , currentFileHeader);
bytes = (int)ze.getSize();
putBytes(bytes, 22 , header);
putBytes(bytes, 24 , currentFileHeader);
preset = true;
}
//file name length (2 bytes) --> CHsize 28
putBytes((int)byteCount, 42, currentFileHeader);
i = s.length();
byteCount += i + 30;
header[26] = (byte)i;
currentFileHeader[28] = (byte)i;
i = i>>>8;
header[27] = (byte)i;
currentFileHeader[29] = (byte)i;
//extra field length (2 bytes) --> CHsize 30
byte [] extra = ze.getExtra();
if (extra != null) {
i = extra.length;
byteCount += i;
header[28] = (byte)i;
header[29] = ((byte)(i>>>8));
}
out.write(header, 0, 30); // writing header
//file name --> (length already specified)
out.write(s.getBytes()); // writing file name
//extra field
if (extra != null) {
out.write(extra,0,i);
}
crc.reset(); // reseting the crc checksum
NrOfEntries++;
entryOpen=true;
}
public void setComment(String comment){
if (comment.length() > 0x0ffff) {
throw new IllegalArgumentException();
}
this.comment = comment;
}
public void setLevel(int lvl) {
}
public void setMethod(int m) {
if (m != DEFLATED && m != STORED){
throw new IllegalArgumentException();
}
method = m;
}
public void write(byte [] buf, int offset, int len) throws IOException {
if (finished) {
throw new IOException();
}
crc.update(buf, offset, len);
if (deflating) {
super.write(buf, offset, len);
}
else {
out.write(buf, offset, len);
dataCount += len;
}
}
/**
** put the 4 bytes of the int value in the byteArray
** LSB goes first ...
*/
private void putBytes(int bytes, int off, byte [] buf) {
buf[off++] = (byte)bytes;
buf[off++] = ((byte)(bytes>>>8));
buf[off++] = ((byte)(bytes>>>16));
buf[off] = ((byte)(bytes>>>24));
}
private void longToDosTime(long time, byte[] bytes, int offset) {
GregorianCalendar gc = new GregorianCalendar(0,0,0);
gc.setTime(new Date(time));
int value = (gc.get(Calendar.HOUR_OF_DAY))<<11;
value |= (gc.get(Calendar.MINUTE) & 0x3f)<<5;
value |= (gc.get(Calendar.SECOND)/2) & 0x1f;
bytes[offset] = (byte)value;
bytes[offset+1] = (byte)(value>>>8);
value = ((gc.get(Calendar.YEAR)-1980)<<9);
value |= (((gc.get(Calendar.MONTH)+1)& 0xf)<<5);
value |= ((gc.get(Calendar.DATE)) & 0x1f);
bytes[offset+2] = (byte)value;
bytes[offset+3] = (byte)(value>>>8);
}
}