Skip to content

Commit e5b9306

Browse files
foxcppemersion
authored andcommitted
Merge support for COMPRESS extension
1 parent c7dce92 commit e5b9306

File tree

9 files changed

+184
-5
lines changed

9 files changed

+184
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ includes:
135135
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
136136
* [UNSELECT](https://tools.ietf.org/html/rfc3691)
137137
* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
138+
* [COMPRESS](https://tools.ietf.org/html/rfc4978)
138139

139140
Support for other extensions is provided via separate packages. See below.
140141

@@ -146,7 +147,6 @@ Commands defined in IMAP extensions are available in other packages. See [the
146147
wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
147148
to learn how to use them.
148149

149-
* [COMPRESS](https://github.com/emersion/go-imap-compress)
150150
* [ENABLE](https://github.com/emersion/go-imap-enable)
151151
* [ID](https://github.com/ProtonMail/go-imap-id)
152152
* [IDLE](https://github.com/emersion/go-imap-idle)

client/client.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ type Client struct {
6666
isTLS bool
6767
serverName string
6868

69-
loggedOut chan struct{}
70-
continues chan<- bool
71-
upgrading bool
69+
loggedOut chan struct{}
70+
continues chan<- bool
71+
upgrading bool
72+
isCompressed bool
7273

7374
handlers []responses.Handler
7475
handlersLocker sync.Mutex

client/cmd_any.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package client
22

33
import (
4+
"compress/flate"
45
"errors"
6+
"net"
57

68
"github.com/emersion/go-imap"
79
"github.com/emersion/go-imap/commands"
10+
"github.com/emersion/go-imap/internal"
811
)
912

1013
// ErrAlreadyLoggedOut is returned if Logout is called when the client is
1114
// already logged out.
1215
var ErrAlreadyLoggedOut = errors.New("Already logged out")
1316

17+
// ErrAlreadyCompress is returned by Client.Compress when compression has
18+
// already been enabled on the client.
19+
var ErrAlreadyCompressed = errors.New("COMPRESS is already enabled")
20+
1421
// Capability requests a listing of capabilities that the server supports.
1522
// Capabilities are often returned by the server with the greeting or with the
1623
// STARTTLS and LOGIN responses, so usually explicitly requesting capabilities
@@ -86,3 +93,35 @@ func (c *Client) Logout() error {
8693
}
8794
return nil
8895
}
96+
97+
// Compress instructs the server to use the named compression mechanism for all
98+
// commands and/or responses.
99+
func (c *Client) Compress(mech string) error {
100+
if c.isCompressed {
101+
return ErrAlreadyCompressed
102+
}
103+
104+
if ok, err := c.Support("COMPRESS=" + mech); !ok || err != nil {
105+
return imap.CompressUnsupportedError{Mechanism: mech}
106+
}
107+
if mech != imap.CompressDeflate {
108+
return imap.CompressUnsupportedError{Mechanism: mech}
109+
}
110+
111+
cmd := &commands.Compress{Mechanism: mech}
112+
err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
113+
if status, err := c.Execute(cmd, nil); err != nil {
114+
return nil, err
115+
} else if err := status.Err(); err != nil {
116+
return nil, err
117+
}
118+
119+
return internal.CreateDeflateConn(conn, flate.DefaultCompression)
120+
})
121+
if err != nil {
122+
return err
123+
}
124+
125+
c.isCompressed = true
126+
return nil
127+
}

commands/compress.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package commands
2+
3+
import (
4+
"errors"
5+
6+
"github.com/emersion/go-imap"
7+
)
8+
9+
// A COMPRESS command.
10+
type Compress struct {
11+
// Name of the compression mechanism.
12+
Mechanism string
13+
}
14+
15+
func (cmd *Compress) Command() *imap.Command {
16+
return &imap.Command{
17+
Name: "COMPRESS",
18+
Arguments: []interface{}{cmd.Mechanism},
19+
}
20+
}
21+
22+
func (cmd *Compress) Parse(fields []interface{}) (err error) {
23+
if len(fields) < 1 {
24+
return errors.New("No enough arguments")
25+
}
26+
27+
var ok bool
28+
if cmd.Mechanism, ok = fields[0].(string); !ok {
29+
return errors.New("Compression mechanism name must be a string")
30+
}
31+
32+
return nil
33+
}

deflate.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package imap
2+
3+
// A CompressUnsuppportedError is returned by Client.Compress when the provided
4+
// compression mechanism is not supported.
5+
type CompressUnsupportedError struct {
6+
Mechanism string
7+
}
8+
9+
func (err CompressUnsupportedError) Error() string {
10+
return "COMPRESS mechanism " + err.Mechanism + " not supported"
11+
}
12+
13+
// Compression algorithms for use with COMPRESS extension (RFC 4978).
14+
const (
15+
// The DEFLATE algorithm, defined in RFC 1951.
16+
CompressDeflate = "DEFLATE"
17+
)

internal/deflate.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package internal
2+
3+
import (
4+
"compress/flate"
5+
"io"
6+
"net"
7+
)
8+
9+
type deflateConn struct {
10+
net.Conn
11+
12+
r io.ReadCloser
13+
w *flate.Writer
14+
}
15+
16+
func (c *deflateConn) Read(b []byte) (int, error) {
17+
return c.r.Read(b)
18+
}
19+
20+
func (c *deflateConn) Write(b []byte) (int, error) {
21+
return c.w.Write(b)
22+
}
23+
24+
type flusher interface {
25+
Flush() error
26+
}
27+
28+
func (c *deflateConn) Flush() error {
29+
if f, ok := c.Conn.(flusher); ok {
30+
if err := f.Flush(); err != nil {
31+
return err
32+
}
33+
}
34+
35+
return c.w.Flush()
36+
}
37+
38+
func (c *deflateConn) Close() error {
39+
if err := c.r.Close(); err != nil {
40+
return err
41+
}
42+
43+
if err := c.w.Close(); err != nil {
44+
return err
45+
}
46+
47+
return c.Conn.Close()
48+
}
49+
50+
func CreateDeflateConn(c net.Conn, level int) (net.Conn, error) {
51+
r := flate.NewReader(c)
52+
w, err := flate.NewWriter(c, level)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return &deflateConn{
58+
Conn: c,
59+
r: r,
60+
w: w,
61+
}, nil
62+
}

server/cmd_any.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package server
22

33
import (
4+
"compress/flate"
5+
"net"
6+
47
"github.com/emersion/go-imap"
58
"github.com/emersion/go-imap/backend"
69
"github.com/emersion/go-imap/commands"
10+
"github.com/emersion/go-imap/internal"
711
"github.com/emersion/go-imap/responses"
812
)
913

@@ -50,3 +54,25 @@ func (cmd *Logout) Handle(conn Conn) error {
5054
conn.Context().State = imap.LogoutState
5155
return nil
5256
}
57+
58+
type Compress struct {
59+
commands.Compress
60+
}
61+
62+
func (cmd *Compress) Handle(conn Conn) error {
63+
if cmd.Mechanism != imap.CompressDeflate {
64+
return imap.CompressUnsupportedError{Mechanism: cmd.Mechanism}
65+
}
66+
return nil
67+
}
68+
69+
func (cmd *Compress) Upgrade(conn Conn) error {
70+
err := conn.Upgrade(func(conn net.Conn) (net.Conn, error) {
71+
return internal.CreateDeflateConn(conn, flate.DefaultCompression)
72+
})
73+
if err != nil {
74+
return err
75+
}
76+
77+
return nil
78+
}

server/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (c *conn) Close() error {
163163
}
164164

165165
func (c *conn) Capabilities() []string {
166-
caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN", "UNSELECT"}
166+
caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN", "UNSELECT", "COMPRESS=DEFLATE"}
167167

168168
appendLimitSet := false
169169
if c.ctx.State == imap.AuthenticatedState {

server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ func New(bkd backend.Backend) *Server {
187187
"UID": func() Handler { return &Uid{} },
188188

189189
"UNSELECT": func() Handler { return &Unselect{} },
190+
"COMPRESS": func() Handler { return &Compress{} },
190191
}
191192

192193
return s

0 commit comments

Comments
 (0)