f44b63ae02
* DeflaterOutputStream.java (deflate): Loop while def.needsInput. (finish): def.deflate needs to be called in a loop. (inbuf, inbufLength): New private fields. (write(int)): Use inbuf. (write(byte[],int,int): Check if pending output in inbuf. * ZipOutputStream.java: Don't use Deflater if stored. Use a Checksum object directly, not via a CheckedOutputStream. (uncompressed_size): New field, (closeEntry): Only write data_directory if needed. (write): If STORED, write directly. Always update crc, and uncompressed_size. (write_entry): Fix lots of protocol erors. From-SVN: r40988
310 lines
7.6 KiB
Java
310 lines
7.6 KiB
Java
/* ZipOutputStream.java - Create a file in zip format
|
|
Copyright (C) 1999, 2000 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307 USA.
|
|
|
|
As a special exception, if you link this library with other files to
|
|
produce an executable, this library does not by itself cause the
|
|
resulting executable to be covered by the GNU General Public License.
|
|
This exception does not however invalidate any other reasons why the
|
|
executable file might be covered by the GNU General Public License. */
|
|
|
|
package java.util.zip;
|
|
|
|
import java.io.*;
|
|
|
|
/* Written using on-line Java Platform 1.2 API Specification
|
|
* and JCL book.
|
|
* Believed complete and correct.
|
|
*/
|
|
|
|
public class ZipOutputStream extends DeflaterOutputStream
|
|
implements ZipConstants
|
|
{
|
|
public static final int STORED = 0;
|
|
public static final int DEFLATED = 8;
|
|
|
|
public void close () throws IOException
|
|
{
|
|
finish ();
|
|
out.close();
|
|
}
|
|
|
|
public void closeEntry () throws IOException
|
|
{
|
|
int compressed_size;
|
|
if (current.method == STORED)
|
|
{
|
|
compressed_size = uncompressed_size;
|
|
}
|
|
else
|
|
{
|
|
super.finish();
|
|
compressed_size = def.getTotalOut();
|
|
}
|
|
long crc = sum.getValue();
|
|
|
|
bytes_written += compressed_size;
|
|
|
|
if (current.getCrc() == -1 || current.getCompressedSize() == -1
|
|
|| current.getSize() == -1)
|
|
{
|
|
current.setCrc(crc);
|
|
current.compressedSize = compressed_size;
|
|
current.setSize(uncompressed_size);
|
|
put4 (0x08074b50);
|
|
put4 ((int) (current.getCrc()));
|
|
put4 ((int) (current.getCompressedSize()));
|
|
put4 ((int) (current.getSize()));
|
|
bytes_written += 16;
|
|
}
|
|
else if (current.getCrc() != crc
|
|
|| current.getCompressedSize() != compressed_size
|
|
|| current.getSize() != uncompressed_size)
|
|
throw new ZipException ("zip entry field incorrect");
|
|
|
|
current.next = chain;
|
|
chain = current;
|
|
current = null;
|
|
}
|
|
|
|
public void write (int bval) throws IOException
|
|
{
|
|
if (current.method == STORED)
|
|
{
|
|
out.write(bval);
|
|
}
|
|
else
|
|
super.write(bval);
|
|
sum.update(bval);
|
|
uncompressed_size += 1;
|
|
}
|
|
|
|
public void write (byte[] buf, int off, int len) throws IOException
|
|
{
|
|
if (current.method == STORED)
|
|
out.write(buf, off, len);
|
|
else
|
|
super.write(buf, off, len);
|
|
sum.update(buf, off, len);
|
|
uncompressed_size += len;
|
|
}
|
|
|
|
public void finish () throws IOException
|
|
{
|
|
if (current != null)
|
|
closeEntry ();
|
|
|
|
// Write the central directory.
|
|
long offset = bytes_written;
|
|
int count = 0;
|
|
int bytes = 0;
|
|
while (chain != null)
|
|
{
|
|
bytes += write_entry (chain, false);
|
|
++count;
|
|
chain = chain.next;
|
|
}
|
|
|
|
// Write the end of the central directory record.
|
|
put4 (0x06054b50);
|
|
// Disk number.
|
|
put2 (0);
|
|
// Another disk number.
|
|
put2 (0);
|
|
put2 (count);
|
|
put2 (count);
|
|
put4 (bytes);
|
|
put4 ((int) offset);
|
|
|
|
byte[] c = comment.getBytes("8859_1");
|
|
put2 (c.length);
|
|
out.write(c);
|
|
}
|
|
|
|
// Helper for finish and putNextEntry.
|
|
private int write_entry (ZipEntry entry, boolean is_local)
|
|
throws IOException
|
|
{
|
|
int bytes = put4 (is_local ? 0x04034b50 : 0x02014b50);
|
|
if (! is_local)
|
|
bytes += put_version ();
|
|
bytes += put_version ();
|
|
|
|
boolean crc_after = false;
|
|
if (is_local
|
|
&& (entry.getCrc() == -1 || entry.getCompressedSize() == -1
|
|
|| entry.getSize() == -1))
|
|
crc_after = true;
|
|
// For the bits field we always indicate `normal' compression,
|
|
// even if that isn't true.
|
|
bytes += put2 (crc_after ? (1 << 3) : 0);
|
|
bytes += put2 (entry.method);
|
|
|
|
bytes += put2(0); // time - FIXME
|
|
bytes += put2(0); // date - FIXME
|
|
|
|
if (crc_after)
|
|
{
|
|
// CRC, compressedSize, and Size are always 0 in this header.
|
|
// The actual values are given after the entry.
|
|
bytes += put4 (0);
|
|
bytes += put4 (0);
|
|
bytes += put4 (0);
|
|
}
|
|
else
|
|
{
|
|
bytes += put4 ((int) (entry.getCrc()));
|
|
bytes += put4 ((int) (entry.getCompressedSize()));
|
|
bytes += put4 ((int) (entry.getSize()));
|
|
}
|
|
|
|
byte[] name = entry.name.getBytes("8859_1");
|
|
bytes += put2 (name.length);
|
|
bytes += put2 (entry.extra == null ? 0 : entry.extra.length);
|
|
|
|
byte[] comment = null;
|
|
if (! is_local)
|
|
{
|
|
if (entry.getComment() == null)
|
|
bytes += put2 (0);
|
|
else
|
|
{
|
|
comment = entry.getComment().getBytes("8859_1");
|
|
bytes += put2 (comment.length);
|
|
}
|
|
|
|
// Disk number start.
|
|
bytes += put2 (0);
|
|
// Internal file attributes.
|
|
bytes += put2 (0);
|
|
// External file attributes.
|
|
bytes += put4 (0);
|
|
// Relative offset of local header.
|
|
bytes += put4 ((int) entry.relativeOffset);
|
|
}
|
|
|
|
out.write (name);
|
|
bytes += name.length;
|
|
if (entry.extra != null)
|
|
{
|
|
out.write(entry.extra);
|
|
bytes += entry.extra.length;
|
|
}
|
|
if (comment != null)
|
|
{
|
|
out.write(comment);
|
|
bytes += comment.length;
|
|
}
|
|
|
|
bytes_written += bytes;
|
|
return bytes;
|
|
}
|
|
|
|
public void putNextEntry (ZipEntry entry) throws IOException
|
|
{
|
|
if (current != null)
|
|
closeEntry ();
|
|
|
|
if (entry.method < 0 )
|
|
entry.method = method;
|
|
if (entry.method == STORED)
|
|
{
|
|
if (entry.getSize() == -1 || entry.getCrc() == -1)
|
|
throw new ZipException ("required entry not set");
|
|
// Just in case.
|
|
entry.compressedSize = entry.getSize();
|
|
}
|
|
entry.relativeOffset = bytes_written;
|
|
write_entry (entry, true);
|
|
current = entry;
|
|
int compr = (method == STORED) ? Deflater.NO_COMPRESSION : level;
|
|
def.reset();
|
|
def.setLevel(compr);
|
|
sum.reset();
|
|
}
|
|
|
|
public void setLevel (int level)
|
|
{
|
|
if (level != Deflater.DEFAULT_COMPRESSION
|
|
&& (level < Deflater.NO_COMPRESSION
|
|
|| level > Deflater.BEST_COMPRESSION))
|
|
throw new IllegalArgumentException ();
|
|
this.level = level;
|
|
}
|
|
|
|
public void setMethod (int method)
|
|
{
|
|
if (method != DEFLATED && method != STORED)
|
|
throw new IllegalArgumentException ();
|
|
this.method = method;
|
|
}
|
|
|
|
public void setComment (String comment)
|
|
{
|
|
if (comment.length() > 65535)
|
|
throw new IllegalArgumentException ();
|
|
this.comment = comment;
|
|
}
|
|
|
|
public ZipOutputStream (OutputStream out)
|
|
{
|
|
super (out, new Deflater (Deflater.DEFAULT_COMPRESSION, true), 8192);
|
|
sum = new CRC32 ();
|
|
}
|
|
|
|
private int put2 (int i) throws IOException
|
|
{
|
|
out.write (i);
|
|
out.write (i >> 8);
|
|
return 2;
|
|
}
|
|
|
|
private int put4 (int i) throws IOException
|
|
{
|
|
out.write (i);
|
|
out.write (i >> 8);
|
|
out.write (i >> 16);
|
|
out.write (i >> 24);
|
|
return 4;
|
|
}
|
|
|
|
private int put_version () throws IOException
|
|
{
|
|
// FIXME: for now we assume Unix, and we ignore the version
|
|
// number.
|
|
return put2 (3 << 8);
|
|
}
|
|
|
|
// The entry we are currently writing, or null if we've called
|
|
// closeEntry.
|
|
private ZipEntry current;
|
|
// The chain of entries which have been written to this file.
|
|
private ZipEntry chain;
|
|
|
|
private int method = DEFLATED;
|
|
private int level = Deflater.DEFAULT_COMPRESSION;
|
|
private String comment = "";
|
|
private long bytes_written;
|
|
|
|
private int uncompressed_size;
|
|
|
|
/** The checksum object. */
|
|
private Checksum sum;
|
|
}
|