diff --git a/openpgp/packet/compressed.go b/openpgp/packet/compressed.go index deb3158..f1bd2f5 100644 --- a/openpgp/packet/compressed.go +++ b/openpgp/packet/compressed.go @@ -19,6 +19,26 @@ type Compressed struct { Body io.Reader } +const ( + NoCompression = flate.NoCompression + BestSpeed = flate.BestSpeed + BestCompression = flate.BestCompression + DefaultCompression = flate.DefaultCompression +) + +// CompressionConfig contains compressor configuration settings. +type CompressionConfig struct { + // Level is the compression level to use. It must be set to + // between -1 and 9, with -1 causing the compressor to use the + // default compression level, 0 causing the compressor to use + // no compression and 1 to 9 representing increasing (better, + // slower) compression levels. If Level is less than -1 or + // more then 9, a non-nil error will be returned during + // encryption. See the constants above for convenient common + // settings for Level. + Level int +} + func (c *Compressed) parse(r io.Reader) error { var buf [1]byte _, err := readFull(r, buf[:]) @@ -39,3 +59,65 @@ func (c *Compressed) parse(r io.Reader) error { return err } + +// compressedWriterCloser represents the serialized compression stream +// header and the compressor. Its Close() method ensures that both the +// compressor and serialized stream header are closed. Its Write() +// method writes to the compressor. +type compressedWriteCloser struct { + sh io.Closer // Stream Header + c io.WriteCloser // Compressor +} + +func (cwc compressedWriteCloser) Write(p []byte) (int, error) { + return cwc.c.Write(p) +} + +func (cwc compressedWriteCloser) Close() (err error) { + err = cwc.c.Close() + if err != nil { + return err + } + + return cwc.sh.Close() +} + +// SerializeCompressed serializes a compressed data packet to w and +// returns a WriteCloser to which the literal data packets themselves +// can be written and which MUST be closed on completion. If cc is +// nil, sensible defaults will be used to configure the compression +// algorithm. +func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) { + compressed, err := serializeStreamHeader(w, packetTypeCompressed) + if err != nil { + return + } + + _, err = compressed.Write([]byte{uint8(algo)}) + if err != nil { + return + } + + level := DefaultCompression + if cc != nil { + level = cc.Level + } + + var compressor io.WriteCloser + switch algo { + case CompressionZIP: + compressor, err = flate.NewWriter(compressed, level) + case CompressionZLIB: + compressor, err = zlib.NewWriterLevel(compressed, level) + default: + s := strconv.Itoa(int(algo)) + err = errors.UnsupportedError("Unsupported compression algorithm: " + s) + } + if err != nil { + return + } + + literaldata = compressedWriteCloser{compressed, compressor} + + return +} diff --git a/openpgp/packet/config.go b/openpgp/packet/config.go index 618da97..8c4f213 100644 --- a/openpgp/packet/config.go +++ b/openpgp/packet/config.go @@ -26,6 +26,12 @@ type Config struct { // Time returns the current time as the number of seconds since the // epoch. If Time is nil, time.Now is used. Time func() time.Time + // DefaultCompressionAlgo is the compression algorithm to be + // applied to the plaintext before encryption. If zero, no + // compression is done. + DefaultCompressionAlgo CompressionAlgo + // CompressionConfig configures the compression settings. + CompressionConfig *CompressionConfig } func (c *Config) Random() io.Reader { @@ -55,3 +61,10 @@ func (c *Config) Now() time.Time { } return c.Time() } + +func (c *Config) Compression() CompressionAlgo { + if c == nil { + return CompressionNone + } + return c.DefaultCompressionAlgo +} diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go index 9b5cd88..b06c019 100644 --- a/openpgp/packet/packet.go +++ b/openpgp/packet/packet.go @@ -488,3 +488,14 @@ func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) { func writeBig(w io.Writer, i *big.Int) error { return writeMPI(w, uint16(i.BitLen()), i.Bytes()) } + +// CompressionAlgo Represents the different compression algorithms +// supported by OpenPGP (except for BZIP2, which is not currently +// supported). See Section 9.3 of RFC 4880. +type CompressionAlgo uint8 + +const ( + CompressionNone CompressionAlgo = 0 + CompressionZIP CompressionAlgo = 1 + CompressionZLIB CompressionAlgo = 2 +) diff --git a/openpgp/write.go b/openpgp/write.go index f89aab9..a67ffca 100644 --- a/openpgp/write.go +++ b/openpgp/write.go @@ -118,11 +118,24 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi if err != nil { return } + + literaldata := w + if algo := config.Compression(); algo != packet.CompressionNone { + var compConfig *packet.CompressionConfig + if config != nil { + compConfig = config.CompressionConfig + } + literaldata, err = packet.SerializeCompressed(w, algo, compConfig) + if err != nil { + return + } + } + var epochSeconds uint32 if !hints.ModTime.IsZero() { epochSeconds = uint32(hints.ModTime.Unix()) } - return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) + return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds) } // intersectPreferences mutates and returns a prefix of a that contains only