aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/emersion/go-imap
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emersion/go-imap')
-rw-r--r--vendor/github.com/emersion/go-imap/.build.yml17
-rw-r--r--vendor/github.com/emersion/go-imap/.gitignore28
-rw-r--r--vendor/github.com/emersion/go-imap/LICENSE23
-rw-r--r--vendor/github.com/emersion/go-imap/README.md178
-rw-r--r--vendor/github.com/emersion/go-imap/client/client.go689
-rw-r--r--vendor/github.com/emersion/go-imap/client/cmd_any.go88
-rw-r--r--vendor/github.com/emersion/go-imap/client/cmd_auth.go380
-rw-r--r--vendor/github.com/emersion/go-imap/client/cmd_noauth.go174
-rw-r--r--vendor/github.com/emersion/go-imap/client/cmd_selected.go367
-rw-r--r--vendor/github.com/emersion/go-imap/client/tag.go24
-rw-r--r--vendor/github.com/emersion/go-imap/command.go57
-rw-r--r--vendor/github.com/emersion/go-imap/commands/append.go93
-rw-r--r--vendor/github.com/emersion/go-imap/commands/authenticate.go124
-rw-r--r--vendor/github.com/emersion/go-imap/commands/capability.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/check.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/close.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/commands.go2
-rw-r--r--vendor/github.com/emersion/go-imap/commands/copy.go47
-rw-r--r--vendor/github.com/emersion/go-imap/commands/create.go38
-rw-r--r--vendor/github.com/emersion/go-imap/commands/delete.go38
-rw-r--r--vendor/github.com/emersion/go-imap/commands/enable.go23
-rw-r--r--vendor/github.com/emersion/go-imap/commands/expunge.go16
-rw-r--r--vendor/github.com/emersion/go-imap/commands/fetch.go63
-rw-r--r--vendor/github.com/emersion/go-imap/commands/idle.go17
-rw-r--r--vendor/github.com/emersion/go-imap/commands/list.go60
-rw-r--r--vendor/github.com/emersion/go-imap/commands/login.go36
-rw-r--r--vendor/github.com/emersion/go-imap/commands/logout.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/move.go48
-rw-r--r--vendor/github.com/emersion/go-imap/commands/noop.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/rename.go51
-rw-r--r--vendor/github.com/emersion/go-imap/commands/search.go57
-rw-r--r--vendor/github.com/emersion/go-imap/commands/select.go45
-rw-r--r--vendor/github.com/emersion/go-imap/commands/starttls.go18
-rw-r--r--vendor/github.com/emersion/go-imap/commands/status.go58
-rw-r--r--vendor/github.com/emersion/go-imap/commands/store.go50
-rw-r--r--vendor/github.com/emersion/go-imap/commands/subscribe.go63
-rw-r--r--vendor/github.com/emersion/go-imap/commands/uid.go44
-rw-r--r--vendor/github.com/emersion/go-imap/commands/unselect.go17
-rw-r--r--vendor/github.com/emersion/go-imap/conn.go284
-rw-r--r--vendor/github.com/emersion/go-imap/date.go71
-rw-r--r--vendor/github.com/emersion/go-imap/imap.go108
-rw-r--r--vendor/github.com/emersion/go-imap/literal.go13
-rw-r--r--vendor/github.com/emersion/go-imap/logger.go8
-rw-r--r--vendor/github.com/emersion/go-imap/mailbox.go314
-rw-r--r--vendor/github.com/emersion/go-imap/message.go1186
-rw-r--r--vendor/github.com/emersion/go-imap/read.go467
-rw-r--r--vendor/github.com/emersion/go-imap/response.go181
-rw-r--r--vendor/github.com/emersion/go-imap/responses/authenticate.go61
-rw-r--r--vendor/github.com/emersion/go-imap/responses/capability.go20
-rw-r--r--vendor/github.com/emersion/go-imap/responses/enabled.go33
-rw-r--r--vendor/github.com/emersion/go-imap/responses/expunge.go43
-rw-r--r--vendor/github.com/emersion/go-imap/responses/fetch.go70
-rw-r--r--vendor/github.com/emersion/go-imap/responses/idle.go38
-rw-r--r--vendor/github.com/emersion/go-imap/responses/list.go57
-rw-r--r--vendor/github.com/emersion/go-imap/responses/responses.go35
-rw-r--r--vendor/github.com/emersion/go-imap/responses/search.go41
-rw-r--r--vendor/github.com/emersion/go-imap/responses/select.go142
-rw-r--r--vendor/github.com/emersion/go-imap/responses/status.go53
-rw-r--r--vendor/github.com/emersion/go-imap/search.go371
-rw-r--r--vendor/github.com/emersion/go-imap/seqset.go289
-rw-r--r--vendor/github.com/emersion/go-imap/status.go136
-rw-r--r--vendor/github.com/emersion/go-imap/utf7/decoder.go149
-rw-r--r--vendor/github.com/emersion/go-imap/utf7/encoder.go91
-rw-r--r--vendor/github.com/emersion/go-imap/utf7/utf7.go34
-rw-r--r--vendor/github.com/emersion/go-imap/write.go255
65 files changed, 7673 insertions, 0 deletions
diff --git a/vendor/github.com/emersion/go-imap/.build.yml b/vendor/github.com/emersion/go-imap/.build.yml
new file mode 100644
index 0000000..2617917
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/.build.yml
@@ -0,0 +1,17 @@
+image: alpine/edge
+packages:
+ - go
+sources:
+ - https://github.com/emersion/go-imap
+artifacts:
+ - coverage.html
+tasks:
+ - build: |
+ cd go-imap
+ go build -race -v ./...
+ - test: |
+ cd go-imap
+ go test -coverprofile=coverage.txt -covermode=atomic ./...
+ - coverage: |
+ cd go-imap
+ go tool cover -html=coverage.txt -o ~/coverage.html
diff --git a/vendor/github.com/emersion/go-imap/.gitignore b/vendor/github.com/emersion/go-imap/.gitignore
new file mode 100644
index 0000000..59506a2
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/.gitignore
@@ -0,0 +1,28 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+/client.go
+/server.go
+coverage.txt
diff --git a/vendor/github.com/emersion/go-imap/LICENSE b/vendor/github.com/emersion/go-imap/LICENSE
new file mode 100644
index 0000000..f55742d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/LICENSE
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 The Go-IMAP Authors
+Copyright (c) 2016 emersion
+Copyright (c) 2016 Proton Technologies AG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/emersion/go-imap/README.md b/vendor/github.com/emersion/go-imap/README.md
new file mode 100644
index 0000000..199ab3b
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/README.md
@@ -0,0 +1,178 @@
+# go-imap
+
+[![godocs.io](https://godocs.io/github.com/emersion/go-imap?status.svg)](https://godocs.io/github.com/emersion/go-imap)
+[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-imap/commits/master.svg)](https://builds.sr.ht/~emersion/go-imap/commits/master?)
+
+An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
+can be used to build a client and/or a server.
+
+## Usage
+
+### Client [![godocs.io](https://godocs.io/github.com/emersion/go-imap/client?status.svg)](https://godocs.io/github.com/emersion/go-imap/client)
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/emersion/go-imap/client"
+ "github.com/emersion/go-imap"
+)
+
+func main() {
+ log.Println("Connecting to server...")
+
+ // Connect to server
+ c, err := client.DialTLS("mail.example.org:993", nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Println("Connected")
+
+ // Don't forget to logout
+ defer c.Logout()
+
+ // Login
+ if err := c.Login("username", "password"); err != nil {
+ log.Fatal(err)
+ }
+ log.Println("Logged in")
+
+ // List mailboxes
+ mailboxes := make(chan *imap.MailboxInfo, 10)
+ done := make(chan error, 1)
+ go func () {
+ done <- c.List("", "*", mailboxes)
+ }()
+
+ log.Println("Mailboxes:")
+ for m := range mailboxes {
+ log.Println("* " + m.Name)
+ }
+
+ if err := <-done; err != nil {
+ log.Fatal(err)
+ }
+
+ // Select INBOX
+ mbox, err := c.Select("INBOX", false)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Println("Flags for INBOX:", mbox.Flags)
+
+ // Get the last 4 messages
+ from := uint32(1)
+ to := mbox.Messages
+ if mbox.Messages > 3 {
+ // We're using unsigned integers here, only subtract if the result is > 0
+ from = mbox.Messages - 3
+ }
+ seqset := new(imap.SeqSet)
+ seqset.AddRange(from, to)
+
+ messages := make(chan *imap.Message, 10)
+ done = make(chan error, 1)
+ go func() {
+ done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
+ }()
+
+ log.Println("Last 4 messages:")
+ for msg := range messages {
+ log.Println("* " + msg.Envelope.Subject)
+ }
+
+ if err := <-done; err != nil {
+ log.Fatal(err)
+ }
+
+ log.Println("Done!")
+}
+```
+
+### Server [![godocs.io](https://godocs.io/github.com/emersion/go-imap/server?status.svg)](https://godocs.io/github.com/emersion/go-imap/server)
+
+```go
+package main
+
+import (
+ "log"
+
+ "github.com/emersion/go-imap/server"
+ "github.com/emersion/go-imap/backend/memory"
+)
+
+func main() {
+ // Create a memory backend
+ be := memory.New()
+
+ // Create a new server
+ s := server.New(be)
+ s.Addr = ":1143"
+ // Since we will use this server for testing only, we can allow plain text
+ // authentication over unencrypted connections
+ s.AllowInsecureAuth = true
+
+ log.Println("Starting IMAP server at localhost:1143")
+ if err := s.ListenAndServe(); err != nil {
+ log.Fatal(err)
+ }
+}
+```
+
+You can now use `telnet localhost 1143` to manually connect to the server.
+
+## Extensions
+
+Support for several IMAP extensions is included in go-imap itself. This
+includes:
+
+* [APPENDLIMIT](https://tools.ietf.org/html/rfc7889)
+* [CHILDREN](https://tools.ietf.org/html/rfc3348)
+* [ENABLE](https://tools.ietf.org/html/rfc5161)
+* [IDLE](https://tools.ietf.org/html/rfc2177)
+* [IMPORTANT](https://tools.ietf.org/html/rfc8457)
+* [LITERAL+](https://tools.ietf.org/html/rfc7888)
+* [MOVE](https://tools.ietf.org/html/rfc6851)
+* [SASL-IR](https://tools.ietf.org/html/rfc4959)
+* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
+* [UNSELECT](https://tools.ietf.org/html/rfc3691)
+
+Support for other extensions is provided via separate packages. See below.
+
+## Extending go-imap
+
+### Extensions
+
+Commands defined in IMAP extensions are available in other packages. See [the
+wiki](https://github.com/emersion/go-imap/wiki/Using-extensions#using-client-extensions)
+to learn how to use them.
+
+* [COMPRESS](https://github.com/emersion/go-imap-compress)
+* [ID](https://github.com/ProtonMail/go-imap-id)
+* [METADATA](https://github.com/emersion/go-imap-metadata)
+* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
+* [QUOTA](https://github.com/emersion/go-imap-quota)
+* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
+* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)
+
+### Server backends
+
+* [Memory](https://github.com/emersion/go-imap/tree/master/backend/memory) (for testing)
+* [Multi](https://github.com/emersion/go-imap-multi)
+* [PGP](https://github.com/emersion/go-imap-pgp)
+* [Proxy](https://github.com/emersion/go-imap-proxy)
+* [Notmuch](https://github.com/stbenjam/go-imap-notmuch) - Experimental gateway for [Notmuch](https://notmuchmail.org/)
+
+### Related projects
+
+* [go-message](https://github.com/emersion/go-message) - parsing and formatting MIME and mail messages
+* [go-msgauth](https://github.com/emersion/go-msgauth) - handle DKIM, DMARC and Authentication-Results
+* [go-pgpmail](https://github.com/emersion/go-pgpmail) - decrypting and encrypting mails with OpenPGP
+* [go-sasl](https://github.com/emersion/go-sasl) - sending and receiving SASL authentications
+* [go-smtp](https://github.com/emersion/go-smtp) - building SMTP clients and servers
+
+## License
+
+MIT
diff --git a/vendor/github.com/emersion/go-imap/client/client.go b/vendor/github.com/emersion/go-imap/client/client.go
new file mode 100644
index 0000000..8b6fc84
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/client.go
@@ -0,0 +1,689 @@
+// Package client provides an IMAP client.
+//
+// It is not safe to use the same Client from multiple goroutines. In general,
+// the IMAP protocol doesn't make it possible to send multiple independent
+// IMAP commands on the same connection.
+package client
+
+import (
+ "crypto/tls"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/commands"
+ "github.com/emersion/go-imap/responses"
+)
+
+// errClosed is used when a connection is closed while waiting for a command
+// response.
+var errClosed = fmt.Errorf("imap: connection closed")
+
+// errUnregisterHandler is returned by a response handler to unregister itself.
+var errUnregisterHandler = fmt.Errorf("imap: unregister handler")
+
+// Update is an unilateral server update.
+type Update interface {
+ update()
+}
+
+// StatusUpdate is delivered when a status update is received.
+type StatusUpdate struct {
+ Status *imap.StatusResp
+}
+
+func (u *StatusUpdate) update() {}
+
+// MailboxUpdate is delivered when a mailbox status changes.
+type MailboxUpdate struct {
+ Mailbox *imap.MailboxStatus
+}
+
+func (u *MailboxUpdate) update() {}
+
+// ExpungeUpdate is delivered when a message is deleted.
+type ExpungeUpdate struct {
+ SeqNum uint32
+}
+
+func (u *ExpungeUpdate) update() {}
+
+// MessageUpdate is delivered when a message attribute changes.
+type MessageUpdate struct {
+ Message *imap.Message
+}
+
+func (u *MessageUpdate) update() {}
+
+// Client is an IMAP client.
+type Client struct {
+ conn *imap.Conn
+ isTLS bool
+ serverName string
+
+ loggedOut chan struct{}
+ continues chan<- bool
+ upgrading bool
+
+ handlers []responses.Handler
+ handlersLocker sync.Mutex
+
+ // The current connection state.
+ state imap.ConnState
+ // The selected mailbox, if there is one.
+ mailbox *imap.MailboxStatus
+ // The cached server capabilities.
+ caps map[string]bool
+ // state, mailbox and caps may be accessed in different goroutines. Protect
+ // access.
+ locker sync.Mutex
+
+ // A channel to which unilateral updates from the server will be sent. An
+ // update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
+ // *ExpungeUpdate. Note that blocking this channel blocks the whole client,
+ // so it's recommended to use a separate goroutine and a buffered channel to
+ // prevent deadlocks.
+ Updates chan<- Update
+
+ // ErrorLog specifies an optional logger for errors accepting connections and
+ // unexpected behavior from handlers. By default, logging goes to os.Stderr
+ // via the log package's standard logger. The logger must be safe to use
+ // simultaneously from multiple goroutines.
+ ErrorLog imap.Logger
+
+ // Timeout specifies a maximum amount of time to wait on a command.
+ //
+ // A Timeout of zero means no timeout. This is the default.
+ Timeout time.Duration
+}
+
+func (c *Client) registerHandler(h responses.Handler) {
+ if h == nil {
+ return
+ }
+
+ c.handlersLocker.Lock()
+ c.handlers = append(c.handlers, h)
+ c.handlersLocker.Unlock()
+}
+
+func (c *Client) handle(resp imap.Resp) error {
+ c.handlersLocker.Lock()
+ for i := len(c.handlers) - 1; i >= 0; i-- {
+ if err := c.handlers[i].Handle(resp); err != responses.ErrUnhandled {
+ if err == errUnregisterHandler {
+ c.handlers = append(c.handlers[:i], c.handlers[i+1:]...)
+ err = nil
+ }
+ c.handlersLocker.Unlock()
+ return err
+ }
+ }
+ c.handlersLocker.Unlock()
+ return responses.ErrUnhandled
+}
+
+func (c *Client) reader() {
+ defer close(c.loggedOut)
+ // Loop while connected.
+ for {
+ connected, err := c.readOnce()
+ if err != nil {
+ c.ErrorLog.Println("error reading response:", err)
+ }
+ if !connected {
+ return
+ }
+ }
+}
+
+func (c *Client) readOnce() (bool, error) {
+ if c.State() == imap.LogoutState {
+ return false, nil
+ }
+
+ resp, err := imap.ReadResp(c.conn.Reader)
+ if err == io.EOF || c.State() == imap.LogoutState {
+ return false, nil
+ } else if err != nil {
+ if imap.IsParseError(err) {
+ return true, err
+ } else {
+ return false, err
+ }
+ }
+
+ if err := c.handle(resp); err != nil && err != responses.ErrUnhandled {
+ c.ErrorLog.Println("cannot handle response ", resp, err)
+ }
+ return true, nil
+}
+
+func (c *Client) writeReply(reply []byte) error {
+ if _, err := c.conn.Writer.Write(reply); err != nil {
+ return err
+ }
+ // Flush reply
+ return c.conn.Writer.Flush()
+}
+
+type handleResult struct {
+ status *imap.StatusResp
+ err error
+}
+
+func (c *Client) execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
+ cmd := cmdr.Command()
+ cmd.Tag = generateTag()
+
+ var replies <-chan []byte
+ if replier, ok := h.(responses.Replier); ok {
+ replies = replier.Replies()
+ }
+
+ if c.Timeout > 0 {
+ err := c.conn.SetDeadline(time.Now().Add(c.Timeout))
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // It's possible the client had a timeout set from a previous command, but no
+ // longer does. Ensure we respect that. The zero time means no deadline.
+ if err := c.conn.SetDeadline(time.Time{}); err != nil {
+ return nil, err
+ }
+ }
+
+ // Check if we are upgrading.
+ upgrading := c.upgrading
+
+ // Add handler before sending command, to be sure to get the response in time
+ // (in tests, the response is sent right after our command is received, so
+ // sometimes the response was received before the setup of this handler)
+ doneHandle := make(chan handleResult, 1)
+ unregister := make(chan struct{})
+ c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
+ select {
+ case <-unregister:
+ // If an error occured while sending the command, abort
+ return errUnregisterHandler
+ default:
+ }
+
+ if s, ok := resp.(*imap.StatusResp); ok && s.Tag == cmd.Tag {
+ // This is the command's status response, we're done
+ doneHandle <- handleResult{s, nil}
+ // Special handling of connection upgrading.
+ if upgrading {
+ c.upgrading = false
+ // Wait for upgrade to finish.
+ c.conn.Wait()
+ }
+ // Cancel any pending literal write
+ select {
+ case c.continues <- false:
+ default:
+ }
+ return errUnregisterHandler
+ }
+
+ if h != nil {
+ // Pass the response to the response handler
+ if err := h.Handle(resp); err != nil && err != responses.ErrUnhandled {
+ // If the response handler returns an error, abort
+ doneHandle <- handleResult{nil, err}
+ return errUnregisterHandler
+ } else {
+ return err
+ }
+ }
+ return responses.ErrUnhandled
+ }))
+
+ // Send the command to the server
+ if err := cmd.WriteTo(c.conn.Writer); err != nil {
+ // Error while sending the command
+ close(unregister)
+
+ if err, ok := err.(imap.LiteralLengthErr); ok {
+ // Expected > Actual
+ // The server is waiting for us to write
+ // more bytes, we don't have them. Run.
+ // Expected < Actual
+ // We are about to send a potentially truncated message, we don't
+ // want this (ths terminating CRLF is not sent at this point).
+ c.conn.Close()
+ return nil, err
+ }
+
+ return nil, err
+ }
+ // Flush writer if we are upgrading
+ if upgrading {
+ if err := c.conn.Writer.Flush(); err != nil {
+ // Error while sending the command
+ close(unregister)
+ return nil, err
+ }
+ }
+
+ for {
+ select {
+ case reply := <-replies:
+ // Response handler needs to send a reply (Used for AUTHENTICATE)
+ if err := c.writeReply(reply); err != nil {
+ close(unregister)
+ return nil, err
+ }
+ case <-c.loggedOut:
+ // If the connection is closed (such as from an I/O error), ensure we
+ // realize this and don't block waiting on a response that will never
+ // come. loggedOut is a channel that closes when the reader goroutine
+ // ends.
+ close(unregister)
+ return nil, errClosed
+ case result := <-doneHandle:
+ return result.status, result.err
+ }
+ }
+}
+
+// State returns the current connection state.
+func (c *Client) State() imap.ConnState {
+ c.locker.Lock()
+ state := c.state
+ c.locker.Unlock()
+ return state
+}
+
+// Mailbox returns the selected mailbox. It returns nil if there isn't one.
+func (c *Client) Mailbox() *imap.MailboxStatus {
+ // c.Mailbox fields are not supposed to change, so we can return the pointer.
+ c.locker.Lock()
+ mbox := c.mailbox
+ c.locker.Unlock()
+ return mbox
+}
+
+// SetState sets this connection's internal state.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the IMAP protocol.
+func (c *Client) SetState(state imap.ConnState, mailbox *imap.MailboxStatus) {
+ c.locker.Lock()
+ c.state = state
+ c.mailbox = mailbox
+ c.locker.Unlock()
+}
+
+// Execute executes a generic command. cmdr is a value that can be converted to
+// a raw command and h is a response handler. The function returns when the
+// command has completed or failed, in this case err is nil. A non-nil err value
+// indicates a network error.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the IMAP protocol.
+func (c *Client) Execute(cmdr imap.Commander, h responses.Handler) (*imap.StatusResp, error) {
+ return c.execute(cmdr, h)
+}
+
+func (c *Client) handleContinuationReqs() {
+ c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
+ if _, ok := resp.(*imap.ContinuationReq); ok {
+ go func() {
+ c.continues <- true
+ }()
+ return nil
+ }
+ return responses.ErrUnhandled
+ }))
+}
+
+func (c *Client) gotStatusCaps(args []interface{}) {
+ c.locker.Lock()
+
+ c.caps = make(map[string]bool)
+ for _, cap := range args {
+ if cap, ok := cap.(string); ok {
+ c.caps[cap] = true
+ }
+ }
+
+ c.locker.Unlock()
+}
+
+// The server can send unilateral data. This function handles it.
+func (c *Client) handleUnilateral() {
+ c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
+ switch resp := resp.(type) {
+ case *imap.StatusResp:
+ if resp.Tag != "*" {
+ return responses.ErrUnhandled
+ }
+
+ switch resp.Type {
+ case imap.StatusRespOk, imap.StatusRespNo, imap.StatusRespBad:
+ if c.Updates != nil {
+ c.Updates <- &StatusUpdate{resp}
+ }
+ case imap.StatusRespBye:
+ c.locker.Lock()
+ c.state = imap.LogoutState
+ c.mailbox = nil
+ c.locker.Unlock()
+
+ c.conn.Close()
+
+ if c.Updates != nil {
+ c.Updates <- &StatusUpdate{resp}
+ }
+ default:
+ return responses.ErrUnhandled
+ }
+ case *imap.DataResp:
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok {
+ return responses.ErrUnhandled
+ }
+
+ switch name {
+ case "CAPABILITY":
+ c.gotStatusCaps(fields)
+ case "EXISTS":
+ if c.Mailbox() == nil {
+ break
+ }
+
+ if messages, err := imap.ParseNumber(fields[0]); err == nil {
+ c.locker.Lock()
+ c.mailbox.Messages = messages
+ c.locker.Unlock()
+
+ c.mailbox.ItemsLocker.Lock()
+ c.mailbox.Items[imap.StatusMessages] = nil
+ c.mailbox.ItemsLocker.Unlock()
+ }
+
+ if c.Updates != nil {
+ c.Updates <- &MailboxUpdate{c.Mailbox()}
+ }
+ case "RECENT":
+ if c.Mailbox() == nil {
+ break
+ }
+
+ if recent, err := imap.ParseNumber(fields[0]); err == nil {
+ c.locker.Lock()
+ c.mailbox.Recent = recent
+ c.locker.Unlock()
+
+ c.mailbox.ItemsLocker.Lock()
+ c.mailbox.Items[imap.StatusRecent] = nil
+ c.mailbox.ItemsLocker.Unlock()
+ }
+
+ if c.Updates != nil {
+ c.Updates <- &MailboxUpdate{c.Mailbox()}
+ }
+ case "EXPUNGE":
+ seqNum, _ := imap.ParseNumber(fields[0])
+
+ if c.Updates != nil {
+ c.Updates <- &ExpungeUpdate{seqNum}
+ }
+ case "FETCH":
+ seqNum, _ := imap.ParseNumber(fields[0])
+ fields, _ := fields[1].([]interface{})
+
+ msg := &imap.Message{SeqNum: seqNum}
+ if err := msg.Parse(fields); err != nil {
+ break
+ }
+
+ if c.Updates != nil {
+ c.Updates <- &MessageUpdate{msg}
+ }
+ default:
+ return responses.ErrUnhandled
+ }
+ default:
+ return responses.ErrUnhandled
+ }
+ return nil
+ }))
+}
+
+func (c *Client) handleGreetAndStartReading() error {
+ var greetErr error
+ gotGreet := false
+
+ c.registerHandler(responses.HandlerFunc(func(resp imap.Resp) error {
+ status, ok := resp.(*imap.StatusResp)
+ if !ok {
+ greetErr = fmt.Errorf("invalid greeting received from server: not a status response")
+ return errUnregisterHandler
+ }
+
+ c.locker.Lock()
+ switch status.Type {
+ case imap.StatusRespPreauth:
+ c.state = imap.AuthenticatedState
+ case imap.StatusRespBye:
+ c.state = imap.LogoutState
+ case imap.StatusRespOk:
+ c.state = imap.NotAuthenticatedState
+ default:
+ c.state = imap.LogoutState
+ c.locker.Unlock()
+ greetErr = fmt.Errorf("invalid greeting received from server: %v", status.Type)
+ return errUnregisterHandler
+ }
+ c.locker.Unlock()
+
+ if status.Code == imap.CodeCapability {
+ c.gotStatusCaps(status.Arguments)
+ }
+
+ gotGreet = true
+ return errUnregisterHandler
+ }))
+
+ // call `readOnce` until we get the greeting or an error
+ for !gotGreet {
+ connected, err := c.readOnce()
+ // Check for read errors
+ if err != nil {
+ // return read errors
+ return err
+ }
+ // Check for invalid greet
+ if greetErr != nil {
+ // return read errors
+ return greetErr
+ }
+ // Check if connection was closed.
+ if !connected {
+ // connection closed.
+ return io.EOF
+ }
+ }
+
+ // We got the greeting, now start the reader goroutine.
+ go c.reader()
+
+ return nil
+}
+
+// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
+// tunnel.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the IMAP protocol.
+func (c *Client) Upgrade(upgrader imap.ConnUpgrader) error {
+ return c.conn.Upgrade(upgrader)
+}
+
+// Writer returns the imap.Writer for this client's connection.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the IMAP protocol.
+func (c *Client) Writer() *imap.Writer {
+ return c.conn.Writer
+}
+
+// IsTLS checks if this client's connection has TLS enabled.
+func (c *Client) IsTLS() bool {
+ return c.isTLS
+}
+
+// LoggedOut returns a channel which is closed when the connection to the server
+// is closed.
+func (c *Client) LoggedOut() <-chan struct{} {
+ return c.loggedOut
+}
+
+// SetDebug defines an io.Writer to which all network activity will be logged.
+// If nil is provided, network activity will not be logged.
+func (c *Client) SetDebug(w io.Writer) {
+ // Need to send a command to unblock the reader goroutine.
+ cmd := new(commands.Noop)
+ err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
+ // Flag connection as in upgrading
+ c.upgrading = true
+ if status, err := c.execute(cmd, nil); err != nil {
+ return nil, err
+ } else if err := status.Err(); err != nil {
+ return nil, err
+ }
+
+ // Wait for reader to block.
+ c.conn.WaitReady()
+
+ c.conn.SetDebug(w)
+ return conn, nil
+ })
+ if err != nil {
+ log.Println("SetDebug:", err)
+ }
+
+}
+
+// New creates a new client from an existing connection.
+func New(conn net.Conn) (*Client, error) {
+ continues := make(chan bool)
+ w := imap.NewClientWriter(nil, continues)
+ r := imap.NewReader(nil)
+
+ c := &Client{
+ conn: imap.NewConn(conn, r, w),
+ loggedOut: make(chan struct{}),
+ continues: continues,
+ state: imap.ConnectingState,
+ ErrorLog: log.New(os.Stderr, "imap/client: ", log.LstdFlags),
+ }
+
+ c.handleContinuationReqs()
+ c.handleUnilateral()
+ if err := c.handleGreetAndStartReading(); err != nil {
+ return c, err
+ }
+
+ plusOk, _ := c.Support("LITERAL+")
+ minusOk, _ := c.Support("LITERAL-")
+ // We don't use non-sync literal if it is bigger than 4096 bytes, so
+ // LITERAL- is fine too.
+ c.conn.AllowAsyncLiterals = plusOk || minusOk
+
+ return c, nil
+}
+
+// Dial connects to an IMAP server using an unencrypted connection.
+func Dial(addr string) (*Client, error) {
+ return DialWithDialer(new(net.Dialer), addr)
+}
+
+type Dialer interface {
+ // Dial connects to the given address.
+ Dial(network, addr string) (net.Conn, error)
+}
+
+// DialWithDialer connects to an IMAP server using an unencrypted connection
+// using dialer.Dial.
+//
+// Among other uses, this allows to apply a dial timeout.
+func DialWithDialer(dialer Dialer, addr string) (*Client, error) {
+ conn, err := dialer.Dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+
+ // We don't return to the caller until we try to receive a greeting. As such,
+ // there is no way to set the client's Timeout for that action. As a
+ // workaround, if the dialer has a timeout set, use that for the connection's
+ // deadline.
+ if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
+ err := conn.SetDeadline(time.Now().Add(netDialer.Timeout))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ c, err := New(conn)
+ if err != nil {
+ return nil, err
+ }
+
+ c.serverName, _, _ = net.SplitHostPort(addr)
+ return c, nil
+}
+
+// DialTLS connects to an IMAP server using an encrypted connection.
+func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
+ return DialWithDialerTLS(new(net.Dialer), addr, tlsConfig)
+}
+
+// DialWithDialerTLS connects to an IMAP server using an encrypted connection
+// using dialer.Dial.
+//
+// Among other uses, this allows to apply a dial timeout.
+func DialWithDialerTLS(dialer Dialer, addr string, tlsConfig *tls.Config) (*Client, error) {
+ conn, err := dialer.Dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+
+ serverName, _, _ := net.SplitHostPort(addr)
+ if tlsConfig == nil {
+ tlsConfig = &tls.Config{}
+ }
+ if tlsConfig.ServerName == "" {
+ tlsConfig = tlsConfig.Clone()
+ tlsConfig.ServerName = serverName
+ }
+ tlsConn := tls.Client(conn, tlsConfig)
+
+ // We don't return to the caller until we try to receive a greeting. As such,
+ // there is no way to set the client's Timeout for that action. As a
+ // workaround, if the dialer has a timeout set, use that for the connection's
+ // deadline.
+ if netDialer, ok := dialer.(*net.Dialer); ok && netDialer.Timeout > 0 {
+ err := tlsConn.SetDeadline(time.Now().Add(netDialer.Timeout))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ c, err := New(tlsConn)
+ if err != nil {
+ return nil, err
+ }
+
+ c.isTLS = true
+ c.serverName = serverName
+ return c, nil
+}
diff --git a/vendor/github.com/emersion/go-imap/client/cmd_any.go b/vendor/github.com/emersion/go-imap/client/cmd_any.go
new file mode 100644
index 0000000..cb0d38a
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/cmd_any.go
@@ -0,0 +1,88 @@
+package client
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/commands"
+)
+
+// ErrAlreadyLoggedOut is returned if Logout is called when the client is
+// already logged out.
+var ErrAlreadyLoggedOut = errors.New("Already logged out")
+
+// Capability requests a listing of capabilities that the server supports.
+// Capabilities are often returned by the server with the greeting or with the
+// STARTTLS and LOGIN responses, so usually explicitly requesting capabilities
+// isn't needed.
+//
+// Most of the time, Support should be used instead.
+func (c *Client) Capability() (map[string]bool, error) {
+ cmd := &commands.Capability{}
+
+ if status, err := c.execute(cmd, nil); err != nil {
+ return nil, err
+ } else if err := status.Err(); err != nil {
+ return nil, err
+ }
+
+ c.locker.Lock()
+ caps := c.caps
+ c.locker.Unlock()
+ return caps, nil
+}
+
+// Support checks if cap is a capability supported by the server. If the server
+// hasn't sent its capabilities yet, Support requests them.
+func (c *Client) Support(cap string) (bool, error) {
+ c.locker.Lock()
+ ok := c.caps != nil
+ c.locker.Unlock()
+
+ // If capabilities are not cached, request them
+ if !ok {
+ if _, err := c.Capability(); err != nil {
+ return false, err
+ }
+ }
+
+ c.locker.Lock()
+ supported := c.caps[cap]
+ c.locker.Unlock()
+
+ return supported, nil
+}
+
+// Noop always succeeds and does nothing.
+//
+// It can be used as a periodic poll for new messages or message status updates
+// during a period of inactivity. It can also be used to reset any inactivity
+// autologout timer on the server.
+func (c *Client) Noop() error {
+ cmd := new(commands.Noop)
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Logout gracefully closes the connection.
+func (c *Client) Logout() error {
+ if c.State() == imap.LogoutState {
+ return ErrAlreadyLoggedOut
+ }
+
+ cmd := new(commands.Logout)
+
+ if status, err := c.execute(cmd, nil); err == errClosed {
+ // Server closed connection, that's what we want anyway
+ return nil
+ } else if err != nil {
+ return err
+ } else if status != nil {
+ return status.Err()
+ }
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/client/cmd_auth.go b/vendor/github.com/emersion/go-imap/client/cmd_auth.go
new file mode 100644
index 0000000..a280017
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/cmd_auth.go
@@ -0,0 +1,380 @@
+package client
+
+import (
+ "errors"
+ "time"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/commands"
+ "github.com/emersion/go-imap/responses"
+)
+
+// ErrNotLoggedIn is returned if a function that requires the client to be
+// logged in is called then the client isn't.
+var ErrNotLoggedIn = errors.New("Not logged in")
+
+func (c *Client) ensureAuthenticated() error {
+ state := c.State()
+ if state != imap.AuthenticatedState && state != imap.SelectedState {
+ return ErrNotLoggedIn
+ }
+ return nil
+}
+
+// Select selects a mailbox so that messages in the mailbox can be accessed. Any
+// currently selected mailbox is deselected before attempting the new selection.
+// Even if the readOnly parameter is set to false, the server can decide to open
+// the mailbox in read-only mode.
+func (c *Client) Select(name string, readOnly bool) (*imap.MailboxStatus, error) {
+ if err := c.ensureAuthenticated(); err != nil {
+ return nil, err
+ }
+
+ cmd := &commands.Select{
+ Mailbox: name,
+ ReadOnly: readOnly,
+ }
+
+ mbox := &imap.MailboxStatus{Name: name, Items: make(map[imap.StatusItem]interface{})}
+ res := &responses.Select{
+ Mailbox: mbox,
+ }
+ c.locker.Lock()
+ c.mailbox = mbox
+ c.locker.Unlock()
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ c.locker.Lock()
+ c.mailbox = nil
+ c.locker.Unlock()
+ return nil, err
+ }
+ if err := status.Err(); err != nil {
+ c.locker.Lock()
+ c.mailbox = nil
+ c.locker.Unlock()
+ return nil, err
+ }
+
+ c.locker.Lock()
+ mbox.ReadOnly = (status.Code == imap.CodeReadOnly)
+ c.state = imap.SelectedState
+ c.locker.Unlock()
+ return mbox, nil
+}
+
+// Create creates a mailbox with the given name.
+func (c *Client) Create(name string) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Create{
+ Mailbox: name,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Delete permanently removes the mailbox with the given name.
+func (c *Client) Delete(name string) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Delete{
+ Mailbox: name,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Rename changes the name of a mailbox.
+func (c *Client) Rename(existingName, newName string) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Rename{
+ Existing: existingName,
+ New: newName,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Subscribe adds the specified mailbox name to the server's set of "active" or
+// "subscribed" mailboxes.
+func (c *Client) Subscribe(name string) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Subscribe{
+ Mailbox: name,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Unsubscribe removes the specified mailbox name from the server's set of
+// "active" or "subscribed" mailboxes.
+func (c *Client) Unsubscribe(name string) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Unsubscribe{
+ Mailbox: name,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// List returns a subset of names from the complete set of all names available
+// to the client.
+//
+// An empty name argument is a special request to return the hierarchy delimiter
+// and the root name of the name given in the reference. The character "*" is a
+// wildcard, and matches zero or more characters at this position. The
+// character "%" is similar to "*", but it does not match a hierarchy delimiter.
+func (c *Client) List(ref, name string, ch chan *imap.MailboxInfo) error {
+ defer close(ch)
+
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.List{
+ Reference: ref,
+ Mailbox: name,
+ }
+ res := &responses.List{Mailboxes: ch}
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Lsub returns a subset of names from the set of names that the user has
+// declared as being "active" or "subscribed".
+func (c *Client) Lsub(ref, name string, ch chan *imap.MailboxInfo) error {
+ defer close(ch)
+
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.List{
+ Reference: ref,
+ Mailbox: name,
+ Subscribed: true,
+ }
+ res := &responses.List{
+ Mailboxes: ch,
+ Subscribed: true,
+ }
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Status requests the status of the indicated mailbox. It does not change the
+// currently selected mailbox, nor does it affect the state of any messages in
+// the queried mailbox.
+//
+// See RFC 3501 section 6.3.10 for a list of items that can be requested.
+func (c *Client) Status(name string, items []imap.StatusItem) (*imap.MailboxStatus, error) {
+ if err := c.ensureAuthenticated(); err != nil {
+ return nil, err
+ }
+
+ cmd := &commands.Status{
+ Mailbox: name,
+ Items: items,
+ }
+ res := &responses.Status{
+ Mailbox: new(imap.MailboxStatus),
+ }
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ return nil, err
+ }
+ return res.Mailbox, status.Err()
+}
+
+// Append appends the literal argument as a new message to the end of the
+// specified 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 and the empty struct.
+func (c *Client) Append(mbox string, flags []string, date time.Time, msg imap.Literal) error {
+ if err := c.ensureAuthenticated(); err != nil {
+ return err
+ }
+
+ cmd := &commands.Append{
+ Mailbox: mbox,
+ Flags: flags,
+ Date: date,
+ Message: msg,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Enable requests the server to enable the named extensions. The extensions
+// which were successfully enabled are returned.
+//
+// See RFC 5161 section 3.1.
+func (c *Client) Enable(caps []string) ([]string, error) {
+ if ok, err := c.Support("ENABLE"); !ok || err != nil {
+ return nil, ErrExtensionUnsupported
+ }
+
+ // ENABLE is invalid if a mailbox has been selected.
+ if c.State() != imap.AuthenticatedState {
+ return nil, ErrNotLoggedIn
+ }
+
+ cmd := &commands.Enable{Caps: caps}
+ res := &responses.Enabled{}
+
+ if status, err := c.Execute(cmd, res); err != nil {
+ return nil, err
+ } else {
+ return res.Caps, status.Err()
+ }
+}
+
+func (c *Client) idle(stop <-chan struct{}) error {
+ cmd := &commands.Idle{}
+
+ res := &responses.Idle{
+ Stop: stop,
+ RepliesCh: make(chan []byte, 10),
+ }
+
+ if status, err := c.Execute(cmd, res); err != nil {
+ return err
+ } else {
+ return status.Err()
+ }
+}
+
+// IdleOptions holds options for Client.Idle.
+type IdleOptions struct {
+ // LogoutTimeout is used to avoid being logged out by the server when
+ // idling. Each LogoutTimeout, the IDLE command is restarted. If set to
+ // zero, a default is used. If negative, this behavior is disabled.
+ LogoutTimeout time.Duration
+ // Poll interval when the server doesn't support IDLE. If zero, a default
+ // is used. If negative, polling is always disabled.
+ PollInterval time.Duration
+}
+
+// Idle indicates to the server that the client is ready to receive unsolicited
+// mailbox update messages. When the client wants to send commands again, it
+// must first close stop.
+//
+// If the server doesn't support IDLE, go-imap falls back to polling.
+func (c *Client) Idle(stop <-chan struct{}, opts *IdleOptions) error {
+ if ok, err := c.Support("IDLE"); err != nil {
+ return err
+ } else if !ok {
+ return c.idleFallback(stop, opts)
+ }
+
+ logoutTimeout := 25 * time.Minute
+ if opts != nil {
+ if opts.LogoutTimeout > 0 {
+ logoutTimeout = opts.LogoutTimeout
+ } else if opts.LogoutTimeout < 0 {
+ return c.idle(stop)
+ }
+ }
+
+ t := time.NewTicker(logoutTimeout)
+ defer t.Stop()
+
+ for {
+ stopOrRestart := make(chan struct{})
+ done := make(chan error, 1)
+ go func() {
+ done <- c.idle(stopOrRestart)
+ }()
+
+ select {
+ case <-t.C:
+ close(stopOrRestart)
+ if err := <-done; err != nil {
+ return err
+ }
+ case <-stop:
+ close(stopOrRestart)
+ return <-done
+ case err := <-done:
+ close(stopOrRestart)
+ if err != nil {
+ return err
+ }
+ }
+ }
+}
+
+func (c *Client) idleFallback(stop <-chan struct{}, opts *IdleOptions) error {
+ pollInterval := time.Minute
+ if opts != nil {
+ if opts.PollInterval > 0 {
+ pollInterval = opts.PollInterval
+ } else if opts.PollInterval < 0 {
+ return ErrExtensionUnsupported
+ }
+ }
+
+ t := time.NewTicker(pollInterval)
+ defer t.Stop()
+
+ for {
+ select {
+ case <-t.C:
+ if err := c.Noop(); err != nil {
+ return err
+ }
+ case <-stop:
+ return nil
+ case <-c.LoggedOut():
+ return errors.New("disconnected while idling")
+ }
+ }
+}
diff --git a/vendor/github.com/emersion/go-imap/client/cmd_noauth.go b/vendor/github.com/emersion/go-imap/client/cmd_noauth.go
new file mode 100644
index 0000000..f9b34d3
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/cmd_noauth.go
@@ -0,0 +1,174 @@
+package client
+
+import (
+ "crypto/tls"
+ "errors"
+ "net"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/commands"
+ "github.com/emersion/go-imap/responses"
+ "github.com/emersion/go-sasl"
+)
+
+var (
+ // ErrAlreadyLoggedIn is returned if Login or Authenticate is called when the
+ // client is already logged in.
+ ErrAlreadyLoggedIn = errors.New("Already logged in")
+ // ErrTLSAlreadyEnabled is returned if StartTLS is called when TLS is already
+ // enabled.
+ ErrTLSAlreadyEnabled = errors.New("TLS is already enabled")
+ // ErrLoginDisabled is returned if Login or Authenticate is called when the
+ // server has disabled authentication. Most of the time, calling enabling TLS
+ // solves the problem.
+ ErrLoginDisabled = errors.New("Login is disabled in current state")
+)
+
+// SupportStartTLS checks if the server supports STARTTLS.
+func (c *Client) SupportStartTLS() (bool, error) {
+ return c.Support("STARTTLS")
+}
+
+// StartTLS starts TLS negotiation.
+func (c *Client) StartTLS(tlsConfig *tls.Config) error {
+ if c.isTLS {
+ return ErrTLSAlreadyEnabled
+ }
+
+ if tlsConfig == nil {
+ tlsConfig = new(tls.Config)
+ }
+ if tlsConfig.ServerName == "" {
+ tlsConfig = tlsConfig.Clone()
+ tlsConfig.ServerName = c.serverName
+ }
+
+ cmd := new(commands.StartTLS)
+
+ err := c.Upgrade(func(conn net.Conn) (net.Conn, error) {
+ // Flag connection as in upgrading
+ c.upgrading = true
+ if status, err := c.execute(cmd, nil); err != nil {
+ return nil, err
+ } else if err := status.Err(); err != nil {
+ return nil, err
+ }
+
+ // Wait for reader to block.
+ c.conn.WaitReady()
+ tlsConn := tls.Client(conn, tlsConfig)
+ if err := tlsConn.Handshake(); err != nil {
+ return nil, err
+ }
+
+ // Capabilities change when TLS is enabled
+ c.locker.Lock()
+ c.caps = nil
+ c.locker.Unlock()
+
+ return tlsConn, nil
+ })
+ if err != nil {
+ return err
+ }
+
+ c.isTLS = true
+ return nil
+}
+
+// SupportAuth checks if the server supports a given authentication mechanism.
+func (c *Client) SupportAuth(mech string) (bool, error) {
+ return c.Support("AUTH=" + mech)
+}
+
+// Authenticate indicates a SASL authentication mechanism to the server. If the
+// server supports the requested authentication mechanism, it performs an
+// authentication protocol exchange to authenticate and identify the client.
+func (c *Client) Authenticate(auth sasl.Client) error {
+ if c.State() != imap.NotAuthenticatedState {
+ return ErrAlreadyLoggedIn
+ }
+
+ mech, ir, err := auth.Start()
+ if err != nil {
+ return err
+ }
+
+ cmd := &commands.Authenticate{
+ Mechanism: mech,
+ }
+
+ irOk, err := c.Support("SASL-IR")
+ if err != nil {
+ return err
+ }
+ if irOk {
+ cmd.InitialResponse = ir
+ }
+
+ res := &responses.Authenticate{
+ Mechanism: auth,
+ InitialResponse: ir,
+ RepliesCh: make(chan []byte, 10),
+ }
+ if irOk {
+ res.InitialResponse = nil
+ }
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ return err
+ }
+ if err = status.Err(); err != nil {
+ return err
+ }
+
+ c.locker.Lock()
+ c.state = imap.AuthenticatedState
+ c.caps = nil // Capabilities change when user is logged in
+ c.locker.Unlock()
+
+ if status.Code == "CAPABILITY" {
+ c.gotStatusCaps(status.Arguments)
+ }
+
+ return nil
+}
+
+// Login identifies the client to the server and carries the plaintext password
+// authenticating this user.
+func (c *Client) Login(username, password string) error {
+ if state := c.State(); state == imap.AuthenticatedState || state == imap.SelectedState {
+ return ErrAlreadyLoggedIn
+ }
+
+ c.locker.Lock()
+ loginDisabled := c.caps != nil && c.caps["LOGINDISABLED"]
+ c.locker.Unlock()
+ if loginDisabled {
+ return ErrLoginDisabled
+ }
+
+ cmd := &commands.Login{
+ Username: username,
+ Password: password,
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ if err = status.Err(); err != nil {
+ return err
+ }
+
+ c.locker.Lock()
+ c.state = imap.AuthenticatedState
+ c.caps = nil // Capabilities change when user is logged in
+ c.locker.Unlock()
+
+ if status.Code == "CAPABILITY" {
+ c.gotStatusCaps(status.Arguments)
+ }
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/client/cmd_selected.go b/vendor/github.com/emersion/go-imap/client/cmd_selected.go
new file mode 100644
index 0000000..0fb71ad
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/cmd_selected.go
@@ -0,0 +1,367 @@
+package client
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/commands"
+ "github.com/emersion/go-imap/responses"
+)
+
+var (
+ // ErrNoMailboxSelected is returned if a command that requires a mailbox to be
+ // selected is called when there isn't.
+ ErrNoMailboxSelected = errors.New("No mailbox selected")
+
+ // ErrExtensionUnsupported is returned if a command uses a extension that
+ // is not supported by the server.
+ ErrExtensionUnsupported = errors.New("The required extension is not supported by the server")
+)
+
+// Check requests a checkpoint of the currently selected mailbox. A checkpoint
+// refers to any implementation-dependent housekeeping associated with the
+// mailbox that is not normally executed as part of each command.
+func (c *Client) Check() error {
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ cmd := new(commands.Check)
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+
+ return status.Err()
+}
+
+// Close permanently removes all messages that have the \Deleted flag set from
+// the currently selected mailbox, and returns to the authenticated state from
+// the selected state.
+func (c *Client) Close() error {
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ cmd := new(commands.Close)
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ } else if err := status.Err(); err != nil {
+ return err
+ }
+
+ c.locker.Lock()
+ c.state = imap.AuthenticatedState
+ c.mailbox = nil
+ c.locker.Unlock()
+ return nil
+}
+
+// Terminate closes the tcp connection
+func (c *Client) Terminate() error {
+ return c.conn.Close()
+}
+
+// Expunge permanently removes all messages that have the \Deleted flag set from
+// the currently selected mailbox. If ch is not nil, sends sequence IDs of each
+// deleted message to this channel.
+func (c *Client) Expunge(ch chan uint32) error {
+ if ch != nil {
+ defer close(ch)
+ }
+
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ cmd := new(commands.Expunge)
+
+ var h responses.Handler
+ if ch != nil {
+ h = &responses.Expunge{SeqNums: ch}
+ }
+
+ status, err := c.execute(cmd, h)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+func (c *Client) executeSearch(uid bool, criteria *imap.SearchCriteria, charset string) (ids []uint32, status *imap.StatusResp, err error) {
+ if c.State() != imap.SelectedState {
+ err = ErrNoMailboxSelected
+ return
+ }
+
+ var cmd imap.Commander = &commands.Search{
+ Charset: charset,
+ Criteria: criteria,
+ }
+ if uid {
+ cmd = &commands.Uid{Cmd: cmd}
+ }
+
+ res := new(responses.Search)
+
+ status, err = c.execute(cmd, res)
+ if err != nil {
+ return
+ }
+
+ err, ids = status.Err(), res.Ids
+ return
+}
+
+func (c *Client) search(uid bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
+ ids, status, err := c.executeSearch(uid, criteria, "UTF-8")
+ if status != nil && status.Code == imap.CodeBadCharset {
+ // Some servers don't support UTF-8
+ ids, _, err = c.executeSearch(uid, criteria, "US-ASCII")
+ }
+ return
+}
+
+// Search searches the mailbox for messages that match the given searching
+// criteria. Searching criteria consist of one or more search keys. The response
+// contains a list of message sequence IDs corresponding to those messages that
+// match the searching criteria. When multiple keys are specified, the result is
+// the intersection (AND function) of all the messages that match those keys.
+// Criteria must be UTF-8 encoded. See RFC 3501 section 6.4.4 for a list of
+// searching criteria. When no criteria has been set, all messages in the mailbox
+// will be searched using ALL criteria.
+func (c *Client) Search(criteria *imap.SearchCriteria) (seqNums []uint32, err error) {
+ return c.search(false, criteria)
+}
+
+// UidSearch is identical to Search, but UIDs are returned instead of message
+// sequence numbers.
+func (c *Client) UidSearch(criteria *imap.SearchCriteria) (uids []uint32, err error) {
+ return c.search(true, criteria)
+}
+
+func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
+ defer close(ch)
+
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ var cmd imap.Commander = &commands.Fetch{
+ SeqSet: seqset,
+ Items: items,
+ }
+ if uid {
+ cmd = &commands.Uid{Cmd: cmd}
+ }
+
+ res := &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
+
+ status, err := c.execute(cmd, res)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Fetch retrieves data associated with a message in the mailbox. See RFC 3501
+// section 6.4.5 for a list of items that can be requested.
+func (c *Client) Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
+ return c.fetch(false, seqset, items, ch)
+}
+
+// UidFetch is identical to Fetch, but seqset is interpreted as containing
+// unique identifiers instead of message sequence numbers.
+func (c *Client) UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error {
+ return c.fetch(true, seqset, items, ch)
+}
+
+func (c *Client) store(uid bool, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
+ if ch != nil {
+ defer close(ch)
+ }
+
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ // TODO: this could break extensions (this only works when item is FLAGS)
+ if fields, ok := value.([]interface{}); ok {
+ for i, field := range fields {
+ if s, ok := field.(string); ok {
+ fields[i] = imap.RawString(s)
+ }
+ }
+ }
+
+ // If ch is nil, the updated values are data which will be lost, so don't
+ // retrieve it.
+ if ch == nil {
+ op, _, err := imap.ParseFlagsOp(item)
+ if err == nil {
+ item = imap.FormatFlagsOp(op, true)
+ }
+ }
+
+ var cmd imap.Commander = &commands.Store{
+ SeqSet: seqset,
+ Item: item,
+ Value: value,
+ }
+ if uid {
+ cmd = &commands.Uid{Cmd: cmd}
+ }
+
+ var h responses.Handler
+ if ch != nil {
+ h = &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
+ }
+
+ status, err := c.execute(cmd, h)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Store alters data associated with a message in the mailbox. If ch is not nil,
+// the updated value of the data will be sent to this channel. See RFC 3501
+// section 6.4.6 for a list of items that can be updated.
+func (c *Client) Store(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
+ return c.store(false, seqset, item, value, ch)
+}
+
+// UidStore is identical to Store, but seqset is interpreted as containing
+// unique identifiers instead of message sequence numbers.
+func (c *Client) UidStore(seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error {
+ return c.store(true, seqset, item, value, ch)
+}
+
+func (c *Client) copy(uid bool, seqset *imap.SeqSet, dest string) error {
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ var cmd imap.Commander = &commands.Copy{
+ SeqSet: seqset,
+ Mailbox: dest,
+ }
+ if uid {
+ cmd = &commands.Uid{Cmd: cmd}
+ }
+
+ status, err := c.execute(cmd, nil)
+ if err != nil {
+ return err
+ }
+ return status.Err()
+}
+
+// Copy copies the specified message(s) to the end of the specified destination
+// mailbox.
+func (c *Client) Copy(seqset *imap.SeqSet, dest string) error {
+ return c.copy(false, seqset, dest)
+}
+
+// UidCopy is identical to Copy, but seqset is interpreted as containing unique
+// identifiers instead of message sequence numbers.
+func (c *Client) UidCopy(seqset *imap.SeqSet, dest string) error {
+ return c.copy(true, seqset, dest)
+}
+
+func (c *Client) move(uid bool, seqset *imap.SeqSet, dest string) error {
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ if ok, err := c.Support("MOVE"); err != nil {
+ return err
+ } else if !ok {
+ return c.moveFallback(uid, seqset, dest)
+ }
+
+ var cmd imap.Commander = &commands.Move{
+ SeqSet: seqset,
+ Mailbox: dest,
+ }
+ if uid {
+ cmd = &commands.Uid{Cmd: cmd}
+ }
+
+ if status, err := c.Execute(cmd, nil); err != nil {
+ return err
+ } else {
+ return status.Err()
+ }
+}
+
+// moveFallback uses COPY, STORE and EXPUNGE for servers which don't support
+// MOVE.
+func (c *Client) moveFallback(uid bool, seqset *imap.SeqSet, dest string) error {
+ item := imap.FormatFlagsOp(imap.AddFlags, true)
+ flags := []interface{}{imap.DeletedFlag}
+ if uid {
+ if err := c.UidCopy(seqset, dest); err != nil {
+ return err
+ }
+
+ if err := c.UidStore(seqset, item, flags, nil); err != nil {
+ return err
+ }
+ } else {
+ if err := c.Copy(seqset, dest); err != nil {
+ return err
+ }
+
+ if err := c.Store(seqset, item, flags, nil); err != nil {
+ return err
+ }
+ }
+
+ return c.Expunge(nil)
+}
+
+// Move moves the specified message(s) to the end of the specified destination
+// mailbox.
+//
+// If the server doesn't support the MOVE extension defined in RFC 6851,
+// go-imap will fallback to copy, store and expunge.
+func (c *Client) Move(seqset *imap.SeqSet, dest string) error {
+ return c.move(false, seqset, dest)
+}
+
+// UidMove is identical to Move, but seqset is interpreted as containing unique
+// identifiers instead of message sequence numbers.
+func (c *Client) UidMove(seqset *imap.SeqSet, dest string) error {
+ return c.move(true, seqset, dest)
+}
+
+// Unselect frees server's resources associated with the selected mailbox and
+// returns the server to the authenticated state. This command performs the same
+// actions as Close, except that no messages are permanently removed from the
+// currently selected mailbox.
+//
+// If client does not support the UNSELECT extension, ErrExtensionUnsupported
+// is returned.
+func (c *Client) Unselect() error {
+ if ok, err := c.Support("UNSELECT"); !ok || err != nil {
+ return ErrExtensionUnsupported
+ }
+
+ if c.State() != imap.SelectedState {
+ return ErrNoMailboxSelected
+ }
+
+ cmd := &commands.Unselect{}
+ if status, err := c.Execute(cmd, nil); err != nil {
+ return err
+ } else if err := status.Err(); err != nil {
+ return err
+ }
+
+ c.SetState(imap.AuthenticatedState, nil)
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/client/tag.go b/vendor/github.com/emersion/go-imap/client/tag.go
new file mode 100644
index 0000000..01526ab
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/client/tag.go
@@ -0,0 +1,24 @@
+package client
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+)
+
+func randomString(n int) (string, error) {
+ b := make([]byte, n)
+ _, err := rand.Read(b)
+ if err != nil {
+ return "", err
+ }
+
+ return base64.RawURLEncoding.EncodeToString(b), nil
+}
+
+func generateTag() string {
+ tag, err := randomString(4)
+ if err != nil {
+ panic(err)
+ }
+ return tag
+}
diff --git a/vendor/github.com/emersion/go-imap/command.go b/vendor/github.com/emersion/go-imap/command.go
new file mode 100644
index 0000000..dac2696
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/command.go
@@ -0,0 +1,57 @@
+package imap
+
+import (
+ "errors"
+ "strings"
+)
+
+// 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. If empty,
+ // the command is untagged.
+ Tag string
+ // The command name.
+ Name string
+ // The command arguments.
+ Arguments []interface{}
+}
+
+// Implements the Commander interface.
+func (cmd *Command) Command() *Command {
+ return cmd
+}
+
+func (cmd *Command) WriteTo(w *Writer) error {
+ tag := cmd.Tag
+ if tag == "" {
+ tag = "*"
+ }
+
+ fields := []interface{}{RawString(tag), RawString(cmd.Name)}
+ fields = append(fields, cmd.Arguments...)
+ return w.writeLine(fields...)
+}
+
+// Parse a command from fields.
+func (cmd *Command) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("imap: cannot parse command: no enough fields")
+ }
+
+ var ok bool
+ if cmd.Tag, ok = fields[0].(string); !ok {
+ return errors.New("imap: cannot parse command: invalid tag")
+ }
+ if cmd.Name, ok = fields[1].(string); !ok {
+ return errors.New("imap: cannot parse command: invalid name")
+ }
+ cmd.Name = strings.ToUpper(cmd.Name) // Command names are case-insensitive
+
+ cmd.Arguments = fields[2:]
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/append.go b/vendor/github.com/emersion/go-imap/commands/append.go
new file mode 100644
index 0000000..d70b584
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/append.go
@@ -0,0 +1,93 @@
+package commands
+
+import (
+ "errors"
+ "time"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Append is an APPEND command, as defined in RFC 3501 section 6.3.11.
+type Append struct {
+ Mailbox string
+ Flags []string
+ Date time.Time
+ Message imap.Literal
+}
+
+func (cmd *Append) Command() *imap.Command {
+ var args []interface{}
+
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+ args = append(args, imap.FormatMailboxName(mailbox))
+
+ if cmd.Flags != nil {
+ flags := make([]interface{}, len(cmd.Flags))
+ for i, flag := range cmd.Flags {
+ flags[i] = imap.RawString(flag)
+ }
+ args = append(args, flags)
+ }
+
+ if !cmd.Date.IsZero() {
+ args = append(args, cmd.Date)
+ }
+
+ args = append(args, cmd.Message)
+
+ return &imap.Command{
+ Name: "APPEND",
+ Arguments: args,
+ }
+}
+
+func (cmd *Append) Parse(fields []interface{}) (err error) {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ // Parse mailbox name
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ // Parse message literal
+ litIndex := len(fields) - 1
+ var ok bool
+ if cmd.Message, ok = fields[litIndex].(imap.Literal); !ok {
+ return errors.New("Message must be a literal")
+ }
+
+ // Remaining fields a optional
+ fields = fields[1:litIndex]
+ if len(fields) > 0 {
+ // Parse flags list
+ if flags, ok := fields[0].([]interface{}); ok {
+ if cmd.Flags, err = imap.ParseStringList(flags); err != nil {
+ return err
+ }
+
+ for i, flag := range cmd.Flags {
+ cmd.Flags[i] = imap.CanonicalFlag(flag)
+ }
+
+ fields = fields[1:]
+ }
+
+ // Parse date
+ if len(fields) > 0 {
+ if date, ok := fields[0].(string); !ok {
+ return errors.New("Date must be a string")
+ } else if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
+ return err
+ }
+ }
+ }
+
+ return
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/authenticate.go b/vendor/github.com/emersion/go-imap/commands/authenticate.go
new file mode 100644
index 0000000..b66f21f
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/authenticate.go
@@ -0,0 +1,124 @@
+package commands
+
+import (
+ "bufio"
+ "encoding/base64"
+ "errors"
+ "io"
+ "strings"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-sasl"
+)
+
+// AuthenticateConn is a connection that supports IMAP authentication.
+type AuthenticateConn interface {
+ io.Reader
+
+ // WriteResp writes an IMAP response to this connection.
+ WriteResp(res imap.WriterTo) error
+}
+
+// Authenticate is an AUTHENTICATE command, as defined in RFC 3501 section
+// 6.2.2.
+type Authenticate struct {
+ Mechanism string
+ InitialResponse []byte
+}
+
+func (cmd *Authenticate) Command() *imap.Command {
+ args := []interface{}{imap.RawString(cmd.Mechanism)}
+ if cmd.InitialResponse != nil {
+ var encodedResponse string
+ if len(cmd.InitialResponse) == 0 {
+ // Empty initial response should be encoded as "=", not empty
+ // string.
+ encodedResponse = "="
+ } else {
+ encodedResponse = base64.StdEncoding.EncodeToString(cmd.InitialResponse)
+ }
+
+ args = append(args, imap.RawString(encodedResponse))
+ }
+ return &imap.Command{
+ Name: "AUTHENTICATE",
+ Arguments: args,
+ }
+}
+
+func (cmd *Authenticate) Parse(fields []interface{}) error {
+ if len(fields) < 1 {
+ return errors.New("Not enough arguments")
+ }
+
+ var ok bool
+ if cmd.Mechanism, ok = fields[0].(string); !ok {
+ return errors.New("Mechanism must be a string")
+ }
+ cmd.Mechanism = strings.ToUpper(cmd.Mechanism)
+
+ if len(fields) != 2 {
+ return nil
+ }
+
+ encodedResponse, ok := fields[1].(string)
+ if !ok {
+ return errors.New("Initial response must be a string")
+ }
+ if encodedResponse == "=" {
+ cmd.InitialResponse = []byte{}
+ return nil
+ }
+
+ var err error
+ cmd.InitialResponse, err = base64.StdEncoding.DecodeString(encodedResponse)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn AuthenticateConn) error {
+ sasl, ok := mechanisms[cmd.Mechanism]
+ if !ok {
+ return errors.New("Unsupported mechanism")
+ }
+
+ scanner := bufio.NewScanner(conn)
+
+ response := cmd.InitialResponse
+ for {
+ challenge, done, err := sasl.Next(response)
+ if err != nil || done {
+ return err
+ }
+
+ encoded := base64.StdEncoding.EncodeToString(challenge)
+ cont := &imap.ContinuationReq{Info: encoded}
+ if err := conn.WriteResp(cont); err != nil {
+ return err
+ }
+
+ if !scanner.Scan() {
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+ return errors.New("unexpected EOF")
+ }
+
+ encoded = scanner.Text()
+ if encoded != "" {
+ if encoded == "*" {
+ return &imap.ErrStatusResp{Resp: &imap.StatusResp{
+ Type: imap.StatusRespBad,
+ Info: "negotiation cancelled",
+ }}
+ }
+ response, err = base64.StdEncoding.DecodeString(encoded)
+ if err != nil {
+ return err
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/capability.go b/vendor/github.com/emersion/go-imap/commands/capability.go
new file mode 100644
index 0000000..3359c0a
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/capability.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Capability is a CAPABILITY command, as defined in RFC 3501 section 6.1.1.
+type Capability struct{}
+
+func (c *Capability) Command() *imap.Command {
+ return &imap.Command{
+ Name: "CAPABILITY",
+ }
+}
+
+func (c *Capability) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/check.go b/vendor/github.com/emersion/go-imap/commands/check.go
new file mode 100644
index 0000000..b90df7c
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/check.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Check is a CHECK command, as defined in RFC 3501 section 6.4.1.
+type Check struct{}
+
+func (cmd *Check) Command() *imap.Command {
+ return &imap.Command{
+ Name: "CHECK",
+ }
+}
+
+func (cmd *Check) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/close.go b/vendor/github.com/emersion/go-imap/commands/close.go
new file mode 100644
index 0000000..cc60658
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/close.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Close is a CLOSE command, as defined in RFC 3501 section 6.4.2.
+type Close struct{}
+
+func (cmd *Close) Command() *imap.Command {
+ return &imap.Command{
+ Name: "CLOSE",
+ }
+}
+
+func (cmd *Close) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/commands.go b/vendor/github.com/emersion/go-imap/commands/commands.go
new file mode 100644
index 0000000..a62b248
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/commands.go
@@ -0,0 +1,2 @@
+// Package commands implements IMAP commands defined in RFC 3501.
+package commands
diff --git a/vendor/github.com/emersion/go-imap/commands/copy.go b/vendor/github.com/emersion/go-imap/commands/copy.go
new file mode 100644
index 0000000..5258f35
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/copy.go
@@ -0,0 +1,47 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Copy is a COPY command, as defined in RFC 3501 section 6.4.7.
+type Copy struct {
+ SeqSet *imap.SeqSet
+ Mailbox string
+}
+
+func (cmd *Copy) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "COPY",
+ Arguments: []interface{}{cmd.SeqSet, imap.FormatMailboxName(mailbox)},
+ }
+}
+
+func (cmd *Copy) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ if seqSet, ok := fields[0].(string); !ok {
+ return errors.New("Invalid sequence set")
+ } else if seqSet, err := imap.ParseSeqSet(seqSet); err != nil {
+ return err
+ } else {
+ cmd.SeqSet = seqSet
+ }
+
+ if mailbox, err := imap.ParseString(fields[1]); err != nil {
+ return err
+ } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/create.go b/vendor/github.com/emersion/go-imap/commands/create.go
new file mode 100644
index 0000000..a1e6fe2
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/create.go
@@ -0,0 +1,38 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Create is a CREATE command, as defined in RFC 3501 section 6.3.3.
+type Create struct {
+ Mailbox string
+}
+
+func (cmd *Create) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "CREATE",
+ Arguments: []interface{}{mailbox},
+ }
+}
+
+func (cmd *Create) Parse(fields []interface{}) error {
+ if len(fields) < 1 {
+ return errors.New("No enough arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/delete.go b/vendor/github.com/emersion/go-imap/commands/delete.go
new file mode 100644
index 0000000..60f4da8
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/delete.go
@@ -0,0 +1,38 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Delete is a DELETE command, as defined in RFC 3501 section 6.3.3.
+type Delete struct {
+ Mailbox string
+}
+
+func (cmd *Delete) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "DELETE",
+ Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
+ }
+}
+
+func (cmd *Delete) Parse(fields []interface{}) error {
+ if len(fields) < 1 {
+ return errors.New("No enough arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/enable.go b/vendor/github.com/emersion/go-imap/commands/enable.go
new file mode 100644
index 0000000..980195e
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/enable.go
@@ -0,0 +1,23 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// An ENABLE command, defined in RFC 5161 section 3.1.
+type Enable struct {
+ Caps []string
+}
+
+func (cmd *Enable) Command() *imap.Command {
+ return &imap.Command{
+ Name: "ENABLE",
+ Arguments: imap.FormatStringList(cmd.Caps),
+ }
+}
+
+func (cmd *Enable) Parse(fields []interface{}) error {
+ var err error
+ cmd.Caps, err = imap.ParseStringList(fields)
+ return err
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/expunge.go b/vendor/github.com/emersion/go-imap/commands/expunge.go
new file mode 100644
index 0000000..af550a4
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/expunge.go
@@ -0,0 +1,16 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Expunge is an EXPUNGE command, as defined in RFC 3501 section 6.4.3.
+type Expunge struct{}
+
+func (cmd *Expunge) Command() *imap.Command {
+ return &imap.Command{Name: "EXPUNGE"}
+}
+
+func (cmd *Expunge) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/fetch.go b/vendor/github.com/emersion/go-imap/commands/fetch.go
new file mode 100644
index 0000000..4eb3ab9
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/fetch.go
@@ -0,0 +1,63 @@
+package commands
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/emersion/go-imap"
+)
+
+// Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5.
+type Fetch struct {
+ SeqSet *imap.SeqSet
+ Items []imap.FetchItem
+}
+
+func (cmd *Fetch) Command() *imap.Command {
+ // Handle FETCH macros separately as they should not be serialized within parentheses
+ if len(cmd.Items) == 1 && (cmd.Items[0] == imap.FetchAll || cmd.Items[0] == imap.FetchFast || cmd.Items[0] == imap.FetchFull) {
+ return &imap.Command{
+ Name: "FETCH",
+ Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Items[0])},
+ }
+ } else {
+ items := make([]interface{}, len(cmd.Items))
+ for i, item := range cmd.Items {
+ items[i] = imap.RawString(item)
+ }
+
+ return &imap.Command{
+ Name: "FETCH",
+ Arguments: []interface{}{cmd.SeqSet, items},
+ }
+ }
+}
+
+func (cmd *Fetch) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ var err error
+ if seqset, ok := fields[0].(string); !ok {
+ return errors.New("Sequence set must be an atom")
+ } else if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
+ return err
+ }
+
+ switch items := fields[1].(type) {
+ case string: // A macro or a single item
+ cmd.Items = imap.FetchItem(strings.ToUpper(items)).Expand()
+ case []interface{}: // A list of items
+ cmd.Items = make([]imap.FetchItem, 0, len(items))
+ for _, v := range items {
+ itemStr, _ := v.(string)
+ item := imap.FetchItem(strings.ToUpper(itemStr))
+ cmd.Items = append(cmd.Items, item.Expand()...)
+ }
+ default:
+ return errors.New("Items must be either a string or a list")
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/idle.go b/vendor/github.com/emersion/go-imap/commands/idle.go
new file mode 100644
index 0000000..4d9669f
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/idle.go
@@ -0,0 +1,17 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// An IDLE command.
+// Se RFC 2177 section 3.
+type Idle struct{}
+
+func (cmd *Idle) Command() *imap.Command {
+ return &imap.Command{Name: "IDLE"}
+}
+
+func (cmd *Idle) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/list.go b/vendor/github.com/emersion/go-imap/commands/list.go
new file mode 100644
index 0000000..52686e9
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/list.go
@@ -0,0 +1,60 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// List is a LIST command, as defined in RFC 3501 section 6.3.8. If Subscribed
+// is set to true, LSUB will be used instead.
+type List struct {
+ Reference string
+ Mailbox string
+
+ Subscribed bool
+}
+
+func (cmd *List) Command() *imap.Command {
+ name := "LIST"
+ if cmd.Subscribed {
+ name = "LSUB"
+ }
+
+ enc := utf7.Encoding.NewEncoder()
+ ref, _ := enc.String(cmd.Reference)
+ mailbox, _ := enc.String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: name,
+ Arguments: []interface{}{ref, mailbox},
+ }
+}
+
+func (cmd *List) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ dec := utf7.Encoding.NewDecoder()
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err := dec.String(mailbox); err != nil {
+ return err
+ } else {
+ // TODO: canonical mailbox path
+ cmd.Reference = imap.CanonicalMailboxName(mailbox)
+ }
+
+ if mailbox, err := imap.ParseString(fields[1]); err != nil {
+ return err
+ } else if mailbox, err := dec.String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/login.go b/vendor/github.com/emersion/go-imap/commands/login.go
new file mode 100644
index 0000000..d0af0b5
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/login.go
@@ -0,0 +1,36 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+)
+
+// Login is a LOGIN command, as defined in RFC 3501 section 6.2.2.
+type Login struct {
+ Username string
+ Password string
+}
+
+func (cmd *Login) Command() *imap.Command {
+ return &imap.Command{
+ Name: "LOGIN",
+ Arguments: []interface{}{cmd.Username, cmd.Password},
+ }
+}
+
+func (cmd *Login) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("Not enough arguments")
+ }
+
+ var err error
+ if cmd.Username, err = imap.ParseString(fields[0]); err != nil {
+ return err
+ }
+ if cmd.Password, err = imap.ParseString(fields[1]); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/logout.go b/vendor/github.com/emersion/go-imap/commands/logout.go
new file mode 100644
index 0000000..e826719
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/logout.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Logout is a LOGOUT command, as defined in RFC 3501 section 6.1.3.
+type Logout struct{}
+
+func (c *Logout) Command() *imap.Command {
+ return &imap.Command{
+ Name: "LOGOUT",
+ }
+}
+
+func (c *Logout) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/move.go b/vendor/github.com/emersion/go-imap/commands/move.go
new file mode 100644
index 0000000..613a870
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/move.go
@@ -0,0 +1,48 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// A MOVE command.
+// See RFC 6851 section 3.1.
+type Move struct {
+ SeqSet *imap.SeqSet
+ Mailbox string
+}
+
+func (cmd *Move) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "MOVE",
+ Arguments: []interface{}{cmd.SeqSet, mailbox},
+ }
+}
+
+func (cmd *Move) Parse(fields []interface{}) (err error) {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ seqset, ok := fields[0].(string)
+ if !ok {
+ return errors.New("Invalid sequence set")
+ }
+ if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
+ return err
+ }
+
+ mailbox, ok := fields[1].(string)
+ if !ok {
+ return errors.New("Mailbox name must be a string")
+ }
+ if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ }
+
+ return
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/noop.go b/vendor/github.com/emersion/go-imap/commands/noop.go
new file mode 100644
index 0000000..da6a1c2
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/noop.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// Noop is a NOOP command, as defined in RFC 3501 section 6.1.2.
+type Noop struct{}
+
+func (c *Noop) Command() *imap.Command {
+ return &imap.Command{
+ Name: "NOOP",
+ }
+}
+
+func (c *Noop) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/rename.go b/vendor/github.com/emersion/go-imap/commands/rename.go
new file mode 100644
index 0000000..37a5fa7
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/rename.go
@@ -0,0 +1,51 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Rename is a RENAME command, as defined in RFC 3501 section 6.3.5.
+type Rename struct {
+ Existing string
+ New string
+}
+
+func (cmd *Rename) Command() *imap.Command {
+ enc := utf7.Encoding.NewEncoder()
+ existingName, _ := enc.String(cmd.Existing)
+ newName, _ := enc.String(cmd.New)
+
+ return &imap.Command{
+ Name: "RENAME",
+ Arguments: []interface{}{imap.FormatMailboxName(existingName), imap.FormatMailboxName(newName)},
+ }
+}
+
+func (cmd *Rename) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ dec := utf7.Encoding.NewDecoder()
+
+ if existingName, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if existingName, err := dec.String(existingName); err != nil {
+ return err
+ } else {
+ cmd.Existing = imap.CanonicalMailboxName(existingName)
+ }
+
+ if newName, err := imap.ParseString(fields[1]); err != nil {
+ return err
+ } else if newName, err := dec.String(newName); err != nil {
+ return err
+ } else {
+ cmd.New = imap.CanonicalMailboxName(newName)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/search.go b/vendor/github.com/emersion/go-imap/commands/search.go
new file mode 100644
index 0000000..72f026c
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/search.go
@@ -0,0 +1,57 @@
+package commands
+
+import (
+ "errors"
+ "io"
+ "strings"
+
+ "github.com/emersion/go-imap"
+)
+
+// Search is a SEARCH command, as defined in RFC 3501 section 6.4.4.
+type Search struct {
+ Charset string
+ Criteria *imap.SearchCriteria
+}
+
+func (cmd *Search) Command() *imap.Command {
+ var args []interface{}
+ if cmd.Charset != "" {
+ args = append(args, imap.RawString("CHARSET"), imap.RawString(cmd.Charset))
+ }
+ args = append(args, cmd.Criteria.Format()...)
+
+ return &imap.Command{
+ Name: "SEARCH",
+ Arguments: args,
+ }
+}
+
+func (cmd *Search) Parse(fields []interface{}) error {
+ if len(fields) == 0 {
+ return errors.New("Missing search criteria")
+ }
+
+ // Parse charset
+ if f, ok := fields[0].(string); ok && strings.EqualFold(f, "CHARSET") {
+ if len(fields) < 2 {
+ return errors.New("Missing CHARSET value")
+ }
+ if cmd.Charset, ok = fields[1].(string); !ok {
+ return errors.New("Charset must be a string")
+ }
+ fields = fields[2:]
+ }
+
+ var charsetReader func(io.Reader) io.Reader
+ charset := strings.ToLower(cmd.Charset)
+ if charset != "utf-8" && charset != "us-ascii" && charset != "" {
+ charsetReader = func(r io.Reader) io.Reader {
+ r, _ = imap.CharsetReader(charset, r)
+ return r
+ }
+ }
+
+ cmd.Criteria = new(imap.SearchCriteria)
+ return cmd.Criteria.ParseWithCharset(fields, charsetReader)
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/select.go b/vendor/github.com/emersion/go-imap/commands/select.go
new file mode 100644
index 0000000..e881eff
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/select.go
@@ -0,0 +1,45 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Select is a SELECT command, as defined in RFC 3501 section 6.3.1. If ReadOnly
+// is set to true, the EXAMINE command will be used instead.
+type Select struct {
+ Mailbox string
+ ReadOnly bool
+}
+
+func (cmd *Select) Command() *imap.Command {
+ name := "SELECT"
+ if cmd.ReadOnly {
+ name = "EXAMINE"
+ }
+
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: name,
+ Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
+ }
+}
+
+func (cmd *Select) Parse(fields []interface{}) error {
+ if len(fields) < 1 {
+ return errors.New("No enough arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/starttls.go b/vendor/github.com/emersion/go-imap/commands/starttls.go
new file mode 100644
index 0000000..d900e5e
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/starttls.go
@@ -0,0 +1,18 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// StartTLS is a STARTTLS command, as defined in RFC 3501 section 6.2.1.
+type StartTLS struct{}
+
+func (cmd *StartTLS) Command() *imap.Command {
+ return &imap.Command{
+ Name: "STARTTLS",
+ }
+}
+
+func (cmd *StartTLS) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/status.go b/vendor/github.com/emersion/go-imap/commands/status.go
new file mode 100644
index 0000000..672dce5
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/status.go
@@ -0,0 +1,58 @@
+package commands
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Status is a STATUS command, as defined in RFC 3501 section 6.3.10.
+type Status struct {
+ Mailbox string
+ Items []imap.StatusItem
+}
+
+func (cmd *Status) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ items := make([]interface{}, len(cmd.Items))
+ for i, item := range cmd.Items {
+ items[i] = imap.RawString(item)
+ }
+
+ return &imap.Command{
+ Name: "STATUS",
+ Arguments: []interface{}{imap.FormatMailboxName(mailbox), items},
+ }
+}
+
+func (cmd *Status) Parse(fields []interface{}) error {
+ if len(fields) < 2 {
+ return errors.New("No enough arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ } else {
+ cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
+ }
+
+ items, ok := fields[1].([]interface{})
+ if !ok {
+ return errors.New("STATUS command parameter is not a list")
+ }
+ cmd.Items = make([]imap.StatusItem, len(items))
+ for i, f := range items {
+ if s, ok := f.(string); !ok {
+ return errors.New("Got a non-string field in a STATUS command parameter")
+ } else {
+ cmd.Items[i] = imap.StatusItem(strings.ToUpper(s))
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/store.go b/vendor/github.com/emersion/go-imap/commands/store.go
new file mode 100644
index 0000000..aeee3e6
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/store.go
@@ -0,0 +1,50 @@
+package commands
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/emersion/go-imap"
+)
+
+// Store is a STORE command, as defined in RFC 3501 section 6.4.6.
+type Store struct {
+ SeqSet *imap.SeqSet
+ Item imap.StoreItem
+ Value interface{}
+}
+
+func (cmd *Store) Command() *imap.Command {
+ return &imap.Command{
+ Name: "STORE",
+ Arguments: []interface{}{cmd.SeqSet, imap.RawString(cmd.Item), cmd.Value},
+ }
+}
+
+func (cmd *Store) Parse(fields []interface{}) error {
+ if len(fields) < 3 {
+ return errors.New("No enough arguments")
+ }
+
+ seqset, ok := fields[0].(string)
+ if !ok {
+ return errors.New("Invalid sequence set")
+ }
+ var err error
+ if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
+ return err
+ }
+
+ if item, ok := fields[1].(string); !ok {
+ return errors.New("Item name must be a string")
+ } else {
+ cmd.Item = imap.StoreItem(strings.ToUpper(item))
+ }
+
+ if len(fields[2:]) == 1 {
+ cmd.Value = fields[2]
+ } else {
+ cmd.Value = fields[2:]
+ }
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/subscribe.go b/vendor/github.com/emersion/go-imap/commands/subscribe.go
new file mode 100644
index 0000000..6540969
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/subscribe.go
@@ -0,0 +1,63 @@
+package commands
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+// Subscribe is a SUBSCRIBE command, as defined in RFC 3501 section 6.3.6.
+type Subscribe struct {
+ Mailbox string
+}
+
+func (cmd *Subscribe) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "SUBSCRIBE",
+ Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
+ }
+}
+
+func (cmd *Subscribe) Parse(fields []interface{}) error {
+ if len(fields) < 0 {
+ return errors.New("No enough arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ }
+ return nil
+}
+
+// An UNSUBSCRIBE command.
+// See RFC 3501 section 6.3.7
+type Unsubscribe struct {
+ Mailbox string
+}
+
+func (cmd *Unsubscribe) Command() *imap.Command {
+ mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
+
+ return &imap.Command{
+ Name: "UNSUBSCRIBE",
+ Arguments: []interface{}{imap.FormatMailboxName(mailbox)},
+ }
+}
+
+func (cmd *Unsubscribe) Parse(fields []interface{}) error {
+ if len(fields) < 0 {
+ return errors.New("No enogh arguments")
+ }
+
+ if mailbox, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/uid.go b/vendor/github.com/emersion/go-imap/commands/uid.go
new file mode 100644
index 0000000..979af14
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/uid.go
@@ -0,0 +1,44 @@
+package commands
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/emersion/go-imap"
+)
+
+// Uid is a UID command, as defined in RFC 3501 section 6.4.8. It wraps another
+// command (e.g. wrapping a Fetch command will result in a UID FETCH).
+type Uid struct {
+ Cmd imap.Commander
+}
+
+func (cmd *Uid) Command() *imap.Command {
+ inner := cmd.Cmd.Command()
+
+ args := []interface{}{imap.RawString(inner.Name)}
+ args = append(args, inner.Arguments...)
+
+ return &imap.Command{
+ Name: "UID",
+ Arguments: args,
+ }
+}
+
+func (cmd *Uid) Parse(fields []interface{}) error {
+ if len(fields) < 0 {
+ return errors.New("No command name specified")
+ }
+
+ name, ok := fields[0].(string)
+ if !ok {
+ return errors.New("Command name must be a string")
+ }
+
+ cmd.Cmd = &imap.Command{
+ Name: strings.ToUpper(name), // Command names are case-insensitive
+ Arguments: fields[1:],
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/commands/unselect.go b/vendor/github.com/emersion/go-imap/commands/unselect.go
new file mode 100644
index 0000000..da5c63d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/commands/unselect.go
@@ -0,0 +1,17 @@
+package commands
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// An UNSELECT command.
+// See RFC 3691 section 2.
+type Unselect struct{}
+
+func (cmd *Unselect) Command() *imap.Command {
+ return &imap.Command{Name: "UNSELECT"}
+}
+
+func (cmd *Unselect) Parse(fields []interface{}) error {
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/conn.go b/vendor/github.com/emersion/go-imap/conn.go
new file mode 100644
index 0000000..09ce633
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/conn.go
@@ -0,0 +1,284 @@
+package imap
+
+import (
+ "bufio"
+ "crypto/tls"
+ "io"
+ "net"
+ "sync"
+)
+
+// A connection state.
+// See RFC 3501 section 3.
+type ConnState int
+
+const (
+ // In the connecting state, the server has not yet sent a greeting and no
+ // command can be issued.
+ ConnectingState = 0
+
+ // In the not authenticated state, the client MUST supply
+ // authentication credentials before most commands will be
+ // permitted. This state is entered when a connection starts
+ // unless the connection has been pre-authenticated.
+ NotAuthenticatedState ConnState = 1 << 0
+
+ // In the authenticated state, the client is authenticated and MUST
+ // select a mailbox to access before commands that affect messages
+ // will be permitted. This state is entered when a
+ // pre-authenticated connection starts, when acceptable
+ // authentication credentials have been provided, after an error in
+ // selecting a mailbox, or after a successful CLOSE command.
+ AuthenticatedState = 1 << 1
+
+ // In a selected state, a mailbox has been selected to access.
+ // This state is entered when a mailbox has been successfully
+ // selected.
+ SelectedState = AuthenticatedState + 1<<2
+
+ // In the logout state, the connection is being terminated. This
+ // state can be entered as a result of a client request (via the
+ // LOGOUT command) or by unilateral action on the part of either
+ // the client or server.
+ LogoutState = 1 << 3
+
+ // ConnectedState is either NotAuthenticatedState, AuthenticatedState or
+ // SelectedState.
+ ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
+)
+
+// A function that upgrades a connection.
+//
+// This should only be used by libraries implementing an IMAP extension (e.g.
+// COMPRESS).
+type ConnUpgrader func(conn net.Conn) (net.Conn, error)
+
+type Waiter struct {
+ start sync.WaitGroup
+ end sync.WaitGroup
+ finished bool
+}
+
+func NewWaiter() *Waiter {
+ w := &Waiter{finished: false}
+ w.start.Add(1)
+ w.end.Add(1)
+ return w
+}
+
+func (w *Waiter) Wait() {
+ if !w.finished {
+ // Signal that we are ready for upgrade to continue.
+ w.start.Done()
+ // Wait for upgrade to finish.
+ w.end.Wait()
+ w.finished = true
+ }
+}
+
+func (w *Waiter) WaitReady() {
+ if !w.finished {
+ // Wait for reader/writer goroutine to be ready for upgrade.
+ w.start.Wait()
+ }
+}
+
+func (w *Waiter) Close() {
+ if !w.finished {
+ // Upgrade is finished, close chanel to release reader/writer
+ w.end.Done()
+ }
+}
+
+type LockedWriter struct {
+ lock sync.Mutex
+ writer io.Writer
+}
+
+// NewLockedWriter - goroutine safe writer.
+func NewLockedWriter(w io.Writer) io.Writer {
+ return &LockedWriter{writer: w}
+}
+
+func (w *LockedWriter) Write(b []byte) (int, error) {
+ w.lock.Lock()
+ defer w.lock.Unlock()
+ return w.writer.Write(b)
+}
+
+type debugWriter struct {
+ io.Writer
+
+ local io.Writer
+ remote io.Writer
+}
+
+// NewDebugWriter creates a new io.Writer that will write local network activity
+// to local and remote network activity to remote.
+func NewDebugWriter(local, remote io.Writer) io.Writer {
+ return &debugWriter{Writer: local, local: local, remote: remote}
+}
+
+type multiFlusher struct {
+ flushers []flusher
+}
+
+func (mf *multiFlusher) Flush() error {
+ for _, f := range mf.flushers {
+ if err := f.Flush(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func newMultiFlusher(flushers ...flusher) flusher {
+ return &multiFlusher{flushers}
+}
+
+// Underlying connection state information.
+type ConnInfo struct {
+ RemoteAddr net.Addr
+ LocalAddr net.Addr
+
+ // nil if connection is not using TLS.
+ TLS *tls.ConnectionState
+}
+
+// An IMAP connection.
+type Conn struct {
+ net.Conn
+ *Reader
+ *Writer
+
+ br *bufio.Reader
+ bw *bufio.Writer
+
+ waiter *Waiter
+
+ // Print all commands and responses to this io.Writer.
+ debug io.Writer
+}
+
+// NewConn creates a new IMAP connection.
+func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
+ c := &Conn{Conn: conn, Reader: r, Writer: w}
+
+ c.init()
+ return c
+}
+
+func (c *Conn) createWaiter() *Waiter {
+ // create new waiter each time.
+ w := NewWaiter()
+ c.waiter = w
+ return w
+}
+
+func (c *Conn) init() {
+ r := io.Reader(c.Conn)
+ w := io.Writer(c.Conn)
+
+ if c.debug != nil {
+ localDebug, remoteDebug := c.debug, c.debug
+ if debug, ok := c.debug.(*debugWriter); ok {
+ localDebug, remoteDebug = debug.local, debug.remote
+ }
+ // If local and remote are the same, then we need a LockedWriter.
+ if localDebug == remoteDebug {
+ localDebug = NewLockedWriter(localDebug)
+ remoteDebug = localDebug
+ }
+
+ if localDebug != nil {
+ w = io.MultiWriter(c.Conn, localDebug)
+ }
+ if remoteDebug != nil {
+ r = io.TeeReader(c.Conn, remoteDebug)
+ }
+ }
+
+ if c.br == nil {
+ c.br = bufio.NewReader(r)
+ c.Reader.reader = c.br
+ } else {
+ c.br.Reset(r)
+ }
+
+ if c.bw == nil {
+ c.bw = bufio.NewWriter(w)
+ c.Writer.Writer = c.bw
+ } else {
+ c.bw.Reset(w)
+ }
+
+ if f, ok := c.Conn.(flusher); ok {
+ c.Writer.Writer = struct {
+ io.Writer
+ flusher
+ }{
+ c.bw,
+ newMultiFlusher(c.bw, f),
+ }
+ }
+}
+
+func (c *Conn) Info() *ConnInfo {
+ info := &ConnInfo{
+ RemoteAddr: c.RemoteAddr(),
+ LocalAddr: c.LocalAddr(),
+ }
+
+ tlsConn, ok := c.Conn.(*tls.Conn)
+ if ok {
+ state := tlsConn.ConnectionState()
+ info.TLS = &state
+ }
+
+ return info
+}
+
+// Write implements io.Writer.
+func (c *Conn) Write(b []byte) (n int, err error) {
+ return c.Writer.Write(b)
+}
+
+// Flush writes any buffered data to the underlying connection.
+func (c *Conn) Flush() error {
+ return c.Writer.Flush()
+}
+
+// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
+// tunnel.
+func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
+ // Block reads and writes during the upgrading process
+ w := c.createWaiter()
+ defer w.Close()
+
+ upgraded, err := upgrader(c.Conn)
+ if err != nil {
+ return err
+ }
+
+ c.Conn = upgraded
+ c.init()
+ return nil
+}
+
+// Called by reader/writer goroutines to wait for Upgrade to finish
+func (c *Conn) Wait() {
+ c.waiter.Wait()
+}
+
+// Called by Upgrader to wait for reader/writer goroutines to be ready for
+// upgrade.
+func (c *Conn) WaitReady() {
+ c.waiter.WaitReady()
+}
+
+// SetDebug defines an io.Writer to which all network activity will be logged.
+// If nil is provided, network activity will not be logged.
+func (c *Conn) SetDebug(w io.Writer) {
+ c.debug = w
+ c.init()
+}
diff --git a/vendor/github.com/emersion/go-imap/date.go b/vendor/github.com/emersion/go-imap/date.go
new file mode 100644
index 0000000..bf99647
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/date.go
@@ -0,0 +1,71 @@
+package imap
+
+import (
+ "fmt"
+ "regexp"
+ "time"
+)
+
+// Date and time layouts.
+// Dovecot adds a leading zero to dates:
+// https://github.com/dovecot/core/blob/4fbd5c5e113078e72f29465ccc96d44955ceadc2/src/lib-imap/imap-date.c#L166
+// Cyrus adds a leading space to dates:
+// https://github.com/cyrusimap/cyrus-imapd/blob/1cb805a3bffbdf829df0964f3b802cdc917e76db/lib/times.c#L543
+// GMail doesn't support leading spaces in dates used in SEARCH commands.
+const (
+ // Defined in RFC 3501 as date-text on page 83.
+ DateLayout = "_2-Jan-2006"
+ // Defined in RFC 3501 as date-time on page 83.
+ DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
+ // Defined in RFC 5322 section 3.3, mentioned as env-date in RFC 3501 page 84.
+ envelopeDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
+ // Use as an example in RFC 3501 page 54.
+ searchDateLayout = "2-Jan-2006"
+)
+
+// time.Time with a specific layout.
+type (
+ Date time.Time
+ DateTime time.Time
+ envelopeDateTime time.Time
+ searchDate time.Time
+)
+
+// Permutations of the layouts defined in RFC 5322, section 3.3.
+var envelopeDateTimeLayouts = [...]string{
+ envelopeDateTimeLayout, // popular, try it first
+ "_2 Jan 2006 15:04:05 -0700",
+ "_2 Jan 2006 15:04:05 MST",
+ "_2 Jan 2006 15:04 -0700",
+ "_2 Jan 2006 15:04 MST",
+ "_2 Jan 06 15:04:05 -0700",
+ "_2 Jan 06 15:04:05 MST",
+ "_2 Jan 06 15:04 -0700",
+ "_2 Jan 06 15:04 MST",
+ "Mon, _2 Jan 2006 15:04:05 -0700",
+ "Mon, _2 Jan 2006 15:04:05 MST",
+ "Mon, _2 Jan 2006 15:04 -0700",
+ "Mon, _2 Jan 2006 15:04 MST",
+ "Mon, _2 Jan 06 15:04:05 -0700",
+ "Mon, _2 Jan 06 15:04:05 MST",
+ "Mon, _2 Jan 06 15:04 -0700",
+ "Mon, _2 Jan 06 15:04 MST",
+}
+
+// TODO: this is a blunt way to strip any trailing CFWS (comment). A sharper
+// one would strip multiple CFWS, and only if really valid according to
+// RFC5322.
+var commentRE = regexp.MustCompile(`[ \t]+\(.*\)$`)
+
+// Try parsing the date based on the layouts defined in RFC 5322, section 3.3.
+// Inspired by https://github.com/golang/go/blob/master/src/net/mail/message.go
+func parseMessageDateTime(maybeDate string) (time.Time, error) {
+ maybeDate = commentRE.ReplaceAllString(maybeDate, "")
+ for _, layout := range envelopeDateTimeLayouts {
+ parsed, err := time.Parse(layout, maybeDate)
+ if err == nil {
+ return parsed, nil
+ }
+ }
+ return time.Time{}, fmt.Errorf("date %s could not be parsed", maybeDate)
+}
diff --git a/vendor/github.com/emersion/go-imap/imap.go b/vendor/github.com/emersion/go-imap/imap.go
new file mode 100644
index 0000000..837d78d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/imap.go
@@ -0,0 +1,108 @@
+// Package imap implements IMAP4rev1 (RFC 3501).
+package imap
+
+import (
+ "errors"
+ "io"
+ "strings"
+)
+
+// A StatusItem is a mailbox status data item that can be retrieved with a
+// STATUS command. See RFC 3501 section 6.3.10.
+type StatusItem string
+
+const (
+ StatusMessages StatusItem = "MESSAGES"
+ StatusRecent StatusItem = "RECENT"
+ StatusUidNext StatusItem = "UIDNEXT"
+ StatusUidValidity StatusItem = "UIDVALIDITY"
+ StatusUnseen StatusItem = "UNSEEN"
+
+ StatusAppendLimit StatusItem = "APPENDLIMIT"
+)
+
+// A FetchItem is a message data item that can be fetched.
+type FetchItem string
+
+// List of items that can be fetched.
+const (
+ // Macros
+ FetchAll FetchItem = "ALL"
+ FetchFast FetchItem = "FAST"
+ FetchFull FetchItem = "FULL"
+
+ // Items
+ FetchBody FetchItem = "BODY"
+ FetchBodyStructure FetchItem = "BODYSTRUCTURE"
+ FetchEnvelope FetchItem = "ENVELOPE"
+ FetchFlags FetchItem = "FLAGS"
+ FetchInternalDate FetchItem = "INTERNALDATE"
+ FetchRFC822 FetchItem = "RFC822"
+ FetchRFC822Header FetchItem = "RFC822.HEADER"
+ FetchRFC822Size FetchItem = "RFC822.SIZE"
+ FetchRFC822Text FetchItem = "RFC822.TEXT"
+ FetchUid FetchItem = "UID"
+)
+
+// Expand expands the item if it's a macro.
+func (item FetchItem) Expand() []FetchItem {
+ switch item {
+ case FetchAll:
+ return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope}
+ case FetchFast:
+ return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size}
+ case FetchFull:
+ return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope, FetchBody}
+ default:
+ return []FetchItem{item}
+ }
+}
+
+// FlagsOp is an operation that will be applied on message flags.
+type FlagsOp string
+
+const (
+ // SetFlags replaces existing flags by new ones.
+ SetFlags FlagsOp = "FLAGS"
+ // AddFlags adds new flags.
+ AddFlags = "+FLAGS"
+ // RemoveFlags removes existing flags.
+ RemoveFlags = "-FLAGS"
+)
+
+// silentOp can be appended to a FlagsOp to prevent the operation from
+// triggering unilateral message updates.
+const silentOp = ".SILENT"
+
+// A StoreItem is a message data item that can be updated.
+type StoreItem string
+
+// FormatFlagsOp returns the StoreItem that executes the flags operation op.
+func FormatFlagsOp(op FlagsOp, silent bool) StoreItem {
+ s := string(op)
+ if silent {
+ s += silentOp
+ }
+ return StoreItem(s)
+}
+
+// ParseFlagsOp parses a flags operation from StoreItem.
+func ParseFlagsOp(item StoreItem) (op FlagsOp, silent bool, err error) {
+ itemStr := string(item)
+ silent = strings.HasSuffix(itemStr, silentOp)
+ if silent {
+ itemStr = strings.TrimSuffix(itemStr, silentOp)
+ }
+ op = FlagsOp(itemStr)
+
+ if op != SetFlags && op != AddFlags && op != RemoveFlags {
+ err = errors.New("Unsupported STORE operation")
+ }
+ return
+}
+
+// CharsetReader, if non-nil, defines a function to generate charset-conversion
+// readers, converting from the provided charset into UTF-8. Charsets are always
+// lower-case. utf-8 and us-ascii charsets are handled by default. One of the
+// the CharsetReader's result values must be non-nil.
+var CharsetReader func(charset string, r io.Reader) (io.Reader, error)
diff --git a/vendor/github.com/emersion/go-imap/literal.go b/vendor/github.com/emersion/go-imap/literal.go
new file mode 100644
index 0000000..b5b7f55
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/literal.go
@@ -0,0 +1,13 @@
+package imap
+
+import (
+ "io"
+)
+
+// A literal, as defined in RFC 3501 section 4.3.
+type Literal interface {
+ io.Reader
+
+ // Len returns the number of bytes of the literal.
+ Len() int
+}
diff --git a/vendor/github.com/emersion/go-imap/logger.go b/vendor/github.com/emersion/go-imap/logger.go
new file mode 100644
index 0000000..fa96cdc
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/logger.go
@@ -0,0 +1,8 @@
+package imap
+
+// Logger is the behaviour used by server/client to
+// report errors for accepting connections and unexpected behavior from handlers.
+type Logger interface {
+ Printf(format string, v ...interface{})
+ Println(v ...interface{})
+}
diff --git a/vendor/github.com/emersion/go-imap/mailbox.go b/vendor/github.com/emersion/go-imap/mailbox.go
new file mode 100644
index 0000000..8f12d4d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/mailbox.go
@@ -0,0 +1,314 @@
+package imap
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/emersion/go-imap/utf7"
+)
+
+// The primary mailbox, as defined in RFC 3501 section 5.1.
+const InboxName = "INBOX"
+
+// CanonicalMailboxName returns the canonical form of a mailbox name. Mailbox names can be
+// case-sensitive or case-insensitive depending on the backend implementation.
+// The special INBOX mailbox is case-insensitive.
+func CanonicalMailboxName(name string) string {
+ if strings.EqualFold(name, InboxName) {
+ return InboxName
+ }
+ return name
+}
+
+// Mailbox attributes definied in RFC 3501 section 7.2.2.
+const (
+ // It is not possible for any child levels of hierarchy to exist under this\
+ // name; no child levels exist now and none can be created in the future.
+ NoInferiorsAttr = "\\Noinferiors"
+ // It is not possible to use this name as a selectable mailbox.
+ NoSelectAttr = "\\Noselect"
+ // The mailbox has been marked "interesting" by the server; the mailbox
+ // probably contains messages that have been added since the last time the
+ // mailbox was selected.
+ MarkedAttr = "\\Marked"
+ // The mailbox does not contain any additional messages since the last time
+ // the mailbox was selected.
+ UnmarkedAttr = "\\Unmarked"
+)
+
+// Mailbox attributes defined in RFC 6154 section 2 (SPECIAL-USE extension).
+const (
+ // This mailbox presents all messages in the user's message store.
+ AllAttr = "\\All"
+ // This mailbox is used to archive messages.
+ ArchiveAttr = "\\Archive"
+ // This mailbox is used to hold draft messages -- typically, messages that are
+ // being composed but have not yet been sent.
+ DraftsAttr = "\\Drafts"
+ // This mailbox presents all messages marked in some way as "important".
+ FlaggedAttr = "\\Flagged"
+ // This mailbox is where messages deemed to be junk mail are held.
+ JunkAttr = "\\Junk"
+ // This mailbox is used to hold copies of messages that have been sent.
+ SentAttr = "\\Sent"
+ // This mailbox is used to hold messages that have been deleted or marked for
+ // deletion.
+ TrashAttr = "\\Trash"
+)
+
+// Mailbox attributes defined in RFC 3348 (CHILDREN extension)
+const (
+ // The presence of this attribute indicates that the mailbox has child
+ // mailboxes.
+ HasChildrenAttr = "\\HasChildren"
+ // The presence of this attribute indicates that the mailbox has no child
+ // mailboxes.
+ HasNoChildrenAttr = "\\HasNoChildren"
+)
+
+// This mailbox attribute is a signal that the mailbox contains messages that
+// are likely important to the user. This attribute is defined in RFC 8457
+// section 3.
+const ImportantAttr = "\\Important"
+
+// Basic mailbox info.
+type MailboxInfo struct {
+ // The mailbox attributes.
+ Attributes []string
+ // The server's path separator.
+ Delimiter string
+ // The mailbox name.
+ Name string
+}
+
+// Parse mailbox info from fields.
+func (info *MailboxInfo) Parse(fields []interface{}) error {
+ if len(fields) < 3 {
+ return errors.New("Mailbox info needs at least 3 fields")
+ }
+
+ var err error
+ if info.Attributes, err = ParseStringList(fields[0]); err != nil {
+ return err
+ }
+
+ var ok bool
+ if info.Delimiter, ok = fields[1].(string); !ok {
+ // The delimiter may be specified as NIL, which gets converted to a nil interface.
+ if fields[1] != nil {
+ return errors.New("Mailbox delimiter must be a string")
+ }
+ info.Delimiter = ""
+ }
+
+ if name, err := ParseString(fields[2]); err != nil {
+ return err
+ } else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
+ return err
+ } else {
+ info.Name = CanonicalMailboxName(name)
+ }
+
+ return nil
+}
+
+// Format mailbox info to fields.
+func (info *MailboxInfo) Format() []interface{} {
+ name, _ := utf7.Encoding.NewEncoder().String(info.Name)
+ attrs := make([]interface{}, len(info.Attributes))
+ for i, attr := range info.Attributes {
+ attrs[i] = RawString(attr)
+ }
+
+ // If the delimiter is NIL, we need to treat it specially by inserting
+ // a nil field (so that it's later converted to an unquoted NIL atom).
+ var del interface{}
+
+ if info.Delimiter != "" {
+ del = info.Delimiter
+ }
+
+ // Thunderbird doesn't understand delimiters if not quoted
+ return []interface{}{attrs, del, FormatMailboxName(name)}
+}
+
+// TODO: optimize this
+func (info *MailboxInfo) match(name, pattern string) bool {
+ i := strings.IndexAny(pattern, "*%")
+ if i == -1 {
+ // No more wildcards
+ return name == pattern
+ }
+
+ // Get parts before and after wildcard
+ chunk, wildcard, rest := pattern[0:i], pattern[i], pattern[i+1:]
+
+ // Check that name begins with chunk
+ if len(chunk) > 0 && !strings.HasPrefix(name, chunk) {
+ return false
+ }
+ name = strings.TrimPrefix(name, chunk)
+
+ // Expand wildcard
+ var j int
+ for j = 0; j < len(name); j++ {
+ if wildcard == '%' && string(name[j]) == info.Delimiter {
+ break // Stop on delimiter if wildcard is %
+ }
+ // Try to match the rest from here
+ if info.match(name[j:], rest) {
+ return true
+ }
+ }
+
+ return info.match(name[j:], rest)
+}
+
+// Match checks if a reference and a pattern matches this mailbox name, as
+// defined in RFC 3501 section 6.3.8.
+func (info *MailboxInfo) Match(reference, pattern string) bool {
+ name := info.Name
+
+ if info.Delimiter != "" && strings.HasPrefix(pattern, info.Delimiter) {
+ reference = ""
+ pattern = strings.TrimPrefix(pattern, info.Delimiter)
+ }
+ if reference != "" {
+ if info.Delimiter != "" && !strings.HasSuffix(reference, info.Delimiter) {
+ reference += info.Delimiter
+ }
+ if !strings.HasPrefix(name, reference) {
+ return false
+ }
+ name = strings.TrimPrefix(name, reference)
+ }
+
+ return info.match(name, pattern)
+}
+
+// A mailbox status.
+type MailboxStatus struct {
+ // The mailbox name.
+ Name string
+ // True if the mailbox is open in read-only mode.
+ ReadOnly bool
+ // The mailbox items that are currently filled in. This map's values
+ // should not be used directly, they must only be used by libraries
+ // implementing extensions of the IMAP protocol.
+ Items map[StatusItem]interface{}
+
+ // The Items map may be accessed in different goroutines. Protect
+ // concurrent writes.
+ ItemsLocker sync.Mutex
+
+ // The mailbox flags.
+ Flags []string
+ // The mailbox permanent flags.
+ PermanentFlags []string
+ // The sequence number of the first unseen message in the mailbox.
+ UnseenSeqNum uint32
+
+ // The number of messages in this mailbox.
+ Messages uint32
+ // The number of messages not seen since the last time the mailbox was opened.
+ Recent uint32
+ // The number of unread messages.
+ Unseen uint32
+ // The next UID.
+ UidNext uint32
+ // Together with a UID, it is a unique identifier for a message.
+ // Must be greater than or equal to 1.
+ UidValidity uint32
+
+ // Per-mailbox limit of message size. Set only if server supports the
+ // APPENDLIMIT extension.
+ AppendLimit uint32
+}
+
+// Create a new mailbox status that will contain the specified items.
+func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
+ status := &MailboxStatus{
+ Name: name,
+ Items: make(map[StatusItem]interface{}),
+ }
+
+ for _, k := range items {
+ status.Items[k] = nil
+ }
+
+ return status
+}
+
+func (status *MailboxStatus) Parse(fields []interface{}) error {
+ status.Items = make(map[StatusItem]interface{})
+
+ var k StatusItem
+ for i, f := range fields {
+ if i%2 == 0 {
+ if kstr, ok := f.(string); !ok {
+ return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
+ } else {
+ k = StatusItem(strings.ToUpper(kstr))
+ }
+ } else {
+ status.Items[k] = nil
+
+ var err error
+ switch k {
+ case StatusMessages:
+ status.Messages, err = ParseNumber(f)
+ case StatusRecent:
+ status.Recent, err = ParseNumber(f)
+ case StatusUnseen:
+ status.Unseen, err = ParseNumber(f)
+ case StatusUidNext:
+ status.UidNext, err = ParseNumber(f)
+ case StatusUidValidity:
+ status.UidValidity, err = ParseNumber(f)
+ case StatusAppendLimit:
+ status.AppendLimit, err = ParseNumber(f)
+ default:
+ status.Items[k] = f
+ }
+
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (status *MailboxStatus) Format() []interface{} {
+ var fields []interface{}
+ for k, v := range status.Items {
+ switch k {
+ case StatusMessages:
+ v = status.Messages
+ case StatusRecent:
+ v = status.Recent
+ case StatusUnseen:
+ v = status.Unseen
+ case StatusUidNext:
+ v = status.UidNext
+ case StatusUidValidity:
+ v = status.UidValidity
+ case StatusAppendLimit:
+ v = status.AppendLimit
+ }
+
+ fields = append(fields, RawString(k), v)
+ }
+ return fields
+}
+
+func FormatMailboxName(name string) interface{} {
+ // Some e-mails servers don't handle quoted INBOX names correctly so we special-case it.
+ if strings.EqualFold(name, "INBOX") {
+ return RawString(name)
+ }
+ return name
+}
diff --git a/vendor/github.com/emersion/go-imap/message.go b/vendor/github.com/emersion/go-imap/message.go
new file mode 100644
index 0000000..bd28325
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/message.go
@@ -0,0 +1,1186 @@
+package imap
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "mime"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// System message flags, defined in RFC 3501 section 2.3.2.
+const (
+ SeenFlag = "\\Seen"
+ AnsweredFlag = "\\Answered"
+ FlaggedFlag = "\\Flagged"
+ DeletedFlag = "\\Deleted"
+ DraftFlag = "\\Draft"
+ RecentFlag = "\\Recent"
+)
+
+// ImportantFlag is a message flag to signal that a message is likely important
+// to the user. This flag is defined in RFC 8457 section 2.
+const ImportantFlag = "$Important"
+
+// TryCreateFlag is a special flag in MailboxStatus.PermanentFlags indicating
+// that it is possible to create new keywords by attempting to store those
+// flags in the mailbox.
+const TryCreateFlag = "\\*"
+
+var flags = []string{
+ SeenFlag,
+ AnsweredFlag,
+ FlaggedFlag,
+ DeletedFlag,
+ DraftFlag,
+ RecentFlag,
+}
+
+// A PartSpecifier specifies which parts of the MIME entity should be returned.
+type PartSpecifier string
+
+// Part specifiers described in RFC 3501 page 55.
+const (
+ // Refers to the entire part, including headers.
+ EntireSpecifier PartSpecifier = ""
+ // Refers to the header of the part. Must include the final CRLF delimiting
+ // the header and the body.
+ HeaderSpecifier = "HEADER"
+ // Refers to the text body of the part, omitting the header.
+ TextSpecifier = "TEXT"
+ // Refers to the MIME Internet Message Body header. Must include the final
+ // CRLF delimiting the header and the body.
+ MIMESpecifier = "MIME"
+)
+
+// CanonicalFlag returns the canonical form of a flag. Flags are case-insensitive.
+//
+// If the flag is defined in RFC 3501, it returns the flag with the case of the
+// RFC. Otherwise, it returns the lowercase version of the flag.
+func CanonicalFlag(flag string) string {
+ for _, f := range flags {
+ if strings.EqualFold(f, flag) {
+ return f
+ }
+ }
+ return strings.ToLower(flag)
+}
+
+func ParseParamList(fields []interface{}) (map[string]string, error) {
+ params := make(map[string]string)
+
+ var k string
+ for i, f := range fields {
+ p, err := ParseString(f)
+ if err != nil {
+ return nil, errors.New("Parameter list contains a non-string: " + err.Error())
+ }
+
+ if i%2 == 0 {
+ k = p
+ } else {
+ params[k] = p
+ k = ""
+ }
+ }
+
+ if k != "" {
+ return nil, errors.New("Parameter list contains a key without a value")
+ }
+ return params, nil
+}
+
+func FormatParamList(params map[string]string) []interface{} {
+ var fields []interface{}
+ for key, value := range params {
+ fields = append(fields, key, value)
+ }
+ return fields
+}
+
+var wordDecoder = &mime.WordDecoder{
+ CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
+ if CharsetReader != nil {
+ return CharsetReader(charset, input)
+ }
+ return nil, fmt.Errorf("imap: unhandled charset %q", charset)
+ },
+}
+
+func decodeHeader(s string) (string, error) {
+ dec, err := wordDecoder.DecodeHeader(s)
+ if err != nil {
+ return s, err
+ }
+ return dec, nil
+}
+
+func encodeHeader(s string) string {
+ return mime.QEncoding.Encode("utf-8", s)
+}
+
+func stringLowered(i interface{}) (string, bool) {
+ s, ok := i.(string)
+ return strings.ToLower(s), ok
+}
+
+func parseHeaderParamList(fields []interface{}) (map[string]string, error) {
+ params, err := ParseParamList(fields)
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range params {
+ if lower := strings.ToLower(k); lower != k {
+ delete(params, k)
+ k = lower
+ }
+
+ params[k], _ = decodeHeader(v)
+ }
+
+ return params, nil
+}
+
+func formatHeaderParamList(params map[string]string) []interface{} {
+ encoded := make(map[string]string)
+ for k, v := range params {
+ encoded[k] = encodeHeader(v)
+ }
+ return FormatParamList(encoded)
+}
+
+// A message.
+type Message struct {
+ // The message sequence number. It must be greater than or equal to 1.
+ SeqNum uint32
+ // The mailbox items that are currently filled in. This map's values
+ // should not be used directly, they must only be used by libraries
+ // implementing extensions of the IMAP protocol.
+ Items map[FetchItem]interface{}
+
+ // The message envelope.
+ Envelope *Envelope
+ // The message body structure (either BODYSTRUCTURE or BODY).
+ BodyStructure *BodyStructure
+ // The message flags.
+ Flags []string
+ // The date the message was received by the server.
+ InternalDate time.Time
+ // The message size.
+ Size uint32
+ // The message unique identifier. It must be greater than or equal to 1.
+ Uid uint32
+ // The message body sections.
+ Body map[*BodySectionName]Literal
+
+ // The order in which items were requested. This order must be preserved
+ // because some bad IMAP clients (looking at you, Outlook!) refuse responses
+ // containing items in a different order.
+ itemsOrder []FetchItem
+}
+
+// Create a new empty message that will contain the specified items.
+func NewMessage(seqNum uint32, items []FetchItem) *Message {
+ msg := &Message{
+ SeqNum: seqNum,
+ Items: make(map[FetchItem]interface{}),
+ Body: make(map[*BodySectionName]Literal),
+ itemsOrder: items,
+ }
+
+ for _, k := range items {
+ msg.Items[k] = nil
+ }
+
+ return msg
+}
+
+// Parse a message from fields.
+func (m *Message) Parse(fields []interface{}) error {
+ m.Items = make(map[FetchItem]interface{})
+ m.Body = map[*BodySectionName]Literal{}
+ m.itemsOrder = nil
+
+ var k FetchItem
+ for i, f := range fields {
+ if i%2 == 0 { // It's a key
+ switch f := f.(type) {
+ case string:
+ k = FetchItem(strings.ToUpper(f))
+ case RawString:
+ k = FetchItem(strings.ToUpper(string(f)))
+ default:
+ return fmt.Errorf("cannot parse message: key is not a string, but a %T", f)
+ }
+ } else { // It's a value
+ m.Items[k] = nil
+ m.itemsOrder = append(m.itemsOrder, k)
+
+ switch k {
+ case FetchBody, FetchBodyStructure:
+ bs, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: BODYSTRUCTURE is not a list, but a %T", f)
+ }
+
+ m.BodyStructure = &BodyStructure{Extended: k == FetchBodyStructure}
+ if err := m.BodyStructure.Parse(bs); err != nil {
+ return err
+ }
+ case FetchEnvelope:
+ env, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: ENVELOPE is not a list, but a %T", f)
+ }
+
+ m.Envelope = &Envelope{}
+ if err := m.Envelope.Parse(env); err != nil {
+ return err
+ }
+ case FetchFlags:
+ flags, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: FLAGS is not a list, but a %T", f)
+ }
+
+ m.Flags = make([]string, len(flags))
+ for i, flag := range flags {
+ s, _ := ParseString(flag)
+ m.Flags[i] = CanonicalFlag(s)
+ }
+ case FetchInternalDate:
+ date, _ := f.(string)
+ m.InternalDate, _ = time.Parse(DateTimeLayout, date)
+ case FetchRFC822Size:
+ m.Size, _ = ParseNumber(f)
+ case FetchUid:
+ m.Uid, _ = ParseNumber(f)
+ default:
+ // Likely to be a section of the body
+ // First check that the section name is correct
+ if section, err := ParseBodySectionName(k); err != nil {
+ // Not a section name, maybe an attribute defined in an IMAP extension
+ m.Items[k] = f
+ } else {
+ m.Body[section], _ = f.(Literal)
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *Message) formatItem(k FetchItem) []interface{} {
+ v := m.Items[k]
+ var kk interface{} = RawString(k)
+
+ switch k {
+ case FetchBody, FetchBodyStructure:
+ // Extension data is only returned with the BODYSTRUCTURE fetch
+ m.BodyStructure.Extended = k == FetchBodyStructure
+ v = m.BodyStructure.Format()
+ case FetchEnvelope:
+ v = m.Envelope.Format()
+ case FetchFlags:
+ flags := make([]interface{}, len(m.Flags))
+ for i, flag := range m.Flags {
+ flags[i] = RawString(flag)
+ }
+ v = flags
+ case FetchInternalDate:
+ v = m.InternalDate
+ case FetchRFC822Size:
+ v = m.Size
+ case FetchUid:
+ v = m.Uid
+ default:
+ for section, literal := range m.Body {
+ if section.value == k {
+ // This can contain spaces, so we can't pass it as a string directly
+ kk = section.resp()
+ v = literal
+ break
+ }
+ }
+ }
+
+ return []interface{}{kk, v}
+}
+
+func (m *Message) Format() []interface{} {
+ var fields []interface{}
+
+ // First send ordered items
+ processed := make(map[FetchItem]bool)
+ for _, k := range m.itemsOrder {
+ if _, ok := m.Items[k]; ok {
+ fields = append(fields, m.formatItem(k)...)
+ processed[k] = true
+ }
+ }
+
+ // Then send other remaining items
+ for k := range m.Items {
+ if !processed[k] {
+ fields = append(fields, m.formatItem(k)...)
+ }
+ }
+
+ return fields
+}
+
+// GetBody gets the body section with the specified name. Returns nil if it's not found.
+func (m *Message) GetBody(section *BodySectionName) Literal {
+ section = section.resp()
+
+ for s, body := range m.Body {
+ if section.Equal(s) {
+ if body == nil {
+ // Server can return nil, we need to treat as empty string per RFC 3501
+ body = bytes.NewReader(nil)
+ }
+ return body
+ }
+ }
+ return nil
+}
+
+// A body section name.
+// See RFC 3501 page 55.
+type BodySectionName struct {
+ BodyPartName
+
+ // If set to true, do not implicitly set the \Seen flag.
+ Peek bool
+ // The substring of the section requested. The first value is the position of
+ // the first desired octet and the second value is the maximum number of
+ // octets desired.
+ Partial []int
+
+ value FetchItem
+}
+
+func (section *BodySectionName) parse(s string) error {
+ section.value = FetchItem(s)
+
+ if s == "RFC822" {
+ s = "BODY[]"
+ }
+ if s == "RFC822.HEADER" {
+ s = "BODY.PEEK[HEADER]"
+ }
+ if s == "RFC822.TEXT" {
+ s = "BODY[TEXT]"
+ }
+
+ partStart := strings.Index(s, "[")
+ if partStart == -1 {
+ return errors.New("Invalid body section name: must contain an open bracket")
+ }
+
+ partEnd := strings.LastIndex(s, "]")
+ if partEnd == -1 {
+ return errors.New("Invalid body section name: must contain a close bracket")
+ }
+
+ name := s[:partStart]
+ part := s[partStart+1 : partEnd]
+ partial := s[partEnd+1:]
+
+ if name == "BODY.PEEK" {
+ section.Peek = true
+ } else if name != "BODY" {
+ return errors.New("Invalid body section name")
+ }
+
+ b := bytes.NewBufferString(part + string(cr) + string(lf))
+ r := NewReader(b)
+ fields, err := r.ReadFields()
+ if err != nil {
+ return err
+ }
+
+ if err := section.BodyPartName.parse(fields); err != nil {
+ return err
+ }
+
+ if len(partial) > 0 {
+ if !strings.HasPrefix(partial, "<") || !strings.HasSuffix(partial, ">") {
+ return errors.New("Invalid body section name: invalid partial")
+ }
+ partial = partial[1 : len(partial)-1]
+
+ partialParts := strings.SplitN(partial, ".", 2)
+
+ var from, length int
+ if from, err = strconv.Atoi(partialParts[0]); err != nil {
+ return errors.New("Invalid body section name: invalid partial: invalid from: " + err.Error())
+ }
+ section.Partial = []int{from}
+
+ if len(partialParts) == 2 {
+ if length, err = strconv.Atoi(partialParts[1]); err != nil {
+ return errors.New("Invalid body section name: invalid partial: invalid length: " + err.Error())
+ }
+ section.Partial = append(section.Partial, length)
+ }
+ }
+
+ return nil
+}
+
+func (section *BodySectionName) FetchItem() FetchItem {
+ if section.value != "" {
+ return section.value
+ }
+
+ s := "BODY"
+ if section.Peek {
+ s += ".PEEK"
+ }
+
+ s += "[" + section.BodyPartName.string() + "]"
+
+ if len(section.Partial) > 0 {
+ s += "<"
+ s += strconv.Itoa(section.Partial[0])
+
+ if len(section.Partial) > 1 {
+ s += "."
+ s += strconv.Itoa(section.Partial[1])
+ }
+
+ s += ">"
+ }
+
+ return FetchItem(s)
+}
+
+// Equal checks whether two sections are equal.
+func (section *BodySectionName) Equal(other *BodySectionName) bool {
+ if section.Peek != other.Peek {
+ return false
+ }
+ if len(section.Partial) != len(other.Partial) {
+ return false
+ }
+ if len(section.Partial) > 0 && section.Partial[0] != other.Partial[0] {
+ return false
+ }
+ if len(section.Partial) > 1 && section.Partial[1] != other.Partial[1] {
+ return false
+ }
+ return section.BodyPartName.Equal(&other.BodyPartName)
+}
+
+func (section *BodySectionName) resp() *BodySectionName {
+ resp := *section // Copy section
+ if resp.Peek {
+ resp.Peek = false
+ }
+ if len(resp.Partial) == 2 {
+ resp.Partial = []int{resp.Partial[0]}
+ }
+ if !strings.HasPrefix(string(resp.value), string(FetchRFC822)) {
+ resp.value = ""
+ }
+ return &resp
+}
+
+// ExtractPartial returns a subset of the specified bytes matching the partial requested in the
+// section name.
+func (section *BodySectionName) ExtractPartial(b []byte) []byte {
+ if len(section.Partial) != 2 {
+ return b
+ }
+
+ from := section.Partial[0]
+ length := section.Partial[1]
+ to := from + length
+ if from > len(b) {
+ return nil
+ }
+ if to > len(b) {
+ to = len(b)
+ }
+ return b[from:to]
+}
+
+// ParseBodySectionName parses a body section name.
+func ParseBodySectionName(s FetchItem) (*BodySectionName, error) {
+ section := new(BodySectionName)
+ err := section.parse(string(s))
+ return section, err
+}
+
+// A body part name.
+type BodyPartName struct {
+ // The specifier of the requested part.
+ Specifier PartSpecifier
+ // The part path. Parts indexes start at 1.
+ Path []int
+ // If Specifier is HEADER, contains header fields that will/won't be returned,
+ // depending of the value of NotFields.
+ Fields []string
+ // If set to true, Fields is a blacklist of fields instead of a whitelist.
+ NotFields bool
+}
+
+func (part *BodyPartName) parse(fields []interface{}) error {
+ if len(fields) == 0 {
+ return nil
+ }
+
+ name, ok := fields[0].(string)
+ if !ok {
+ return errors.New("Invalid body section name: part name must be a string")
+ }
+
+ args := fields[1:]
+
+ path := strings.Split(strings.ToUpper(name), ".")
+
+ end := 0
+loop:
+ for i, node := range path {
+ switch PartSpecifier(node) {
+ case EntireSpecifier, HeaderSpecifier, MIMESpecifier, TextSpecifier:
+ part.Specifier = PartSpecifier(node)
+ end = i + 1
+ break loop
+ }
+
+ index, err := strconv.Atoi(node)
+ if err != nil {
+ return errors.New("Invalid body part name: " + err.Error())
+ }
+ if index <= 0 {
+ return errors.New("Invalid body part name: index <= 0")
+ }
+
+ part.Path = append(part.Path, index)
+ }
+
+ if part.Specifier == HeaderSpecifier && len(path) > end && path[end] == "FIELDS" && len(args) > 0 {
+ end++
+ if len(path) > end && path[end] == "NOT" {
+ part.NotFields = true
+ }
+
+ names, ok := args[0].([]interface{})
+ if !ok {
+ return errors.New("Invalid body part name: HEADER.FIELDS must have a list argument")
+ }
+
+ for _, namei := range names {
+ if name, ok := namei.(string); ok {
+ part.Fields = append(part.Fields, name)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (part *BodyPartName) string() string {
+ path := make([]string, len(part.Path))
+ for i, index := range part.Path {
+ path[i] = strconv.Itoa(index)
+ }
+
+ if part.Specifier != EntireSpecifier {
+ path = append(path, string(part.Specifier))
+ }
+
+ if part.Specifier == HeaderSpecifier && len(part.Fields) > 0 {
+ path = append(path, "FIELDS")
+
+ if part.NotFields {
+ path = append(path, "NOT")
+ }
+ }
+
+ s := strings.Join(path, ".")
+
+ if len(part.Fields) > 0 {
+ s += " (" + strings.Join(part.Fields, " ") + ")"
+ }
+
+ return s
+}
+
+// Equal checks whether two body part names are equal.
+func (part *BodyPartName) Equal(other *BodyPartName) bool {
+ if part.Specifier != other.Specifier {
+ return false
+ }
+ if part.NotFields != other.NotFields {
+ return false
+ }
+ if len(part.Path) != len(other.Path) {
+ return false
+ }
+ for i, node := range part.Path {
+ if node != other.Path[i] {
+ return false
+ }
+ }
+ if len(part.Fields) != len(other.Fields) {
+ return false
+ }
+ for _, field := range part.Fields {
+ found := false
+ for _, f := range other.Fields {
+ if strings.EqualFold(field, f) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+// An address.
+type Address struct {
+ // The personal name.
+ PersonalName string
+ // The SMTP at-domain-list (source route).
+ AtDomainList string
+ // The mailbox name.
+ MailboxName string
+ // The host name.
+ HostName string
+}
+
+// Address returns the mailbox address (e.g. "foo@example.org").
+func (addr *Address) Address() string {
+ return addr.MailboxName + "@" + addr.HostName
+}
+
+// Parse an address from fields.
+func (addr *Address) Parse(fields []interface{}) error {
+ if len(fields) < 4 {
+ return errors.New("Address doesn't contain 4 fields")
+ }
+
+ if s, err := ParseString(fields[0]); err == nil {
+ addr.PersonalName, _ = decodeHeader(s)
+ }
+ if s, err := ParseString(fields[1]); err == nil {
+ addr.AtDomainList, _ = decodeHeader(s)
+ }
+
+ s, err := ParseString(fields[2])
+ if err != nil {
+ return errors.New("Mailbox name could not be parsed")
+ }
+ addr.MailboxName, _ = decodeHeader(s)
+
+ s, err = ParseString(fields[3])
+ if err != nil {
+ return errors.New("Host name could not be parsed")
+ }
+ addr.HostName, _ = decodeHeader(s)
+
+ return nil
+}
+
+// Format an address to fields.
+func (addr *Address) Format() []interface{} {
+ fields := make([]interface{}, 4)
+
+ if addr.PersonalName != "" {
+ fields[0] = encodeHeader(addr.PersonalName)
+ }
+ if addr.AtDomainList != "" {
+ fields[1] = addr.AtDomainList
+ }
+ if addr.MailboxName != "" {
+ fields[2] = addr.MailboxName
+ }
+ if addr.HostName != "" {
+ fields[3] = addr.HostName
+ }
+
+ return fields
+}
+
+// Parse an address list from fields.
+func ParseAddressList(fields []interface{}) (addrs []*Address) {
+ for _, f := range fields {
+ if addrFields, ok := f.([]interface{}); ok {
+ addr := &Address{}
+ if err := addr.Parse(addrFields); err == nil {
+ addrs = append(addrs, addr)
+ }
+ }
+ }
+
+ return
+}
+
+// Format an address list to fields.
+func FormatAddressList(addrs []*Address) interface{} {
+ if len(addrs) == 0 {
+ return nil
+ }
+
+ fields := make([]interface{}, len(addrs))
+
+ for i, addr := range addrs {
+ fields[i] = addr.Format()
+ }
+
+ return fields
+}
+
+// A message envelope, ie. message metadata from its headers.
+// See RFC 3501 page 77.
+type Envelope struct {
+ // The message date.
+ Date time.Time
+ // The message subject.
+ Subject string
+ // The From header addresses.
+ From []*Address
+ // The message senders.
+ Sender []*Address
+ // The Reply-To header addresses.
+ ReplyTo []*Address
+ // The To header addresses.
+ To []*Address
+ // The Cc header addresses.
+ Cc []*Address
+ // The Bcc header addresses.
+ Bcc []*Address
+ // The In-Reply-To header. Contains the parent Message-Id.
+ InReplyTo string
+ // The Message-Id header.
+ MessageId string
+}
+
+// Parse an envelope from fields.
+func (e *Envelope) Parse(fields []interface{}) error {
+ if len(fields) < 10 {
+ return errors.New("ENVELOPE doesn't contain 10 fields")
+ }
+
+ if date, ok := fields[0].(string); ok {
+ e.Date, _ = parseMessageDateTime(date)
+ }
+ if subject, err := ParseString(fields[1]); err == nil {
+ e.Subject, _ = decodeHeader(subject)
+ }
+ if from, ok := fields[2].([]interface{}); ok {
+ e.From = ParseAddressList(from)
+ }
+ if sender, ok := fields[3].([]interface{}); ok {
+ e.Sender = ParseAddressList(sender)
+ }
+ if replyTo, ok := fields[4].([]interface{}); ok {
+ e.ReplyTo = ParseAddressList(replyTo)
+ }
+ if to, ok := fields[5].([]interface{}); ok {
+ e.To = ParseAddressList(to)
+ }
+ if cc, ok := fields[6].([]interface{}); ok {
+ e.Cc = ParseAddressList(cc)
+ }
+ if bcc, ok := fields[7].([]interface{}); ok {
+ e.Bcc = ParseAddressList(bcc)
+ }
+ if inReplyTo, ok := fields[8].(string); ok {
+ e.InReplyTo = inReplyTo
+ }
+ if msgId, ok := fields[9].(string); ok {
+ e.MessageId = msgId
+ }
+
+ return nil
+}
+
+// Format an envelope to fields.
+func (e *Envelope) Format() (fields []interface{}) {
+ fields = make([]interface{}, 0, 10)
+ fields = append(fields, envelopeDateTime(e.Date))
+ if e.Subject != "" {
+ fields = append(fields, encodeHeader(e.Subject))
+ } else {
+ fields = append(fields, nil)
+ }
+ fields = append(fields,
+ FormatAddressList(e.From),
+ FormatAddressList(e.Sender),
+ FormatAddressList(e.ReplyTo),
+ FormatAddressList(e.To),
+ FormatAddressList(e.Cc),
+ FormatAddressList(e.Bcc),
+ )
+ if e.InReplyTo != "" {
+ fields = append(fields, e.InReplyTo)
+ } else {
+ fields = append(fields, nil)
+ }
+ if e.MessageId != "" {
+ fields = append(fields, e.MessageId)
+ } else {
+ fields = append(fields, nil)
+ }
+ return fields
+}
+
+// A body structure.
+// See RFC 3501 page 74.
+type BodyStructure struct {
+ // Basic fields
+
+ // The MIME type (e.g. "text", "image")
+ MIMEType string
+ // The MIME subtype (e.g. "plain", "png")
+ MIMESubType string
+ // The MIME parameters.
+ Params map[string]string
+
+ // The Content-Id header.
+ Id string
+ // The Content-Description header.
+ Description string
+ // The Content-Encoding header.
+ Encoding string
+ // The Content-Length header.
+ Size uint32
+
+ // Type-specific fields
+
+ // The children parts, if multipart.
+ Parts []*BodyStructure
+ // The envelope, if message/rfc822.
+ Envelope *Envelope
+ // The body structure, if message/rfc822.
+ BodyStructure *BodyStructure
+ // The number of lines, if text or message/rfc822.
+ Lines uint32
+
+ // Extension data
+
+ // True if the body structure contains extension data.
+ Extended bool
+
+ // The Content-Disposition header field value.
+ Disposition string
+ // The Content-Disposition header field parameters.
+ DispositionParams map[string]string
+ // The Content-Language header field, if multipart.
+ Language []string
+ // The content URI, if multipart.
+ Location []string
+
+ // The MD5 checksum.
+ MD5 string
+}
+
+func (bs *BodyStructure) Parse(fields []interface{}) error {
+ if len(fields) == 0 {
+ return nil
+ }
+
+ // Initialize params map
+ bs.Params = make(map[string]string)
+
+ switch fields[0].(type) {
+ case []interface{}: // A multipart body part
+ bs.MIMEType = "multipart"
+
+ end := 0
+ for i, fi := range fields {
+ switch f := fi.(type) {
+ case []interface{}: // A part
+ part := new(BodyStructure)
+ if err := part.Parse(f); err != nil {
+ return err
+ }
+ bs.Parts = append(bs.Parts, part)
+ case string:
+ end = i
+ }
+
+ if end > 0 {
+ break
+ }
+ }
+
+ bs.MIMESubType, _ = fields[end].(string)
+ end++
+
+ // GMail seems to return only 3 extension data fields. Parse as many fields
+ // as we can.
+ if len(fields) > end {
+ bs.Extended = true // Contains extension data
+
+ params, _ := fields[end].([]interface{})
+ bs.Params, _ = parseHeaderParamList(params)
+ end++
+ }
+ if len(fields) > end {
+ if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
+ if s, ok := disp[0].(string); ok {
+ bs.Disposition, _ = decodeHeader(s)
+ bs.Disposition = strings.ToLower(bs.Disposition)
+ }
+ if params, ok := disp[1].([]interface{}); ok {
+ bs.DispositionParams, _ = parseHeaderParamList(params)
+ }
+ }
+ end++
+ }
+ if len(fields) > end {
+ switch langs := fields[end].(type) {
+ case string:
+ bs.Language = []string{langs}
+ case []interface{}:
+ bs.Language, _ = ParseStringList(langs)
+ default:
+ bs.Language = nil
+ }
+ end++
+ }
+ if len(fields) > end {
+ location, _ := fields[end].([]interface{})
+ bs.Location, _ = ParseStringList(location)
+ end++
+ }
+ case string: // A non-multipart body part
+ if len(fields) < 7 {
+ return errors.New("Non-multipart body part doesn't have 7 fields")
+ }
+
+ bs.MIMEType, _ = stringLowered(fields[0])
+ bs.MIMESubType, _ = stringLowered(fields[1])
+
+ params, _ := fields[2].([]interface{})
+ bs.Params, _ = parseHeaderParamList(params)
+
+ bs.Id, _ = fields[3].(string)
+ if desc, err := ParseString(fields[4]); err == nil {
+ bs.Description, _ = decodeHeader(desc)
+ }
+ bs.Encoding, _ = stringLowered(fields[5])
+ bs.Size, _ = ParseNumber(fields[6])
+
+ end := 7
+
+ // Type-specific fields
+ if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
+ if len(fields)-end < 3 {
+ return errors.New("Missing type-specific fields for message/rfc822")
+ }
+
+ envelope, _ := fields[end].([]interface{})
+ bs.Envelope = new(Envelope)
+ bs.Envelope.Parse(envelope)
+
+ structure, _ := fields[end+1].([]interface{})
+ bs.BodyStructure = new(BodyStructure)
+ bs.BodyStructure.Parse(structure)
+
+ bs.Lines, _ = ParseNumber(fields[end+2])
+
+ end += 3
+ }
+ if strings.EqualFold(bs.MIMEType, "text") {
+ if len(fields)-end < 1 {
+ return errors.New("Missing type-specific fields for text/*")
+ }
+
+ bs.Lines, _ = ParseNumber(fields[end])
+ end++
+ }
+
+ // GMail seems to return only 3 extension data fields. Parse as many fields
+ // as we can.
+ if len(fields) > end {
+ bs.Extended = true // Contains extension data
+
+ bs.MD5, _ = fields[end].(string)
+ end++
+ }
+ if len(fields) > end {
+ if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
+ if s, ok := disp[0].(string); ok {
+ bs.Disposition, _ = decodeHeader(s)
+ bs.Disposition = strings.ToLower(bs.Disposition)
+ }
+ if params, ok := disp[1].([]interface{}); ok {
+ bs.DispositionParams, _ = parseHeaderParamList(params)
+ }
+ }
+ end++
+ }
+ if len(fields) > end {
+ switch langs := fields[end].(type) {
+ case string:
+ bs.Language = []string{langs}
+ case []interface{}:
+ bs.Language, _ = ParseStringList(langs)
+ default:
+ bs.Language = nil
+ }
+ end++
+ }
+ if len(fields) > end {
+ location, _ := fields[end].([]interface{})
+ bs.Location, _ = ParseStringList(location)
+ end++
+ }
+ }
+
+ return nil
+}
+
+func (bs *BodyStructure) Format() (fields []interface{}) {
+ if strings.EqualFold(bs.MIMEType, "multipart") {
+ for _, part := range bs.Parts {
+ fields = append(fields, part.Format())
+ }
+
+ fields = append(fields, bs.MIMESubType)
+
+ if bs.Extended {
+ extended := make([]interface{}, 4)
+
+ if bs.Params != nil {
+ extended[0] = formatHeaderParamList(bs.Params)
+ }
+ if bs.Disposition != "" {
+ extended[1] = []interface{}{
+ encodeHeader(bs.Disposition),
+ formatHeaderParamList(bs.DispositionParams),
+ }
+ }
+ if bs.Language != nil {
+ extended[2] = FormatStringList(bs.Language)
+ }
+ if bs.Location != nil {
+ extended[3] = FormatStringList(bs.Location)
+ }
+
+ fields = append(fields, extended...)
+ }
+ } else {
+ fields = make([]interface{}, 7)
+ fields[0] = bs.MIMEType
+ fields[1] = bs.MIMESubType
+ fields[2] = formatHeaderParamList(bs.Params)
+
+ if bs.Id != "" {
+ fields[3] = bs.Id
+ }
+ if bs.Description != "" {
+ fields[4] = encodeHeader(bs.Description)
+ }
+ if bs.Encoding != "" {
+ fields[5] = bs.Encoding
+ }
+
+ fields[6] = bs.Size
+
+ // Type-specific fields
+ if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
+ var env interface{}
+ if bs.Envelope != nil {
+ env = bs.Envelope.Format()
+ }
+
+ var bsbs interface{}
+ if bs.BodyStructure != nil {
+ bsbs = bs.BodyStructure.Format()
+ }
+
+ fields = append(fields, env, bsbs, bs.Lines)
+ }
+ if strings.EqualFold(bs.MIMEType, "text") {
+ fields = append(fields, bs.Lines)
+ }
+
+ // Extension data
+ if bs.Extended {
+ extended := make([]interface{}, 4)
+
+ if bs.MD5 != "" {
+ extended[0] = bs.MD5
+ }
+ if bs.Disposition != "" {
+ extended[1] = []interface{}{
+ encodeHeader(bs.Disposition),
+ formatHeaderParamList(bs.DispositionParams),
+ }
+ }
+ if bs.Language != nil {
+ extended[2] = FormatStringList(bs.Language)
+ }
+ if bs.Location != nil {
+ extended[3] = FormatStringList(bs.Location)
+ }
+
+ fields = append(fields, extended...)
+ }
+ }
+
+ return
+}
+
+// Filename parses the body structure's filename, if it's an attachment. An
+// empty string is returned if the filename isn't specified. An error is
+// returned if and only if a charset error occurs, in which case the undecoded
+// filename is returned too.
+func (bs *BodyStructure) Filename() (string, error) {
+ raw, ok := bs.DispositionParams["filename"]
+ if !ok {
+ // Using "name" in Content-Type is discouraged
+ raw = bs.Params["name"]
+ }
+ return decodeHeader(raw)
+}
+
+// BodyStructureWalkFunc is the type of the function called for each body
+// structure visited by BodyStructure.Walk. The path argument contains the IMAP
+// part path (see BodyPartName).
+//
+// The function should return true to visit all of the part's children or false
+// to skip them.
+type BodyStructureWalkFunc func(path []int, part *BodyStructure) (walkChildren bool)
+
+// Walk walks the body structure tree, calling f for each part in the tree,
+// including bs itself. The parts are visited in DFS pre-order.
+func (bs *BodyStructure) Walk(f BodyStructureWalkFunc) {
+ // Non-multipart messages only have part 1
+ if len(bs.Parts) == 0 {
+ f([]int{1}, bs)
+ return
+ }
+
+ bs.walk(f, nil)
+}
+
+func (bs *BodyStructure) walk(f BodyStructureWalkFunc, path []int) {
+ if !f(path, bs) {
+ return
+ }
+
+ for i, part := range bs.Parts {
+ num := i + 1
+
+ partPath := append([]int(nil), path...)
+ partPath = append(partPath, num)
+
+ part.walk(f, partPath)
+ }
+}
diff --git a/vendor/github.com/emersion/go-imap/read.go b/vendor/github.com/emersion/go-imap/read.go
new file mode 100644
index 0000000..112ee28
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/read.go
@@ -0,0 +1,467 @@
+package imap
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "strconv"
+ "strings"
+)
+
+const (
+ sp = ' '
+ cr = '\r'
+ lf = '\n'
+ dquote = '"'
+ literalStart = '{'
+ literalEnd = '}'
+ listStart = '('
+ listEnd = ')'
+ respCodeStart = '['
+ respCodeEnd = ']'
+)
+
+const (
+ crlf = "\r\n"
+ nilAtom = "NIL"
+)
+
+// TODO: add CTL to atomSpecials
+var (
+ quotedSpecials = string([]rune{dquote, '\\'})
+ respSpecials = string([]rune{respCodeEnd})
+ atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
+)
+
+type parseError struct {
+ error
+}
+
+func newParseError(text string) error {
+ return &parseError{errors.New(text)}
+}
+
+// IsParseError returns true if the provided error is a parse error produced by
+// Reader.
+func IsParseError(err error) bool {
+ _, ok := err.(*parseError)
+ return ok
+}
+
+// A string reader.
+type StringReader interface {
+ // ReadString reads until the first occurrence of delim in the input,
+ // returning a string containing the data up to and including the delimiter.
+ // See https://golang.org/pkg/bufio/#Reader.ReadString
+ ReadString(delim byte) (line string, err error)
+}
+
+type reader interface {
+ io.Reader
+ io.RuneScanner
+ StringReader
+}
+
+// ParseNumber parses a number.
+func ParseNumber(f interface{}) (uint32, error) {
+ // Useful for tests
+ if n, ok := f.(uint32); ok {
+ return n, nil
+ }
+
+ var s string
+ switch f := f.(type) {
+ case RawString:
+ s = string(f)
+ case string:
+ s = f
+ default:
+ return 0, newParseError("expected a number, got a non-atom")
+ }
+
+ nbr, err := strconv.ParseUint(string(s), 10, 32)
+ if err != nil {
+ return 0, &parseError{err}
+ }
+
+ return uint32(nbr), nil
+}
+
+// ParseString parses a string, which is either a literal, a quoted string or an
+// atom.
+func ParseString(f interface{}) (string, error) {
+ if s, ok := f.(string); ok {
+ return s, nil
+ }
+
+ // Useful for tests
+ if a, ok := f.(RawString); ok {
+ return string(a), nil
+ }
+
+ if l, ok := f.(Literal); ok {
+ b := make([]byte, l.Len())
+ if _, err := io.ReadFull(l, b); err != nil {
+ return "", err
+ }
+ return string(b), nil
+ }
+
+ return "", newParseError("expected a string")
+}
+
+// Convert a field list to a string list.
+func ParseStringList(f interface{}) ([]string, error) {
+ fields, ok := f.([]interface{})
+ if !ok {
+ return nil, newParseError("expected a string list, got a non-list")
+ }
+
+ list := make([]string, len(fields))
+ for i, f := range fields {
+ var err error
+ if list[i], err = ParseString(f); err != nil {
+ return nil, newParseError("cannot parse string in string list: " + err.Error())
+ }
+ }
+ return list, nil
+}
+
+func trimSuffix(str string, suffix rune) string {
+ return str[:len(str)-1]
+}
+
+// An IMAP reader.
+type Reader struct {
+ MaxLiteralSize uint32 // The maximum literal size.
+
+ reader
+
+ continues chan<- bool
+
+ brackets int
+ inRespCode bool
+}
+
+func (r *Reader) ReadSp() error {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return err
+ }
+ if char != sp {
+ return newParseError("expected a space")
+ }
+ return nil
+}
+
+func (r *Reader) ReadCrlf() (err error) {
+ var char rune
+
+ if char, _, err = r.ReadRune(); err != nil {
+ return
+ }
+ if char == lf {
+ return
+ }
+ if char != cr {
+ err = newParseError("line doesn't end with a CR")
+ return
+ }
+
+ if char, _, err = r.ReadRune(); err != nil {
+ return
+ }
+ if char != lf {
+ err = newParseError("line doesn't end with a LF")
+ }
+
+ return
+}
+
+func (r *Reader) ReadAtom() (interface{}, error) {
+ r.brackets = 0
+
+ var atom string
+ for {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: list-wildcards and \
+ if r.brackets == 0 && (char == listStart || char == literalStart || char == dquote) {
+ return nil, newParseError("atom contains forbidden char: " + string(char))
+ }
+ if char == cr || char == lf {
+ break
+ }
+ if r.brackets == 0 && (char == sp || char == listEnd) {
+ break
+ }
+ if char == respCodeEnd {
+ if r.brackets == 0 {
+ if r.inRespCode {
+ break
+ } else {
+ return nil, newParseError("atom contains bad brackets nesting")
+ }
+ }
+ r.brackets--
+ }
+ if char == respCodeStart {
+ r.brackets++
+ }
+
+ atom += string(char)
+ }
+
+ r.UnreadRune()
+
+ if atom == nilAtom {
+ return nil, nil
+ }
+
+ return atom, nil
+}
+
+func (r *Reader) ReadLiteral() (Literal, error) {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return nil, err
+ } else if char != literalStart {
+ return nil, newParseError("literal string doesn't start with an open brace")
+ }
+
+ lstr, err := r.ReadString(byte(literalEnd))
+ if err != nil {
+ return nil, err
+ }
+ lstr = trimSuffix(lstr, literalEnd)
+
+ nonSync := strings.HasSuffix(lstr, "+")
+ if nonSync {
+ lstr = trimSuffix(lstr, '+')
+ }
+
+ n, err := strconv.ParseUint(lstr, 10, 32)
+ if err != nil {
+ return nil, newParseError("cannot parse literal length: " + err.Error())
+ }
+ if r.MaxLiteralSize > 0 && uint32(n) > r.MaxLiteralSize {
+ return nil, newParseError("literal exceeding maximum size")
+ }
+
+ if err := r.ReadCrlf(); err != nil {
+ return nil, err
+ }
+
+ // Send continuation request if necessary
+ if r.continues != nil && !nonSync {
+ r.continues <- true
+ }
+
+ // Read literal
+ b := make([]byte, n)
+ if _, err := io.ReadFull(r, b); err != nil {
+ return nil, err
+ }
+ return bytes.NewBuffer(b), nil
+}
+
+func (r *Reader) ReadQuotedString() (string, error) {
+ if char, _, err := r.ReadRune(); err != nil {
+ return "", err
+ } else if char != dquote {
+ return "", newParseError("quoted string doesn't start with a double quote")
+ }
+
+ var buf bytes.Buffer
+ var escaped bool
+ for {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return "", err
+ }
+
+ if char == '\\' && !escaped {
+ escaped = true
+ } else {
+ if char == cr || char == lf {
+ r.UnreadRune()
+ return "", newParseError("CR or LF not allowed in quoted string")
+ }
+ if char == dquote && !escaped {
+ break
+ }
+
+ if !strings.ContainsRune(quotedSpecials, char) && escaped {
+ return "", newParseError("quoted string cannot contain backslash followed by a non-quoted-specials char")
+ }
+
+ buf.WriteRune(char)
+ escaped = false
+ }
+ }
+
+ return buf.String(), nil
+}
+
+func (r *Reader) ReadFields() (fields []interface{}, err error) {
+ var char rune
+ for {
+ if char, _, err = r.ReadRune(); err != nil {
+ return
+ }
+ if err = r.UnreadRune(); err != nil {
+ return
+ }
+
+ var field interface{}
+ ok := true
+ switch char {
+ case literalStart:
+ field, err = r.ReadLiteral()
+ case dquote:
+ field, err = r.ReadQuotedString()
+ case listStart:
+ field, err = r.ReadList()
+ case listEnd:
+ ok = false
+ case cr:
+ return
+ default:
+ field, err = r.ReadAtom()
+ }
+
+ if err != nil {
+ return
+ }
+ if ok {
+ fields = append(fields, field)
+ }
+
+ if char, _, err = r.ReadRune(); err != nil {
+ return
+ }
+ if char == cr || char == lf || char == listEnd || char == respCodeEnd {
+ if char == cr || char == lf {
+ r.UnreadRune()
+ }
+ return
+ }
+ if char == listStart {
+ r.UnreadRune()
+ continue
+ }
+ if char != sp {
+ err = newParseError("fields are not separated by a space")
+ return
+ }
+ }
+}
+
+func (r *Reader) ReadList() (fields []interface{}, err error) {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return
+ }
+ if char != listStart {
+ err = newParseError("list doesn't start with an open parenthesis")
+ return
+ }
+
+ fields, err = r.ReadFields()
+ if err != nil {
+ return
+ }
+
+ r.UnreadRune()
+ if char, _, err = r.ReadRune(); err != nil {
+ return
+ }
+ if char != listEnd {
+ err = newParseError("list doesn't end with a close parenthesis")
+ }
+ return
+}
+
+func (r *Reader) ReadLine() (fields []interface{}, err error) {
+ fields, err = r.ReadFields()
+ if err != nil {
+ return
+ }
+
+ r.UnreadRune()
+ err = r.ReadCrlf()
+ return
+}
+
+func (r *Reader) ReadRespCode() (code StatusRespCode, fields []interface{}, err error) {
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return
+ }
+ if char != respCodeStart {
+ err = newParseError("response code doesn't start with an open bracket")
+ return
+ }
+
+ r.inRespCode = true
+ fields, err = r.ReadFields()
+ r.inRespCode = false
+ if err != nil {
+ return
+ }
+
+ if len(fields) == 0 {
+ err = newParseError("response code doesn't contain any field")
+ return
+ }
+
+ codeStr, ok := fields[0].(string)
+ if !ok {
+ err = newParseError("response code doesn't start with a string atom")
+ return
+ }
+ if codeStr == "" {
+ err = newParseError("response code is empty")
+ return
+ }
+ code = StatusRespCode(strings.ToUpper(codeStr))
+
+ fields = fields[1:]
+
+ r.UnreadRune()
+ char, _, err = r.ReadRune()
+ if err != nil {
+ return
+ }
+ if char != respCodeEnd {
+ err = newParseError("response code doesn't end with a close bracket")
+ }
+ return
+}
+
+func (r *Reader) ReadInfo() (info string, err error) {
+ info, err = r.ReadString(byte(lf))
+ if err != nil {
+ return
+ }
+ info = strings.TrimSuffix(info, string(lf))
+ info = strings.TrimSuffix(info, string(cr))
+ info = strings.TrimLeft(info, " ")
+
+ return
+}
+
+func NewReader(r reader) *Reader {
+ return &Reader{reader: r}
+}
+
+func NewServerReader(r reader, continues chan<- bool) *Reader {
+ return &Reader{reader: r, continues: continues}
+}
+
+type Parser interface {
+ Parse(fields []interface{}) error
+}
diff --git a/vendor/github.com/emersion/go-imap/response.go b/vendor/github.com/emersion/go-imap/response.go
new file mode 100644
index 0000000..611d03e
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/response.go
@@ -0,0 +1,181 @@
+package imap
+
+import (
+ "strings"
+)
+
+// Resp is an IMAP response. It is either a *DataResp, a
+// *ContinuationReq or a *StatusResp.
+type Resp interface {
+ resp()
+}
+
+// ReadResp reads a single response from a Reader.
+func ReadResp(r *Reader) (Resp, error) {
+ atom, err := r.ReadAtom()
+ if err != nil {
+ return nil, err
+ }
+ tag, ok := atom.(string)
+ if !ok {
+ return nil, newParseError("response tag is not an atom")
+ }
+
+ if tag == "+" {
+ if err := r.ReadSp(); err != nil {
+ r.UnreadRune()
+ }
+
+ resp := &ContinuationReq{}
+ resp.Info, err = r.ReadInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ return resp, nil
+ }
+
+ if err := r.ReadSp(); err != nil {
+ return nil, err
+ }
+
+ // Can be either data or status
+ // Try to parse a status
+ var fields []interface{}
+ if atom, err := r.ReadAtom(); err == nil {
+ fields = append(fields, atom)
+
+ if err := r.ReadSp(); err == nil {
+ if name, ok := atom.(string); ok {
+ status := StatusRespType(name)
+ switch status {
+ case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
+ resp := &StatusResp{
+ Tag: tag,
+ Type: status,
+ }
+
+ char, _, err := r.ReadRune()
+ if err != nil {
+ return nil, err
+ }
+ r.UnreadRune()
+
+ if char == '[' {
+ // Contains code & arguments
+ resp.Code, resp.Arguments, err = r.ReadRespCode()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ resp.Info, err = r.ReadInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ return resp, nil
+ }
+ }
+ } else {
+ r.UnreadRune()
+ }
+ } else {
+ r.UnreadRune()
+ }
+
+ // Not a status so it's data
+ resp := &DataResp{Tag: tag}
+
+ var remaining []interface{}
+ remaining, err = r.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+
+ resp.Fields = append(fields, remaining...)
+ return resp, nil
+}
+
+// DataResp is an IMAP response containing data.
+type DataResp struct {
+ // 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{}
+}
+
+// NewUntaggedResp creates a new untagged response.
+func NewUntaggedResp(fields []interface{}) *DataResp {
+ return &DataResp{
+ Tag: "*",
+ Fields: fields,
+ }
+}
+
+func (r *DataResp) resp() {}
+
+func (r *DataResp) WriteTo(w *Writer) error {
+ tag := RawString(r.Tag)
+ if tag == "" {
+ tag = RawString("*")
+ }
+
+ fields := []interface{}{RawString(tag)}
+ fields = append(fields, r.Fields...)
+ return w.writeLine(fields...)
+}
+
+// ContinuationReq is a continuation request response.
+type ContinuationReq struct {
+ // The info message sent with the continuation request.
+ Info string
+}
+
+func (r *ContinuationReq) resp() {}
+
+func (r *ContinuationReq) WriteTo(w *Writer) error {
+ if err := w.writeString("+"); err != nil {
+ return err
+ }
+
+ if r.Info != "" {
+ if err := w.writeString(string(sp) + r.Info); err != nil {
+ return err
+ }
+ }
+
+ return w.writeCrlf()
+}
+
+// ParseNamedResp attempts to parse a named data response.
+func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
+ data, ok := resp.(*DataResp)
+ if !ok || len(data.Fields) == 0 {
+ return
+ }
+
+ // Some responses (namely EXISTS and RECENT) are formatted like so:
+ // [num] [name] [...]
+ // Which is fucking stupid. But we handle that here by checking if the
+ // response name is a number and then rearranging it.
+ if len(data.Fields) > 1 {
+ name, ok := data.Fields[1].(string)
+ if ok {
+ if _, err := ParseNumber(data.Fields[0]); err == nil {
+ fields := []interface{}{data.Fields[0]}
+ fields = append(fields, data.Fields[2:]...)
+ return strings.ToUpper(name), fields, true
+ }
+ }
+ }
+
+ // IMAP commands are formatted like this:
+ // [name] [...]
+ name, ok = data.Fields[0].(string)
+ if !ok {
+ return
+ }
+ return strings.ToUpper(name), data.Fields[1:], true
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/authenticate.go b/vendor/github.com/emersion/go-imap/responses/authenticate.go
new file mode 100644
index 0000000..8e134cb
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/authenticate.go
@@ -0,0 +1,61 @@
+package responses
+
+import (
+ "encoding/base64"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-sasl"
+)
+
+// An AUTHENTICATE response.
+type Authenticate struct {
+ Mechanism sasl.Client
+ InitialResponse []byte
+ RepliesCh chan []byte
+}
+
+// Implements
+func (r *Authenticate) Replies() <-chan []byte {
+ return r.RepliesCh
+}
+
+func (r *Authenticate) writeLine(l string) error {
+ r.RepliesCh <- []byte(l + "\r\n")
+ return nil
+}
+
+func (r *Authenticate) cancel() error {
+ return r.writeLine("*")
+}
+
+func (r *Authenticate) Handle(resp imap.Resp) error {
+ cont, ok := resp.(*imap.ContinuationReq)
+ if !ok {
+ return ErrUnhandled
+ }
+
+ // 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 := r.writeLine(encoded); err != nil {
+ return err
+ }
+ r.InitialResponse = nil
+ return nil
+ }
+
+ challenge, err := base64.StdEncoding.DecodeString(cont.Info)
+ if err != nil {
+ r.cancel()
+ return err
+ }
+
+ reply, err := r.Mechanism.Next(challenge)
+ if err != nil {
+ r.cancel()
+ return err
+ }
+
+ encoded := base64.StdEncoding.EncodeToString(reply)
+ return r.writeLine(encoded)
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/capability.go b/vendor/github.com/emersion/go-imap/responses/capability.go
new file mode 100644
index 0000000..483cb2e
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/capability.go
@@ -0,0 +1,20 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// A CAPABILITY response.
+// See RFC 3501 section 7.2.1
+type Capability struct {
+ Caps []string
+}
+
+func (r *Capability) WriteTo(w *imap.Writer) error {
+ fields := []interface{}{imap.RawString("CAPABILITY")}
+ for _, cap := range r.Caps {
+ fields = append(fields, imap.RawString(cap))
+ }
+
+ return imap.NewUntaggedResp(fields).WriteTo(w)
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/enabled.go b/vendor/github.com/emersion/go-imap/responses/enabled.go
new file mode 100644
index 0000000..fc4e27b
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/enabled.go
@@ -0,0 +1,33 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// An ENABLED response, defined in RFC 5161 section 3.2.
+type Enabled struct {
+ Caps []string
+}
+
+func (r *Enabled) Handle(resp imap.Resp) error {
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != "ENABLED" {
+ return ErrUnhandled
+ }
+
+ if caps, err := imap.ParseStringList(fields); err != nil {
+ return err
+ } else {
+ r.Caps = append(r.Caps, caps...)
+ }
+
+ return nil
+}
+
+func (r *Enabled) WriteTo(w *imap.Writer) error {
+ fields := []interface{}{imap.RawString("ENABLED")}
+ for _, cap := range r.Caps {
+ fields = append(fields, imap.RawString(cap))
+ }
+ return imap.NewUntaggedResp(fields).WriteTo(w)
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/expunge.go b/vendor/github.com/emersion/go-imap/responses/expunge.go
new file mode 100644
index 0000000..bce3bf1
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/expunge.go
@@ -0,0 +1,43 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+const expungeName = "EXPUNGE"
+
+// An EXPUNGE response.
+// See RFC 3501 section 7.4.1
+type Expunge struct {
+ SeqNums chan uint32
+}
+
+func (r *Expunge) Handle(resp imap.Resp) error {
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != expungeName {
+ return ErrUnhandled
+ }
+
+ if len(fields) == 0 {
+ return errNotEnoughFields
+ }
+
+ seqNum, err := imap.ParseNumber(fields[0])
+ if err != nil {
+ return err
+ }
+
+ r.SeqNums <- seqNum
+ return nil
+}
+
+func (r *Expunge) WriteTo(w *imap.Writer) error {
+ for seqNum := range r.SeqNums {
+ resp := imap.NewUntaggedResp([]interface{}{seqNum, imap.RawString(expungeName)})
+ if err := resp.WriteTo(w); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/fetch.go b/vendor/github.com/emersion/go-imap/responses/fetch.go
new file mode 100644
index 0000000..691ebcb
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/fetch.go
@@ -0,0 +1,70 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+const fetchName = "FETCH"
+
+// A FETCH response.
+// See RFC 3501 section 7.4.2
+type Fetch struct {
+ Messages chan *imap.Message
+ SeqSet *imap.SeqSet
+ Uid bool
+}
+
+func (r *Fetch) Handle(resp imap.Resp) error {
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != fetchName {
+ return ErrUnhandled
+ } else if len(fields) < 1 {
+ return errNotEnoughFields
+ }
+
+ seqNum, err := imap.ParseNumber(fields[0])
+ if err != nil {
+ return err
+ }
+
+ msgFields, _ := fields[1].([]interface{})
+ msg := &imap.Message{SeqNum: seqNum}
+ if err := msg.Parse(msgFields); err != nil {
+ return err
+ }
+
+ if r.Uid && msg.Uid == 0 {
+ // we requested UIDs and got a message without one --> unilateral update --> ignore
+ return ErrUnhandled
+ }
+
+ var num uint32
+ if r.Uid {
+ num = msg.Uid
+ } else {
+ num = seqNum
+ }
+
+ // Check whether we obtained a result we requested with our SeqSet
+ // If the result is not contained in our SeqSet we have to handle an additional special case:
+ // In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
+ // is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
+ // Thus, such a result is correct and has to be returned by us.
+ if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
+ return ErrUnhandled
+ }
+
+ r.Messages <- msg
+ return nil
+}
+
+func (r *Fetch) WriteTo(w *imap.Writer) error {
+ var err error
+ for msg := range r.Messages {
+ resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.RawString(fetchName), msg.Format()})
+ if err == nil {
+ err = resp.WriteTo(w)
+ }
+ }
+ return err
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/idle.go b/vendor/github.com/emersion/go-imap/responses/idle.go
new file mode 100644
index 0000000..b5efcac
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/idle.go
@@ -0,0 +1,38 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+// An IDLE response.
+type Idle struct {
+ RepliesCh chan []byte
+ Stop <-chan struct{}
+
+ gotContinuationReq bool
+}
+
+func (r *Idle) Replies() <-chan []byte {
+ return r.RepliesCh
+}
+
+func (r *Idle) stop() {
+ r.RepliesCh <- []byte("DONE\r\n")
+}
+
+func (r *Idle) Handle(resp imap.Resp) error {
+ // Wait for a continuation request
+ if _, ok := resp.(*imap.ContinuationReq); ok && !r.gotContinuationReq {
+ r.gotContinuationReq = true
+
+ // We got a continuation request, wait for r.Stop to be closed
+ go func() {
+ <-r.Stop
+ r.stop()
+ }()
+
+ return nil
+ }
+
+ return ErrUnhandled
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/list.go b/vendor/github.com/emersion/go-imap/responses/list.go
new file mode 100644
index 0000000..e080fc1
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/list.go
@@ -0,0 +1,57 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+const (
+ listName = "LIST"
+ lsubName = "LSUB"
+)
+
+// A LIST response.
+// If Subscribed is set to true, LSUB will be used instead.
+// See RFC 3501 section 7.2.2
+type List struct {
+ Mailboxes chan *imap.MailboxInfo
+ Subscribed bool
+}
+
+func (r *List) Name() string {
+ if r.Subscribed {
+ return lsubName
+ } else {
+ return listName
+ }
+}
+
+func (r *List) Handle(resp imap.Resp) error {
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != r.Name() {
+ return ErrUnhandled
+ }
+
+ mbox := &imap.MailboxInfo{}
+ if err := mbox.Parse(fields); err != nil {
+ return err
+ }
+
+ r.Mailboxes <- mbox
+ return nil
+}
+
+func (r *List) WriteTo(w *imap.Writer) error {
+ respName := r.Name()
+
+ for mbox := range r.Mailboxes {
+ fields := []interface{}{imap.RawString(respName)}
+ fields = append(fields, mbox.Format()...)
+
+ resp := imap.NewUntaggedResp(fields)
+ if err := resp.WriteTo(w); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/responses.go b/vendor/github.com/emersion/go-imap/responses/responses.go
new file mode 100644
index 0000000..4d035ee
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/responses.go
@@ -0,0 +1,35 @@
+// IMAP responses defined in RFC 3501.
+package responses
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+)
+
+// ErrUnhandled is used when a response hasn't been handled.
+var ErrUnhandled = errors.New("imap: unhandled response")
+
+var errNotEnoughFields = errors.New("imap: not enough fields in response")
+
+// Handler handles responses.
+type Handler interface {
+ // Handle processes a response. If the response cannot be processed,
+ // ErrUnhandledResp must be returned.
+ Handle(resp imap.Resp) error
+}
+
+// HandlerFunc is a function that handles responses.
+type HandlerFunc func(resp imap.Resp) error
+
+// Handle implements Handler.
+func (f HandlerFunc) Handle(resp imap.Resp) error {
+ return f(resp)
+}
+
+// Replier is a Handler that needs to send raw data (for instance
+// AUTHENTICATE).
+type Replier interface {
+ Handler
+ Replies() <-chan []byte
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/search.go b/vendor/github.com/emersion/go-imap/responses/search.go
new file mode 100644
index 0000000..028dbc7
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/search.go
@@ -0,0 +1,41 @@
+package responses
+
+import (
+ "github.com/emersion/go-imap"
+)
+
+const searchName = "SEARCH"
+
+// A SEARCH response.
+// See RFC 3501 section 7.2.5
+type Search struct {
+ Ids []uint32
+}
+
+func (r *Search) Handle(resp imap.Resp) error {
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != searchName {
+ return ErrUnhandled
+ }
+
+ r.Ids = make([]uint32, len(fields))
+ for i, f := range fields {
+ if id, err := imap.ParseNumber(f); err != nil {
+ return err
+ } else {
+ r.Ids[i] = id
+ }
+ }
+
+ return nil
+}
+
+func (r *Search) WriteTo(w *imap.Writer) (err error) {
+ fields := []interface{}{imap.RawString(searchName)}
+ for _, id := range r.Ids {
+ fields = append(fields, id)
+ }
+
+ resp := imap.NewUntaggedResp(fields)
+ return resp.WriteTo(w)
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/select.go b/vendor/github.com/emersion/go-imap/responses/select.go
new file mode 100644
index 0000000..e450963
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/select.go
@@ -0,0 +1,142 @@
+package responses
+
+import (
+ "fmt"
+
+ "github.com/emersion/go-imap"
+)
+
+// A SELECT response.
+type Select struct {
+ Mailbox *imap.MailboxStatus
+}
+
+func (r *Select) Handle(resp imap.Resp) error {
+ if r.Mailbox == nil {
+ r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
+ }
+ mbox := r.Mailbox
+
+ switch resp := resp.(type) {
+ case *imap.DataResp:
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != "FLAGS" {
+ return ErrUnhandled
+ } else if len(fields) < 1 {
+ return errNotEnoughFields
+ }
+
+ flags, _ := fields[0].([]interface{})
+ mbox.Flags, _ = imap.ParseStringList(flags)
+ case *imap.StatusResp:
+ if len(resp.Arguments) < 1 {
+ return ErrUnhandled
+ }
+
+ var item imap.StatusItem
+ switch resp.Code {
+ case "UNSEEN":
+ mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
+ case "PERMANENTFLAGS":
+ flags, _ := resp.Arguments[0].([]interface{})
+ mbox.PermanentFlags, _ = imap.ParseStringList(flags)
+ case "UIDNEXT":
+ mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
+ item = imap.StatusUidNext
+ case "UIDVALIDITY":
+ mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
+ item = imap.StatusUidValidity
+ default:
+ return ErrUnhandled
+ }
+
+ if item != "" {
+ mbox.ItemsLocker.Lock()
+ mbox.Items[item] = nil
+ mbox.ItemsLocker.Unlock()
+ }
+ default:
+ return ErrUnhandled
+ }
+ return nil
+}
+
+func (r *Select) WriteTo(w *imap.Writer) error {
+ mbox := r.Mailbox
+
+ if mbox.Flags != nil {
+ flags := make([]interface{}, len(mbox.Flags))
+ for i, f := range mbox.Flags {
+ flags[i] = imap.RawString(f)
+ }
+ res := imap.NewUntaggedResp([]interface{}{imap.RawString("FLAGS"), flags})
+ if err := res.WriteTo(w); err != nil {
+ return err
+ }
+ }
+
+ if mbox.PermanentFlags != nil {
+ flags := make([]interface{}, len(mbox.PermanentFlags))
+ for i, f := range mbox.PermanentFlags {
+ flags[i] = imap.RawString(f)
+ }
+ statusRes := &imap.StatusResp{
+ Type: imap.StatusRespOk,
+ Code: imap.CodePermanentFlags,
+ Arguments: []interface{}{flags},
+ Info: "Flags permitted.",
+ }
+ if err := statusRes.WriteTo(w); err != nil {
+ return err
+ }
+ }
+
+ if mbox.UnseenSeqNum > 0 {
+ statusRes := &imap.StatusResp{
+ Type: imap.StatusRespOk,
+ Code: imap.CodeUnseen,
+ Arguments: []interface{}{mbox.UnseenSeqNum},
+ Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
+ }
+ if err := statusRes.WriteTo(w); err != nil {
+ return err
+ }
+ }
+
+ for k := range r.Mailbox.Items {
+ switch k {
+ case imap.StatusMessages:
+ res := imap.NewUntaggedResp([]interface{}{mbox.Messages, imap.RawString("EXISTS")})
+ if err := res.WriteTo(w); err != nil {
+ return err
+ }
+ case imap.StatusRecent:
+ res := imap.NewUntaggedResp([]interface{}{mbox.Recent, imap.RawString("RECENT")})
+ if err := res.WriteTo(w); err != nil {
+ return err
+ }
+ case imap.StatusUidNext:
+ statusRes := &imap.StatusResp{
+ Type: imap.StatusRespOk,
+ Code: imap.CodeUidNext,
+ Arguments: []interface{}{mbox.UidNext},
+ Info: "Predicted next UID",
+ }
+ if err := statusRes.WriteTo(w); err != nil {
+ return err
+ }
+ case imap.StatusUidValidity:
+ statusRes := &imap.StatusResp{
+ Type: imap.StatusRespOk,
+ Code: imap.CodeUidValidity,
+ Arguments: []interface{}{mbox.UidValidity},
+ Info: "UIDs valid",
+ }
+ if err := statusRes.WriteTo(w); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/emersion/go-imap/responses/status.go b/vendor/github.com/emersion/go-imap/responses/status.go
new file mode 100644
index 0000000..6a8570c
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/responses/status.go
@@ -0,0 +1,53 @@
+package responses
+
+import (
+ "errors"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/utf7"
+)
+
+const statusName = "STATUS"
+
+// A STATUS response.
+// See RFC 3501 section 7.2.4
+type Status struct {
+ Mailbox *imap.MailboxStatus
+}
+
+func (r *Status) Handle(resp imap.Resp) error {
+ if r.Mailbox == nil {
+ r.Mailbox = &imap.MailboxStatus{}
+ }
+ mbox := r.Mailbox
+
+ name, fields, ok := imap.ParseNamedResp(resp)
+ if !ok || name != statusName {
+ return ErrUnhandled
+ } else if len(fields) < 2 {
+ return errNotEnoughFields
+ }
+
+ if name, err := imap.ParseString(fields[0]); err != nil {
+ return err
+ } else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
+ return err
+ } else {
+ mbox.Name = imap.CanonicalMailboxName(name)
+ }
+
+ var items []interface{}
+ if items, ok = fields[1].([]interface{}); !ok {
+ return errors.New("STATUS response expects a list as second argument")
+ }
+
+ mbox.Items = nil
+ return mbox.Parse(items)
+}
+
+func (r *Status) WriteTo(w *imap.Writer) error {
+ mbox := r.Mailbox
+ name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
+ fields := []interface{}{imap.RawString(statusName), imap.FormatMailboxName(name), mbox.Format()}
+ return imap.NewUntaggedResp(fields).WriteTo(w)
+}
diff --git a/vendor/github.com/emersion/go-imap/search.go b/vendor/github.com/emersion/go-imap/search.go
new file mode 100644
index 0000000..0ecb24d
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/search.go
@@ -0,0 +1,371 @@
+package imap
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net/textproto"
+ "strings"
+ "time"
+)
+
+func maybeString(mystery interface{}) string {
+ if s, ok := mystery.(string); ok {
+ return s
+ }
+ return ""
+}
+
+func convertField(f interface{}, charsetReader func(io.Reader) io.Reader) string {
+ // An IMAP string contains only 7-bit data, no need to decode it
+ if s, ok := f.(string); ok {
+ return s
+ }
+
+ // If no charset is provided, getting directly the string is faster
+ if charsetReader == nil {
+ if stringer, ok := f.(fmt.Stringer); ok {
+ return stringer.String()
+ }
+ }
+
+ // Not a string, it must be a literal
+ l, ok := f.(Literal)
+ if !ok {
+ return ""
+ }
+
+ var r io.Reader = l
+ if charsetReader != nil {
+ if dec := charsetReader(r); dec != nil {
+ r = dec
+ }
+ }
+
+ b := make([]byte, l.Len())
+ if _, err := io.ReadFull(r, b); err != nil {
+ return ""
+ }
+ return string(b)
+}
+
+func popSearchField(fields []interface{}) (interface{}, []interface{}, error) {
+ if len(fields) == 0 {
+ return nil, nil, errors.New("imap: no enough fields for search key")
+ }
+ return fields[0], fields[1:], nil
+}
+
+// SearchCriteria is a search criteria. A message matches the criteria if and
+// only if it matches each one of its fields.
+type SearchCriteria struct {
+ SeqNum *SeqSet // Sequence number is in sequence set
+ Uid *SeqSet // UID is in sequence set
+
+ // Time and timezone are ignored
+ Since time.Time // Internal date is since this date
+ Before time.Time // Internal date is before this date
+ SentSince time.Time // Date header field is since this date
+ SentBefore time.Time // Date header field is before this date
+
+ Header textproto.MIMEHeader // Each header field value is present
+ Body []string // Each string is in the body
+ Text []string // Each string is in the text (header + body)
+
+ WithFlags []string // Each flag is present
+ WithoutFlags []string // Each flag is not present
+
+ Larger uint32 // Size is larger than this number
+ Smaller uint32 // Size is smaller than this number
+
+ Not []*SearchCriteria // Each criteria doesn't match
+ Or [][2]*SearchCriteria // Each criteria pair has at least one match of two
+}
+
+// NewSearchCriteria creates a new search criteria.
+func NewSearchCriteria() *SearchCriteria {
+ return &SearchCriteria{Header: make(textproto.MIMEHeader)}
+}
+
+func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.Reader) io.Reader) ([]interface{}, error) {
+ if len(fields) == 0 {
+ return nil, nil
+ }
+
+ f := fields[0]
+ fields = fields[1:]
+
+ if subfields, ok := f.([]interface{}); ok {
+ return fields, c.ParseWithCharset(subfields, charsetReader)
+ }
+
+ key, ok := f.(string)
+ if !ok {
+ return nil, fmt.Errorf("imap: invalid search criteria field type: %T", f)
+ }
+ key = strings.ToUpper(key)
+
+ var err error
+ switch key {
+ case "ALL":
+ // Nothing to do
+ case "ANSWERED", "DELETED", "DRAFT", "FLAGGED", "RECENT", "SEEN":
+ c.WithFlags = append(c.WithFlags, CanonicalFlag("\\"+key))
+ case "BCC", "CC", "FROM", "SUBJECT", "TO":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ }
+ if c.Header == nil {
+ c.Header = make(textproto.MIMEHeader)
+ }
+ c.Header.Add(key, convertField(f, charsetReader))
+ case "BEFORE":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else if c.Before.IsZero() || t.Before(c.Before) {
+ c.Before = t
+ }
+ case "BODY":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else {
+ c.Body = append(c.Body, convertField(f, charsetReader))
+ }
+ case "HEADER":
+ var f1, f2 interface{}
+ if f1, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if f2, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else {
+ if c.Header == nil {
+ c.Header = make(textproto.MIMEHeader)
+ }
+ c.Header.Add(maybeString(f1), convertField(f2, charsetReader))
+ }
+ case "KEYWORD":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else {
+ c.WithFlags = append(c.WithFlags, CanonicalFlag(maybeString(f)))
+ }
+ case "LARGER":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if n, err := ParseNumber(f); err != nil {
+ return nil, err
+ } else if c.Larger == 0 || n > c.Larger {
+ c.Larger = n
+ }
+ case "NEW":
+ c.WithFlags = append(c.WithFlags, RecentFlag)
+ c.WithoutFlags = append(c.WithoutFlags, SeenFlag)
+ case "NOT":
+ not := new(SearchCriteria)
+ if fields, err = not.parseField(fields, charsetReader); err != nil {
+ return nil, err
+ }
+ c.Not = append(c.Not, not)
+ case "OLD":
+ c.WithoutFlags = append(c.WithoutFlags, RecentFlag)
+ case "ON":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else {
+ c.Since = t
+ c.Before = t.Add(24 * time.Hour)
+ }
+ case "OR":
+ c1, c2 := new(SearchCriteria), new(SearchCriteria)
+ if fields, err = c1.parseField(fields, charsetReader); err != nil {
+ return nil, err
+ } else if fields, err = c2.parseField(fields, charsetReader); err != nil {
+ return nil, err
+ }
+ c.Or = append(c.Or, [2]*SearchCriteria{c1, c2})
+ case "SENTBEFORE":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else if c.SentBefore.IsZero() || t.Before(c.SentBefore) {
+ c.SentBefore = t
+ }
+ case "SENTON":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else {
+ c.SentSince = t
+ c.SentBefore = t.Add(24 * time.Hour)
+ }
+ case "SENTSINCE":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else if c.SentSince.IsZero() || t.After(c.SentSince) {
+ c.SentSince = t
+ }
+ case "SINCE":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if t, err := time.Parse(DateLayout, maybeString(f)); err != nil {
+ return nil, err
+ } else if c.Since.IsZero() || t.After(c.Since) {
+ c.Since = t
+ }
+ case "SMALLER":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if n, err := ParseNumber(f); err != nil {
+ return nil, err
+ } else if c.Smaller == 0 || n < c.Smaller {
+ c.Smaller = n
+ }
+ case "TEXT":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else {
+ c.Text = append(c.Text, convertField(f, charsetReader))
+ }
+ case "UID":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else if c.Uid, err = ParseSeqSet(maybeString(f)); err != nil {
+ return nil, err
+ }
+ case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN":
+ unflag := strings.TrimPrefix(key, "UN")
+ c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag("\\"+unflag))
+ case "UNKEYWORD":
+ if f, fields, err = popSearchField(fields); err != nil {
+ return nil, err
+ } else {
+ c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
+ }
+ default: // Try to parse a sequence set
+ if c.SeqNum, err = ParseSeqSet(key); err != nil {
+ return nil, err
+ }
+ }
+
+ return fields, nil
+}
+
+// ParseWithCharset parses a search criteria from the provided fields.
+// charsetReader is an optional function that converts from the fields charset
+// to UTF-8.
+func (c *SearchCriteria) ParseWithCharset(fields []interface{}, charsetReader func(io.Reader) io.Reader) error {
+ for len(fields) > 0 {
+ var err error
+ if fields, err = c.parseField(fields, charsetReader); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Format formats search criteria to fields. UTF-8 is used.
+func (c *SearchCriteria) Format() []interface{} {
+ var fields []interface{}
+
+ if c.SeqNum != nil {
+ fields = append(fields, c.SeqNum)
+ }
+ if c.Uid != nil {
+ fields = append(fields, RawString("UID"), c.Uid)
+ }
+
+ if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour {
+ fields = append(fields, RawString("ON"), searchDate(c.Since))
+ } else {
+ if !c.Since.IsZero() {
+ fields = append(fields, RawString("SINCE"), searchDate(c.Since))
+ }
+ if !c.Before.IsZero() {
+ fields = append(fields, RawString("BEFORE"), searchDate(c.Before))
+ }
+ }
+ if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour {
+ fields = append(fields, RawString("SENTON"), searchDate(c.SentSince))
+ } else {
+ if !c.SentSince.IsZero() {
+ fields = append(fields, RawString("SENTSINCE"), searchDate(c.SentSince))
+ }
+ if !c.SentBefore.IsZero() {
+ fields = append(fields, RawString("SENTBEFORE"), searchDate(c.SentBefore))
+ }
+ }
+
+ for key, values := range c.Header {
+ var prefields []interface{}
+ switch key {
+ case "Bcc", "Cc", "From", "Subject", "To":
+ prefields = []interface{}{RawString(strings.ToUpper(key))}
+ default:
+ prefields = []interface{}{RawString("HEADER"), key}
+ }
+ for _, value := range values {
+ fields = append(fields, prefields...)
+ fields = append(fields, value)
+ }
+ }
+
+ for _, value := range c.Body {
+ fields = append(fields, RawString("BODY"), value)
+ }
+ for _, value := range c.Text {
+ fields = append(fields, RawString("TEXT"), value)
+ }
+
+ for _, flag := range c.WithFlags {
+ var subfields []interface{}
+ switch flag {
+ case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag:
+ subfields = []interface{}{RawString(strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
+ default:
+ subfields = []interface{}{RawString("KEYWORD"), RawString(flag)}
+ }
+ fields = append(fields, subfields...)
+ }
+ for _, flag := range c.WithoutFlags {
+ var subfields []interface{}
+ switch flag {
+ case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag:
+ subfields = []interface{}{RawString("UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\")))}
+ case RecentFlag:
+ subfields = []interface{}{RawString("OLD")}
+ default:
+ subfields = []interface{}{RawString("UNKEYWORD"), RawString(flag)}
+ }
+ fields = append(fields, subfields...)
+ }
+
+ if c.Larger > 0 {
+ fields = append(fields, RawString("LARGER"), c.Larger)
+ }
+ if c.Smaller > 0 {
+ fields = append(fields, RawString("SMALLER"), c.Smaller)
+ }
+
+ for _, not := range c.Not {
+ fields = append(fields, RawString("NOT"), not.Format())
+ }
+
+ for _, or := range c.Or {
+ fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format())
+ }
+
+ // Not a single criteria given, add ALL criteria as fallback
+ if len(fields) == 0 {
+ fields = append(fields, RawString("ALL"))
+ }
+
+ return fields
+}
diff --git a/vendor/github.com/emersion/go-imap/seqset.go b/vendor/github.com/emersion/go-imap/seqset.go
new file mode 100644
index 0000000..abe6afc
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/seqset.go
@@ -0,0 +1,289 @@
+package imap
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// ErrBadSeqSet is used to report problems with the format of a sequence set
+// value.
+type ErrBadSeqSet string
+
+func (err ErrBadSeqSet) Error() string {
+ return fmt.Sprintf("imap: bad sequence set value %q", string(err))
+}
+
+// Seq represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
+// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
+// represented by setting Start = Stop. Zero is used to represent "*", which is
+// safe because seq-number uses nz-number rule. The order of values is always
+// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
+type Seq struct {
+ Start, Stop uint32
+}
+
+// parseSeqNumber parses a single seq-number value (non-zero uint32 or "*").
+func parseSeqNumber(v string) (uint32, error) {
+ if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
+ return uint32(n), nil
+ } else if v == "*" {
+ return 0, nil
+ }
+ return 0, ErrBadSeqSet(v)
+}
+
+// parseSeq creates a new seq instance by parsing strings in the format "n" or
+// "n:m", where n and/or m may be "*". An error is returned for invalid values.
+func parseSeq(v string) (s Seq, err error) {
+ if sep := strings.IndexRune(v, ':'); sep < 0 {
+ s.Start, err = parseSeqNumber(v)
+ s.Stop = s.Start
+ return
+ } else if s.Start, err = parseSeqNumber(v[:sep]); err == nil {
+ if s.Stop, err = parseSeqNumber(v[sep+1:]); err == nil {
+ if (s.Stop < s.Start && s.Stop != 0) || s.Start == 0 {
+ s.Start, s.Stop = s.Stop, s.Start
+ }
+ return
+ }
+ }
+ return s, ErrBadSeqSet(v)
+}
+
+// Contains returns true if the seq-number q is contained in sequence value s.
+// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
+// contains "*" and all numbers >= n.
+func (s Seq) Contains(q uint32) bool {
+ if q == 0 {
+ return s.Stop == 0 // "*" is contained only in "*" and "n:*"
+ }
+ return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
+}
+
+// Less returns true if s precedes and does not contain seq-number q.
+func (s Seq) Less(q uint32) bool {
+ return (s.Stop < q || q == 0) && s.Stop != 0
+}
+
+// Merge combines sequence values s and t into a single union if the two
+// intersect or one is a superset of the other. The order of s and t does not
+// matter. If the values cannot be merged, s is returned unmodified and ok is
+// set to false.
+func (s Seq) Merge(t Seq) (union Seq, ok bool) {
+ if union = s; s == t {
+ ok = true
+ return
+ }
+ if s.Start != 0 && t.Start != 0 {
+ // s and t are any combination of "n", "n:m", or "n:*"
+ if s.Start > t.Start {
+ s, t = t, s
+ }
+ // s starts at or before t, check where it ends
+ if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
+ return s, true // s is a superset of t
+ }
+ // s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
+ if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
+ return Seq{s.Start, t.Stop}, true // s intersects or touches t
+ }
+ return
+ }
+ // exactly one of s and t is "*"
+ if s.Start == 0 {
+ if t.Stop == 0 {
+ return t, true // s is "*", t is "n:*"
+ }
+ } else if s.Stop == 0 {
+ return s, true // s is "n:*", t is "*"
+ }
+ return
+}
+
+// String returns sequence value s as a seq-number or seq-range string.
+func (s Seq) String() string {
+ if s.Start == s.Stop {
+ if s.Start == 0 {
+ return "*"
+ }
+ return strconv.FormatUint(uint64(s.Start), 10)
+ }
+ b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
+ if s.Stop == 0 {
+ return string(append(b, ':', '*'))
+ }
+ return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
+}
+
+// SeqSet is used to represent a set of message sequence numbers or UIDs (see
+// sequence-set ABNF rule). The zero value is an empty set.
+type SeqSet struct {
+ Set []Seq
+}
+
+// ParseSeqSet returns a new SeqSet instance after parsing the set string.
+func ParseSeqSet(set string) (s *SeqSet, err error) {
+ s = new(SeqSet)
+ return s, s.Add(set)
+}
+
+// Add inserts new sequence values into the set. The string format is described
+// by RFC 3501 sequence-set ABNF rule. If an error is encountered, all values
+// inserted successfully prior to the error remain in the set.
+func (s *SeqSet) Add(set string) error {
+ for _, sv := range strings.Split(set, ",") {
+ v, err := parseSeq(sv)
+ if err != nil {
+ return err
+ }
+ s.insert(v)
+ }
+ return nil
+}
+
+// AddNum inserts new sequence numbers into the set. The value 0 represents "*".
+func (s *SeqSet) AddNum(q ...uint32) {
+ for _, v := range q {
+ s.insert(Seq{v, v})
+ }
+}
+
+// AddRange inserts a new sequence range into the set.
+func (s *SeqSet) AddRange(Start, Stop uint32) {
+ if (Stop < Start && Stop != 0) || Start == 0 {
+ s.insert(Seq{Stop, Start})
+ } else {
+ s.insert(Seq{Start, Stop})
+ }
+}
+
+// AddSet inserts all values from t into s.
+func (s *SeqSet) AddSet(t *SeqSet) {
+ for _, v := range t.Set {
+ s.insert(v)
+ }
+}
+
+// Clear removes all values from the set.
+func (s *SeqSet) Clear() {
+ s.Set = s.Set[:0]
+}
+
+// Empty returns true if the sequence set does not contain any values.
+func (s SeqSet) Empty() bool {
+ return len(s.Set) == 0
+}
+
+// Dynamic returns true if the set contains "*" or "n:*" values.
+func (s SeqSet) Dynamic() bool {
+ return len(s.Set) > 0 && s.Set[len(s.Set)-1].Stop == 0
+}
+
+// Contains returns true if the non-zero sequence number or UID q is contained
+// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
+// responsibility to handle the special case where q is the maximum UID in the
+// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
+// it doesn't know what the maximum value is).
+func (s SeqSet) Contains(q uint32) bool {
+ if _, ok := s.search(q); ok {
+ return q != 0
+ }
+ return false
+}
+
+// String returns a sorted representation of all contained sequence values.
+func (s SeqSet) String() string {
+ if len(s.Set) == 0 {
+ return ""
+ }
+ b := make([]byte, 0, 64)
+ for _, v := range s.Set {
+ b = append(b, ',')
+ if v.Start == 0 {
+ b = append(b, '*')
+ continue
+ }
+ b = strconv.AppendUint(b, uint64(v.Start), 10)
+ if v.Start != v.Stop {
+ if v.Stop == 0 {
+ b = append(b, ':', '*')
+ continue
+ }
+ b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
+ }
+ }
+ return string(b[1:])
+}
+
+// insert adds sequence value v to the set.
+func (s *SeqSet) insert(v Seq) {
+ i, _ := s.search(v.Start)
+ merged := false
+ if i > 0 {
+ // try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
+ s.Set[i-1], merged = s.Set[i-1].Merge(v)
+ }
+ if i == len(s.Set) {
+ // v was either merged with the last entry or needs to be appended
+ if !merged {
+ s.insertAt(i, v)
+ }
+ return
+ } else if merged {
+ i--
+ } else if s.Set[i], merged = s.Set[i].Merge(v); !merged {
+ s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
+ return
+ }
+ // v was merged with s.Set[i], continue trying to merge until the end
+ for j := i + 1; j < len(s.Set); j++ {
+ if s.Set[i], merged = s.Set[i].Merge(s.Set[j]); !merged {
+ if j > i+1 {
+ // cut out all entries between i and j that were merged
+ s.Set = append(s.Set[:i+1], s.Set[j:]...)
+ }
+ return
+ }
+ }
+ // everything after s.Set[i] was merged
+ s.Set = s.Set[:i+1]
+}
+
+// insertAt inserts a new sequence value v at index i, resizing s.Set as needed.
+func (s *SeqSet) insertAt(i int, v Seq) {
+ if n := len(s.Set); i == n {
+ // insert at the end
+ s.Set = append(s.Set, v)
+ return
+ } else if n < cap(s.Set) {
+ // enough space, shift everything at and after i to the right
+ s.Set = s.Set[:n+1]
+ copy(s.Set[i+1:], s.Set[i:])
+ } else {
+ // allocate new slice and copy everything, n is at least 1
+ set := make([]Seq, n+1, n*2)
+ copy(set, s.Set[:i])
+ copy(set[i+1:], s.Set[i:])
+ s.Set = set
+ }
+ s.Set[i] = v
+}
+
+// search attempts to find the index of the sequence set value that contains q.
+// If no values contain q, the returned index is the position where q should be
+// inserted and ok is set to false.
+func (s SeqSet) search(q uint32) (i int, ok bool) {
+ min, max := 0, len(s.Set)-1
+ for min < max {
+ if mid := (min + max) >> 1; s.Set[mid].Less(q) {
+ min = mid + 1
+ } else {
+ max = mid
+ }
+ }
+ if max < 0 || s.Set[min].Less(q) {
+ return len(s.Set), false // q is the new largest value
+ }
+ return min, s.Set[min].Contains(q)
+}
diff --git a/vendor/github.com/emersion/go-imap/status.go b/vendor/github.com/emersion/go-imap/status.go
new file mode 100644
index 0000000..81ffd1b
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/status.go
@@ -0,0 +1,136 @@
+package imap
+
+import (
+ "errors"
+)
+
+// A status response type.
+type StatusRespType string
+
+// Status response types defined in RFC 3501 section 7.1.
+const (
+ // The OK response indicates an information message from the server. When
+ // tagged, it indicates successful completion of the associated command.
+ // The untagged form indicates an information-only message.
+ StatusRespOk StatusRespType = "OK"
+
+ // The NO response indicates an operational error message from the
+ // server. When tagged, it indicates unsuccessful completion of the
+ // associated command. The untagged form indicates a warning; the
+ // command can still complete successfully.
+ StatusRespNo StatusRespType = "NO"
+
+ // The BAD response indicates an error message from the server. When
+ // tagged, it reports a protocol-level error in the client's command;
+ // the tag indicates the command that caused the error. The untagged
+ // form indicates a protocol-level error for which the associated
+ // command can not be determined; it can also indicate an internal
+ // server failure.
+ StatusRespBad StatusRespType = "BAD"
+
+ // The PREAUTH response is always untagged, and is one of three
+ // possible greetings at connection startup. It indicates that the
+ // connection has already been authenticated by external means; thus
+ // no LOGIN command is needed.
+ StatusRespPreauth StatusRespType = "PREAUTH"
+
+ // The BYE response is always untagged, and indicates that the server
+ // is about to close the connection.
+ StatusRespBye StatusRespType = "BYE"
+)
+
+type StatusRespCode string
+
+// Status response codes defined in RFC 3501 section 7.1.
+const (
+ CodeAlert StatusRespCode = "ALERT"
+ CodeBadCharset StatusRespCode = "BADCHARSET"
+ CodeCapability StatusRespCode = "CAPABILITY"
+ CodeParse StatusRespCode = "PARSE"
+ CodePermanentFlags StatusRespCode = "PERMANENTFLAGS"
+ CodeReadOnly StatusRespCode = "READ-ONLY"
+ CodeReadWrite StatusRespCode = "READ-WRITE"
+ CodeTryCreate StatusRespCode = "TRYCREATE"
+ CodeUidNext StatusRespCode = "UIDNEXT"
+ CodeUidValidity StatusRespCode = "UIDVALIDITY"
+ CodeUnseen StatusRespCode = "UNSEEN"
+)
+
+// A status response.
+// See RFC 3501 section 7.1
+type StatusResp struct {
+ // The response tag. If empty, it defaults to *.
+ Tag string
+ // The status type.
+ Type StatusRespType
+ // The status code.
+ // See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml
+ Code StatusRespCode
+ // Arguments provided with the status code.
+ Arguments []interface{}
+ // The status info.
+ Info string
+}
+
+func (r *StatusResp) resp() {}
+
+// If this status is NO or BAD, returns an error with the status info.
+// Otherwise, returns nil.
+func (r *StatusResp) Err() error {
+ if r == nil {
+ // No status response, connection closed before we get one
+ return errors.New("imap: connection closed during command execution")
+ }
+
+ if r.Type == StatusRespNo || r.Type == StatusRespBad {
+ return errors.New(r.Info)
+ }
+ return nil
+}
+
+func (r *StatusResp) WriteTo(w *Writer) error {
+ tag := RawString(r.Tag)
+ if tag == "" {
+ tag = "*"
+ }
+
+ if err := w.writeFields([]interface{}{RawString(tag), RawString(r.Type)}); err != nil {
+ return err
+ }
+
+ if err := w.writeString(string(sp)); err != nil {
+ return err
+ }
+
+ if r.Code != "" {
+ if err := w.writeRespCode(r.Code, r.Arguments); err != nil {
+ return err
+ }
+
+ if err := w.writeString(string(sp)); err != nil {
+ return err
+ }
+ }
+
+ if err := w.writeString(r.Info); err != nil {
+ return err
+ }
+
+ return w.writeCrlf()
+}
+
+// ErrStatusResp can be returned by a server.Handler to replace the default status
+// response. The response tag must be empty.
+//
+// To suppress default response, Resp should be set to nil.
+type ErrStatusResp struct {
+ // Response to send instead of default.
+ Resp *StatusResp
+}
+
+func (err *ErrStatusResp) Error() string {
+ if err.Resp == nil {
+ return "imap: suppressed response"
+ }
+ return err.Resp.Info
+}
diff --git a/vendor/github.com/emersion/go-imap/utf7/decoder.go b/vendor/github.com/emersion/go-imap/utf7/decoder.go
new file mode 100644
index 0000000..cfcba8c
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/utf7/decoder.go
@@ -0,0 +1,149 @@
+package utf7
+
+import (
+ "errors"
+ "unicode/utf16"
+ "unicode/utf8"
+
+ "golang.org/x/text/transform"
+)
+
+// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7.
+var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
+
+type decoder struct {
+ ascii bool
+}
+
+func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ for i := 0; i < len(src); i++ {
+ ch := src[i]
+
+ if ch < min || ch > max { // Illegal code point in ASCII mode
+ err = ErrInvalidUTF7
+ return
+ }
+
+ if ch != '&' {
+ if nDst+1 > len(dst) {
+ err = transform.ErrShortDst
+ return
+ }
+
+ nSrc++
+
+ dst[nDst] = ch
+ nDst++
+
+ d.ascii = true
+ continue
+ }
+
+ // Find the end of the Base64 or "&-" segment
+ start := i + 1
+ for i++; i < len(src) && src[i] != '-'; i++ {
+ if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
+ err = ErrInvalidUTF7
+ return
+ }
+ }
+
+ if i == len(src) { // Implicit shift ("&...")
+ if atEOF {
+ err = ErrInvalidUTF7
+ } else {
+ err = transform.ErrShortSrc
+ }
+ return
+ }
+
+ var b []byte
+ if i == start { // Escape sequence "&-"
+ b = []byte{'&'}
+ d.ascii = true
+ } else { // Control or non-ASCII code points in base64
+ if !d.ascii { // Null shift ("&...-&...-")
+ err = ErrInvalidUTF7
+ return
+ }
+
+ b = decode(src[start:i])
+ d.ascii = false
+ }
+
+ if len(b) == 0 { // Bad encoding
+ err = ErrInvalidUTF7
+ return
+ }
+
+ if nDst+len(b) > len(dst) {
+ d.ascii = true
+ err = transform.ErrShortDst
+ return
+ }
+
+ nSrc = i + 1
+
+ for _, ch := range b {
+ dst[nDst] = ch
+ nDst++
+ }
+ }
+
+ if atEOF {
+ d.ascii = true
+ }
+
+ return
+}
+
+func (d *decoder) Reset() {
+ d.ascii = true
+}
+
+// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8.
+// A nil slice is returned if the encoding is invalid.
+func decode(b64 []byte) []byte {
+ var b []byte
+
+ // Allocate a single block of memory large enough to store the Base64 data
+ // (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
+ // Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
+ // double the space allocation for UTF-8.
+ if n := len(b64); b64[n-1] == '=' {
+ return nil
+ } else if n&3 == 0 {
+ b = make([]byte, b64Enc.DecodedLen(n)*3)
+ } else {
+ n += 4 - n&3
+ b = make([]byte, n+b64Enc.DecodedLen(n)*3)
+ copy(b[copy(b, b64):n], []byte("=="))
+ b64, b = b[:n], b[n:]
+ }
+
+ // Decode Base64 into the first 1/3rd of b
+ n, err := b64Enc.Decode(b, b64)
+ if err != nil || n&1 == 1 {
+ return nil
+ }
+
+ // Decode UTF-16-BE into the remaining 2/3rds of b
+ b, s := b[:n], b[n:]
+ j := 0
+ for i := 0; i < n; i += 2 {
+ r := rune(b[i])<<8 | rune(b[i+1])
+ if utf16.IsSurrogate(r) {
+ if i += 2; i == n {
+ return nil
+ }
+ r2 := rune(b[i])<<8 | rune(b[i+1])
+ if r = utf16.DecodeRune(r, r2); r == repl {
+ return nil
+ }
+ } else if min <= r && r <= max {
+ return nil
+ }
+ j += utf8.EncodeRune(s[j:], r)
+ }
+ return s[:j]
+}
diff --git a/vendor/github.com/emersion/go-imap/utf7/encoder.go b/vendor/github.com/emersion/go-imap/utf7/encoder.go
new file mode 100644
index 0000000..8414d10
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/utf7/encoder.go
@@ -0,0 +1,91 @@
+package utf7
+
+import (
+ "unicode/utf16"
+ "unicode/utf8"
+
+ "golang.org/x/text/transform"
+)
+
+type encoder struct{}
+
+func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ for i := 0; i < len(src); {
+ ch := src[i]
+
+ var b []byte
+ if min <= ch && ch <= max {
+ b = []byte{ch}
+ if ch == '&' {
+ b = append(b, '-')
+ }
+
+ i++
+ } else {
+ start := i
+
+ // Find the next printable ASCII code point
+ i++
+ for i < len(src) && (src[i] < min || src[i] > max) {
+ i++
+ }
+
+ if !atEOF && i == len(src) {
+ err = transform.ErrShortSrc
+ return
+ }
+
+ b = encode(src[start:i])
+ }
+
+ if nDst+len(b) > len(dst) {
+ err = transform.ErrShortDst
+ return
+ }
+
+ nSrc = i
+
+ for _, ch := range b {
+ dst[nDst] = ch
+ nDst++
+ }
+ }
+
+ return
+}
+
+func (e *encoder) Reset() {}
+
+// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
+// removes the padding, and adds UTF-7 shifts.
+func encode(s []byte) []byte {
+ // len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
+ // control code points (see table below).
+ b := make([]byte, 0, len(s)+4)
+ for len(s) > 0 {
+ r, size := utf8.DecodeRune(s)
+ if r > utf8.MaxRune {
+ r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
+ }
+ s = s[size:]
+ if r1, r2 := utf16.EncodeRune(r); r1 != repl {
+ b = append(b, byte(r1>>8), byte(r1))
+ r = r2
+ }
+ b = append(b, byte(r>>8), byte(r))
+ }
+
+ // Encode as base64
+ n := b64Enc.EncodedLen(len(b)) + 2
+ b64 := make([]byte, n)
+ b64Enc.Encode(b64[1:], b)
+
+ // Strip padding
+ n -= 2 - (len(b)+2)%3
+ b64 = b64[:n]
+
+ // Add UTF-7 shifts
+ b64[0] = '&'
+ b64[n-1] = '-'
+ return b64
+}
diff --git a/vendor/github.com/emersion/go-imap/utf7/utf7.go b/vendor/github.com/emersion/go-imap/utf7/utf7.go
new file mode 100644
index 0000000..b9dd962
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/utf7/utf7.go
@@ -0,0 +1,34 @@
+// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
+package utf7
+
+import (
+ "encoding/base64"
+
+ "golang.org/x/text/encoding"
+)
+
+const (
+ min = 0x20 // Minimum self-representing UTF-7 value
+ max = 0x7E // Maximum self-representing UTF-7 value
+
+ repl = '\uFFFD' // Unicode replacement code point
+)
+
+var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
+
+type enc struct{}
+
+func (e enc) NewDecoder() *encoding.Decoder {
+ return &encoding.Decoder{
+ Transformer: &decoder{true},
+ }
+}
+
+func (e enc) NewEncoder() *encoding.Encoder {
+ return &encoding.Encoder{
+ Transformer: &encoder{},
+ }
+}
+
+// Encoding is the modified UTF-7 encoding.
+var Encoding encoding.Encoding = enc{}
diff --git a/vendor/github.com/emersion/go-imap/write.go b/vendor/github.com/emersion/go-imap/write.go
new file mode 100644
index 0000000..c295e4e
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/write.go
@@ -0,0 +1,255 @@
+package imap
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strconv"
+ "time"
+ "unicode"
+)
+
+type flusher interface {
+ Flush() error
+}
+
+type (
+ // A raw string.
+ RawString string
+)
+
+type WriterTo interface {
+ WriteTo(w *Writer) error
+}
+
+func formatNumber(num uint32) string {
+ return strconv.FormatUint(uint64(num), 10)
+}
+
+// Convert a string list to a field list.
+func FormatStringList(list []string) (fields []interface{}) {
+ fields = make([]interface{}, len(list))
+ for i, v := range list {
+ fields[i] = v
+ }
+ return
+}
+
+// Check if a string is 8-bit clean.
+func isAscii(s string) bool {
+ for _, c := range s {
+ if c > unicode.MaxASCII || unicode.IsControl(c) {
+ return false
+ }
+ }
+ return true
+}
+
+// An IMAP writer.
+type Writer struct {
+ io.Writer
+
+ AllowAsyncLiterals bool
+
+ continues <-chan bool
+}
+
+// Helper function to write a string to w.
+func (w *Writer) writeString(s string) error {
+ _, err := io.WriteString(w.Writer, s)
+ return err
+}
+
+func (w *Writer) writeCrlf() error {
+ if err := w.writeString(crlf); err != nil {
+ return err
+ }
+
+ return w.Flush()
+}
+
+func (w *Writer) writeNumber(num uint32) error {
+ return w.writeString(formatNumber(num))
+}
+
+func (w *Writer) writeQuoted(s string) error {
+ return w.writeString(strconv.Quote(s))
+}
+
+func (w *Writer) writeQuotedOrLiteral(s string) error {
+ if !isAscii(s) {
+ // IMAP doesn't allow 8-bit data outside literals
+ return w.writeLiteral(bytes.NewBufferString(s))
+ }
+
+ return w.writeQuoted(s)
+}
+
+func (w *Writer) writeDateTime(t time.Time, layout string) error {
+ if t.IsZero() {
+ return w.writeString(nilAtom)
+ }
+ return w.writeQuoted(t.Format(layout))
+}
+
+func (w *Writer) writeFields(fields []interface{}) error {
+ for i, field := range fields {
+ if i > 0 { // Write separator
+ if err := w.writeString(string(sp)); err != nil {
+ return err
+ }
+ }
+
+ if err := w.writeField(field); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (w *Writer) writeList(fields []interface{}) error {
+ if err := w.writeString(string(listStart)); err != nil {
+ return err
+ }
+
+ if err := w.writeFields(fields); err != nil {
+ return err
+ }
+
+ return w.writeString(string(listEnd))
+}
+
+// LiteralLengthErr is returned when the Len() of the Literal object does not
+// match the actual length of the byte stream.
+type LiteralLengthErr struct {
+ Actual int
+ Expected int
+}
+
+func (e LiteralLengthErr) Error() string {
+ return fmt.Sprintf("imap: size of Literal is not equal to Len() (%d != %d)", e.Expected, e.Actual)
+}
+
+func (w *Writer) writeLiteral(l Literal) error {
+ if l == nil {
+ return w.writeString(nilAtom)
+ }
+
+ unsyncLiteral := w.AllowAsyncLiterals && l.Len() <= 4096
+
+ header := string(literalStart) + strconv.Itoa(l.Len())
+ if unsyncLiteral {
+ header += string('+')
+ }
+ header += 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 !unsyncLiteral && w.continues != nil {
+ // Make sure to flush the writer, otherwise we may never receive a continuation request
+ if err := w.Flush(); err != nil {
+ return err
+ }
+
+ if !<-w.continues {
+ return fmt.Errorf("imap: cannot send literal: no continuation request received")
+ }
+ }
+
+ // In case of bufio.Buffer, it will be 0 after io.Copy.
+ literalLen := int64(l.Len())
+
+ n, err := io.CopyN(w, l, literalLen)
+ if err != nil {
+ if err == io.EOF && n != literalLen {
+ return LiteralLengthErr{int(n), l.Len()}
+ }
+ return err
+ }
+ extra, _ := io.Copy(ioutil.Discard, l)
+ if extra != 0 {
+ return LiteralLengthErr{int(n + extra), l.Len()}
+ }
+
+ return nil
+}
+
+func (w *Writer) writeField(field interface{}) error {
+ if field == nil {
+ return w.writeString(nilAtom)
+ }
+
+ switch field := field.(type) {
+ case RawString:
+ return w.writeString(string(field))
+ case string:
+ return w.writeQuotedOrLiteral(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 envelopeDateTime:
+ return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
+ case searchDate:
+ return w.writeDateTime(time.Time(field), searchDateLayout)
+ case Date:
+ return w.writeDateTime(time.Time(field), DateLayout)
+ case DateTime:
+ return w.writeDateTime(time.Time(field), DateTimeLayout)
+ case time.Time:
+ return w.writeDateTime(field, DateTimeLayout)
+ case *SeqSet:
+ return w.writeString(field.String())
+ case *BodySectionName:
+ // Can contain spaces - that's why we don't just pass it as a string
+ return w.writeString(string(field.FetchItem()))
+ }
+
+ return fmt.Errorf("imap: cannot format field: %v", field)
+}
+
+func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
+ if err := w.writeString(string(respCodeStart)); err != nil {
+ return err
+ }
+
+ fields := []interface{}{RawString(code)}
+ fields = append(fields, args...)
+
+ if err := w.writeFields(fields); err != nil {
+ return err
+ }
+
+ return w.writeString(string(respCodeEnd))
+}
+
+func (w *Writer) writeLine(fields ...interface{}) error {
+ if err := w.writeFields(fields); err != nil {
+ return err
+ }
+
+ return w.writeCrlf()
+}
+
+func (w *Writer) Flush() error {
+ if f, ok := w.Writer.(flusher); ok {
+ return f.Flush()
+ }
+ return nil
+}
+
+func NewWriter(w io.Writer) *Writer {
+ return &Writer{Writer: w}
+}
+
+func NewClientWriter(w io.Writer, continues <-chan bool) *Writer {
+ return &Writer{Writer: w, continues: continues}
+}