Skip to content

Commit 6a6113f

Browse files
committed
imapserver: add COMPRESS
1 parent 6892256 commit 6a6113f

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

imapserver/capability.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func (c *Conn) availableCaps() []imap.Cap {
5757
} else if c.state == imap.ConnStateNotAuthenticated {
5858
caps = append(caps, imap.CapLoginDisabled)
5959
}
60+
if c.canCompress() {
61+
caps = append(caps, imap.Cap("COMPRESS=DEFLATE"))
62+
}
6063
if c.state == imap.ConnStateAuthenticated || c.state == imap.ConnStateSelected {
6164
if available.Has(imap.CapIMAP4rev1) {
6265
caps = append(caps, []imap.Cap{

imapserver/compress.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package imapserver
2+
3+
import (
4+
"bytes"
5+
"compress/flate"
6+
"io"
7+
8+
"github.com/emersion/go-imap/v2"
9+
"github.com/emersion/go-imap/v2/internal/imapwire"
10+
)
11+
12+
func (c *Conn) canCompress() bool {
13+
switch c.state {
14+
case imap.ConnStateAuthenticated, imap.ConnStateSelected:
15+
return true // TODO
16+
default:
17+
return false
18+
}
19+
}
20+
21+
func (c *Conn) handleCompress(tag string, dec *imapwire.Decoder) error {
22+
var algo string
23+
if !dec.ExpectSP() || !dec.ExpectAtom(&algo) || !dec.ExpectCRLF() {
24+
return dec.Err()
25+
}
26+
27+
if !c.canCompress() {
28+
return &imap.Error{
29+
Type: imap.StatusResponseTypeBad,
30+
Text: "COMPRESS not available",
31+
}
32+
}
33+
if algo != "DEFLATE" {
34+
return &imap.Error{
35+
Type: imap.StatusResponseTypeNo,
36+
Text: "Unsupported compression algorithm",
37+
}
38+
}
39+
40+
// Do not allow to write uncompressed data past this point: keep c.encMutex
41+
// locked until the end
42+
enc := newResponseEncoder(c)
43+
defer enc.end()
44+
45+
err := writeStatusResp(enc.Encoder, tag, &imap.StatusResponse{
46+
Type: imap.StatusResponseTypeOK,
47+
Text: "Begin compression now",
48+
})
49+
if err != nil {
50+
return err
51+
}
52+
53+
// Drain buffered data from our bufio.Reader
54+
var buf bytes.Buffer
55+
if _, err := io.CopyN(&buf, c.br, int64(c.br.Buffered())); err != nil {
56+
panic(err) // unreachable
57+
}
58+
59+
var r io.Reader
60+
if buf.Len() > 0 {
61+
r = io.MultiReader(&buf, c.conn)
62+
} else {
63+
r = c.conn
64+
}
65+
66+
c.mutex.Lock()
67+
// TODO
68+
c.mutex.Unlock()
69+
70+
w, err := flate.NewWriter(c.conn, flate.DefaultCompression)
71+
if err != nil {
72+
panic(err) // can only happen due to bad arguments
73+
}
74+
75+
rw := c.server.options.wrapReadWriter(struct {
76+
io.Reader
77+
io.Writer
78+
}{
79+
Reader: flate.NewReader(r),
80+
Writer: w,
81+
})
82+
c.br.Reset(rw)
83+
c.bw.Reset(rw)
84+
85+
return nil
86+
}

imapserver/conn.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error {
266266
err = c.handleMove(dec, numKind)
267267
case "SEARCH", "UID SEARCH":
268268
err = c.handleSearch(tag, dec, numKind)
269+
case "COMPRESS":
270+
err = c.handleCompress(tag, dec)
269271
default:
270272
if c.state == imap.ConnStateNotAuthenticated {
271273
// Don't allow a single unknown command before authentication to

0 commit comments

Comments
 (0)