Skip to content

Commit

Permalink
imapserver: add COMPRESS
Browse files Browse the repository at this point in the history
  • Loading branch information
emersion committed May 8, 2024
1 parent 6892256 commit 6a6113f
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
3 changes: 3 additions & 0 deletions imapserver/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ func (c *Conn) availableCaps() []imap.Cap {
} else if c.state == imap.ConnStateNotAuthenticated {
caps = append(caps, imap.CapLoginDisabled)
}
if c.canCompress() {
caps = append(caps, imap.Cap("COMPRESS=DEFLATE"))
}
if c.state == imap.ConnStateAuthenticated || c.state == imap.ConnStateSelected {
if available.Has(imap.CapIMAP4rev1) {
caps = append(caps, []imap.Cap{
Expand Down
86 changes: 86 additions & 0 deletions imapserver/compress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package imapserver

import (
"bytes"
"compress/flate"
"io"

"github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/internal/imapwire"
)

func (c *Conn) canCompress() bool {
switch c.state {
case imap.ConnStateAuthenticated, imap.ConnStateSelected:
return true // TODO
default:
return false
}
}

func (c *Conn) handleCompress(tag string, dec *imapwire.Decoder) error {
var algo string
if !dec.ExpectSP() || !dec.ExpectAtom(&algo) || !dec.ExpectCRLF() {
return dec.Err()
}

if !c.canCompress() {
return &imap.Error{
Type: imap.StatusResponseTypeBad,
Text: "COMPRESS not available",
}
}
if algo != "DEFLATE" {
return &imap.Error{
Type: imap.StatusResponseTypeNo,
Text: "Unsupported compression algorithm",
}
}

// Do not allow to write uncompressed data past this point: keep c.encMutex
// locked until the end
enc := newResponseEncoder(c)
defer enc.end()

err := writeStatusResp(enc.Encoder, tag, &imap.StatusResponse{
Type: imap.StatusResponseTypeOK,
Text: "Begin compression now",
})
if err != nil {
return err
}

// Drain buffered data from our bufio.Reader
var buf bytes.Buffer
if _, err := io.CopyN(&buf, c.br, int64(c.br.Buffered())); err != nil {
panic(err) // unreachable
}

var r io.Reader
if buf.Len() > 0 {
r = io.MultiReader(&buf, c.conn)
} else {
r = c.conn
}

c.mutex.Lock()
// TODO
c.mutex.Unlock()

w, err := flate.NewWriter(c.conn, flate.DefaultCompression)
if err != nil {
panic(err) // can only happen due to bad arguments
}

rw := c.server.options.wrapReadWriter(struct {
io.Reader
io.Writer
}{
Reader: flate.NewReader(r),
Writer: w,
})
c.br.Reset(rw)
c.bw.Reset(rw)

return nil
}
2 changes: 2 additions & 0 deletions imapserver/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error {
err = c.handleMove(dec, numKind)
case "SEARCH", "UID SEARCH":
err = c.handleSearch(tag, dec, numKind)
case "COMPRESS":
err = c.handleCompress(tag, dec)
default:
if c.state == imap.ConnStateNotAuthenticated {
// Don't allow a single unknown command before authentication to
Expand Down

0 comments on commit 6a6113f

Please sign in to comment.