|
| 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 | +} |
0 commit comments