Repository: emersion/go-imap
Pull request #45 (2016-08-11 12:06:08)

common: makes Writer an interface
External packages should not have access to Writer.

Breaks compatibility with dates and extensions.


Patch:
diff --git a/backend/mailbox.go b/backend/mailbox.go
index 01cf90e..82aa2db 100644
--- a/backend/mailbox.go
+++ b/backend/mailbox.go
@@ -51,7 +51,7 @@ type Mailbox interface {
 	//
 	// If the Backend implements Updater, it must notify the client immediately
 	// via a mailbox update.
-	CreateMessage(flags []string, date *time.Time, body []byte) error
+	CreateMessage(flags []string, date time.Time, body []byte) error
 
 	// Alter flags for the specified message(s).
 	//
diff --git a/backend/memory/backend.go b/backend/memory/backend.go
index 28483f4..13de796 100644
--- a/backend/memory/backend.go
+++ b/backend/memory/backend.go
@@ -43,7 +43,7 @@ Hi there :)`
 					Uid: 6,
 					Flags: []string{"\\Seen"},
 					Envelope: &common.Envelope{
-						Date: &now,
+						Date: now,
 						Subject: "Hello World!",
 						From: []*common.Address{},
 						Sender: []*common.Address{},
@@ -51,7 +51,7 @@ Hi there :)`
 					},
 					BodyStructure: &common.BodyStructure{MimeType: "text", MimeSubType: "plain"},
 					Size: uint32(len(body)),
-					InternalDate: &now,
+					InternalDate: now,
 				}, []byte(body)},
 			},
 			user: user,
diff --git a/backend/memory/mailbox.go b/backend/memory/mailbox.go
index 944b79e..c59f79a 100644
--- a/backend/memory/mailbox.go
+++ b/backend/memory/mailbox.go
@@ -119,10 +119,9 @@ func (mbox *Mailbox) SearchMessages(uid bool, criteria *common.SearchCriteria) (
 	return
 }
 
