package org.bouncycastle.openpgp; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Date; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.PacketTags; import org.bouncycastle.util.Strings; /** * Generator for producing literal data packets. * <p> * A PGPLiteralData is used by invoking one of the open functions to create an OutputStream that raw * data can be supplied to for encoding: * <ul> * <li>If the length of the data to be written is known in advance, use * {@link #open(OutputStream, char, String, long, Date)} to create a packet containing a single * literal data object.</li> * <li>If the length of the data is unknown, use * {@link #open(OutputStream, char, String, Date, byte[])} to create a packet consisting of a series * of literal data objects (partials).</li> * </ul> * </p><p> * A PGPLiteralDataGenerator is usually used to wrap the OutputStream * {@link PGPEncryptedDataGenerator#open(OutputStream, byte[]) obtained} from a * {@link PGPEncryptedDataGenerator} or a {@link PGPCompressedDataGenerator}. * </p><p> * Once literal data 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 PGPLiteralDataGenerator implements StreamGenerator { /** Format tag for binary literal data */ public static final char BINARY = PGPLiteralData.BINARY; /** Format tag for textual literal data */ public static final char TEXT = PGPLiteralData.TEXT; /** Format tag for UTF-8 encoded textual literal data */ public static final char UTF8 = PGPLiteralData.UTF8; /** * The special name indicating a "for your eyes only" packet. */ // TODO: Not used? public static final String CONSOLE = PGPLiteralData.CONSOLE; /** * The special time for a modification time of "now" or * the present time. */ public static final Date NOW = PGPLiteralData.NOW; private BCPGOutputStream pkOut; private boolean oldFormat = false; /** * Constructs a generator for literal data objects. */ public PGPLiteralDataGenerator() { } /** * Constructs a generator for literal data objects, specifying to use new or old (PGP 2.6.x * compatible) format. * <p> * This can be used for compatibility with PGP 2.6.x. * </p> * @param oldFormat <code>true</code> to use PGP 2.6.x compatible format. */ public PGPLiteralDataGenerator( boolean oldFormat) { this.oldFormat = oldFormat; } private void writeHeader( OutputStream out, char format, byte[] encName, long modificationTime) throws IOException { out.write(format); out.write((byte)encName.length); for (int i = 0; i != encName.length; i++) { out.write(encName[i]); } long modDate = modificationTime / 1000; out.write((byte)(modDate >> 24)); out.write((byte)(modDate >> 16)); out.write((byte)(modDate >> 8)); out.write((byte)(modDate)); } /** * Open a literal data packet, returning a stream to store the data inside the packet. * <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 the underlying output stream to write the literal data packet to. * @param format the format of the literal data that will be written to the output stream (one * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}). * @param name the name of the "file" to encode in the literal data object. * @param length the length of the data that will be written. * @param modificationTime the time of last modification we want stored. */ public OutputStream open( OutputStream out, char format, String name, long length, Date modificationTime) throws IOException { if (pkOut != null) { throw new IllegalStateException("generator already in open state"); } byte[] encName = Strings.toUTF8ByteArray(name); pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + encName.length + 4, oldFormat); writeHeader(pkOut, format, encName, modificationTime.getTime()); return new WrappedGeneratorStream(pkOut, this); } /** * Open a literal data packet, returning a stream to store the data inside the packet as an * indefinite-length stream. The stream is written out as a series of partial packets with a * chunk size determined by 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. * * @param out the underlying output stream to write the literal data packet to. * @param format the format of the literal data that will be written to the output stream (one * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}). * @param name the name of the "file" to encode in the literal data object. * @param modificationTime the time of last modification we want stored (will be stored to * second level precision). * @param buffer a buffer to use to buffer and write partial packets. The returned stream takes * ownership of the buffer. * * @return the output stream to write data to. * @throws IOException if an error occurs writing stream header information to the provider * output stream. * @throws IllegalStateException if this generator already has an open OutputStream. */ public OutputStream open( OutputStream out, char format, String name, Date modificationTime, byte[] buffer) throws IOException { if (pkOut != null) { throw new IllegalStateException("generator already in open state"); } pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, buffer); byte[] encName = Strings.toUTF8ByteArray(name); writeHeader(pkOut, format, encName, modificationTime.getTime()); return new WrappedGeneratorStream(pkOut, this); } /** * Open a literal data packet for the passed in File object, returning an output stream for * saving the file contents. * <p> * This method configures the generator to store the file contents in a single literal data * packet, taking the filename and modification time from the file, but does not store the * actual file data. * </p><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> * @param out the underlying output stream to write the literal data packet to. * @param format the format of the literal data that will be written to the output stream (one * of {@link #BINARY}, {@link #TEXT} or {@link #UTF8}). * @param file the file to determine the length and filename from. * @return the output stream to write data to. * @throws IOException if an error occurs writing stream header information to the provider * output stream. * @throws IllegalStateException if this generator already has an open OutputStream. */ public OutputStream open( OutputStream out, char format, File file) throws IOException { return open(out, format, file.getName(), file.length(), new Date(file.lastModified())); } /** * Close the literal data packet - this is equivalent to calling close on the stream * returned by the open() method. * * @throws IOException */ public void close() throws IOException { if (pkOut != null) { pkOut.finish(); pkOut.flush(); pkOut = null; } } }