package org.bouncycastle.openpgp; import java.io.IOException; import java.io.OutputStream; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import org.bouncycastle.apache.bzip2.CBZip2OutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.PacketTags; /** * Generator for producing compressed data packets. * <p> * A PGPCompressedDataGenerator is used by invoking one of the open functions to create an * OutputStream that raw data can be supplied to for compression: * <ul> * <li>If the data needs to written out in blocks, use {@link #open(OutputStream, byte[])} to create a * packet consisting of a series of compressed data objects (partials).</li> * </ul> * </p> * <p> * A PGPCompressedDataGenerator is usually used to wrap the OutputStream * {@link PGPEncryptedDataGenerator#open(OutputStream, byte[]) obtained} from a * {@link PGPEncryptedDataGenerator} (i.e. to compress data prior to encrypting it). * </p><p> * Raw data is not typically written directly to the OutputStream obtained from a * PGPCompressedDataGenerator. The OutputStream is usually wrapped by a * {@link PGPLiteralDataGenerator}, which encodes the raw data prior to compression. * </p> * <p> * Once data for compression has been written to the constructed OutputStream, writing of the object * stream is completed by closing the OutputStream obtained from the <code>#open()</code> method, or * equivalently invoking {@link #close()} on this generator. * </p> */ public class PGPCompressedDataGenerator implements CompressionAlgorithmTags, StreamGenerator { private int algorithm; private int compression; private OutputStream dOut; private BCPGOutputStream pkOut; /** * Construct a new compressed data generator. * * @param algorithm the identifier of the {@link CompressionAlgorithmTags compression algorithm} * to use. */ public PGPCompressedDataGenerator( int algorithm) { this(algorithm, Deflater.DEFAULT_COMPRESSION); } /** * Construct a new compressed data generator. * * @param algorithm the identifier of the {@link CompressionAlgorithmTags compression algorithm} * to use. * @param compression the {@link Deflater} compression level to use. */ public PGPCompressedDataGenerator( int algorithm, int compression) { switch (algorithm) { case CompressionAlgorithmTags.UNCOMPRESSED: case CompressionAlgorithmTags.ZIP: case CompressionAlgorithmTags.ZLIB: case CompressionAlgorithmTags.BZIP2: break; default: throw new IllegalArgumentException("unknown compression algorithm"); } if (compression != Deflater.DEFAULT_COMPRESSION) { if ((compression < Deflater.NO_COMPRESSION) || (compression > Deflater.BEST_COMPRESSION)) { throw new IllegalArgumentException("unknown compression level: " + compression); } } this.algorithm = algorithm; this.compression = compression; } /** * Return an OutputStream which will save the data being written to * the compressed object. * <p> * The stream created can be closed off by either calling close() * on the stream or close() on the generator. Closing the returned * stream does not close off the OutputStream parameter out. * * @param out underlying OutputStream to be used. * @return OutputStream * @throws IOException, IllegalStateException */ public OutputStream open( OutputStream out) throws IOException { if (dOut != null) { throw new IllegalStateException("generator already in open state"); } this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA); doOpen(); return new WrappedGeneratorStream(dOut, this); } /** * Return an OutputStream which will compress the data as it is written to it. The stream will * be written out in chunks (partials) according to the size of the passed in buffer. * <p> * The stream created can be closed off by either calling close() on the stream or close() on * the generator. Closing the returned stream does not close off the OutputStream parameter out. * <p> * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 bytes * worth of the buffer will be used. * </p> * <p> * <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools. Only recent * OpenPGP implementations are capable of accepting these streams. * </p> * * @param out the stream to write compressed packets to. * @param buffer a buffer to use to buffer and write partial packets. The returned stream takes * ownership of the buffer and will use it to buffer plaintext data for compression. * @return the output stream to write data to. * @throws IOException if an error occurs writing stream header information to the provider * output stream. * @throws PGPException * @throws IllegalStateException if this generator already has an open OutputStream. */ public OutputStream open( OutputStream out, byte[] buffer) throws IOException, PGPException { if (dOut != null) { throw new IllegalStateException("generator already in open state"); } this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer); doOpen(); return new WrappedGeneratorStream(dOut, this); } private void doOpen() throws IOException { pkOut.write(algorithm); switch (algorithm) { case CompressionAlgorithmTags.UNCOMPRESSED: dOut = pkOut; break; case CompressionAlgorithmTags.ZIP: dOut = new SafeDeflaterOutputStream(pkOut, compression, true); break; case CompressionAlgorithmTags.ZLIB: dOut = new SafeDeflaterOutputStream(pkOut, compression, false); break; case CompressionAlgorithmTags.BZIP2: dOut = new SafeCBZip2OutputStream(pkOut); break; default: // Constructor should guard against this possibility throw new IllegalStateException(); } } /** * Close the compressed object - this is equivalent to calling close on the stream * returned by the open() method. * * @throws IOException */ public void close() throws IOException { if (dOut != null) { if (dOut != pkOut) { dOut.close(); } dOut = null; pkOut.finish(); pkOut.flush(); pkOut = null; } } private static class SafeCBZip2OutputStream extends CBZip2OutputStream { public SafeCBZip2OutputStream(OutputStream output) throws IOException { super(output); } public void close() throws IOException { finish(); } } private class SafeDeflaterOutputStream extends DeflaterOutputStream { public SafeDeflaterOutputStream(OutputStream output, int compression, boolean nowrap) { super(output, new Deflater(compression, nowrap)); } public void close() throws IOException { finish(); def.end(); } } }