-func (mbox *Mailbox) CreateMessage(flags []string, date *time.Time, body []byte) error {
-	if date == nil {
-		now := time.Now()
-		date = &now
+func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body []byte) error {
+	if date.IsZero() {
+		date = time.Now()
 	}
 
 	mbox.messages = append(mbox.messages, &Message{&common.Message{
diff --git a/client/client.go b/client/client.go
index 0b9e227..5da8485 100644
--- a/client/client.go
+++ b/client/client.go
@@ -141,12 +141,7 @@ func (c *Client) execute(cmdr imap.Commander, res imap.RespHandlerFrom) (status
 	c.addHandler(statusHdlr)
 	defer c.removeHandler(statusHdlr)
 
-	_, err = cmd.WriteTo(c.conn.Writer)
-	if err != nil {
-		return
-	}
-
-	if err = c.conn.Flush(); err != nil {
+	if err = cmd.WriteTo(c.conn); err != nil {
 		return
 	}
 
diff --git a/client/cmd_auth.go b/client/cmd_auth.go
index fc965f3..8ceab3d 100644
--- a/client/cmd_auth.go
+++ b/client/cmd_auth.go
@@ -244,7 +244,7 @@ func (c *Client) Status(name string, items []string) (mbox *imap.MailboxStatus,
 // destination mailbox. This argument SHOULD be in the format of an RFC 2822
 // message.
 // flags and date are optional arguments and can be set to nil.
-func (c *Client) Append(mbox string, flags []string, date *time.Time, msg *imap.Literal) (err error) {
+func (c *Client) Append(mbox string, flags []string, date time.Time, msg *imap.Literal) (err error) {
 	if c.State != imap.AuthenticatedState && c.State != imap.SelectedState {
 		err = ErrNotLoggedIn
 		return
diff --git a/commands/append.go b/commands/append.go
index cee3be1..77420c1 100644
--- a/commands/append.go
+++ b/commands/append.go
@@ -13,7 +13,7 @@ import (
 type Append struct {
 	Mailbox string
 	Flags []string
-	Date *time.Time
+	Date time.Time
 	Message *imap.Literal
 }
 
@@ -31,7 +31,7 @@ func (cmd *Append) Command() *imap.Command {
 		args = append(args, flags)
 	}
 
-	if cmd.Date != nil {
+	if !cmd.Date.IsZero() {
 		args = append(args, cmd.Date)
 	}
 
@@ -81,7 +81,7 @@ func (cmd *Append) Parse(fields []interface{}) (err error) {
 			if !ok {
 				return errors.New("Date must be a string")
 			}
-			if cmd.Date, err = imap.ParseDate(date); err != nil {
+			if cmd.Date, err = imap.ParseDateTime(date); err != nil {
 				return err
 			}
 		}
diff --git a/commands/authenticate.go b/commands/authenticate.go
index f6614b9..3aebf82 100644
--- a/commands/authenticate.go
+++ b/commands/authenticate.go
@@ -36,7 +36,7 @@ func (cmd *Authenticate) Parse(fields []interface{}) error {
 	return nil
 }
 
-func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r *imap.Reader, w *imap.Writer) (err error) {
+func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r *imap.Reader, w imap.Writer) (err error) {
 	sasl, ok := mechanisms[cmd.Mechanism]
 	if !ok {
 		err = errors.New("Unsupported mechanism")
@@ -57,9 +57,6 @@ func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, r *imap.Reade
 		if err = cont.WriteTo(w); err != nil {
 			return
 		}
-		if err = w.Flush(); err != nil {
-			return
-		}
 
 		if encoded, err = r.ReadInfo(); err != nil {
 			return
diff --git a/common/command.go b/common/command.go
index 9e4cf8f..8789bb4 100644
--- a/common/command.go
+++ b/common/command.go
@@ -37,9 +37,15 @@ const (
 	Uid = "UID"
 )
 
+// A value that can be converted to a command.
+type Commander interface {
+	Command() *Command
+}
+
 // A command.
 type Command struct {
-	// The command tag. It acts as a unique identifier for this command.
+	// The command tag. It acts as a unique identifier for this command. If empty,
+	// the command is untagged.
 	Tag string
 	// The command name.
 	Name string
@@ -52,55 +58,34 @@ func (cmd *Command) Command() *Command {
 	return cmd
 }
 
-func (cmd *Command) WriteTo(w *Writer) (N int64, err error) {
-	n, err := w.writeString(cmd.Tag + string(sp) + cmd.Name)
-	N += int64(n)
-	if err != nil {
-		return
-	}
-
-	if len(cmd.Arguments) > 0 {
-		n, err = w.WriteSp()
-		N += int64(n)
-		if err != nil {
-			return
-		}
+func (cmd *Command) WriteTo(w Writer) error {
+	ww := w.writer()
 
-		n, err = w.WriteFields(cmd.Arguments)
-		N += int64(n)
-		if err != nil {
-			return
-		}
+	tag := cmd.Tag
+	if tag == "" {
+		tag = "*"
 	}
 
-	n, err = w.WriteCrlf()
-	N += int64(n)
-	return
+	fields := []interface{}{tag, cmd.Name}
+	fields = append(fields, cmd.Arguments...)
+	return ww.writeLine(fields...)
 }
 
 // Parse a command from fields.
 func (cmd *Command) Parse(fields []interface{}) error {
 	if len(fields) < 2 {
-		return errors.New("Cannot parse command")
+		return errors.New("imap: cannot parse command: no enough fields")
 	}
 
 	var ok bool
 	if cmd.Tag, ok = fields[0].(string); !ok {
-		return errors.New("Cannot parse command tag")
+		return errors.New("imap: cannot parse command: invalid tag")
 	}
 	if cmd.Name, ok = fields[1].(string); !ok {
-		return errors.New("Cannot parse command name")
+		return errors.New("imap: cannot parse command: invalid name")
 	}
-
-	// Command names are case-insensitive
-	cmd.Name = strings.ToUpper(cmd.Name)
+	cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
 
 	cmd.Arguments = fields[2:]
-
 	return nil
 }
-
-// A value that can be converted to a command.
-type Commander interface {
-	Command() *Command
-}
diff --git a/common/conn.go b/common/conn.go
index 527c8fe..27e5433 100644
--- a/common/conn.go
+++ b/common/conn.go
@@ -51,7 +51,7 @@ type ConnUpgrader func(conn net.Conn) (net.Conn, error)
 type Conn struct {
 	net.Conn
 	*Reader
-	*Writer
+	Writer
 
 	waits chan struct{}
 
@@ -69,16 +69,23 @@ func (c *Conn) init() {
 	}
 
 	c.Reader.reader = bufio.NewReader(r)
-	c.Writer.writer = bufio.NewWriter(w)
+	c.writer().Writer = bufio.NewWriter(w)
+}
+
+func (c *Conn) Write(b []byte) (n int, err error) {
+	return c.Writer.Write(b)
 }
 
 // Write any buffered data to the underlying connection.
 func (c *Conn) Flush() (err error) {
-	if err = c.Writer.Flush(); err != nil {
+	if err = c.writer().Flush(); err != nil {
 		return
 	}
 
-	if f, ok := c.Conn.(Flusher); ok {
+	f, ok := c.Conn.(interface {
+		Flush() error
+	})
+	if ok {
 		if err = f.Flush(); err != nil {
 			return
 		}
@@ -118,7 +125,7 @@ func (c *Conn) SetDebug(debug bool) {
 }
 
 // Create a new IMAP connection.
-func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
+func NewConn(conn net.Conn, r *Reader, w Writer) *Conn {
 	c := &Conn{Conn: conn, Reader: r, Writer: w}
 
 	c.init()
diff --git a/common/message.go b/common/message.go
index 8519fe6..1a3a518 100644
--- a/common/message.go
+++ b/common/message.go
@@ -18,15 +18,6 @@ const (
 	RecentFlag = "\\Recent"
 )
 
-// Parse an IMAP date.
-func ParseDate(date string) (*time.Time, error) {
-	t, err := time.Parse("2-Jan-2006 15:04:05 -0700", date)
-	if err != nil {
-		return nil, err
-	}
-	return &t, nil
-}
-
 // A message.
 type Message struct {
 	// The message sequence number. It must be greater than or equal to 1.
@@ -43,7 +34,7 @@ type Message struct {
 	// The message flags.
 	Flags []string
 	// The date the message was received by th server.
-	InternalDate *time.Time
+	InternalDate time.Time
 	// The message size.
 	Size uint32
 	// The message unique identifier. It must be greater than or equal to 1.
@@ -103,7 +94,7 @@ func (m *Message) Parse(fields []interface{}) error {
 				}
 			case "INTERNALDATE":
 				date, _ := f.(string)
-				m.InternalDate, _ = ParseDate(date)
+				m.InternalDate, _ = ParseDateTime(date)
 			case "RFC822.SIZE":
 				m.Size, _ = ParseNumber(f)
 			case "UID":
@@ -453,7 +444,7 @@ func (part *BodyPartName) String() (s string) {
 // See RFC 3501 page 77.
 type Envelope struct {
 	// The message date.
-	Date *time.Time
+	Date time.Time
 	// The message subject.
 	Subject string
 	// The From header addresses.
@@ -481,7 +472,7 @@ func (e *Envelope) Parse(fields []interface{}) error {
 	}
 
 	if date, ok := fields[0].(string); ok {
-		e.Date, _ = ParseDate(date)
+		e.Date, _ = ParseDateTime(date)
 	}
 	if subject, ok := fields[1].(string); ok {
 		e.Subject = subject
diff --git a/common/read.go b/common/read.go
index 863bc2a..d800611 100644
--- a/common/read.go
+++ b/common/read.go
@@ -5,6 +5,7 @@ import (
 	"io"
 	"strconv"
 	"strings"
+	"time"
 )
 
 const (
@@ -20,6 +21,11 @@ const (
 	respCodeEnd = ']'
 )
 
+const (
+	crlf = "\r\n"
+	nilAtom = "NIL"
+)
+
 // TODO: add CTL to atomSpecials
 var (
 	quotedSpecials = string([]rune{dquote, '\\'})
@@ -57,13 +63,13 @@ type reader interface {
 }
 
 // Convert a field to a number.
-func ParseNumber(input interface{}) (uint32, error) {
-	str, ok := input.(string)
+func ParseNumber(f interface{}) (uint32, error) {
+	s, ok := f.(string)
 	if !ok {
-		return 0, newParseError("Number is not an atom")
+		return 0, newParseError("imap: number is not a string")
 	}
 
-	nbr, err := strconv.ParseUint(str, 10, 32)
+	nbr, err := strconv.ParseUint(s, 10, 32)
 	if err != nil {
 		return 0, err
 	}
@@ -71,6 +77,26 @@ func ParseNumber(input interface{}) (uint32, error) {
 	return uint32(nbr), nil
 }
 
+func ParseDate(f interface{}) (t time.Time, err error) {
+	s, ok := f.(string)
+	if !ok {
+		err = newParseError("imap: date is not a string")
+		return
+	}
+
+ 	return time.Parse("2-Jan-2006", s)
+}
+
+func ParseDateTime(f interface{}) (t time.Time, err error) {
+	s, ok := f.(string)
+	if !ok {
+		err = newParseError("imap: date-time is not a string")
+		return
+	}
+
+ 	return time.Parse("2-Jan-2006 15:04:05 -0700", s)
+}
+
 // Convert a field list to a string list.
 func ParseStringList(fields []interface{}) ([]string, error) {
 	list := make([]string, len(fields))
diff --git a/common/response.go b/common/response.go
index 203fc48..a4f82d9 100644
--- a/common/response.go
+++ b/common/response.go
@@ -4,17 +4,22 @@ import (
 	"errors"
 )
 
+// A value that can be converted to a Resp.
+type Responser interface {
+	Response() *Resp
+}
+
 // A response.
 // See RFC 3501 section 2.2.2
 type Resp struct {
-	// The response tag. Can be either * for untagged responses, + for continuation
+	// The response tag. Can be either "" for untagged responses, "+" for continuation
 	// requests or a previous command's tag.
 	Tag string
 	// The parsed response fields.
 	Fields []interface{}
 }
 
-func (r *Resp) WriteTo(w *Writer) (err error) {
+func (r *Resp) WriteTo(w Writer) error {
 	tag := r.Tag
 	if tag == "" {
 		tag = "*"
@@ -22,13 +27,7 @@ func (r *Resp) WriteTo(w *Writer) (err error) {
 
 	fields := []interface{}{tag}
 	fields = append(fields, r.Fields...)
-
-	if _, err = w.WriteFields(fields); err != nil {
-		return
-	}
-
-	_, err = w.WriteCrlf()
-	return
+	return w.writer().writeLine(fields...)
 }
 
 // Create a new untagged response.
@@ -45,26 +44,20 @@ type ContinuationResp struct {
 	Info string
 }
 
-func (r *ContinuationResp) WriteTo(w *Writer) (err error) {
-	if _, err = w.WriteString("+"); err != nil {
-		return
+func (r *ContinuationResp) WriteTo(w Writer) error {
+	ww := w.writer()
+
+	if err := ww.writeString("+"); err != nil {
+		return err
 	}
 
 	if r.Info != "" {
-		if _, err = w.WriteSp(); err != nil {
-			return
+		if err := ww.writeString(string(sp) + r.Info); err != nil {
+			return err
 		}
-
-		_, err = w.WriteInfo(r.Info)
-	} else {
-		_, err = w.WriteCrlf()
 	}
-	return
-}
 
-// A value that can be converted to a Resp.
-type Responser interface {
-	Response() *Resp
+	return ww.writeCrlf()
 }
 
 // Read a single response from a Reader. Returns either a continuation request,
diff --git a/common/search.go b/common/search.go
index 265c700..de9e709 100644
--- a/common/search.go
+++ b/common/search.go
@@ -6,19 +6,6 @@ import (
 	"time"
 )
 
-const searchDateLayout = "2-Jan-2006"
-
-// Parse a date in the search format.
-func ParseSearchDate(s string) (*time.Time, error) {
-	t, err := time.Parse(searchDateLayout, s)
-	return &t, err
-}
-
-// Format a date to the search format.
-func FormatSearchDate(t *time.Time) string {
-	return t.Format(searchDateLayout)
-}
-
 // TODO: support AND with same fields (e.g. BCC mickey BCC mouse)
 
 // A search criteria.
@@ -27,7 +14,7 @@ type SearchCriteria struct {
 	SeqSet *SeqSet
 	Answered bool
 	Bcc string
-	Before *time.Time
+	Before time.Time
 	Body string
 	Cc string
 	Deleted bool
@@ -40,14 +27,14 @@ type SearchCriteria struct {
 	New bool
 	Not *SearchCriteria
 	Old bool
-	On *time.Time
+	On time.Time
 	Or [2]*SearchCriteria
 	Recent bool
 	Seen bool
-	SentBefore *time.Time
-	SentOn *time.Time
-	SentSince *time.Time
-	Since *time.Time
+	SentBefore time.Time
+	SentOn time.Time
+	SentSince time.Time
+	Since time.Time
 	Smaller uint32
 	Subject string
 	Text string
@@ -81,9 +68,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Bcc, _ = fields[i].(string)
 		case "BEFORE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.Before, _ = ParseSearchDate(date)
-			}
+			c.Before, _ = ParseDate(fields[i])
 		case "BODY":
 			i++
 			c.Body, _ = fields[i].(string)
@@ -126,9 +111,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Old = true
 		case "ON":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.On, _ = ParseSearchDate(date)
-			}
+			c.On, _ = ParseDate(fields[i])
 		case "OR":
 			i++
 			leftFields, _ := fields[i].([]interface{})
@@ -149,24 +132,16 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Seen = true
 		case "SENTBEFORE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentBefore, _ = ParseSearchDate(date)
-			}
+			c.SentBefore, _ = ParseDate(fields[i])
 		case "SENTON":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentOn, _ = ParseSearchDate(date)
-			}
+			c.SentOn, _ = ParseDate(fields[i])
 		case "SENTSINCE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.SentSince, _ = ParseSearchDate(date)
-			}
+			c.SentSince, _ = ParseDate(fields[i])
 		case "SINCE":
 			i++
-			if date, ok := fields[i].(string); ok {
-				c.Since, _ = ParseSearchDate(date)
-			}
+			c.Since, _ = ParseDate(fields[i])
 		case "SMALLER":
 			i++
 			c.Smaller, _ = ParseNumber(fields[i])
@@ -220,8 +195,8 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Bcc != "" {
 		fields = append(fields, "BCC", c.Bcc)
 	}
-	if c.Before != nil {
-		fields = append(fields, "BEFORE", FormatSearchDate(c.Before))
+	if !c.Before.IsZero() {
+		fields = append(fields, "BEFORE", FormatDate(c.Before))
 	}
 	if c.Body != "" {
 		fields = append(fields, "BODY", c.Body)
@@ -259,8 +234,8 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Old {
 		fields = append(fields, "OLD")
 	}
-	if c.On != nil {
-		fields = append(fields, "ON", FormatSearchDate(c.On))
+	if !c.On.IsZero() {
+		fields = append(fields, "ON", FormatDate(c.On))
 	}
 	if c.Or[0] != nil && c.Or[1] != nil {
 		fields = append(fields, "OR", c.Or[0].Format(), c.Or[1].Format())
@@ -271,17 +246,17 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 	if c.Seen {
 		fields = append(fields, "SEEN")
 	}
-	if c.SentBefore != nil {
-		fields = append(fields, "SENTBEFORE", FormatSearchDate(c.SentBefore))
+	if !c.SentBefore.IsZero() {
+		fields = append(fields, "SENTBEFORE", FormatDate(c.SentBefore))
 	}
-	if c.SentOn != nil {
-		fields = append(fields, "SENTON", FormatSearchDate(c.SentOn))
+	if !c.SentOn.IsZero() {
+		fields = append(fields, "SENTON", FormatDate(c.SentOn))
 	}
-	if c.SentSince != nil {
-		fields = append(fields, "SENTSINCE", FormatSearchDate(c.SentSince))
+	if !c.SentSince.IsZero() {
+		fields = append(fields, "SENTSINCE", FormatDate(c.SentSince))
 	}
-	if c.Since != nil {
-		fields = append(fields, "SINCE", FormatSearchDate(c.Since))
+	if !c.Since.IsZero() {
+		fields = append(fields, "SINCE", FormatDate(c.Since))
 	}
 	if c.Smaller != 0 {
 		fields = append(fields, "SMALLER", c.Smaller)
diff --git a/common/status.go b/common/status.go
index 340c0af..6e72d67 100644
--- a/common/status.go
+++ b/common/status.go
@@ -79,7 +79,7 @@ type StatusResp struct {
 func (r *StatusResp) Err() error {
 	if r == nil {
 		// No status response, connection closed before we get one
-		return errors.New("Connection closed during command execution")
+		return errors.New("imap: connection closed during command execution")
 	}
 
 	if r.Type == StatusNo || r.Type == StatusBad {
@@ -88,30 +88,35 @@ func (r *StatusResp) Err() error {
 	return nil
 }
 
-func (r *StatusResp) WriteTo(w *Writer) (err error) {
+func (r *StatusResp) WriteTo(w Writer) error {
+	ww := w.writer()
+
 	tag := r.Tag
 	if tag == "" {
 		tag = "*"
 	}
 
-	if _, err = w.WriteFields([]interface{}{tag, string(r.Type)}); err != nil {
-		return
+	if err := ww.writeFields([]interface{}{tag, string(r.Type)}); err != nil {
+		return err
 	}
 
-	if _, err = w.WriteSp(); err != nil {
-		return
+	if err := ww.writeString(string(sp)); err != nil {
+		return err
 	}
 
 	if r.Code != "" {
-		if _, err = w.WriteRespCode(r.Code, r.Arguments); err != nil {
-			return
+		if err := ww.writeRespCode(r.Code, r.Arguments); err != nil {
+			return err
 		}
 
-		if _, err = w.WriteSp(); err != nil {
-			return
+		if err := ww.writeString(string(sp)); err != nil {
+			return err
 		}
 	}
 
-	_, err = w.WriteInfo(r.Info)
-	return
+	if err := ww.writeString(r.Info); err != nil {
+		return err
+	}
+
+	return ww.writeCrlf()
 }
diff --git a/common/write.go b/common/write.go
index ac47f54..e3d8b91 100644
--- a/common/write.go
+++ b/common/write.go
@@ -1,7 +1,7 @@
 package common
 
 import (
-	"errors"
+	"fmt"
 	"io"
 	"strconv"
 	"strings"
@@ -12,25 +12,29 @@ import (
 // A string that will be quoted.
 type Quoted string
 
-// A string writer.
-type StringWriter interface {
-	// WriteString writes a string. It returns the number of bytes written. If the
-	// count is less than len(s), it also returns an error explaining why the
-	// write is short.
-	// See https://golang.org/pkg/bufio/#writeString
-	WriteString(s string) (int, error)
-}
+// An IMAP writer.
+type Writer interface {
+	io.Writer
 
-// An interface implemented by net.Conn that allows to flush buffered data to
-// the remote.
-type Flusher interface {
-	// Write any buffered data to the underlying connection.
 	Flush() error
+
+	writer() *writer
+}
+
+type WriterTo interface {
+	WriteTo(w Writer) error
+}
+
+func formatNumber(num uint32) string {
+	return strconv.FormatUint(uint64(num), 10)
 }
 
-// Format an IMAP date.
-func FormatDate(date *time.Time) string {
-	return date.Format("2-Jan-2006 15:04:05 -0700")
+func FormatDate(t time.Time) string {
+	return t.Format("2-Jan-2006")
+}
+
+func FormatDateTime(t time.Time) string {
+	return t.Format("2-Jan-2006 15:04:05 -0700")
 }
 
 // Convert a string list to a field list.
@@ -52,234 +56,183 @@ func isAscii(s string) bool {
 	return true
 }
 
-type writer interface {
+type writer struct {
 	io.Writer
-}
-
-// An IMAP writer.
-type Writer struct {
-	writer
 
 	continues <-chan bool
 }
 
-func (w *Writer) writeString(s string) (int, error) {
-	return io.WriteString(w.writer, s)
+func (w *writer) writer() *writer {
+	return w
 }
 
-// Write a separator.
-func (w *Writer) WriteSp() (int, error) {
-	return w.writeString(string(sp))
+// Helper function to write a string to w.
+func (w *writer) writeString(s string) error {
+	_, err := io.WriteString(w.Writer, s)
+	return err
 }
 
-// Write a CRLF.
-func (w *Writer) WriteCrlf() (int, error) {
-	return w.writeString(string(cr) + string(lf))
-}
+func (w *writer) writeCrlf() error {
+	if err := w.writeString(crlf); err != nil {
+		return err
+	}
 
-// Write NIL.
-func (w *Writer) WriteNil() (int, error) {
-	return w.writeString("NIL")
+	return w.Flush()
 }
 
-func (w *Writer) WriteNumber(num uint32) (int, error) {
-	return w.writeString(strconv.Itoa(int(num)))
+func (w *writer) writeNumber(num uint32) error {
+	return w.writeString(formatNumber(num))
 }
 
-func (w *Writer) writeAtomString(s string) (int, error) {
-	return w.writeString(s)
+func (w *writer) writeQuoted(s string) error {
+	return w.writeString(strconv.Quote(s))
 }
 
-func (w *Writer) writeQuotedString(s string) (int, error) {
-	return w.writeString(strconv.Quote(s))
+func (w *writer) writeAtom(s string) error {
+	return w.writeString(s)
 }
 
-func (w *Writer) WriteString(s string) (int, error) {
+func (w *writer) writeAstring(s string) error {
 	if !isAscii(s) {
 		// IMAP doesn't allow 8-bit data outside literals
-		return w.WriteLiteral(NewLiteral([]byte(s)))
+		return w.writeLiteral(NewLiteral([]byte(s)))
 	}
 
 	specials := string([]rune{dquote, listStart, listEnd, literalStart, sp})
-	if strings.ToUpper(s) == "NIL" || s == "" || strings.ContainsAny(s, specials) {
-		return w.writeQuotedString(s)
+	if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, specials) {
+		return w.writeQuoted(s)
 	}
 
-	return w.writeAtomString(s)
+	return w.writeAtom(s)
 }
 
-func (w *Writer) WriteDate(date *time.Time) (int, error) {
-	if date == nil {
-		return w.WriteNil()
+func (w *writer) writeDateTime(t time.Time) error {
+	if t.IsZero() {
+		return w.writeAtom(nilAtom)
 	}
-	return w.writeQuotedString(FormatDate(date))
+	return w.writeQuoted(FormatDateTime(t))
 }
 
-func (w *Writer) WriteFields(fields []interface{}) (N int, err error) {
-	var n int
-
+func (w *writer) writeFields(fields []interface{}) error {
 	for i, field := range fields {
-		// Write separator
-		if i > 0 {
-			if n, err = w.WriteSp(); err != nil {
-				return
-			}
-			N += n
-		}
-
-		if field == nil {
-			n, err = w.WriteNil()
-		} else {
-			switch f := field.(type) {
-			case string:
-				n, err = w.WriteString(f)
-			case Quoted:
-				n, err = w.writeQuotedString(string(f))
-			case int:
-				n, err = w.WriteNumber(uint32(f))
-			case uint32:
-				n, err = w.WriteNumber(f)
-			case *Literal:
-				n, err = w.WriteLiteral(f)
-			case []interface{}:
-				n, err = w.WriteList(f)
-			case *time.Time:
-				n, err = w.WriteDate(f)
-			case *SeqSet:
-				n, err = w.writeString(f.String())
-			case *BodySectionName:
-				n, err = w.writeString(f.String())
-			default:
-				err = errors.New("Cannot format argument #" + strconv.Itoa(i))
+		if i > 0 { // Write separator
+			if err := w.writeString(string(sp)); err != nil {
+				return err
 			}
 		}
 
-		N += n
-		if err != nil {
-			return
+		if err := w.writeField(field); err != nil {
+			return err
 		}
 	}
 
-	return
+	return nil
 }
 
-func (w *Writer) WriteList(fields []interface{}) (N int, err error) {
-	n, err := w.writeString(string(listStart))
-	N += n
-	if err != nil {
-		return
-	}
-
-	n, err = w.WriteFields(fields)
-	N += n
-	if err != nil {
-		return
+func (w *writer) writeList(fields []interface{}) error {
+	if err := w.writeString(string(listStart)); err != nil {
+		return err
 	}
 
-	n, err = w.writeString(string(listEnd))
-	N += n
-	return
-}
-
-func (w *Writer) writeLiteralField(literal *Literal) (N int, err error) {
-	field := string(literalStart) + strconv.Itoa(literal.Len()) + string(literalEnd)
-	n, err := w.writeString(field)
-	N += n
-	if err != nil {
-		return
+	if err := w.writeFields(fields); err != nil {
+		return err
 	}
 
-	n, err = w.WriteCrlf()
-	N += n
-	return
+	return w.writeString(string(listEnd))
 }
 
-func (w *Writer) WriteLiteral(literal *Literal) (N int, err error) {
-	if literal == nil {
-		return w.WriteNil()
-	}
-
-	n, err := w.writeLiteralField(literal)
-	N += n
-	if err != nil {
-		return
+func (w *writer) writeLiteral(l *Literal) error {
+	if l == nil {
+		return w.writeString(nilAtom)
 	}
 
-	// Make sure all buffered data is flushed because we'll maybe wait for a
-	// continuation request
-	if err = w.Flush(); err != nil {
-		return
+	header := string(literalStart) + strconv.Itoa(l.Len()) + string(literalEnd) + crlf
+	if err := w.writeString(header); err != nil {
+		return err
 	}
 
 	// If a channel is available, wait for a continuation request before sending data
 	if w.continues != nil {
 		// Make sure to flush the writer, otherwise we may never receive a continuation request
-		if err = w.Flush(); err != nil {
-			return
+		if err := w.Flush(); err != nil {
+			return err
 		}
 
 		if !<-w.continues {
-			err = errors.New("Cannot send literal: no continuation request received")
-			return
+			return fmt.Errorf("imap: cannot send literal: no continuation request received")
 		}
 	}
 
-	n, err = w.Write(literal.Bytes())
-	N += n
-	return
+	_, err := w.Write(l.Bytes())
+	return err
 }
 
-func (w *Writer) WriteRespCode(code string, args []interface{}) (N int, err error) {
-	n, err := w.writeString(string(respCodeStart))
-	if err != nil {
-		return
+func (w *writer) writeField(field interface{}) error {
+	if field == nil {
+		return w.writeAtom(nilAtom)
 	}
-	N += n
 
-	fields := []interface{}{code}
-	fields = append(fields, args...)
-
-	if n, err = w.WriteFields(fields); err != nil {
-		return
+	switch field := field.(type) {
+	case string:
+		return w.writeAstring(field)
+	case Quoted:
+		return w.writeQuoted(string(field))
+	case int:
+		return w.writeNumber(uint32(field))
+	case uint32:
+		return w.writeNumber(field)
+	case *Literal:
+		return w.writeLiteral(field)
+	case []interface{}:
+		return w.writeList(field)
+	case time.Time:
+		return w.writeDateTime(field)
+	case *SeqSet:
+		return w.writeString(field.String())
+	case *BodySectionName:
+		return w.writeString(field.String())
 	}
-	N += n
 
-	n, err = w.writeString(string(respCodeEnd))
-	N += n
-	return
+	return fmt.Errorf("imap: cannot format field: %v", field)
 }
 
-func (w *Writer) WriteInfo(info string) (N int, err error) {
-	n, err := w.writeString(info)
-	if err != nil {
-		return
+func (w *writer) writeRespCode(code string, args []interface{}) error {
+	if err := w.writeString(string(respCodeStart)); err != nil {
+		return err
 	}
-	N += n
 
-	n, err = w.WriteCrlf()
-	if err != nil {
-		return
+	fields := []interface{}{code}
+	fields = append(fields, args...)
+
+	if err := w.writeFields(fields); err != nil {
+		return err
 	}
-	N += n
 
-	return
+	return w.writeString(string(respCodeEnd))
 }
 
-func (w *Writer) Flush() (err error) {
-	if f, ok := w.writer.(Flusher); ok {
-		err = f.Flush()
+func (w *writer) writeLine(fields ...interface{}) error {
+	if err := w.writeFields(fields); err != nil {
+		return err
 	}
-	return
+
+	return w.writeCrlf()
 }
 
-func NewWriter(w writer) *Writer {
-	return &Writer{writer: w}
+func (w *writer) Flush() error {
+	f, ok := w.Writer.(interface {
+		Flush() error
+	})
+	if ok {
+		return f.Flush()
+	}
+	return nil
 }
 
-func NewClientWriter(w writer, continues <-chan bool) *Writer {
-	return &Writer{writer: w, continues: continues}
+func NewWriter(w io.Writer) Writer {
+	return &writer{Writer: w}
 }
 
-type WriterTo interface {
-	WriteTo(w *Writer) error
+func NewClientWriter(w io.Writer, continues <-chan bool) Writer {
+	return &writer{Writer: w, continues: continues}
 }
diff --git a/responses/authenticate.go b/responses/authenticate.go
index 5316451..005dfe2 100644
--- a/responses/authenticate.go
+++ b/responses/authenticate.go
@@ -11,7 +11,7 @@ import (
 type Authenticate struct {
 	Mechanism sasl.Client
 	InitialResponse []byte
-	Writer *imap.Writer
+	Writer imap.Writer
 }
 
 func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
@@ -20,8 +20,7 @@ func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 	// Cancel auth if an error occurs
 	defer (func () {
 		if err != nil {
-			w.WriteString("*")
-			w.WriteCrlf()
+			w.Write([]byte("*\r\n"))
 		}
 	})()
 
@@ -36,10 +35,7 @@ func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 		// Empty challenge, send initial response as stated in RFC 2222 section 5.1
 		if cont.Info == "" && r.InitialResponse != nil {
 			encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
-			if _, err = w.WriteString(encoded); err != nil {
-				return
-			}
-			if _, err = w.WriteCrlf(); err != nil {
+			if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
 				return
 			}
 			if err = w.Flush(); err != nil {
@@ -63,10 +59,7 @@ func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) {
 		}
 
 		encoded := base64.StdEncoding.EncodeToString(res)
-		if _, err = w.WriteString(encoded); err != nil {
-			return
-		}
-		if _, err = w.WriteCrlf(); err != nil {
+		if _, err = w.Write([]byte(encoded + "\r\n")); err != nil {
 			return
 		}
 		if err = w.Flush(); err != nil {
diff --git a/responses/capability.go b/responses/capability.go
index af9481a..2fc8f29 100644
--- a/responses/capability.go
+++ b/responses/capability.go
@@ -10,16 +10,6 @@ type Capability struct {
 	Caps []string
 }
 
-func (r *Capability) WriteTo(w *imap.Writer) error {
-	fields := []interface{}{imap.Capability}
-	for _, cap := range r.Caps {
-		fields = append(fields, cap)
-	}
-
-	res := &imap.Resp{Fields: fields}
-	return res.WriteTo(w)
-}
-
 func (r *Capability) HandleFrom(hdlr imap.RespHandler) (err error) {
 	for h := range hdlr {
 		caps, ok := h.AcceptNamedResp(imap.Capability)
@@ -37,3 +27,13 @@ func (r *Capability) HandleFrom(hdlr imap.RespHandler) (err error) {
 
 	return
 }
+
+func (r *Capability) WriteTo(w imap.Writer) error {
+	fields := []interface{}{imap.Capability}
+	for _, cap := range r.Caps {
+		fields = append(fields, cap)
+	}
+
+	res := &imap.Resp{Fields: fields}
+	return res.WriteTo(w)
+}
diff --git a/responses/expunge.go b/responses/expunge.go
index e247b75..4a33284 100644
--- a/responses/expunge.go
+++ b/responses/expunge.go
@@ -30,7 +30,7 @@ func (r *Expunge) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Expunge) WriteTo(w *imap.Writer) error {
+func (r *Expunge) WriteTo(w imap.Writer) error {
 	for seqNum := range r.SeqNums {
 		res := imap.NewUntaggedResp([]interface{}{seqNum, imap.Expunge})
 
diff --git a/responses/fetch.go b/responses/fetch.go
index e0ab700..4cd2a23 100644
--- a/responses/fetch.go
+++ b/responses/fetch.go
@@ -40,7 +40,7 @@ func (r *Fetch) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Fetch) WriteTo(w *imap.Writer) error {
+func (r *Fetch) WriteTo(w imap.Writer) error {
 	for msg := range r.Messages {
 		res := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.Fetch, msg.Format()})
 
diff --git a/responses/list.go b/responses/list.go
index 76bd667..5b05ed0 100644
--- a/responses/list.go
+++ b/responses/list.go
@@ -40,7 +40,7 @@ func (r *List) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *List) WriteTo(w *imap.Writer) (err error) {
+func (r *List) WriteTo(w imap.Writer) (err error) {
 	name := r.Name()
 
 	for mbox := range r.Mailboxes {
diff --git a/responses/search.go b/responses/search.go
index 98340b6..a6737f7 100644
--- a/responses/search.go
+++ b/responses/search.go
@@ -26,7 +26,7 @@ func (r *Search) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Search) WriteTo(w *imap.Writer) (err error) {
+func (r *Search) WriteTo(w imap.Writer) (err error) {
 	fields := []interface{}{imap.Search}
 	for _, id := range r.Ids {
 		fields = append(fields, id)
diff --git a/responses/select.go b/responses/select.go
index 74b11af..07a7639 100644
--- a/responses/select.go
+++ b/responses/select.go
@@ -58,7 +58,7 @@ func (r *Select) HandleFrom(hdlr imap.RespHandler) (err error) {
 	return
 }
 
-func (r *Select) WriteTo(w *imap.Writer) (err error) {
+func (r *Select) WriteTo(w imap.Writer) (err error) {
 	status := r.Mailbox
 
 	for _, item := range r.Mailbox.Items {
diff --git a/responses/status.go b/responses/status.go
index 7253c5d..f1e2302 100644
--- a/responses/status.go
+++ b/responses/status.go
@@ -71,7 +71,7 @@ func (r *Status) HandleFrom(hdlr imap.RespHandler) error {
 	return nil
 }
 
-func (r *Status) WriteTo(w *imap.Writer) error {
+func (r *Status) WriteTo(w imap.Writer) error {
 	mbox := r.Mailbox
 
 	var fields []interface{}
diff --git a/server/cmd_any.go b/server/cmd_any.go
index 8091e41..2538dff 100644
--- a/server/cmd_any.go
+++ b/server/cmd_any.go
@@ -11,8 +11,8 @@ type Capability struct {
 	commands.Capability
 }
 
-func (cmd *Capability) Handle(conn Conn) error {
-	res := &responses.Capability{Caps: conn.Capabilities()}
+func (cmd *Capability) Handle(conn *Conn) error {
+	res := &responses.Capability{Caps: conn.getCaps()}
 	return conn.WriteResp(res)
 }
 
@@ -20,11 +20,10 @@ type Noop struct {
 	commands.Noop
 }
 
-func (cmd *Noop) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox != nil {
+func (cmd *Noop) Handle(conn *Conn) error {
+	if conn.Mailbox != nil {
 		// If a mailbox is selected, NOOP can be used to poll for server updates
-		if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok {
+		if mbox, ok := conn.Mailbox.(backend.UpdaterMailbox); ok {
 			return mbox.Poll()
 		}
 	}
@@ -36,7 +35,7 @@ type Logout struct {
 	commands.Logout
 }
 
-func (cmd *Logout) Handle(conn Conn) error {
+func (cmd *Logout) Handle(conn *Conn) error {
 	res := &common.StatusResp{
 		Type: common.StatusBye,
 		Info: "Closing connection",
@@ -47,6 +46,6 @@ func (cmd *Logout) Handle(conn Conn) error {
 	}
 
 	// Request to close the connection
-	conn.Context().State = common.LogoutState
+	conn.State = common.LogoutState
 	return nil
 }
diff --git a/server/cmd_auth.go b/server/cmd_auth.go
index 0a04e4d..3313d8f 100644
--- a/server/cmd_auth.go
+++ b/server/cmd_auth.go
@@ -18,13 +18,12 @@ type Select struct {
 	commands.Select
 }
 
-func (cmd *Select) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Select) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
+	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -40,8 +39,8 @@ func (cmd *Select) Handle(conn Conn) error {
 		return err
 	}
 
-	ctx.Mailbox = mbox
-	ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
+	conn.Mailbox = mbox
+	conn.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
 
 	res := &responses.Select{Mailbox: status}
 	if err := conn.WriteResp(res); err != nil {
@@ -49,7 +48,7 @@ func (cmd *Select) Handle(conn Conn) error {
 	}
 
 	code := common.CodeReadWrite
-	if ctx.MailboxReadOnly {
+	if conn.MailboxReadOnly {
 		code = common.CodeReadOnly
 	}
 	return ErrStatusResp(&common.StatusResp{
@@ -62,52 +61,48 @@ type Create struct {
 	commands.Create
 }
 
-func (cmd *Create) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Create) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return ctx.User.CreateMailbox(cmd.Mailbox)
+	return conn.User.CreateMailbox(cmd.Mailbox)
 }
 
 type Delete struct {
 	commands.Delete
 }
 
-func (cmd *Delete) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Delete) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return ctx.User.DeleteMailbox(cmd.Mailbox)
+	return conn.User.DeleteMailbox(cmd.Mailbox)
 }
 
 type Rename struct {
 	commands.Rename
 }
 
-func (cmd *Rename) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Rename) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	return ctx.User.RenameMailbox(cmd.Existing, cmd.New)
+	return conn.User.RenameMailbox(cmd.Existing, cmd.New)
 }
 
 type Subscribe struct {
 	commands.Subscribe
 }
 
-func (cmd *Subscribe) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Subscribe) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
+	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -119,13 +114,12 @@ type Unsubscribe struct {
 	commands.Unsubscribe
 }
 
-func (cmd *Unsubscribe) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Unsubscribe) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
+	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -137,9 +131,8 @@ type List struct {
 	commands.List
 }
 
-func (cmd *List) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *List) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
@@ -153,7 +146,7 @@ func (cmd *List) Handle(conn Conn) error {
 		close(done)
 	})()
 
-	mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed)
+	mailboxes, err := conn.User.ListMailboxes(cmd.Subscribed)
 	if err != nil {
 		close(ch)
 		return err
@@ -194,13 +187,12 @@ type Status struct {
 	commands.Status
 }
 
-func (cmd *Status) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Status) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
+	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		return err
 	}
@@ -218,13 +210,12 @@ type Append struct {
 	commands.Append
 }
 
-func (cmd *Append) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.User == nil {
+func (cmd *Append) Handle(conn *Conn) error {
+	if conn.User == nil {
 		return ErrNotAuthenticated
 	}
 
-	mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
+	mbox, err := conn.User.GetMailbox(cmd.Mailbox)
 	if err != nil {
 		// TODO: add [TRYCREATE] to the NO response
 		return err
@@ -236,7 +227,7 @@ func (cmd *Append) Handle(conn Conn) error {
 
 	// If APPEND targets the currently selected mailbox, send an untagged EXISTS
 	// Do this only if the backend doesn't send updates itself
-	if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
+	if conn.Server.Updates == nil && conn.Mailbox != nil && conn.Mailbox.Name() == mbox.Name() {
 		status, err := mbox.Status([]string{common.MailboxMessages})
 		if err != nil {
 			return err
diff --git a/server/cmd_noauth.go b/server/cmd_noauth.go
index 7102b54..07cff97 100644
--- a/server/cmd_noauth.go
+++ b/server/cmd_noauth.go
@@ -21,15 +21,14 @@ type StartTLS struct {
 	commands.StartTLS
 }
 
-func (cmd *StartTLS) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.State != common.NotAuthenticatedState {
+func (cmd *StartTLS) Handle(conn *Conn) error {
+	if conn.State != common.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
 	if conn.IsTLS() {
 		return errors.New("TLS is already enabled")
 	}
-	if conn.Server().TLSConfig == nil {
+	if conn.Server.TLSConfig == nil {
 		return errors.New("TLS support not enabled")
 	}
 
@@ -41,62 +40,51 @@ func (cmd *StartTLS) Handle(conn Conn) error {
 	})
 }
 
-func (cmd *StartTLS) Upgrade(conn Conn) error {
-	tlsConfig := conn.Server().TLSConfig
+func (cmd *StartTLS) Upgrade(conn *Conn) error {
+	tlsConfig := conn.Server.TLSConfig
 
-	var tlsConn *tls.Conn
 	err := conn.Upgrade(func (conn net.Conn) (net.Conn, error) {
-		tlsConn = tls.Server(conn, tlsConfig)
-		err := tlsConn.Handshake()
-		return tlsConn, err
+		upgraded := tls.Server(conn, tlsConfig)
+		err := upgraded.Handshake()
+		return upgraded, err
 	})
 	if err != nil {
 		return err
 	}
 
-	conn.conn().tlsConn = tlsConn
+	conn.isTLS = true
 
-	res := &responses.Capability{Caps: conn.Capabilities()}
+	res := &responses.Capability{Caps: conn.getCaps()}
 	return conn.WriteResp(res)
 }
 
-func afterAuthStatus(conn Conn) error {
+func afterAuthStatus(conn *Conn) error {
 	return ErrStatusResp(&common.StatusResp{
 		Type: common.StatusOk,
 		Code: common.CodeCapability,
-		Arguments: common.FormatStringList(conn.Capabilities()),
+		Arguments: common.FormatStringList(conn.getCaps()),
 	})
 }
 
-func canAuth(conn Conn) bool {
-	for _, cap := range conn.Capabilities() {
-		if cap == "AUTH=PLAIN" {
-			return true
-		}
-	}
-	return false
-}
-
 type Login struct {
 	commands.Login
 }
 
-func (cmd *Login) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.State != common.NotAuthenticatedState {
+func (cmd *Login) Handle(conn *Conn) error {
+	if conn.State != common.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
-	if !canAuth(conn) {
+	if !conn.CanAuth() {
 		return ErrAuthDisabled
 	}
 
-	user, err := conn.Server().Backend.Login(cmd.Username, cmd.Password)
+	user, err := conn.Server.Backend.Login(cmd.Username, cmd.Password)
 	if err != nil {
 		return err
 	}
 
-	ctx.State = common.AuthenticatedState
-	ctx.User = user
+	conn.State = common.AuthenticatedState
+	conn.User = user
 	return afterAuthStatus(conn)
 }
 
@@ -104,22 +92,20 @@ type Authenticate struct {
 	commands.Authenticate
 }
 
-func (cmd *Authenticate) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.State != common.NotAuthenticatedState {
+func (cmd *Authenticate) Handle(conn *Conn) error {
+	if conn.State != common.NotAuthenticatedState {
 		return ErrAlreadyAuthenticated
 	}
-	if !canAuth(conn) {
+	if !conn.CanAuth() {
 		return ErrAuthDisabled
 	}
 
 	mechanisms := map[string]sasl.Server{}
-	for name, newSasl := range conn.Server().auths {
+	for name, newSasl := range conn.Server.auths {
 		mechanisms[name] = newSasl(conn)
 	}
 
-	// TODO: do not use Reader and Writer here
-	err := cmd.Authenticate.Handle(mechanisms, conn.conn().Reader, conn.conn().Writer)
+	err := cmd.Authenticate.Handle(mechanisms, conn.Reader, conn.Writer)
 	if err != nil {
 		return err
 	}
diff --git a/server/cmd_selected.go b/server/cmd_selected.go
index 16fbe43..453534a 100644
--- a/server/cmd_selected.go
+++ b/server/cmd_selected.go
@@ -20,38 +20,36 @@ type UidHandler interface {
 	Handler
 
 	// Handle this command using UIDs for a given connection.
-	UidHandle(conn Conn) error
+	UidHandle(conn *Conn) error
 }
 
 type Check struct {
 	commands.Check
 }
 
-func (cmd *Check) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Check) Handle(conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if ctx.MailboxReadOnly {
+	if conn.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
-	return ctx.Mailbox.Check()
+	return conn.Mailbox.Check()
 }
 
 type Close struct {
 	commands.Close
 }
 
-func (cmd *Close) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Close) Handle(conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	mailbox := ctx.Mailbox
-	ctx.Mailbox = nil
-	ctx.MailboxReadOnly = false
+	mailbox := conn.Mailbox
+	conn.Mailbox = nil
+	conn.MailboxReadOnly = false
 
 	if err := mailbox.Expunge(); err != nil {
 		return err
@@ -65,32 +63,31 @@ type Expunge struct {
 	commands.Expunge
 }
 
-func (cmd *Expunge) Handle(conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Expunge) Handle(conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if ctx.MailboxReadOnly {
+	if conn.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
 	// Get a list of messages that will be deleted
 	// That will allow us to send expunge updates if the backend doesn't support it
 	var seqnums []uint32
-	if conn.Server().Updates == nil {
+	if conn.Server.Updates == nil {
 		var err error
-		seqnums, err = ctx.Mailbox.SearchMessages(false, &common.SearchCriteria{Deleted: true})
+		seqnums, err = conn.Mailbox.SearchMessages(false, &common.SearchCriteria{Deleted: true})
 		if err != nil {
 			return err
 		}
 	}
 
-	if err := ctx.Mailbox.Expunge(); err != nil {
+	if err := conn.Mailbox.Expunge(); err != nil {
 		return err
 	}
 
 	// If the backend doesn't support expunge updates, let's do it ourselves
-	if conn.Server().Updates == nil {
+	if conn.Server.Updates == nil {
 		done := make(chan error)
 		defer close(done)
 
@@ -120,13 +117,12 @@ type Search struct {
 	commands.Search
 }
 
-func (cmd *Search) handle(uid bool, conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Search) handle(uid bool, conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	ids, err := ctx.Mailbox.SearchMessages(uid, cmd.Criteria)
+	ids, err := conn.Mailbox.SearchMessages(uid, cmd.Criteria)
 	if err != nil {
 		return err
 	}
@@ -135,11 +131,11 @@ func (cmd *Search) handle(uid bool, conn Conn) error {
 	return conn.WriteResp(res)
 }
 
-func (cmd *Search) Handle(conn Conn) error {
+func (cmd *Search) Handle(conn *Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Search) UidHandle(conn Conn) error {
+func (cmd *Search) UidHandle(conn *Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -147,22 +143,22 @@ type Fetch struct {
 	commands.Fetch
 }
 
-func (cmd *Fetch) handle(uid bool, conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Fetch) handle(uid bool, conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
 	ch := make(chan *common.Message)
 	res := &responses.Fetch{Messages: ch}
 
-	done := make(chan error, 1)
+	// TODO: if ListMessages() fails, make sure the goroutine is stopped
+	done := make(chan error)
 	go (func () {
 		done <- conn.WriteResp(res)
 		close(done)
 	})()
 
-	err := ctx.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
+	err := conn.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
 	if err != nil {
 		return err
 	}
@@ -170,11 +166,11 @@ func (cmd *Fetch) handle(uid bool, conn Conn) error {
 	return <-done
 }
 
-func (cmd *Fetch) Handle(conn Conn) error {
+func (cmd *Fetch) Handle(conn *Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Fetch) UidHandle(conn Conn) error {
+func (cmd *Fetch) UidHandle(conn *Conn) error {
 	// Append UID to the list of requested items if it isn't already present
 	hasUid := false
 	for _, item := range cmd.Items {
@@ -194,12 +190,11 @@ type Store struct {
 	commands.Store
 }
 
-func (cmd *Store) handle(uid bool, conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Store) handle(uid bool, conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
-	if ctx.MailboxReadOnly {
+	if conn.MailboxReadOnly {
 		return ErrMailboxReadOnly
 	}
 
@@ -225,17 +220,16 @@ func (cmd *Store) handle(uid bool, conn Conn) error {
 
 	// If the backend supports message updates, this will prevent this connection
 	// from receiving them
-	// TODO: find a better way to do this, without conn.silent
-	conn.conn().silent = silent
-	err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
-	conn.conn().silent = false
+	conn.silent = silent
+	err = conn.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
+	conn.silent = false
 	if err != nil {
 		return err
 	}
 
 	// Not silent: send FETCH updates if the backend doesn't support message
 	// updates
-	if conn.Server().Updates == nil && !silent {
+	if conn.Server.Updates == nil && !silent {
 		inner := &Fetch{}
 		inner.SeqSet = cmd.SeqSet
 		inner.Items = []string{"FLAGS"}
@@ -251,11 +245,11 @@ func (cmd *Store) handle(uid bool, conn Conn) error {
 	return nil
 }
 
-func (cmd *Store) Handle(conn Conn) error {
+func (cmd *Store) Handle(conn *Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Store) UidHandle(conn Conn) error {
+func (cmd *Store) UidHandle(conn *Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -263,20 +257,19 @@ type Copy struct {
 	commands.Copy
 }
 
-func (cmd *Copy) handle(uid bool, conn Conn) error {
-	ctx := conn.Context()
-	if ctx.Mailbox == nil {
+func (cmd *Copy) handle(uid bool, conn *Conn) error {
+	if conn.Mailbox == nil {
 		return ErrNoMailboxSelected
 	}
 
-	return ctx.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
+	return conn.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
 }
 
-func (cmd *Copy) Handle(conn Conn) error {
+func (cmd *Copy) Handle(conn *Conn) error {
 	return cmd.handle(false, conn)
 }
 
-func (cmd *Copy) UidHandle(conn Conn) error {
+func (cmd *Copy) UidHandle(conn *Conn) error {
 	return cmd.handle(true, conn)
 }
 
@@ -284,9 +277,9 @@ type Uid struct {
 	commands.Uid
 }
 
-func (cmd *Uid) Handle(conn Conn) error {
+func (cmd *Uid) Handle(conn *Conn) error {
 	inner := cmd.Cmd.Command()
-	hdlr, err := conn.Server().commandHandler(inner)
+	hdlr, err := conn.Server.getCommandHandler(inner)
 	if err != nil {
 		return err
 	}
diff --git a/server/conn.go b/server/conn.go
index 7a3d936..15026b2 100644
--- a/server/conn.go
+++ b/server/conn.go
@@ -10,29 +10,16 @@ import (
 	"github.com/emersion/go-imap/backend"
 )
 
-// A connection.
-type Conn interface {
-	// Get this connection's server.
-	Server() *Server
-	// Get this connection's context.
-	Context() *Context
-	// Get a list of capabilities enabled for this connection.
-	Capabilities() []string
-	// Write a response to this connection.
-	WriteResp(res common.WriterTo) error
-	// Check if TLS is enabled on this connection.
-	IsTLS() bool
-	// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
-	// tunnel.
-	Upgrade(upgrader common.ConnUpgrader) error
-	// Close this connection.
-	Close() error
-
-	conn() *conn
-}
+type Conn struct {
+	*common.Conn
 
-// A connection's context.
-type Context struct {
+	isTLS bool
+	continues chan bool
+	silent bool
+	locker sync.Locker
+
+	// This connection's server.
+	Server *Server
 	// This connection's current state.
 	State common.ConnState
 	// If the client is logged in, the user.
@@ -43,55 +30,8 @@ type Context struct {
 	MailboxReadOnly bool
 }
 
-type conn struct {
-	*common.Conn
-
-	s *Server
-	ctx *Context
-	tlsConn *tls.Conn
-	continues chan bool
-	silent bool
-	locker sync.Locker
-}
-
-func newConn(s *Server, c net.Conn) *conn {
-	continues := make(chan bool)
-	r := common.NewServerReader(nil, continues)
-	w := common.NewWriter(nil)
-
-	tlsConn, _ := c.(*tls.Conn)
-
-	conn := &conn{
-		Conn: common.NewConn(c, r, w),
-
-		s: s,
-		ctx: &Context{
-			State: common.NotAuthenticatedState,
-		},
-		tlsConn: tlsConn,
-		continues: continues,
-		locker: &sync.Mutex{},
-	}
-
-	go conn.sendContinuationReqs()
-
-	return conn
-}
-
-func (c *conn) conn() *conn {
-	return c
-}
-
-func (c *conn) Server() *Server {
-	return c.s
-}
-
-func (c *conn) Context() *Context {
-	return c.ctx
-}
-
 // Write a response to this connection.
-func (c *conn) WriteResp(res common.WriterTo) error {
+func (c *Conn) WriteResp(res common.WriterTo) error {
 	c.locker.Lock()
 	defer c.locker.Unlock()
 
@@ -103,9 +43,9 @@ func (c *conn) WriteResp(res common.WriterTo) error {
 }
 
 // Close this connection.
-func (c *conn) Close() error {
-	if c.ctx.User != nil {
-		c.ctx.User.Logout()
+func (c *Conn) Close() error {
+	if c.User != nil {
+		c.User.Logout()
 	}
 
 	if err := c.Conn.Close(); err != nil {
@@ -114,29 +54,30 @@ func (c *conn) Close() error {
 
 	close(c.continues)
 
-	c.ctx.State = common.LogoutState
+	c.State = common.LogoutState
 	return nil
 }
 
-func (c *conn) Capabilities() (caps []string) {
-	caps = c.s.Capabilities(c.ctx.State)
+func (c *Conn) getCaps() (caps []string) {
+	caps = []string{"IMAP4rev1"}
 
-	if c.ctx.State == common.NotAuthenticatedState {
-		if !c.IsTLS() && c.s.TLSConfig != nil {
+	if c.State == common.NotAuthenticatedState {
+		if !c.IsTLS() && c.Server.TLSConfig != nil {
 			caps = append(caps, "STARTTLS")
 		}
 
-		if !c.canAuth() {
+		if !c.CanAuth() {
 			caps = append(caps, "LOGINDISABLED")
 		} else {
 			caps = append(caps, "AUTH=PLAIN")
 		}
 	}
 
+	caps = append(caps, c.Server.getCaps(c.State)...)
 	return
 }
 
-func (c *conn) sendContinuationReqs() {
+func (c *Conn) sendContinuationReqs() {
 	for range c.continues {
 		cont := &common.ContinuationResp{Info: "send literal"}
 		if err := c.WriteResp(cont); err != nil {
@@ -145,8 +86,8 @@ func (c *conn) sendContinuationReqs() {
 	}
 }
 
-func (c *conn) greet() error {
-	caps := c.Capabilities()
+func (c *Conn) greet() error {
+	caps := c.getCaps()
 	args := make([]interface{}, len(caps))
 	for i, cap := range caps {
 		args[i] = cap
@@ -163,11 +104,34 @@ func (c *conn) greet() error {
 }
 
 // Check if this connection is encrypted.
-func (c *conn) IsTLS() bool {
-	return c.tlsConn != nil
+func (c *Conn) IsTLS() bool {
+	return c.isTLS
 }
 
 // Check if the client can use plain text authentication.
-func (c *conn) canAuth() bool {
-	return c.IsTLS() || c.s.AllowInsecureAuth
+func (c *Conn) CanAuth() bool {
+	return c.IsTLS() || c.Server.AllowInsecureAuth
+}
+
+func newConn(s *Server, c net.Conn) *Conn {
+	continues := make(chan bool)
+	r := common.NewServerReader(nil, continues)
+	w := common.NewWriter(nil)
+
+	_, isTLS := c.(*tls.Conn)
+
+	conn := &Conn{
+		Conn: common.NewConn(c, r, w),
+
+		isTLS: isTLS,
+		continues: continues,
+		locker: &sync.Mutex{},
+
+		Server: s,
+		State: common.NotAuthenticatedState,
+	}
+
+	go conn.sendContinuationReqs()
+
+	return conn
 }
diff --git a/server/server.go b/server/server.go
index 40e271f..86ee07f 100644
--- a/server/server.go
+++ b/server/server.go
@@ -23,7 +23,7 @@ type Handler interface {
 	//
 	// By default, after this function has returned a status response is sent. To
 	// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
-	Handle(conn Conn) error
+	Handle(conn *Conn) error
 }
 
 // A connection upgrader. If a Handler is also an Upgrader, the connection will
@@ -33,32 +33,14 @@ type Handler interface {
 // COMPRESS).
 type Upgrader interface {
 	// Upgrade the connection. This method should call conn.Upgrade().
-	Upgrade(conn Conn) error
+	Upgrade(conn *Conn) error
 }
 
 // A function that creates handlers.
 type HandlerFactory func() Handler
 
 // A function that creates SASL servers.
-type SaslServerFactory func(conn Conn) sasl.Server
-
-// An IMAP extension.
-type Extension interface {
-	// Get capabilities provided by this extension for a given connection state.
-	Capabilities(state common.ConnState) []string
-	// Get the command handler factory for the provided command name.
-	Command(name string) HandlerFactory
-}
-
-// An extension that provides additional features to each connection.
-type ConnExtension interface {
-	Extension
-
-	// This function will be called when a client connects to the server. It can
-	// be used to add new features to the default Conn interface by implementing
-	// new methods.
-	NewConn(c Conn) Conn
-}
+type SaslServerFactory func(conn *Conn) sasl.Server
 
 type errStatusResp struct {
 	resp *common.StatusResp
@@ -85,11 +67,11 @@ func ErrNoStatusResp() error {
 // An IMAP server.
 type Server struct {
 	listener net.Listener
-	conns []Conn
+	conns []*Conn
 
+	caps map[string]common.ConnState
 	commands map[string]HandlerFactory
 	auths map[string]SaslServerFactory
-	extensions []Extension
 
 	// TCP address to listen on.
 	Addr string
@@ -108,11 +90,12 @@ type Server struct {
 // Create a new IMAP server from an existing listener.
 func New(bkd backend.Backend) *Server {
 	s := &Server{
+		caps: map[string]common.ConnState{},
 		Backend: bkd,
 	}
 
 	s.auths = map[string]SaslServerFactory{
-		"PLAIN": func(conn Conn) sasl.Server {
+		"PLAIN": func(conn *Conn) sasl.Server {
 			return sasl.NewPlainServer(func(identity, username, password string) error {
 				if identity != "" && identity != username {
 					return errors.New("Identities not supported")
@@ -123,9 +106,8 @@ func New(bkd backend.Backend) *Server {
 					return err
 				}
 
-				ctx := conn.Context()
-				ctx.State = common.AuthenticatedState
-				ctx.User = user
+				conn.State = common.AuthenticatedState
+				conn.User = user
 				return nil
 			})
 		},
@@ -186,15 +168,9 @@ func (s *Server) Serve(l net.Listener) error {
 			return err
 		}
 
-		var conn Conn = newConn(s, c)
+		conn := newConn(s, c)
 		if s.Debug {
-			conn.conn().SetDebug(true)
-		}
-
-		for _, ext := range s.extensions {
-			if ext, ok := ext.(ConnExtension); ok {
-				conn = ext.NewConn(conn)
-			}
+			conn.SetDebug(true)
 		}
 
 		go s.handleConn(conn)
@@ -237,7 +213,7 @@ func (s *Server) ListenAndServeTLS() error {
 	return s.Serve(l)
 }
 
-func (s *Server) handleConn(conn Conn) error {
+func (s *Server) handleConn(conn *Conn) error {
 	s.conns = append(s.conns, conn)
 	defer (func() {
 		conn.Close()
@@ -251,21 +227,21 @@ func (s *Server) handleConn(conn Conn) error {
 	})()
 
 	// Send greeting
-	if err := conn.conn().greet(); err != nil {
+	if err := conn.greet(); err != nil {
 		return err
 	}
 
 	for {
-		if conn.Context().State == common.LogoutState {
+		if conn.State == common.LogoutState {
 			return nil
 		}
 
 		var res *common.StatusResp
 		var up Upgrader
 
-		conn.conn().Wait()
-		fields, err := conn.conn().ReadLine()
-		if err == io.EOF || conn.Context().State == common.LogoutState {
+		conn.Wait()
+		fields, err := conn.ReadLine()
+		if err == io.EOF || conn.State == common.LogoutState {
 			return nil
 		}
 		if err != nil {
@@ -315,21 +291,9 @@ func (s *Server) handleConn(conn Conn) error {
 	}
 }
 
-// Get a command handler factory for the provided command name.
-func (s *Server) Command(name string) HandlerFactory {
-	// Extensions can override builtin commands
-	for _, ext := range s.extensions {
-		if h := ext.Command(name); h != nil {
-			return h
-		}
-	}
-
-	return s.commands[name]
-}
-
-func (s *Server) commandHandler(cmd *common.Command) (hdlr Handler, err error) {
-	newHandler := s.Command(cmd.Name)
-	if newHandler == nil {
+func (s *Server) getCommandHandler(cmd *common.Command) (hdlr Handler, err error) {
+	newHandler, ok := s.commands[cmd.Name]
+	if !ok {
 		err = errors.New("Unknown command")
 		return
 	}
@@ -339,8 +303,8 @@ func (s *Server) commandHandler(cmd *common.Command) (hdlr Handler, err error) {
 	return
 }
 
-func (s *Server) handleCommand(cmd *common.Command, conn Conn) (res *common.StatusResp, up Upgrader, err error) {
-	hdlr, err := s.commandHandler(cmd)
+func (s *Server) handleCommand(cmd *common.Command, conn *Conn) (res *common.StatusResp, up Upgrader, err error) {
+	hdlr, err := s.getCommandHandler(cmd)
 	if err != nil {
 		return
 	}
@@ -416,27 +380,25 @@ func (s *Server) listenUpdates() (err error) {
 		}
 
 		for _, conn := range s.conns {
-			ctx := conn.Context()
-
-			if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) {
+			if update.Username != "" && (conn.User == nil || conn.User.Username() != update.Username) {
 				continue
 			}
-			if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) {
+			if update.Mailbox != "" && (conn.Mailbox == nil || conn.Mailbox.Name() != update.Mailbox) {
 				continue
 			}
-			if conn.conn().silent {
+			if conn.silent {
 				// If silent is set, do not send message updates
 				if _, ok := res.(*responses.Fetch); ok {
 					continue
 				}
 			}
 
-			conn.conn().locker.Lock()
-			if _, err := conn.conn().Writer.Write(b.Bytes()); err != nil {
+			conn.locker.Lock()
+			if _, err := conn.Writer.Write(b.Bytes()); err != nil {
 				log.Println("WARN: error sending unilateral update:", err)
 			}
-			conn.conn().Flush()
-			conn.conn().locker.Unlock()
+			conn.Flush()
+			conn.locker.Unlock()
 		}
 
 		if update.Done != nil {
@@ -445,12 +407,11 @@ func (s *Server) listenUpdates() (err error) {
 	}
 }
 
-// Get this server's capabilities for the provided connection state.
-func (s *Server) Capabilities(currentState common.ConnState) (caps []string) {
-	caps = []string{"IMAP4rev1"}
-
-	for _, ext := range s.extensions {
-		caps = append(caps, ext.Capabilities(currentState)...)
+func (s *Server) getCaps(currentState common.ConnState) (caps []string) {
+	for name, state := range s.caps {
+		if currentState & state != 0 {
+			caps = append(caps, name)
+		}
 	}
 	return
 }
@@ -468,18 +429,26 @@ func (s *Server) Close() error {
 	return nil
 }
 
-// Enable an IMAP extension on this server.
+// Register a new capability that will be advertised by this server.
 //
 // This function should not be called directly, it must only be used by
 // libraries implementing extensions of the IMAP protocol.
-func (s *Server) Enable(extension Extension) {
-	s.extensions = append(s.extensions, extension)
+func (s *Server) RegisterCapability(name string, state common.ConnState) {
+	s.caps[name] = state
 }
 
-// Enable an authentication mechanism on this server.
+// Register a new authentication mechanism for this server.
 //
 // This function should not be called directly, it must only be used by
 // libraries implementing extensions of the IMAP protocol.
-func (s *Server) EnableAuth(name string, f SaslServerFactory) {
+func (s *Server) RegisterAuth(name string, f SaslServerFactory) {
 	s.auths[name] = f
 }
+
+// Register a new command for this server.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the IMAP protocol.
+func (s *Server) RegisterCommand(name string, f HandlerFactory) {
+	s.commands[name] = f
+}


Test patch:
diff --git a/client/cmd_auth_test.go b/client/cmd_auth_test.go
index b4d8459..a6d09c4 100644
--- a/client/cmd_auth_test.go
+++ b/client/cmd_auth_test.go
@@ -343,7 +343,7 @@ func TestClient_Append(t *testing.T) {
 
 		date := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
 		literal := common.NewLiteral([]byte(msg))
-		err = c.Append("INBOX", []string{"\\Seen", "\\Draft"}, &date, literal)
+		err = c.Append("INBOX", []string{"\\Seen", "\\Draft"}, date, literal)
 		return
 	}
 
diff --git a/client/cmd_selected_test.go b/client/cmd_selected_test.go
index cf6bf14..4cb85be 100644
--- a/client/cmd_selected_test.go
+++ b/client/cmd_selected_test.go
@@ -109,7 +109,7 @@ func TestClient_Search(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
 		c.State = common.SelectedState
 
-		date, _ := common.ParseSearchDate("1-Feb-1994")
+		date, _ := common.ParseDate("1-Feb-1994")
 		criteria := &common.SearchCriteria{
 			Deleted: true,
 			From: "Smith",
diff --git a/common/command_test.go b/common/command_test.go
index 4b36716..ed03ca8 100644
--- a/common/command_test.go
+++ b/common/command_test.go
@@ -27,7 +27,7 @@ func TestCommand_WriteTo_NoArgs(t *testing.T) {
 		Name: "NOOP",
 	}
 
-	if _, err := cmd.WriteTo(w); err != nil {
+	if err := cmd.WriteTo(w); err != nil {
 		t.Fatal(err)
 	}
 	if b.String() != "A001 NOOP\r\n" {
@@ -45,7 +45,7 @@ func TestCommand_WriteTo_WithArgs(t *testing.T) {
 		Arguments: []interface{}{"username", "password"},
 	}
 
-	if _, err := cmd.WriteTo(w); err != nil {
+	if err := cmd.WriteTo(w); err != nil {
 		t.Fatal(err)
 	}
 	if b.String() != "A002 LOGIN username password\r\n" {
diff --git a/common/message_test.go b/common/message_test.go
index a02fcb3..c515f0a 100644
--- a/common/message_test.go
+++ b/common/message_test.go
@@ -1,4 +1,4 @@
-package common_test
+package common
 
 import (
 	"bytes"
@@ -6,15 +6,13 @@ import (
 	"reflect"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
 func formatFields(fields []interface{}) (string, error) {
 	b := &bytes.Buffer{}
-	w := common.NewWriter(b)
+	w := NewWriter(b).writer()
 
-	if _, err := w.WriteList(fields); err != nil {
+	if err := w.writeList(fields); err != nil {
 		return "", fmt.Errorf("Cannot format %v: %v", fields, err)
 	}
 
@@ -32,7 +30,7 @@ func TestParseDate(t *testing.T) {
 		},
 	}
 	for _, test := range tests {
-		date, err := common.ParseDate(test.dateStr)
+		date, err := ParseDateTime(test.dateStr)
 		if err != nil {
 			t.Errorf("Failed parsing %q: %v", test.dateStr, err)
 			continue
@@ -44,23 +42,23 @@ func TestParseDate(t *testing.T) {
 }
 
 var messageTests = []struct{
-	message *common.Message
+	message *Message
 	fields []interface{}
 }{
 	{
-		message: &common.Message{
+		message: &Message{
 			Items: []string{"ENVELOPE", "BODYSTRUCTURE", "FLAGS", "RFC822.SIZE", "UID"},
-			Body: map[*common.BodySectionName]*common.Literal{},
+			Body: map[*BodySectionName]*Literal{},
 			Envelope: envelopeTests[0].envelope,
 			BodyStructure: bodyStructureTests[0].bodyStructure,
-			Flags: []string{common.SeenFlag, common.AnsweredFlag},
+			Flags: []string{SeenFlag, AnsweredFlag},
 			Size: 4242,
 			Uid: 2424,
 		},
 		fields: []interface{}{
 			"ENVELOPE", envelopeTests[0].fields,
 			"BODYSTRUCTURE", bodyStructureTests[0].fields,
-			"FLAGS", []interface{}{common.SeenFlag, common.AnsweredFlag},
+			"FLAGS", []interface{}{SeenFlag, AnsweredFlag},
 			"RFC822.SIZE", "4242",
 			"UID", "2424",
 		},
@@ -69,7 +67,7 @@ var messageTests = []struct{
 
 func TestMessage_Parse(t *testing.T) {
 	for i, test := range messageTests {
-		m := common.NewMessage()
+		m := NewMessage()
 		if err := m.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse message for #%v:", i, err)
 		} else if !reflect.DeepEqual(m, test.message) {
@@ -98,69 +96,69 @@ func TestMessage_Format(t *testing.T) {
 
 var bodySectionNameTests = []struct{
 	raw string
-	parsed *common.BodySectionName
+	parsed *BodySectionName
 	formatted string
 }{
 	{
 		raw: "BODY[]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}},
 	},
 	{
 		raw: "RFC822",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}},
 		formatted: "BODY[]",
 	},
 	{
 		raw: "BODY[HEADER]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier}},
 	},
 	{
 		raw: "BODY.PEEK[]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}, Peek: true},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}, Peek: true},
 	},
 	{
 		raw: "BODY[TEXT]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.TextSpecifier}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: TextSpecifier}},
 	},
 	{
 		raw: "RFC822.TEXT",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.TextSpecifier}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: TextSpecifier}},
 		formatted: "BODY[TEXT]",
 	},
 	{
 		raw: "RFC822.HEADER",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier}, Peek: true},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier}, Peek: true},
 		formatted: "BODY.PEEK[HEADER]",
 	},
 	{
 		raw: "BODY[]<0.512>",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{}, Partial: []int{0, 512}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{}, Partial: []int{0, 512}},
 	},
 	{
 		raw: "BODY[1.2.3]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Path: []int{1, 2, 3}}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Path: []int{1, 2, 3}}},
 	},
 	{
 		raw: "BODY[1.2.3.HEADER]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Path: []int{1, 2, 3}}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Path: []int{1, 2, 3}}},
 	},
 	{
 		raw: "BODY[5.MIME]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.MimeSpecifier, Path: []int{5}}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: MimeSpecifier, Path: []int{5}}},
 	},
 	{
 		raw: "BODY[HEADER.FIELDS (From To)]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Fields: []string{"From", "To"}}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"From", "To"}}},
 	},
 	{
 		raw: "BODY[HEADER.FIELDS.NOT (Content-Id)]",
-		parsed: &common.BodySectionName{BodyPartName: &common.BodyPartName{Specifier: common.HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
+		parsed: &BodySectionName{BodyPartName: &BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
 	},
 }
 
 func TestNewBodySectionName(t *testing.T) {
 	for i, test := range bodySectionNameTests {
-		bsn, err := common.NewBodySectionName(test.raw)
+		bsn, err := NewBodySectionName(test.raw)
 		if err != nil {
 			t.Errorf("Cannot parse #%v: %v", i, err)
 			continue
@@ -225,7 +223,7 @@ func TestBodySectionName_ExtractPartial(t *testing.T) {
 	}
 
 	for i, test := range tests {
-		bsn, err := common.NewBodySectionName(test.bsn)
+		bsn, err := NewBodySectionName(test.bsn)
 		if err != nil {
 			t.Errorf("Cannot parse body section name #%v: %v", i, err)
 			continue
@@ -241,19 +239,19 @@ func TestBodySectionName_ExtractPartial(t *testing.T) {
 var t = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("", -6*60*60))
 
 var envelopeTests = []struct{
-	envelope *common.Envelope
+	envelope *Envelope
 	fields []interface{}
 }{
 	{
-		envelope: &common.Envelope{
-			Date: &t,
+		envelope: &Envelope{
+			Date: t,
 			Subject: "Hello World!",
-			From: []*common.Address{addrTests[0].addr},
-			Sender: []*common.Address{},
-			ReplyTo: []*common.Address{},
-			To: []*common.Address{},
-			Cc: []*common.Address{},
-			Bcc: []*common.Address{},
+			From: []*Address{addrTests[0].addr},
+			Sender: []*Address{},
+			ReplyTo: []*Address{},
+			To: []*Address{},
+			Cc: []*Address{},
+			Bcc: []*Address{},
 			InReplyTo: "42@example.org",
 			MessageId: "43@example.org",
 		},
@@ -274,7 +272,7 @@ var envelopeTests = []struct{
 
 func TestEnvelope_Parse(t *testing.T) {
 	for i, test := range envelopeTests {
-		e := &common.Envelope{}
+		e := &Envelope{}
 
 		if err := e.Parse(test.fields); err != nil {
 			t.Error("Error parsing envelope:", err)
@@ -304,11 +302,11 @@ func TestEnvelope_Format(t *testing.T) {
 
 var addrTests = []struct{
 	fields []interface{}
-	addr *common.Address
+	addr *Address
 }{
 	{
 		fields: []interface{}{"The NSA", nil, "root", "nsa.gov"},
-		addr: &common.Address{
+		addr: &Address{
 			PersonalName: "The NSA",
 			MailboxName: "root",
 			HostName: "nsa.gov",
@@ -318,7 +316,7 @@ var addrTests = []struct{
 
 func TestAddress_Parse(t *testing.T) {
 	for i, test := range addrTests {
-		addr := &common.Address{}
+		addr := &Address{}
 
 		if err := addr.Parse(test.fields); err != nil {
 			t.Error("Error parsing address:", err)
@@ -339,18 +337,18 @@ func TestAddress_Format(t *testing.T) {
 
 func TestAddressList(t *testing.T) {
 	fields := make([]interface{}, len(addrTests))
-	addrs := make([]*common.Address, len(addrTests))
+	addrs := make([]*Address, len(addrTests))
 	for i, test := range addrTests {
 		fields[i] = test.fields
 		addrs[i] = test.addr
 	}
 
-	gotAddrs := common.ParseAddressList(fields)
+	gotAddrs := ParseAddressList(fields)
 	if !reflect.DeepEqual(gotAddrs, addrs) {
 		t.Error("Invalid address list: got", gotAddrs, "but expected", addrs)
 	}
 
-	gotFields := common.FormatAddressList(addrs)
+	gotFields := FormatAddressList(addrs)
 	if !reflect.DeepEqual(gotFields, fields) {
 		t.Error("Invalid address list fields: got", gotFields, "but expected", fields)
 	}
@@ -372,7 +370,7 @@ var paramsListTest = []struct{
 
 func TestParseParamList(t *testing.T) {
 	for i, test := range paramsListTest {
-		if params, err := common.ParseParamList(test.fields); err != nil {
+		if params, err := ParseParamList(test.fields); err != nil {
 			t.Errorf("Cannot parse params fields for #%v: %v", i, err)
 		} else if !reflect.DeepEqual(params, test.params) {
 			t.Errorf("Invalid params for #%v: got %v but expected %v", i, params, test.params)
@@ -382,19 +380,19 @@ func TestParseParamList(t *testing.T) {
 	// Malformed params lists
 
 	fields := []interface{}{"cc", []interface{}{"dille"}}
-	if params, err := common.ParseParamList(fields); err == nil {
+	if params, err := ParseParamList(fields); err == nil {
 		t.Error("Parsed invalid params list:", params)
 	}
 
 	fields = []interface{}{"cc"}
-	if params, err := common.ParseParamList(fields); err == nil {
+	if params, err := ParseParamList(fields); err == nil {
 		t.Error("Parsed invalid params list:", params)
 	}
 }
 
 func TestFormatParamList(t *testing.T) {
 	for i, test := range paramsListTest {
-		fields := common.FormatParamList(test.params)
+		fields := FormatParamList(test.params)
 
 		if !reflect.DeepEqual(fields, test.fields) {
 			t.Errorf("Invalid params fields for #%v: got %v but expected %v", i, fields, test.fields)
@@ -404,11 +402,11 @@ func TestFormatParamList(t *testing.T) {
 
 var bodyStructureTests = []struct{
 	fields []interface{}
-	bodyStructure *common.BodyStructure
+	bodyStructure *BodyStructure
 }{
 	{
 		fields: []interface{}{"image", "jpeg", []interface{}{}, "<foo4%25foo1@bar.net>", "A picture of cat", "base64", "4242"},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "image",
 			MimeSubType: "jpeg",
 			Params: map[string]string{},
@@ -420,7 +418,7 @@ var bodyStructureTests = []struct{
 	},
 	{
 		fields: []interface{}{"text", "plain", []interface{}{"charset", "utf-8"}, nil, nil, "us-ascii", "42", "2"},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "text",
 			MimeSubType: "plain",
 			Params: map[string]string{"charset": "utf-8"},
@@ -432,26 +430,26 @@ var bodyStructureTests = []struct{
 	{
 		fields: []interface{}{
 			"message", "rfc822", []interface{}{}, nil, nil, "us-ascii", "42",
-			(&common.Envelope{}).Format(),
-			(&common.BodyStructure{}).Format(),
+			(&Envelope{}).Format(),
+			(&BodyStructure{}).Format(),
 			"67",
 		},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "message",
 			MimeSubType: "rfc822",
 			Params: map[string]string{},
 			Encoding: "us-ascii",
 			Size: 42,
 			Lines: 67,
-			Envelope: &common.Envelope{
-				From: []*common.Address{},
-				Sender: []*common.Address{},
-				ReplyTo: []*common.Address{},
-				To: []*common.Address{},
-				Cc: []*common.Address{},
-				Bcc: []*common.Address{},
+			Envelope: &Envelope{
+				From: []*Address{},
+				Sender: []*Address{},
+				ReplyTo: []*Address{},
+				To: []*Address{},
+				Cc: []*Address{},
+				Bcc: []*Address{},
 			},
-			BodyStructure: &common.BodyStructure{
+			BodyStructure: &BodyStructure{
 				Params: map[string]string{},
 			},
 		},
@@ -459,7 +457,7 @@ var bodyStructureTests = []struct{
 	{
 		fields: []interface{}{"application", "pdf", []interface{}{}, nil, nil, "base64", "4242",
 			"e0323a9039add2978bf5b49550572c7c", "attachment", []interface{}{"en-US"}, []interface{}{}},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "application",
 			MimeSubType: "pdf",
 			Params: map[string]string{},
@@ -478,12 +476,12 @@ var bodyStructureTests = []struct{
 			[]interface{}{"text", "html", []interface{}{}, nil, nil, "us-ascii", "106", "36"},
 			"alternative",
 		},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "multipart",
 			MimeSubType: "alternative",
 			Params: map[string]string{},
-			Parts: []*common.BodyStructure{
-				&common.BodyStructure{
+			Parts: []*BodyStructure{
+				&BodyStructure{
 					MimeType: "text",
 					MimeSubType: "plain",
 					Params: map[string]string{},
@@ -491,7 +489,7 @@ var bodyStructureTests = []struct{
 					Size: 87,
 					Lines: 22,
 				},
-				&common.BodyStructure{
+				&BodyStructure{
 					MimeType: "text",
 					MimeSubType: "html",
 					Params: map[string]string{},
@@ -507,12 +505,12 @@ var bodyStructureTests = []struct{
 			[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", "87", "22"},
 			"alternative", []interface{}{"hello", "world"}, "inline", []interface{}{"en-US"}, []interface{}{},
 		},
-		bodyStructure: &common.BodyStructure{
+		bodyStructure: &BodyStructure{
 			MimeType: "multipart",
 			MimeSubType: "alternative",
 			Params: map[string]string{"hello": "world"},
-			Parts: []*common.BodyStructure{
-				&common.BodyStructure{
+			Parts: []*BodyStructure{
+				&BodyStructure{
 					MimeType: "text",
 					MimeSubType: "plain",
 					Params: map[string]string{},
@@ -531,7 +529,7 @@ var bodyStructureTests = []struct{
 
 func TestBodyStructure_Parse(t *testing.T) {
 	for i, test := range bodyStructureTests {
-		bs := &common.BodyStructure{}
+		bs := &BodyStructure{}
 
 		if err := bs.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse #%v: %v", i, err)
diff --git a/common/search_test.go b/common/search_test.go
index a60d6de..94b85c4 100644
--- a/common/search_test.go
+++ b/common/search_test.go
@@ -1,23 +1,21 @@
-package common_test
+package common
 
 import (
 	"reflect"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
 // Note to myself: writing these broing tests actually fixed 2 bugs :P
 
-var searchSeqSet1, _ = common.NewSeqSet("1:42")
-var searchSeqSet2, _ = common.NewSeqSet("743:938")
+var searchSeqSet1, _ = NewSeqSet("1:42")
+var searchSeqSet2, _ = NewSeqSet("743:938")
 var searchDate1 = time.Date(1997, 11, 21, 0, 0, 0, 0, time.UTC)
 var searchDate2 = time.Date(1984, 11, 5, 0, 0, 0, 0, time.UTC)
 
 var searchCriteriaTests = []struct{
 	fields []interface{}
-	criteria *common.SearchCriteria
+	criteria *SearchCriteria
 }{
 	{
 		fields: []interface{}{
@@ -51,11 +49,11 @@ var searchCriteriaTests = []struct{
 			"UNKEYWORD", "microsoft",
 			"UNSEEN",
 		},
-		criteria: &common.SearchCriteria{
+		criteria: &SearchCriteria{
 			SeqSet: searchSeqSet1,
 			Answered: true,
 			Bcc: "root@nsa.gov",
-			Before: &searchDate1,
+			Before: searchDate1,
 			Body: "hey there",
 			Cc: "root@gchq.gov.uk",
 			Deleted: true,
@@ -66,13 +64,13 @@ var searchCriteriaTests = []struct{
 			Keyword: "cc",
 			Larger: 4242,
 			New: true,
-			Not: &common.SearchCriteria{Old: true, On: &searchDate2},
-			Or: [2]*common.SearchCriteria{
-				&common.SearchCriteria{Recent: true, SentOn: &searchDate1},
-				&common.SearchCriteria{Seen: true, SentBefore: &searchDate2},
+			Not: &SearchCriteria{Old: true, On: searchDate2},
+			Or: [2]*SearchCriteria{
+				&SearchCriteria{Recent: true, SentOn: searchDate1},
+				&SearchCriteria{Seen: true, SentBefore: searchDate2},
 			},
-			SentSince: &searchDate1,
-			Since: &searchDate2,
+			SentSince: searchDate1,
+			Since: searchDate2,
 			Smaller: 643,
 			Subject: "saucisse royale",
 			Text: "DILLE",
@@ -103,7 +101,7 @@ func TestSearchCriteria_Format(t *testing.T) {
 
 func TestSearchCriteria_Parse(t *testing.T) {
 	for i, test := range searchCriteriaTests {
-		criteria := &common.SearchCriteria{}
+		criteria := &SearchCriteria{}
 
 		if err := criteria.Parse(test.fields); err != nil {
 			t.Errorf("Cannot parse search criteria for #%v: %v", i, err)
diff --git a/common/write_test.go b/common/write_test.go
index 9e695fd..5c0034e 100644
--- a/common/write_test.go
+++ b/common/write_test.go
@@ -1,34 +1,21 @@
-package common_test
+package common
 
 import (
 	"bytes"
 	"testing"
 	"time"
-
-	"github.com/emersion/go-imap/common"
 )
 
-func newWriter() (w *common.Writer, b *bytes.Buffer) {
+func newWriter() (w *writer, b *bytes.Buffer) {
 	b = &bytes.Buffer{}
-	w = common.NewWriter(b)
+	w = NewWriter(b).writer()
 	return
 }
 
-func TestWriter_WriteSp(t *testing.T) {
-	w, b := newWriter()
-
-	if _, err := w.WriteSp(); err != nil {
-		t.Error(err)
-	}
-	if b.String() != " " {
-		t.Error("Not a separator")
-	}
-}
-
 func TestWriter_WriteCrlf(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteCrlf(); err != nil {
+	if err := w.writeCrlf(); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\r\n" {
@@ -36,10 +23,10 @@ func TestWriter_WriteCrlf(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteNil(t *testing.T) {
+func TestWriter_WriteField_Nil(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteNil(); err != nil {
+	if err := w.writeField(nil); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "NIL" {
@@ -47,10 +34,10 @@ func TestWriter_WriteNil(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteNumber(t *testing.T) {
+func TestWriter_WriteField_Number(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteNumber(42); err != nil {
+	if err := w.writeField(uint32(42)); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "42" {
@@ -58,10 +45,10 @@ func TestWriter_WriteNumber(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Atom(t *testing.T) {
+func TestWriter_WriteField_Atom(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("BODY[]"); err != nil {
+	if err := w.writeField("BODY[]"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "BODY[]" {
@@ -72,7 +59,7 @@ func TestWriter_WriteString_Atom(t *testing.T) {
 func TestWriter_WriteString_Quoted(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("I love potatoes!"); err != nil {
+	if err := w.writeField("I love potatoes!"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"I love potatoes!\"" {
@@ -83,7 +70,7 @@ func TestWriter_WriteString_Quoted(t *testing.T) {
 func TestWriter_WriteString_Quoted_WithSpecials(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("I love \"1984\"!"); err != nil {
+	if err := w.writeField("I love \"1984\"!"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"I love \\\"1984\\\"!\"" {
@@ -91,10 +78,21 @@ func TestWriter_WriteString_Quoted_WithSpecials(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_8bit(t *testing.T) {
+func TestWriter_WriteField_ForcedQuoted(t *testing.T) {
+	w, b := newWriter()
+
+	if err := w.writeField(Quoted("dille")); err != nil {
+		t.Error(err)
+	}
+	if b.String() != "\"dille\"" {
+		t.Error("Not the expected atom:", b.String())
+	}
+}
+
+func TestWriter_WriteField_8bitString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("☺"); err != nil {
+	if err := w.writeField("☺"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "{3}\r\n☺" {
@@ -102,10 +100,10 @@ func TestWriter_WriteString_8bit(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Nil(t *testing.T) {
+func TestWriter_WriteField_NilString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString("NIL"); err != nil {
+	if err := w.writeField("NIL"); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"NIL\"" {
@@ -113,10 +111,10 @@ func TestWriter_WriteString_Nil(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteString_Empty(t *testing.T) {
+func TestWriter_WriteField_EmptyString(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteString(""); err != nil {
+	if err := w.writeField(""); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"\"" {
@@ -124,11 +122,11 @@ func TestWriter_WriteString_Empty(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteDate(t *testing.T) {
+func TestWriter_WriteField_DateTime(t *testing.T) {
 	w, b := newWriter()
 
-	date := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
-	if _, err := w.WriteDate(&date); err != nil {
+	dt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	if err := w.writeField(dt); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "\"10-Nov-2009 23:00:00 +0000\"" {
@@ -136,10 +134,11 @@ func TestWriter_WriteDate(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteDate_Nil(t *testing.T) {
+func TestWriter_WriteField_ZeroDateTime(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteDate(nil); err != nil {
+	dt := time.Time{}
+	if err := w.writeField(dt); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "NIL" {
@@ -150,7 +149,7 @@ func TestWriter_WriteDate_Nil(t *testing.T) {
 func TestWriter_WriteFields(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteFields([]interface{}{"hey", 42}); err != nil {
+	if err := w.writeFields([]interface{}{"hey", 42}); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "hey 42" {
@@ -158,10 +157,10 @@ func TestWriter_WriteFields(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteList_Simple(t *testing.T) {
+func TestWriter_WriteField_SimpleList(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteList([]interface{}{"hey", 42}); err != nil {
+	if err := w.writeField([]interface{}{"hey", 42}); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "(hey 42)" {
@@ -169,7 +168,7 @@ func TestWriter_WriteList_Simple(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteList_Nested(t *testing.T) {
+func TestWriter_WriteField_NestedList(t *testing.T) {
 	w, b := newWriter()
 
 	list := []interface{}{
@@ -181,7 +180,7 @@ func TestWriter_WriteList_Nested(t *testing.T) {
 		22,
 	}
 
-	if _, err := w.WriteList(list); err != nil {
+	if err := w.writeField(list); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "(toplevel (nested 0) 22)" {
@@ -189,12 +188,12 @@ func TestWriter_WriteList_Nested(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteLiteral(t *testing.T) {
+func TestWriter_WriteField_Literal(t *testing.T) {
 	w, b := newWriter()
 
-	literal := common.NewLiteral([]byte("hello world"))
+	literal := NewLiteral([]byte("hello world"))
 
-	if _, err := w.WriteLiteral(literal); err != nil {
+	if err := w.writeField(literal); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "{11}\r\nhello world" {
@@ -205,7 +204,7 @@ func TestWriter_WriteLiteral(t *testing.T) {
 func TestWriter_WriteRespCode_NoArgs(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteRespCode("READ-ONLY", nil); err != nil {
+	if err := w.writeRespCode("READ-ONLY", nil); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "[READ-ONLY]" {
@@ -217,7 +216,7 @@ func TestWriter_WriteRespCode_WithArgs(t *testing.T) {
 	w, b := newWriter()
 
 	args := []interface{}{"IMAP4rev1", "STARTTLS", "LOGINDISABLED"}
-	if _, err := w.WriteRespCode("CAPABILITY", args); err != nil {
+	if err := w.writeRespCode("CAPABILITY", args); err != nil {
 		t.Error(err)
 	}
 	if b.String() != "[CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED]" {
@@ -225,13 +224,13 @@ func TestWriter_WriteRespCode_WithArgs(t *testing.T) {
 	}
 }
 
-func TestWriter_WriteInfo(t *testing.T) {
+func TestWriter_WriteLine(t *testing.T) {
 	w, b := newWriter()
 
-	if _, err := w.WriteInfo("NOOP completed"); err != nil {
+	if err := w.writeLine("*", "OK"); err != nil {
 		t.Error(err)
 	}
-	if b.String() != "NOOP completed\r\n" {
-		t.Error("Not the expected info")
+	if b.String() != "* OK\r\n" {
+		t.Error("Not the expected line:", b.String())
 	}
 }
diff --git a/server/cmd_any_test.go b/server/cmd_any_test.go
index 1c94157..f2ab6fd 100644
--- a/server/cmd_any_test.go
+++ b/server/cmd_any_test.go
@@ -7,7 +7,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/emersion/go-imap/common"
 	"github.com/emersion/go-imap/server"
 )
 
@@ -67,33 +66,3 @@ func TestLogout(t *testing.T) {
 		t.Fatal("Bad status response:", scanner.Text())
 	}
 }
-
-type xnoop struct {}
-
-func (ext *xnoop) Capabilities(common.ConnState) []string {
-	return []string{"XNOOP"}
-}
-
-func (ext *xnoop) Command(string) server.HandlerFactory {
-	return nil
-}
-
-func TestServer_Enable(t *testing.T) {
-	s, c, scanner := testServerGreeted(t)
-	defer c.Close()
-	defer s.Close()
-
-	s.Enable(&xnoop{})
-
-	io.WriteString(c, "a001 CAPABILITY\r\n")
-
-	scanner.Scan()
-	if scanner.Text() != "* CAPABILITY IMAP4rev1 XNOOP AUTH=PLAIN" {
-		t.Fatal("Bad capability:", scanner.Text())
-	}
-
-	scanner.Scan()
-	if !strings.HasPrefix(scanner.Text(), "a001 OK ") {
-		t.Fatal("Bad status response:", scanner.Text())
-	}
-}