main
1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package packet
6
7import (
8 "compress/bzip2"
9 "compress/flate"
10 "compress/zlib"
11 "io"
12 "strconv"
13
14 "github.com/ProtonMail/go-crypto/openpgp/errors"
15)
16
17// Compressed represents a compressed OpenPGP packet. The decompressed contents
18// will contain more OpenPGP packets. See RFC 4880, section 5.6.
19type Compressed struct {
20 Body io.Reader
21}
22
23const (
24 NoCompression = flate.NoCompression
25 BestSpeed = flate.BestSpeed
26 BestCompression = flate.BestCompression
27 DefaultCompression = flate.DefaultCompression
28)
29
30// CompressionConfig contains compressor configuration settings.
31type CompressionConfig struct {
32 // Level is the compression level to use. It must be set to
33 // between -1 and 9, with -1 causing the compressor to use the
34 // default compression level, 0 causing the compressor to use
35 // no compression and 1 to 9 representing increasing (better,
36 // slower) compression levels. If Level is less than -1 or
37 // more then 9, a non-nil error will be returned during
38 // encryption. See the constants above for convenient common
39 // settings for Level.
40 Level int
41}
42
43// decompressionReader ensures that the whole compression packet is read.
44type decompressionReader struct {
45 compressed io.Reader
46 decompressed io.ReadCloser
47 readAll bool
48}
49
50func newDecompressionReader(r io.Reader, decompressor io.ReadCloser) *decompressionReader {
51 return &decompressionReader{
52 compressed: r,
53 decompressed: decompressor,
54 }
55}
56
57func (dr *decompressionReader) Read(data []byte) (n int, err error) {
58 if dr.readAll {
59 return 0, io.EOF
60 }
61 n, err = dr.decompressed.Read(data)
62 if err == io.EOF {
63 dr.readAll = true
64 // Close the decompressor.
65 if errDec := dr.decompressed.Close(); errDec != nil {
66 return n, errDec
67 }
68 // Consume all remaining data from the compressed packet.
69 consumeAll(dr.compressed)
70 }
71 return n, err
72}
73
74func (c *Compressed) parse(r io.Reader) error {
75 var buf [1]byte
76 _, err := readFull(r, buf[:])
77 if err != nil {
78 return err
79 }
80
81 switch buf[0] {
82 case 0:
83 c.Body = r
84 case 1:
85 c.Body = newDecompressionReader(r, flate.NewReader(r))
86 case 2:
87 decompressor, err := zlib.NewReader(r)
88 if err != nil {
89 return err
90 }
91 c.Body = newDecompressionReader(r, decompressor)
92 case 3:
93 c.Body = newDecompressionReader(r, io.NopCloser(bzip2.NewReader(r)))
94 default:
95 err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
96 }
97
98 return err
99}
100
101// compressedWriterCloser represents the serialized compression stream
102// header and the compressor. Its Close() method ensures that both the
103// compressor and serialized stream header are closed. Its Write()
104// method writes to the compressor.
105type compressedWriteCloser struct {
106 sh io.Closer // Stream Header
107 c io.WriteCloser // Compressor
108}
109
110func (cwc compressedWriteCloser) Write(p []byte) (int, error) {
111 return cwc.c.Write(p)
112}
113
114func (cwc compressedWriteCloser) Close() (err error) {
115 err = cwc.c.Close()
116 if err != nil {
117 return err
118 }
119
120 return cwc.sh.Close()
121}
122
123// SerializeCompressed serializes a compressed data packet to w and
124// returns a WriteCloser to which the literal data packets themselves
125// can be written and which MUST be closed on completion. If cc is
126// nil, sensible defaults will be used to configure the compression
127// algorithm.
128func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) {
129 compressed, err := serializeStreamHeader(w, packetTypeCompressed)
130 if err != nil {
131 return
132 }
133
134 _, err = compressed.Write([]byte{uint8(algo)})
135 if err != nil {
136 return
137 }
138
139 level := DefaultCompression
140 if cc != nil {
141 level = cc.Level
142 }
143
144 var compressor io.WriteCloser
145 switch algo {
146 case CompressionZIP:
147 compressor, err = flate.NewWriter(compressed, level)
148 case CompressionZLIB:
149 compressor, err = zlib.NewWriterLevel(compressed, level)
150 default:
151 s := strconv.Itoa(int(algo))
152 err = errors.UnsupportedError("Unsupported compression algorithm: " + s)
153 }
154 if err != nil {
155 return
156 }
157
158 literaldata = compressedWriteCloser{compressed, compressor}
159
160 return
161}