Skip to content

Commit 0b21b49

Browse files
committed
imapclient: add COMPRESS
1 parent ca0ddb7 commit 0b21b49

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

imapclient/compress.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package imapclient
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"compress/flate"
7+
"io"
8+
"net"
9+
)
10+
11+
// CompressDeflateOptions contains options for Client.CompressDeflate.
12+
type CompressDeflateOptions struct{}
13+
14+
// CompressDeflate enables connection-level compression.
15+
//
16+
// Unlike other commands, this method blocks until the command completes.
17+
//
18+
// A nil options pointer is equivalent to a zero options value.
19+
func (c *Client) CompressDeflate(options *CompressDeflateOptions) error {
20+
upgradeDone := make(chan struct{})
21+
cmd := &compressCommand{
22+
upgradeDone: upgradeDone,
23+
}
24+
enc := c.beginCommand("COMPRESS", cmd)
25+
enc.SP().Atom("DEFLATE")
26+
enc.flush()
27+
defer enc.end()
28+
29+
// The client MUST NOT send any further commands until it has seen the
30+
// result of COMPRESS.
31+
32+
if err := cmd.Wait(); err != nil {
33+
return err
34+
}
35+
36+
// The decoder goroutine will invoke Client.upgradeCompress
37+
<-upgradeDone
38+
return nil
39+
}
40+
41+
func (c *Client) upgradeCompress() {
42+
// Drain buffered data from our bufio.Reader
43+
var buf bytes.Buffer
44+
if _, err := io.CopyN(&buf, c.br, int64(c.br.Buffered())); err != nil {
45+
panic(err) // unreachable
46+
}
47+
48+
// TODO: wrap TLS conn
49+
var r io.Reader
50+
if buf.Len() > 0 {
51+
r = io.MultiReader(&buf, c.conn)
52+
} else {
53+
r = c.conn
54+
}
55+
56+
w, err := flate.NewWriter(c.conn, flate.DefaultCompression)
57+
if err != nil {
58+
panic(err) // can only happen due to bad arguments
59+
}
60+
61+
conn := &compressConn{
62+
Conn: c.conn,
63+
r: flate.NewReader(r),
64+
w: w,
65+
}
66+
rw := c.options.wrapReadWriter(conn)
67+
68+
c.br.Reset(rw)
69+
// Unfortunately we can't re-use the bufio.Writer here, it races with
70+
// Client.CompressDeflate
71+
c.bw = bufio.NewWriter(rw)
72+
}
73+
74+
type compressCommand struct {
75+
cmd
76+
upgradeDone chan<- struct{}
77+
}
78+
79+
type compressConn struct {
80+
net.Conn
81+
r io.ReadCloser
82+
w *flate.Writer
83+
}
84+
85+
func (conn *compressConn) Read(b []byte) (int, error) {
86+
return conn.r.Read(b)
87+
}
88+
89+
func (conn *compressConn) Write(b []byte) (int, error) {
90+
return conn.w.Write(b)
91+
}
92+
93+
func (conn *compressConn) Close() error {
94+
if err := conn.r.Close(); err != nil {
95+
return err
96+
}
97+
if err := conn.w.Close(); err != nil {
98+
return err
99+
}
100+
return conn.Conn.Close()
101+
}

response.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ const (
4949

5050
// APPENDLIMIT
5151
ResponseCodeTooBig ResponseCode = "TOOBIG"
52+
53+
// COMPRESS
54+
ResponseCodeCompressionActive ResponseCode = "COMPRESSIONACTIVE"
5255
)
5356

5457
// StatusResponse is a generic status response.

0 commit comments

Comments
 (0)