aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan <me@jordan.im>2023-02-04 23:54:03 -0700
committerJordan <me@jordan.im>2023-02-04 23:54:03 -0700
commitc4159d895ac399ca55326f7b4ff8bfbf8402e654 (patch)
tree45340ca429c16f683b375695d01e03d65ebf22b0
downloadpigeon-c4159d895ac399ca55326f7b4ff8bfbf8402e654.tar.gz
pigeon-c4159d895ac399ca55326f7b4ff8bfbf8402e654.zip
initial commit
-rw-r--r--.gitignore3
-rw-r--r--Makefile23
-rw-r--r--README22
-rw-r--r--UNLICENSE24
-rw-r--r--go.mod11
-rw-r--r--go.sum12
-rw-r--r--message.eml.example8
-rw-r--r--pigeon.go325
-rw-r--r--pigeon.json.example14
-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
-rw-r--r--vendor/github.com/emersion/go-sasl/.build.yml19
-rw-r--r--vendor/github.com/emersion/go-sasl/.gitignore24
-rw-r--r--vendor/github.com/emersion/go-sasl/LICENSE21
-rw-r--r--vendor/github.com/emersion/go-sasl/README.md17
-rw-r--r--vendor/github.com/emersion/go-sasl/anonymous.go56
-rw-r--r--vendor/github.com/emersion/go-sasl/external.go26
-rw-r--r--vendor/github.com/emersion/go-sasl/login.go89
-rw-r--r--vendor/github.com/emersion/go-sasl/oauthbearer.go191
-rw-r--r--vendor/github.com/emersion/go-sasl/plain.go77
-rw-r--r--vendor/github.com/emersion/go-sasl/sasl.go45
-rw-r--r--vendor/github.com/emersion/go-smtp/.build.yml17
-rw-r--r--vendor/github.com/emersion/go-smtp/.gitignore26
-rw-r--r--vendor/github.com/emersion/go-smtp/LICENSE24
-rw-r--r--vendor/github.com/emersion/go-smtp/README.md181
-rw-r--r--vendor/github.com/emersion/go-smtp/backend.go108
-rw-r--r--vendor/github.com/emersion/go-smtp/client.go722
-rw-r--r--vendor/github.com/emersion/go-smtp/conn.go986
-rw-r--r--vendor/github.com/emersion/go-smtp/data.go147
-rw-r--r--vendor/github.com/emersion/go-smtp/lengthlimit_reader.go47
-rw-r--r--vendor/github.com/emersion/go-smtp/parse.go72
-rw-r--r--vendor/github.com/emersion/go-smtp/server.go292
-rw-r--r--vendor/github.com/emersion/go-smtp/smtp.go30
-rw-r--r--vendor/golang.org/x/text/AUTHORS3
-rw-r--r--vendor/golang.org/x/text/CONTRIBUTORS3
-rw-r--r--vendor/golang.org/x/text/LICENSE27
-rw-r--r--vendor/golang.org/x/text/PATENTS22
-rw-r--r--vendor/golang.org/x/text/encoding/encoding.go335
-rw-r--r--vendor/golang.org/x/text/encoding/internal/identifier/identifier.go81
-rw-r--r--vendor/golang.org/x/text/encoding/internal/identifier/mib.go1619
-rw-r--r--vendor/golang.org/x/text/transform/transform.go709
-rw-r--r--vendor/modules.txt18
105 files changed, 14149 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f2bc918
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+autoreply
+message.eml
+pigeon.json
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ac4846a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+.POSIX:
+.SUFFIXES:
+
+GO = go
+RM = rm
+GOFLAGS = -o pigeon
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+
+goflags = $(GOFLAGS)
+
+all: pigeon
+
+pigeon:
+ $(GO) build $(goflags) -ldflags "-X main.buildPrefix=$(PREFIX)"
+
+clean:
+ $(RM) -f pigeon
+
+install: all
+ mkdir -p $(DESTDIR)$(BINDIR)
+ cp -f pigeon $(DESTDIR)$(BINDIR)
+
diff --git a/README b/README
new file mode 100644
index 0000000..9d77e5a
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+pigeon is a simple RFC-compliant email autoresponder; it monitors IMAP for
+mailbox updates and replies to unseen messages with content rendered from the
+configured template. pigeon supports per-address cooldown timers to prevent mail
+loops, and can ignore messages sent to alias addresses.
+
+pigeon is opinionated and is intended to be configured with accounts used by
+automated systems or unattended (and presently empty) inboxes; it's designed to
+accommodate a particularly narrow use case.
+
+pigeon's idling logic is borrowed from aerc[1] and makes use of emersion's
+well-designed go-imap[2] and go-smtp[3] libraries.
+
+Usage of ./pigeon:
+ -conf string
+ path to JSON configuration (default "./pigeon.json")
+ -eml string
+ path to EML message (default "./message.eml")
+
+[1] https://sr.ht/~rjarry/aerc/
+[2] https://github.com/emersion/go-imap
+[3] https://github.com/emersion/go-smtp
+
diff --git a/UNLICENSE b/UNLICENSE
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+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 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.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..1a35ccf
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module pigeon
+
+go 1.19
+
+require (
+ github.com/emersion/go-imap v1.2.1
+ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
+ github.com/emersion/go-smtp v0.16.0
+)
+
+require golang.org/x/text v0.3.7 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7ccbe2d
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,12 @@
+github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
+github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
+github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
+github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
+github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
+github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
+github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
+github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/message.eml.example b/message.eml.example
new file mode 100644
index 0000000..041a56e
--- /dev/null
+++ b/message.eml.example
@@ -0,0 +1,8 @@
+From: "{{.FromName}}" <{{.FromEmail}}>
+To: <{{.ToEmail}}>
+Subject: {{.Subject}}
+Date: {{.Date}}
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+
+{{.Message}}
diff --git a/pigeon.go b/pigeon.go
new file mode 100644
index 0000000..c169129
--- /dev/null
+++ b/pigeon.go
@@ -0,0 +1,325 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "sync"
+ "text/template"
+ "time"
+
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/client"
+ "github.com/emersion/go-sasl"
+ "github.com/emersion/go-smtp"
+)
+
+type Config struct {
+ FromEmail string `json:"from-email"`
+ FromName string `json:"from-name"`
+ Subject string `json:"subject"`
+ IMAPServer string `json:"imap-server"`
+ IMAPEmail string `json:"imap-email"`
+ IMAPPass string `json:"imap-pass"`
+ SMTPServer string `json:"smtp-server"`
+ SMTPEmail string `json:"smtp-email"`
+ SMTPPass string `json:"smtp-pass"`
+ Message string `json:"message"`
+ Cooldown string `json:"cooldown"`
+ IgnoreAlias bool `json:"ignore-alias"`
+}
+
+type Email struct {
+ ToEmail string
+ FromName string
+ FromEmail string
+ Subject string
+ Date string
+ Message string
+}
+
+type worker struct {
+ config Config
+ sender sasl.Client
+ client *client.Client
+ idler *idler
+ mbox *imap.MailboxStatus
+ seen seen
+ emlPath string
+}
+
+type seen struct {
+ sync.Mutex
+ emails map[string]time.Time
+ cooldown time.Duration
+}
+
+type idler struct {
+ sync.Mutex
+ client *client.Client
+ stop chan struct{}
+ done chan error
+ waiting bool
+ idleing bool
+}
+
+var (
+ errIdleTimeout = fmt.Errorf("idle timeout")
+ errIdleModeHangs = fmt.Errorf("idle mode hangs; waiting to reconnect")
+)
+
+func newIdler(c *client.Client) *idler {
+ return &idler{client: c, done: make(chan error)}
+}
+
+func (i *idler) setWaiting(wait bool) {
+ i.Lock()
+ i.waiting = wait
+ i.Unlock()
+}
+
+func (i *idler) isWaiting() bool {
+ i.Lock()
+ defer i.Unlock()
+ return i.waiting
+}
+
+func (i *idler) isReady() bool {
+ i.Lock()
+ defer i.Unlock()
+ return (!i.waiting && i.client != nil &&
+ i.client.State() == imap.SelectedState)
+}
+
+func (i *idler) setIdleing(v bool) {
+ i.Lock()
+ defer i.Unlock()
+ i.idleing = v
+}
+
+func (i *idler) Start() {
+ switch {
+ case i.isReady():
+ i.stop = make(chan struct{})
+ go func() {
+ select {
+ case <-i.stop:
+ log.Println("Idle debounced...")
+ i.done <- nil
+ case <-time.After(10 * time.Millisecond):
+ i.setIdleing(true)
+ log.Println("Entered idle mode...")
+ now := time.Now()
+ err := i.client.Idle(i.stop,
+ &client.IdleOptions{
+ LogoutTimeout: 0,
+ PollInterval: 0,
+ })
+ i.setIdleing(false)
+ i.done <- err
+ log.Printf("Elapsed idle time: %v", time.Since(now))
+ }
+ }()
+ case i.isWaiting():
+ log.Println("Not started: wait for idle to exit...")
+ default:
+ log.Println("Not started: client not ready...")
+ }
+}
+
+func (i *idler) Stop() error {
+ var reterr error
+ switch {
+ case i.isReady():
+ close(i.stop)
+ select {
+ case err := <-i.done:
+ if err == nil {
+ log.Println("Idle done...")
+ } else {
+ log.Printf("Idle done with err: %v", err)
+ }
+ reterr = nil
+ case <-time.After(10 * time.Second):
+ log.Println("Idle err (timeout); waiting in background...")
+ i.waitOnIdle()
+
+ reterr = errIdleTimeout
+ }
+ case i.isWaiting():
+ log.Println("Not stopped: still idleing/hanging...")
+ reterr = errIdleModeHangs
+ default:
+ log.Println("Not stopped: client not ready...")
+ reterr = nil
+ }
+ return reterr
+}
+
+func (i *idler) waitOnIdle() {
+ i.setWaiting(true)
+ log.Println("Waiting for idle in background...")
+ go func() {
+ err := <-i.done
+ if err == nil {
+ log.Println("Idle waited...")
+ } else {
+ log.Printf("Idle waited; with err: %v", err)
+ }
+ i.setWaiting(false)
+ i.stop = make(chan struct{})
+ fmt.Println("restart")
+ i.Start()
+ }()
+}
+
+func (w *worker) handleUpdate(update *client.MailboxUpdate) {
+ log.Println("New update, stopping idle...")
+ defer func() {
+ w.idler.Start()
+ }()
+ if err := w.idler.Stop(); err != nil {
+ log.Fatal(err)
+ }
+
+ log.Println("Searching for messages...")
+ criteria := imap.NewSearchCriteria()
+ criteria.WithoutFlags = []string{imap.SeenFlag}
+ ids, err := w.client.Search(criteria)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(ids) == 0 {
+ log.Println("No IDs Found")
+ return
+ }
+ log.Println("IDs found:", ids)
+ seqset := new(imap.SeqSet)
+ seqset.AddNum(ids...)
+ messages := make(chan *imap.Message, 10)
+ done := make(chan error, 1)
+ go func() {
+ done <- w.client.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
+ }()
+
+ tmpl, err := template.ParseFiles(w.emlPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for msg := range messages {
+ log.Println("* ", msg.Envelope.From[0].Address(), msg.Envelope.Subject)
+ if w.config.IgnoreAlias {
+ foundAddress := false
+ for _, toEmail := range msg.Envelope.To {
+ if toEmail.Address() == w.config.FromEmail {
+ foundAddress = true
+ }
+ }
+ if !foundAddress {
+ log.Println("Our address absent from 'to' set, skipping...")
+ continue
+ }
+ }
+ eml := Email{
+ ToEmail: msg.Envelope.From[0].Address(),
+ FromEmail: w.config.FromEmail,
+ FromName: w.config.FromName,
+ Subject: w.config.Subject,
+ Date: time.Now().Format(time.RFC1123Z),
+ Message: w.config.Message,
+ }
+ if prevSend, exists := w.seen.emails[eml.ToEmail]; exists {
+ log.Println("Address exists, checking time...")
+ diff := time.Now().Sub(prevSend)
+ if diff < w.seen.cooldown {
+ log.Println("Address seen too recently, skipping...")
+ continue
+ }
+ }
+ log.Println("Sending email...")
+ to := []string{msg.Envelope.From[0].Address()}
+ buf := new(bytes.Buffer)
+ err := tmpl.Execute(buf, &eml)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = smtp.SendMail(w.config.SMTPServer, w.sender, w.config.SMTPEmail, to, buf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Println("Adding address to seen map...")
+ w.seen.Lock()
+ w.seen.emails[eml.ToEmail] = time.Now()
+ w.seen.Unlock()
+ }
+
+ log.Println("Marking messages as seen...")
+ item := imap.FormatFlagsOp(imap.AddFlags, true)
+ flags := []interface{}{imap.SeenFlag}
+ err = w.client.Store(seqset, item, flags, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := <-done; err != nil {
+ log.Fatal(err)
+ }
+}
+
+func main() {
+ var configPath string
+ var w worker
+ var err error
+
+ flag.StringVar(&configPath, "conf", "./pigeon.json", "path to JSON configuration")
+ flag.StringVar(&w.emlPath, "eml", "./message.eml", "path to EML message")
+ flag.Parse()
+ conf, err := os.ReadFile(configPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = json.Unmarshal(conf, &w.config)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if cd, err := time.ParseDuration(w.config.Cooldown); err != nil {
+ log.Fatal(err)
+ } else {
+ w.seen.cooldown = cd
+ }
+ w.seen.emails = make(map[string]time.Time)
+
+ log.Println("Connecting to server...")
+ w.client, err = client.DialTLS(w.config.IMAPServer, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // w.client.SetDebug(os.Stdout)
+ defer w.client.Logout()
+
+ if err := w.client.Login(w.config.IMAPEmail, w.config.IMAPPass); err != nil {
+ log.Fatal(err)
+ }
+ log.Println("Logged in")
+
+ w.idler = newIdler(w.client)
+ w.mbox, err = w.client.Select("INBOX", false)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ w.sender = sasl.NewPlainClient("", w.config.SMTPEmail, w.config.SMTPPass)
+ updates := make(chan client.Update, 1)
+ w.client.Updates = updates
+ w.idler.Start()
+ for {
+ update := <-updates
+ switch update := update.(type) {
+ case *client.MailboxUpdate:
+ log.Println("Mailbox update received, processing...")
+ w.handleUpdate(update)
+ }
+ }
+}
diff --git a/pigeon.json.example b/pigeon.json.example
new file mode 100644
index 0000000..dcd91e6
--- /dev/null
+++ b/pigeon.json.example
@@ -0,0 +1,14 @@
+{
+ "from-email": "john@example.com",
+ "from-name": "John Doe",
+ "subject": "On vacation! :)",
+ "imap-server": "example.com:993",
+ "imap-email": "john@example.com",
+ "imap-pass": "password",
+ "smtp-server": "example.com:587",
+ "smtp-email": "john@example.com",
+ "smtp-pass": "password",
+ "cooldown": "24h",
+ "ignore-alias": true,
+ "message": "Hello,\r\n\r\nSorry I missed you!\r\n\r\nJohn",
+}
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}
+}
diff --git a/vendor/github.com/emersion/go-sasl/.build.yml b/vendor/github.com/emersion/go-sasl/.build.yml
new file mode 100644
index 0000000..daa6006
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/.build.yml
@@ -0,0 +1,19 @@
+image: alpine/latest
+packages:
+ - go
+ # Required by codecov
+ - bash
+ - findutils
+sources:
+ - https://github.com/emersion/go-sasl
+tasks:
+ - build: |
+ cd go-sasl
+ go build -v ./...
+ - test: |
+ cd go-sasl
+ go test -coverprofile=coverage.txt -covermode=atomic ./...
+ - upload-coverage: |
+ cd go-sasl
+ export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
+ curl -s https://codecov.io/bash | bash
diff --git a/vendor/github.com/emersion/go-sasl/.gitignore b/vendor/github.com/emersion/go-sasl/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/.gitignore
@@ -0,0 +1,24 @@
+# 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
diff --git a/vendor/github.com/emersion/go-sasl/LICENSE b/vendor/github.com/emersion/go-sasl/LICENSE
new file mode 100644
index 0000000..dc1922e
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 emersion
+
+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-sasl/README.md b/vendor/github.com/emersion/go-sasl/README.md
new file mode 100644
index 0000000..1f8a682
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/README.md
@@ -0,0 +1,17 @@
+# go-sasl
+
+[![GoDoc](https://godoc.org/github.com/emersion/go-sasl?status.svg)](https://godoc.org/github.com/emersion/go-sasl)
+[![Build Status](https://travis-ci.org/emersion/go-sasl.svg?branch=master)](https://travis-ci.org/emersion/go-sasl)
+
+A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go.
+
+Implemented mechanisms:
+* [ANONYMOUS](https://tools.ietf.org/html/rfc4505)
+* [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A)
+* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead)
+* [PLAIN](https://tools.ietf.org/html/rfc4616)
+* [OAUTHBEARER](https://tools.ietf.org/html/rfc7628)
+
+## License
+
+MIT
diff --git a/vendor/github.com/emersion/go-sasl/anonymous.go b/vendor/github.com/emersion/go-sasl/anonymous.go
new file mode 100644
index 0000000..8ccb817
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/anonymous.go
@@ -0,0 +1,56 @@
+package sasl
+
+// The ANONYMOUS mechanism name.
+const Anonymous = "ANONYMOUS"
+
+type anonymousClient struct {
+ Trace string
+}
+
+func (c *anonymousClient) Start() (mech string, ir []byte, err error) {
+ mech = Anonymous
+ ir = []byte(c.Trace)
+ return
+}
+
+func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) {
+ return nil, ErrUnexpectedServerChallenge
+}
+
+// A client implementation of the ANONYMOUS authentication mechanism, as
+// described in RFC 4505.
+func NewAnonymousClient(trace string) Client {
+ return &anonymousClient{trace}
+}
+
+// Get trace information from clients logging in anonymously.
+type AnonymousAuthenticator func(trace string) error
+
+type anonymousServer struct {
+ done bool
+ authenticate AnonymousAuthenticator
+}
+
+func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) {
+ if s.done {
+ err = ErrUnexpectedClientResponse
+ return
+ }
+
+ // No initial response, send an empty challenge
+ if response == nil {
+ return []byte{}, false, nil
+ }
+
+ s.done = true
+
+ err = s.authenticate(string(response))
+ done = true
+ return
+}
+
+// A server implementation of the ANONYMOUS authentication mechanism, as
+// described in RFC 4505.
+func NewAnonymousServer(authenticator AnonymousAuthenticator) Server {
+ return &anonymousServer{authenticate: authenticator}
+}
diff --git a/vendor/github.com/emersion/go-sasl/external.go b/vendor/github.com/emersion/go-sasl/external.go
new file mode 100644
index 0000000..da070c8
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/external.go
@@ -0,0 +1,26 @@
+package sasl
+
+// The EXTERNAL mechanism name.
+const External = "EXTERNAL"
+
+type externalClient struct {
+ Identity string
+}
+
+func (a *externalClient) Start() (mech string, ir []byte, err error) {
+ mech = External
+ ir = []byte(a.Identity)
+ return
+}
+
+func (a *externalClient) Next(challenge []byte) (response []byte, err error) {
+ return nil, ErrUnexpectedServerChallenge
+}
+
+// An implementation of the EXTERNAL authentication mechanism, as described in
+// RFC 4422. Authorization identity may be left blank to indicate that the
+// client is requesting to act as the identity associated with the
+// authentication credentials.
+func NewExternalClient(identity string) Client {
+ return &externalClient{identity}
+}
diff --git a/vendor/github.com/emersion/go-sasl/login.go b/vendor/github.com/emersion/go-sasl/login.go
new file mode 100644
index 0000000..3847ee1
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/login.go
@@ -0,0 +1,89 @@
+package sasl
+
+import (
+ "bytes"
+)
+
+// The LOGIN mechanism name.
+const Login = "LOGIN"
+
+var expectedChallenge = []byte("Password:")
+
+type loginClient struct {
+ Username string
+ Password string
+}
+
+func (a *loginClient) Start() (mech string, ir []byte, err error) {
+ mech = "LOGIN"
+ ir = []byte(a.Username)
+ return
+}
+
+func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
+ if bytes.Compare(challenge, expectedChallenge) != 0 {
+ return nil, ErrUnexpectedServerChallenge
+ } else {
+ return []byte(a.Password), nil
+ }
+}
+
+// A client implementation of the LOGIN authentication mechanism for SMTP,
+// as described in http://www.iana.org/go/draft-murchison-sasl-login
+//
+// It is considered obsolete, and should not be used when other mechanisms are
+// available. For plaintext password authentication use PLAIN mechanism.
+func NewLoginClient(username, password string) Client {
+ return &loginClient{username, password}
+}
+
+// Authenticates users with an username and a password.
+type LoginAuthenticator func(username, password string) error
+
+type loginState int
+
+const (
+ loginNotStarted loginState = iota
+ loginWaitingUsername
+ loginWaitingPassword
+)
+
+type loginServer struct {
+ state loginState
+ username, password string
+ authenticate LoginAuthenticator
+}
+
+// A server implementation of the LOGIN authentication mechanism, as described
+// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
+//
+// LOGIN is obsolete and should only be enabled for legacy clients that cannot
+// be updated to use PLAIN.
+func NewLoginServer(authenticator LoginAuthenticator) Server {
+ return &loginServer{authenticate: authenticator}
+}
+
+func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
+ switch a.state {
+ case loginNotStarted:
+ // Check for initial response field, as per RFC4422 section 3
+ if response == nil {
+ challenge = []byte("Username:")
+ break
+ }
+ a.state++
+ fallthrough
+ case loginWaitingUsername:
+ a.username = string(response)
+ challenge = []byte("Password:")
+ case loginWaitingPassword:
+ a.password = string(response)
+ err = a.authenticate(a.username, a.password)
+ done = true
+ default:
+ err = ErrUnexpectedClientResponse
+ }
+
+ a.state++
+ return
+}
diff --git a/vendor/github.com/emersion/go-sasl/oauthbearer.go b/vendor/github.com/emersion/go-sasl/oauthbearer.go
new file mode 100644
index 0000000..a0639b1
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/oauthbearer.go
@@ -0,0 +1,191 @@
+package sasl
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// The OAUTHBEARER mechanism name.
+const OAuthBearer = "OAUTHBEARER"
+
+type OAuthBearerError struct {
+ Status string `json:"status"`
+ Schemes string `json:"schemes"`
+ Scope string `json:"scope"`
+}
+
+type OAuthBearerOptions struct {
+ Username string
+ Token string
+ Host string
+ Port int
+}
+
+// Implements error
+func (err *OAuthBearerError) Error() string {
+ return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
+}
+
+type oauthBearerClient struct {
+ OAuthBearerOptions
+}
+
+func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) {
+ mech = OAuthBearer
+ var str = "n,a=" + a.Username + ","
+
+ if a.Host != "" {
+ str += "\x01host=" + a.Host
+ }
+
+ if a.Port != 0 {
+ str += "\x01port=" + strconv.Itoa(a.Port)
+ }
+ str += "\x01auth=Bearer " + a.Token + "\x01\x01"
+ ir = []byte(str)
+ return
+}
+
+func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) {
+ authBearerErr := &OAuthBearerError{}
+ if err := json.Unmarshal(challenge, authBearerErr); err != nil {
+ return nil, err
+ } else {
+ return nil, authBearerErr
+ }
+}
+
+// An implementation of the OAUTHBEARER authentication mechanism, as
+// described in RFC 7628.
+func NewOAuthBearerClient(opt *OAuthBearerOptions) Client {
+ return &oauthBearerClient{*opt}
+}
+
+type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
+
+type oauthBearerServer struct {
+ done bool
+ failErr error
+ authenticate OAuthBearerAuthenticator
+}
+
+func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) {
+ blob, err := json.Marshal(OAuthBearerError{
+ Status: "invalid_request",
+ Schemes: "bearer",
+ })
+ if err != nil {
+ panic(err) // wtf
+ }
+ a.failErr = errors.New(descr)
+ return blob, false, nil
+}
+
+func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
+ // Per RFC, we cannot just send an error, we need to return JSON-structured
+ // value as a challenge and then after getting dummy response from the
+ // client stop the exchange.
+ if a.failErr != nil {
+ // Server libraries (go-smtp, go-imap) will not call Next on
+ // protocol-specific SASL cancel response ('*'). However, GS2 (and
+ // indirectly OAUTHBEARER) defines a protocol-independent way to do so
+ // using 0x01.
+ if len(response) != 1 && response[0] != 0x01 {
+ return nil, true, errors.New("unexpected response")
+ }
+ return nil, true, a.failErr
+ }
+
+ if a.done {
+ err = ErrUnexpectedClientResponse
+ return
+ }
+
+ // Generate empty challenge.
+ if response == nil {
+ return []byte{}, false, nil
+ }
+
+ a.done = true
+
+ // Cut n,a=username,\x01host=...\x01auth=...
+ // into
+ // n
+ // a=username
+ // \x01host=...\x01auth=...\x01\x01
+ parts := bytes.SplitN(response, []byte{','}, 3)
+ if len(parts) != 3 {
+ return a.fail("Invalid response")
+ }
+ if !bytes.Equal(parts[0], []byte{'n'}) {
+ return a.fail("Invalid response, missing 'n'")
+ }
+ opts := OAuthBearerOptions{}
+ if !bytes.HasPrefix(parts[1], []byte("a=")) {
+ return a.fail("Invalid response, missing 'a'")
+ }
+ opts.Username = string(bytes.TrimPrefix(parts[1], []byte("a=")))
+
+ // Cut \x01host=...\x01auth=...\x01\x01
+ // into
+ // *empty*
+ // host=...
+ // auth=...
+ // *empty*
+ //
+ // Note that this code does not do a lot of checks to make sure the input
+ // follows the exact format specified by RFC.
+ params := bytes.Split(parts[2], []byte{0x01})
+ for _, p := range params {
+ // Skip empty fields (one at start and end).
+ if len(p) == 0 {
+ continue
+ }
+
+ pParts := bytes.SplitN(p, []byte{'='}, 2)
+ if len(pParts) != 2 {
+ return a.fail("Invalid response, missing '='")
+ }
+
+ switch string(pParts[0]) {
+ case "host":
+ opts.Host = string(pParts[1])
+ case "port":
+ port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
+ if err != nil {
+ return a.fail("Invalid response, malformed 'port' value")
+ }
+ opts.Port = int(port)
+ case "auth":
+ const prefix = "bearer "
+ strValue := string(pParts[1])
+ // Token type is case-insensitive.
+ if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
+ return a.fail("Unsupported token type")
+ }
+ opts.Token = strValue[len(prefix):]
+ default:
+ return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
+ }
+ }
+
+ authzErr := a.authenticate(opts)
+ if authzErr != nil {
+ blob, err := json.Marshal(authzErr)
+ if err != nil {
+ panic(err) // wtf
+ }
+ a.failErr = authzErr
+ return blob, false, nil
+ }
+
+ return nil, true, nil
+}
+
+func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server {
+ return &oauthBearerServer{authenticate: auth}
+}
diff --git a/vendor/github.com/emersion/go-sasl/plain.go b/vendor/github.com/emersion/go-sasl/plain.go
new file mode 100644
index 0000000..344ed17
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/plain.go
@@ -0,0 +1,77 @@
+package sasl
+
+import (
+ "bytes"
+ "errors"
+)
+
+// The PLAIN mechanism name.
+const Plain = "PLAIN"
+
+type plainClient struct {
+ Identity string
+ Username string
+ Password string
+}
+
+func (a *plainClient) Start() (mech string, ir []byte, err error) {
+ mech = "PLAIN"
+ ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
+ return
+}
+
+func (a *plainClient) Next(challenge []byte) (response []byte, err error) {
+ return nil, ErrUnexpectedServerChallenge
+}
+
+// A client implementation of the PLAIN authentication mechanism, as described
+// in RFC 4616. Authorization identity may be left blank to indicate that it is
+// the same as the username.
+func NewPlainClient(identity, username, password string) Client {
+ return &plainClient{identity, username, password}
+}
+
+// Authenticates users with an identity, a username and a password. If the
+// identity is left blank, it indicates that it is the same as the username.
+// If identity is not empty and the server doesn't support it, an error must be
+// returned.
+type PlainAuthenticator func(identity, username, password string) error
+
+type plainServer struct {
+ done bool
+ authenticate PlainAuthenticator
+}
+
+func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) {
+ if a.done {
+ err = ErrUnexpectedClientResponse
+ return
+ }
+
+ // No initial response, send an empty challenge
+ if response == nil {
+ return []byte{}, false, nil
+ }
+
+ a.done = true
+
+ parts := bytes.Split(response, []byte("\x00"))
+ if len(parts) != 3 {
+ err = errors.New("Invalid response")
+ return
+ }
+
+ identity := string(parts[0])
+ username := string(parts[1])
+ password := string(parts[2])
+
+ err = a.authenticate(identity, username, password)
+ done = true
+ return
+}
+
+// A server implementation of the PLAIN authentication mechanism, as described
+// in RFC 4616.
+func NewPlainServer(authenticator PlainAuthenticator) Server {
+ return &plainServer{authenticate: authenticator}
+}
diff --git a/vendor/github.com/emersion/go-sasl/sasl.go b/vendor/github.com/emersion/go-sasl/sasl.go
new file mode 100644
index 0000000..c209144
--- /dev/null
+++ b/vendor/github.com/emersion/go-sasl/sasl.go
@@ -0,0 +1,45 @@
+// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422.
+package sasl
+
+// Note:
+// Most of this code was copied, with some modifications, from net/smtp. It
+// would be better if Go provided a standard package (e.g. crypto/sasl) that
+// could be shared by SMTP, IMAP, and other packages.
+
+import (
+ "errors"
+)
+
+// Common SASL errors.
+var (
+ ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response")
+ ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge")
+)
+
+// Client interface to perform challenge-response authentication.
+type Client interface {
+ // Begins SASL authentication with the server. It returns the
+ // authentication mechanism name and "initial response" data (if required by
+ // the selected mechanism). A non-nil error causes the client to abort the
+ // authentication attempt.
+ //
+ // A nil ir value is different from a zero-length value. The nil value
+ // indicates that the selected mechanism does not use an initial response,
+ // while a zero-length value indicates an empty initial response, which must
+ // be sent to the server.
+ Start() (mech string, ir []byte, err error)
+
+ // Continues challenge-response authentication. A non-nil error causes
+ // the client to abort the authentication attempt.
+ Next(challenge []byte) (response []byte, err error)
+}
+
+// Server interface to perform challenge-response authentication.
+type Server interface {
+ // Begins or continues challenge-response authentication. If the client
+ // supplies an initial response, response is non-nil.
+ //
+ // If the authentication is finished, done is set to true. If the
+ // authentication has failed, an error is returned.
+ Next(response []byte) (challenge []byte, done bool, err error)
+}
diff --git a/vendor/github.com/emersion/go-smtp/.build.yml b/vendor/github.com/emersion/go-smtp/.build.yml
new file mode 100644
index 0000000..46854b9
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/.build.yml
@@ -0,0 +1,17 @@
+image: alpine/edge
+packages:
+ - go
+sources:
+ - https://github.com/emersion/go-smtp
+artifacts:
+ - coverage.html
+tasks:
+ - build: |
+ cd go-smtp
+ go build -v ./...
+ - test: |
+ cd go-smtp
+ go test -coverprofile=coverage.txt -covermode=atomic ./...
+ - coverage: |
+ cd go-smtp
+ go tool cover -html=coverage.txt -o ~/coverage.html
diff --git a/vendor/github.com/emersion/go-smtp/.gitignore b/vendor/github.com/emersion/go-smtp/.gitignore
new file mode 100644
index 0000000..dc3e55d
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/.gitignore
@@ -0,0 +1,26 @@
+# 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
+
+/main.go
diff --git a/vendor/github.com/emersion/go-smtp/LICENSE b/vendor/github.com/emersion/go-smtp/LICENSE
new file mode 100644
index 0000000..92ce700
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/LICENSE
@@ -0,0 +1,24 @@
+The MIT License (MIT)
+
+Copyright (c) 2010 The Go Authors
+Copyright (c) 2014 Gleez Technologies
+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-smtp/README.md b/vendor/github.com/emersion/go-smtp/README.md
new file mode 100644
index 0000000..6992498
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/README.md
@@ -0,0 +1,181 @@
+# go-smtp
+
+[![godocs.io](https://godocs.io/github.com/emersion/go-smtp?status.svg)](https://godocs.io/github.com/emersion/go-smtp)
+[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-smtp/commits.svg)](https://builds.sr.ht/~emersion/go-smtp/commits?)
+
+An ESMTP client and server library written in Go.
+
+## Features
+
+* ESMTP client & server implementing [RFC 5321](https://tools.ietf.org/html/rfc5321)
+* Support for SMTP [AUTH](https://tools.ietf.org/html/rfc4954) and [PIPELINING](https://tools.ietf.org/html/rfc2920)
+* UTF-8 support for subject and message
+* [LMTP](https://tools.ietf.org/html/rfc2033) support
+
+## Usage
+
+### Client
+
+```go
+package main
+
+import (
+ "log"
+ "strings"
+
+ "github.com/emersion/go-sasl"
+ "github.com/emersion/go-smtp"
+)
+
+func main() {
+ // Setup authentication information.
+ auth := sasl.NewPlainClient("", "user@example.com", "password")
+
+ // Connect to the server, authenticate, set the sender and recipient,
+ // and send the email all in one step.
+ to := []string{"recipient@example.net"}
+ msg := strings.NewReader("To: recipient@example.net\r\n" +
+ "Subject: discount Gophers!\r\n" +
+ "\r\n" +
+ "This is the email body.\r\n")
+ err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+```
+
+If you need more control, you can use `Client` instead. For example, if you
+want to send an email via a server without TLS or auth support, you can do
+something like this:
+
+```go
+package main
+
+import (
+ "log"
+ "strings"
+
+ "github.com/emersion/go-smtp"
+)
+
+func main() {
+ // Setup an unencrypted connection to a local mail server.
+ c, err := smtp.Dial("localhost:25")
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ // Set the sender and recipient, and send the email all in one step.
+ to := []string{"recipient@example.net"}
+ msg := strings.NewReader("To: recipient@example.net\r\n" +
+ "Subject: discount Gophers!\r\n" +
+ "\r\n" +
+ "This is the email body.\r\n")
+ err := c.SendMail("sender@example.org", to, msg)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+```
+
+### Server
+
+```go
+package main
+
+import (
+ "errors"
+ "io"
+ "io/ioutil"
+ "log"
+ "time"
+
+ "github.com/emersion/go-smtp"
+)
+
+// The Backend implements SMTP server methods.
+type Backend struct{}
+
+func (bkd *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
+ return &Session{}, nil
+}
+
+// A Session is returned after EHLO.
+type Session struct{}
+
+func (s *Session) AuthPlain(username, password string) error {
+ if username != "username" || password != "password" {
+ return errors.New("Invalid username or password")
+ }
+ return nil
+}
+
+func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
+ log.Println("Mail from:", from)
+ return nil
+}
+
+func (s *Session) Rcpt(to string) error {
+ log.Println("Rcpt to:", to)
+ return nil
+}
+
+func (s *Session) Data(r io.Reader) error {
+ if b, err := ioutil.ReadAll(r); err != nil {
+ return err
+ } else {
+ log.Println("Data:", string(b))
+ }
+ return nil
+}
+
+func (s *Session) Reset() {}
+
+func (s *Session) Logout() error {
+ return nil
+}
+
+func main() {
+ be := &Backend{}
+
+ s := smtp.NewServer(be)
+
+ s.Addr = ":1025"
+ s.Domain = "localhost"
+ s.ReadTimeout = 10 * time.Second
+ s.WriteTimeout = 10 * time.Second
+ s.MaxMessageBytes = 1024 * 1024
+ s.MaxRecipients = 50
+ s.AllowInsecureAuth = true
+
+ log.Println("Starting server at", s.Addr)
+ if err := s.ListenAndServe(); err != nil {
+ log.Fatal(err)
+ }
+}
+```
+
+You can use the server manually with `telnet`:
+```
+$ telnet localhost 1025
+EHLO localhost
+AUTH PLAIN
+AHVzZXJuYW1lAHBhc3N3b3Jk
+MAIL FROM:<root@nsa.gov>
+RCPT TO:<root@gchq.gov.uk>
+DATA
+Hey <3
+.
+```
+
+## Relationship with net/smtp
+
+The Go standard library provides a SMTP client implementation in `net/smtp`.
+However `net/smtp` is frozen: it's not getting any new features. go-smtp
+provides a server implementation and a number of client improvements.
+
+## Licence
+
+MIT
diff --git a/vendor/github.com/emersion/go-smtp/backend.go b/vendor/github.com/emersion/go-smtp/backend.go
new file mode 100644
index 0000000..59cea3a
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/backend.go
@@ -0,0 +1,108 @@
+package smtp
+
+import (
+ "io"
+)
+
+var (
+ ErrAuthRequired = &SMTPError{
+ Code: 502,
+ EnhancedCode: EnhancedCode{5, 7, 0},
+ Message: "Please authenticate first",
+ }
+ ErrAuthUnsupported = &SMTPError{
+ Code: 502,
+ EnhancedCode: EnhancedCode{5, 7, 0},
+ Message: "Authentication not supported",
+ }
+)
+
+// A SMTP server backend.
+type Backend interface {
+ NewSession(c *Conn) (Session, error)
+}
+
+type BodyType string
+
+const (
+ Body7Bit BodyType = "7BIT"
+ Body8BitMIME BodyType = "8BITMIME"
+ BodyBinaryMIME BodyType = "BINARYMIME"
+)
+
+// MailOptions contains custom arguments that were
+// passed as an argument to the MAIL command.
+type MailOptions struct {
+ // Value of BODY= argument, 7BIT, 8BITMIME or BINARYMIME.
+ Body BodyType
+
+ // Size of the body. Can be 0 if not specified by client.
+ Size int
+
+ // TLS is required for the message transmission.
+ //
+ // The message should be rejected if it can't be transmitted
+ // with TLS.
+ RequireTLS bool
+
+ // The message envelope or message header contains UTF-8-encoded strings.
+ // This flag is set by SMTPUTF8-aware (RFC 6531) client.
+ UTF8 bool
+
+ // The authorization identity asserted by the message sender in decoded
+ // form with angle brackets stripped.
+ //
+ // nil value indicates missing AUTH, non-nil empty string indicates
+ // AUTH=<>.
+ //
+ // Defined in RFC 4954.
+ Auth *string
+}
+
+// Session is used by servers to respond to an SMTP client.
+//
+// The methods are called when the remote client issues the matching command.
+type Session interface {
+ // Discard currently processed message.
+ Reset()
+
+ // Free all resources associated with session.
+ Logout() error
+
+ // Authenticate the user using SASL PLAIN.
+ AuthPlain(username, password string) error
+
+ // Set return path for currently processed message.
+ Mail(from string, opts *MailOptions) error
+ // Add recipient for currently processed message.
+ Rcpt(to string) error
+ // Set currently processed message contents and send it.
+ //
+ // r must be consumed before Data returns.
+ Data(r io.Reader) error
+}
+
+// LMTPSession is an add-on interface for Session. It can be implemented by
+// LMTP servers to provide extra functionality.
+type LMTPSession interface {
+ // LMTPData is the LMTP-specific version of Data method.
+ // It can be optionally implemented by the backend to provide
+ // per-recipient status information when it is used over LMTP
+ // protocol.
+ //
+ // LMTPData implementation sets status information using passed
+ // StatusCollector by calling SetStatus once per each AddRcpt
+ // call, even if AddRcpt was called multiple times with
+ // the same argument. SetStatus must not be called after
+ // LMTPData returns.
+ //
+ // Return value of LMTPData itself is used as a status for
+ // recipients that got no status set before using StatusCollector.
+ LMTPData(r io.Reader, status StatusCollector) error
+}
+
+// StatusCollector allows a backend to provide per-recipient status
+// information.
+type StatusCollector interface {
+ SetStatus(rcptTo string, err error)
+}
diff --git a/vendor/github.com/emersion/go-smtp/client.go b/vendor/github.com/emersion/go-smtp/client.go
new file mode 100644
index 0000000..2b455ba
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/client.go
@@ -0,0 +1,722 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package smtp
+
+import (
+ "crypto/tls"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/textproto"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/emersion/go-sasl"
+)
+
+// A Client represents a client connection to an SMTP server.
+type Client struct {
+ // Text is the textproto.Conn used by the Client. It is exported to allow for
+ // clients to add extensions.
+ Text *textproto.Conn
+
+ // keep a reference to the connection so it can be used to create a TLS
+ // connection later
+ conn net.Conn
+ // whether the Client is using TLS
+ tls bool
+ serverName string
+ lmtp bool
+ // map of supported extensions
+ ext map[string]string
+ // supported auth mechanisms
+ auth []string
+ localName string // the name to use in HELO/EHLO/LHLO
+ didHello bool // whether we've said HELO/EHLO/LHLO
+ helloError error // the error from the hello
+ rcpts []string // recipients accumulated for the current session
+
+ // Time to wait for command responses (this includes 3xx reply to DATA).
+ CommandTimeout time.Duration
+ // Time to wait for responses after final dot.
+ SubmissionTimeout time.Duration
+
+ // Logger for all network activity.
+ DebugWriter io.Writer
+}
+
+// 30 seconds was chosen as it's the
+// same duration as http.DefaultTransport's timeout.
+var defaultTimeout = 30 * time.Second
+
+// Dial returns a new Client connected to an SMTP server at addr.
+// The addr must include a port, as in "mail.example.com:smtp".
+func Dial(addr string) (*Client, error) {
+ conn, err := net.DialTimeout("tcp", addr, defaultTimeout)
+ if err != nil {
+ return nil, err
+ }
+ host, _, _ := net.SplitHostPort(addr)
+ return NewClient(conn, host)
+}
+
+// DialTLS returns a new Client connected to an SMTP server via TLS at addr.
+// The addr must include a port, as in "mail.example.com:smtps".
+//
+// A nil tlsConfig is equivalent to a zero tls.Config.
+func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
+ tlsDialer := tls.Dialer{
+ NetDialer: &net.Dialer{
+ Timeout: defaultTimeout,
+ },
+ Config: tlsConfig,
+ }
+ conn, err := tlsDialer.Dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+ host, _, _ := net.SplitHostPort(addr)
+ return NewClient(conn, host)
+}
+
+// NewClient returns a new Client using an existing connection and host as a
+// server name to be used when authenticating.
+func NewClient(conn net.Conn, host string) (*Client, error) {
+ c := &Client{
+ serverName: host,
+ localName: "localhost",
+ // As recommended by RFC 5321. For DATA command reply (3xx one) RFC
+ // recommends a slightly shorter timeout but we do not bother
+ // differentiating these.
+ CommandTimeout: 5 * time.Minute,
+ // 10 minutes + 2 minute buffer in case the server is doing transparent
+ // forwarding and also follows recommended timeouts.
+ SubmissionTimeout: 12 * time.Minute,
+ }
+
+ c.setConn(conn)
+
+ // Initial greeting timeout. RFC 5321 recommends 5 minutes.
+ c.conn.SetDeadline(time.Now().Add(5 * time.Minute))
+ defer c.conn.SetDeadline(time.Time{})
+
+ _, _, err := c.Text.ReadResponse(220)
+ if err != nil {
+ c.Text.Close()
+ if protoErr, ok := err.(*textproto.Error); ok {
+ return nil, toSMTPErr(protoErr)
+ }
+ return nil, err
+ }
+
+ return c, nil
+}
+
+// NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
+// existing connection and host as a server name to be used when authenticating.
+func NewClientLMTP(conn net.Conn, host string) (*Client, error) {
+ c, err := NewClient(conn, host)
+ if err != nil {
+ return nil, err
+ }
+ c.lmtp = true
+ return c, nil
+}
+
+// setConn sets the underlying network connection for the client.
+func (c *Client) setConn(conn net.Conn) {
+ c.conn = conn
+
+ var r io.Reader = conn
+ var w io.Writer = conn
+
+ r = &lineLimitReader{
+ R: conn,
+ // Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
+ LineLimit: 2000,
+ }
+
+ r = io.TeeReader(r, clientDebugWriter{c})
+ w = io.MultiWriter(w, clientDebugWriter{c})
+
+ rwc := struct {
+ io.Reader
+ io.Writer
+ io.Closer
+ }{
+ Reader: r,
+ Writer: w,
+ Closer: conn,
+ }
+ c.Text = textproto.NewConn(rwc)
+
+ _, isTLS := conn.(*tls.Conn)
+ c.tls = isTLS
+}
+
+// Close closes the connection.
+func (c *Client) Close() error {
+ return c.Text.Close()
+}
+
+// hello runs a hello exchange if needed.
+func (c *Client) hello() error {
+ if !c.didHello {
+ c.didHello = true
+ err := c.ehlo()
+ if err != nil {
+ c.helloError = c.helo()
+ }
+ }
+ return c.helloError
+}
+
+// Hello sends a HELO or EHLO to the server as the given host name.
+// Calling this method is only necessary if the client needs control
+// over the host name used. The client will introduce itself as "localhost"
+// automatically otherwise. If Hello is called, it must be called before
+// any of the other methods.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Hello(localName string) error {
+ if err := validateLine(localName); err != nil {
+ return err
+ }
+ if c.didHello {
+ return errors.New("smtp: Hello called after other methods")
+ }
+ c.localName = localName
+ return c.hello()
+}
+
+// cmd is a convenience function that sends a command and returns the response
+// textproto.Error returned by c.Text.ReadResponse is converted into SMTPError.
+func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
+ c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
+ defer c.conn.SetDeadline(time.Time{})
+
+ id, err := c.Text.Cmd(format, args...)
+ if err != nil {
+ return 0, "", err
+ }
+ c.Text.StartResponse(id)
+ defer c.Text.EndResponse(id)
+ code, msg, err := c.Text.ReadResponse(expectCode)
+ if err != nil {
+ if protoErr, ok := err.(*textproto.Error); ok {
+ smtpErr := toSMTPErr(protoErr)
+ return code, smtpErr.Message, smtpErr
+ }
+ return code, msg, err
+ }
+ return code, msg, nil
+}
+
+// helo sends the HELO greeting to the server. It should be used only when the
+// server does not support ehlo.
+func (c *Client) helo() error {
+ c.ext = nil
+ _, _, err := c.cmd(250, "HELO %s", c.localName)
+ return err
+}
+
+// ehlo sends the EHLO (extended hello) greeting to the server. It
+// should be the preferred greeting for servers that support it.
+func (c *Client) ehlo() error {
+ cmd := "EHLO"
+ if c.lmtp {
+ cmd = "LHLO"
+ }
+
+ _, msg, err := c.cmd(250, "%s %s", cmd, c.localName)
+ if err != nil {
+ return err
+ }
+ ext := make(map[string]string)
+ extList := strings.Split(msg, "\n")
+ if len(extList) > 1 {
+ extList = extList[1:]
+ for _, line := range extList {
+ args := strings.SplitN(line, " ", 2)
+ if len(args) > 1 {
+ ext[args[0]] = args[1]
+ } else {
+ ext[args[0]] = ""
+ }
+ }
+ }
+ if mechs, ok := ext["AUTH"]; ok {
+ c.auth = strings.Split(mechs, " ")
+ }
+ c.ext = ext
+ return err
+}
+
+// StartTLS sends the STARTTLS command and encrypts all further communication.
+// Only servers that advertise the STARTTLS extension support this function.
+//
+// A nil config is equivalent to a zero tls.Config.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) StartTLS(config *tls.Config) error {
+ if err := c.hello(); err != nil {
+ return err
+ }
+ _, _, err := c.cmd(220, "STARTTLS")
+ if err != nil {
+ return err
+ }
+ if config == nil {
+ config = &tls.Config{}
+ }
+ if config.ServerName == "" {
+ // Make a copy to avoid polluting argument
+ config = config.Clone()
+ config.ServerName = c.serverName
+ }
+ if testHookStartTLS != nil {
+ testHookStartTLS(config)
+ }
+ c.setConn(tls.Client(c.conn, config))
+ return c.ehlo()
+}
+
+// TLSConnectionState returns the client's TLS connection state.
+// The return values are their zero values if StartTLS did
+// not succeed.
+func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
+ tc, ok := c.conn.(*tls.Conn)
+ if !ok {
+ return
+ }
+ return tc.ConnectionState(), true
+}
+
+// Verify checks the validity of an email address on the server.
+// If Verify returns nil, the address is valid. A non-nil return
+// does not necessarily indicate an invalid address. Many servers
+// will not verify addresses for security reasons.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Verify(addr string) error {
+ if err := validateLine(addr); err != nil {
+ return err
+ }
+ if err := c.hello(); err != nil {
+ return err
+ }
+ _, _, err := c.cmd(250, "VRFY %s", addr)
+ return err
+}
+
+// Auth authenticates a client using the provided authentication mechanism.
+// Only servers that advertise the AUTH extension support this function.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Auth(a sasl.Client) error {
+ if err := c.hello(); err != nil {
+ return err
+ }
+ encoding := base64.StdEncoding
+ mech, resp, err := a.Start()
+ if err != nil {
+ return err
+ }
+ resp64 := make([]byte, encoding.EncodedLen(len(resp)))
+ encoding.Encode(resp64, resp)
+ code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
+ for err == nil {
+ var msg []byte
+ switch code {
+ case 334:
+ msg, err = encoding.DecodeString(msg64)
+ case 235:
+ // the last message isn't base64 because it isn't a challenge
+ msg = []byte(msg64)
+ default:
+ err = toSMTPErr(&textproto.Error{Code: code, Msg: msg64})
+ }
+ if err == nil {
+ if code == 334 {
+ resp, err = a.Next(msg)
+ } else {
+ resp = nil
+ }
+ }
+ if err != nil {
+ // abort the AUTH
+ c.cmd(501, "*")
+ break
+ }
+ if resp == nil {
+ break
+ }
+ resp64 = make([]byte, encoding.EncodedLen(len(resp)))
+ encoding.Encode(resp64, resp)
+ code, msg64, err = c.cmd(0, string(resp64))
+ }
+ return err
+}
+
+// Mail issues a MAIL command to the server using the provided email address.
+// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
+// parameter.
+// This initiates a mail transaction and is followed by one or more Rcpt calls.
+//
+// If opts is not nil, MAIL arguments provided in the structure will be added
+// to the command. Handling of unsupported options depends on the extension.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Mail(from string, opts *MailOptions) error {
+ if err := validateLine(from); err != nil {
+ return err
+ }
+ if err := c.hello(); err != nil {
+ return err
+ }
+ cmdStr := "MAIL FROM:<%s>"
+ if _, ok := c.ext["8BITMIME"]; ok {
+ cmdStr += " BODY=8BITMIME"
+ }
+ if _, ok := c.ext["SIZE"]; ok && opts != nil && opts.Size != 0 {
+ cmdStr += " SIZE=" + strconv.Itoa(opts.Size)
+ }
+ if opts != nil && opts.RequireTLS {
+ if _, ok := c.ext["REQUIRETLS"]; ok {
+ cmdStr += " REQUIRETLS"
+ } else {
+ return errors.New("smtp: server does not support REQUIRETLS")
+ }
+ }
+ if opts != nil && opts.UTF8 {
+ if _, ok := c.ext["SMTPUTF8"]; ok {
+ cmdStr += " SMTPUTF8"
+ } else {
+ return errors.New("smtp: server does not support SMTPUTF8")
+ }
+ }
+ if opts != nil && opts.Auth != nil {
+ if _, ok := c.ext["AUTH"]; ok {
+ cmdStr += " AUTH=" + encodeXtext(*opts.Auth)
+ }
+ // We can safely discard parameter if server does not support AUTH.
+ }
+ _, _, err := c.cmd(250, cmdStr, from)
+ return err
+}
+
+// Rcpt issues a RCPT command to the server using the provided email address.
+// A call to Rcpt must be preceded by a call to Mail and may be followed by
+// a Data call or another Rcpt call.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Rcpt(to string) error {
+ if err := validateLine(to); err != nil {
+ return err
+ }
+ if _, _, err := c.cmd(25, "RCPT TO:<%s>", to); err != nil {
+ return err
+ }
+ c.rcpts = append(c.rcpts, to)
+ return nil
+}
+
+type dataCloser struct {
+ c *Client
+ io.WriteCloser
+ statusCb func(rcpt string, status *SMTPError)
+ closed bool
+}
+
+func (d *dataCloser) Close() error {
+ if d.closed {
+ return fmt.Errorf("smtp: data writer closed twice")
+ }
+
+ if err := d.WriteCloser.Close(); err != nil {
+ return err
+ }
+
+ d.c.conn.SetDeadline(time.Now().Add(d.c.SubmissionTimeout))
+ defer d.c.conn.SetDeadline(time.Time{})
+
+ expectedResponses := len(d.c.rcpts)
+ if d.c.lmtp {
+ for expectedResponses > 0 {
+ rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses]
+ if _, _, err := d.c.Text.ReadResponse(250); err != nil {
+ if protoErr, ok := err.(*textproto.Error); ok {
+ if d.statusCb != nil {
+ d.statusCb(rcpt, toSMTPErr(protoErr))
+ }
+ } else {
+ return err
+ }
+ } else if d.statusCb != nil {
+ d.statusCb(rcpt, nil)
+ }
+ expectedResponses--
+ }
+ } else {
+ _, _, err := d.c.Text.ReadResponse(250)
+ if err != nil {
+ if protoErr, ok := err.(*textproto.Error); ok {
+ return toSMTPErr(protoErr)
+ }
+ return err
+ }
+ }
+
+ d.closed = true
+ return nil
+}
+
+// Data issues a DATA command to the server and returns a writer that
+// can be used to write the mail headers and body. The caller should
+// close the writer before calling any more methods on c. A call to
+// Data must be preceded by one or more calls to Rcpt.
+//
+// If server returns an error, it will be of type *SMTPError.
+func (c *Client) Data() (io.WriteCloser, error) {
+ _, _, err := c.cmd(354, "DATA")
+ if err != nil {
+ return nil, err
+ }
+ return &dataCloser{c: c, WriteCloser: c.Text.DotWriter()}, nil
+}
+
+// LMTPData is the LMTP-specific version of the Data method. It accepts a callback
+// that will be called for each status response received from the server.
+//
+// Status callback will receive a SMTPError argument for each negative server
+// reply and nil for each positive reply. I/O errors will not be reported using
+// callback and instead will be returned by the Close method of io.WriteCloser.
+// Callback will be called for each successfull Rcpt call done before in the
+// same order.
+func (c *Client) LMTPData(statusCb func(rcpt string, status *SMTPError)) (io.WriteCloser, error) {
+ if !c.lmtp {
+ return nil, errors.New("smtp: not a LMTP client")
+ }
+
+ _, _, err := c.cmd(354, "DATA")
+ if err != nil {
+ return nil, err
+ }
+ return &dataCloser{c: c, WriteCloser: c.Text.DotWriter(), statusCb: statusCb}, nil
+}
+
+// SendMail will use an existing connection to send an email from
+// address from, to addresses to, with message r.
+//
+// This function does not start TLS, nor does it perform authentication. Use
+// StartTLS and Auth before-hand if desirable.
+//
+// The addresses in the to parameter are the SMTP RCPT addresses.
+//
+// The r parameter should be an RFC 822-style email with headers
+// first, a blank line, and then the message body. The lines of r
+// should be CRLF terminated. The r headers should usually include
+// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
+// messages is accomplished by including an email address in the to
+// parameter but not including it in the r headers.
+func (c *Client) SendMail(from string, to []string, r io.Reader) error {
+ var err error
+
+ if err = c.Mail(from, nil); err != nil {
+ return err
+ }
+ for _, addr := range to {
+ if err = c.Rcpt(addr); err != nil {
+ return err
+ }
+ }
+ w, err := c.Data()
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(w, r)
+ if err != nil {
+ return err
+ }
+ err = w.Close()
+ if err != nil {
+ return err
+ }
+ return c.Quit()
+}
+
+var testHookStartTLS func(*tls.Config) // nil, except for tests
+
+// SendMail connects to the server at addr, switches to TLS, authenticates with
+// the optional SASL client, and then sends an email from address from, to
+// addresses to, with message r. The addr must include a port, as in
+// "mail.example.com:smtp".
+//
+// The addresses in the to parameter are the SMTP RCPT addresses.
+//
+// The r parameter should be an RFC 822-style email with headers
+// first, a blank line, and then the message body. The lines of r
+// should be CRLF terminated. The r headers should usually include
+// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
+// messages is accomplished by including an email address in the to
+// parameter but not including it in the r headers.
+//
+// SendMail is intended to be used for very simple use-cases. If you want to
+// customize SendMail's behavior, use a Client instead.
+//
+// The SendMail function and the go-smtp package are low-level
+// mechanisms and provide no support for DKIM signing (see go-msgauth), MIME
+// attachments (see the mime/multipart package or the go-message package), or
+// other mail functionality.
+func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
+ if err := validateLine(from); err != nil {
+ return err
+ }
+ for _, recp := range to {
+ if err := validateLine(recp); err != nil {
+ return err
+ }
+ }
+ c, err := Dial(addr)
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ if err = c.hello(); err != nil {
+ return err
+ }
+ if ok, _ := c.Extension("STARTTLS"); !ok {
+ return errors.New("smtp: server doesn't support STARTTLS")
+ }
+ if err = c.StartTLS(nil); err != nil {
+ return err
+ }
+ if a != nil && c.ext != nil {
+ if _, ok := c.ext["AUTH"]; !ok {
+ return errors.New("smtp: server doesn't support AUTH")
+ }
+ if err = c.Auth(a); err != nil {
+ return err
+ }
+ }
+ return c.SendMail(from, to, r)
+}
+
+// Extension reports whether an extension is support by the server.
+// The extension name is case-insensitive. If the extension is supported,
+// Extension also returns a string that contains any parameters the
+// server specifies for the extension.
+func (c *Client) Extension(ext string) (bool, string) {
+ if err := c.hello(); err != nil {
+ return false, ""
+ }
+ if c.ext == nil {
+ return false, ""
+ }
+ ext = strings.ToUpper(ext)
+ param, ok := c.ext[ext]
+ return ok, param
+}
+
+// Reset sends the RSET command to the server, aborting the current mail
+// transaction.
+func (c *Client) Reset() error {
+ if err := c.hello(); err != nil {
+ return err
+ }
+ if _, _, err := c.cmd(250, "RSET"); err != nil {
+ return err
+ }
+ c.rcpts = nil
+ return nil
+}
+
+// Noop sends the NOOP command to the server. It does nothing but check
+// that the connection to the server is okay.
+func (c *Client) Noop() error {
+ if err := c.hello(); err != nil {
+ return err
+ }
+ _, _, err := c.cmd(250, "NOOP")
+ return err
+}
+
+// Quit sends the QUIT command and closes the connection to the server.
+//
+// If Quit fails the connection is not closed, Close should be used
+// in this case.
+func (c *Client) Quit() error {
+ if err := c.hello(); err != nil {
+ return err
+ }
+ _, _, err := c.cmd(221, "QUIT")
+ if err != nil {
+ return err
+ }
+ return c.Text.Close()
+}
+
+func parseEnhancedCode(s string) (EnhancedCode, error) {
+ parts := strings.Split(s, ".")
+ if len(parts) != 3 {
+ return EnhancedCode{}, fmt.Errorf("wrong amount of enhanced code parts")
+ }
+
+ code := EnhancedCode{}
+ for i, part := range parts {
+ num, err := strconv.Atoi(part)
+ if err != nil {
+ return code, err
+ }
+ code[i] = num
+ }
+ return code, nil
+}
+
+// toSMTPErr converts textproto.Error into SMTPError, parsing
+// enhanced status code if it is present.
+func toSMTPErr(protoErr *textproto.Error) *SMTPError {
+ if protoErr == nil {
+ return nil
+ }
+ smtpErr := &SMTPError{
+ Code: protoErr.Code,
+ Message: protoErr.Msg,
+ }
+
+ parts := strings.SplitN(protoErr.Msg, " ", 2)
+ if len(parts) != 2 {
+ return smtpErr
+ }
+
+ enchCode, err := parseEnhancedCode(parts[0])
+ if err != nil {
+ return smtpErr
+ }
+
+ msg := parts[1]
+
+ // Per RFC 2034, enhanced code should be prepended to each line.
+ msg = strings.ReplaceAll(msg, "\n"+parts[0]+" ", "\n")
+
+ smtpErr.EnhancedCode = enchCode
+ smtpErr.Message = msg
+ return smtpErr
+}
+
+type clientDebugWriter struct {
+ c *Client
+}
+
+func (cdw clientDebugWriter) Write(b []byte) (int, error) {
+ if cdw.c.DebugWriter == nil {
+ return len(b), nil
+ }
+ return cdw.c.DebugWriter.Write(b)
+}
diff --git a/vendor/github.com/emersion/go-smtp/conn.go b/vendor/github.com/emersion/go-smtp/conn.go
new file mode 100644
index 0000000..72a67d8
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/conn.go
@@ -0,0 +1,986 @@
+package smtp
+
+import (
+ "crypto/tls"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/textproto"
+ "regexp"
+ "runtime/debug"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+// Number of errors we'll tolerate per connection before closing. Defaults to 3.
+const errThreshold = 3
+
+type Conn struct {
+ conn net.Conn
+ text *textproto.Conn
+ server *Server
+ helo string
+
+ // Number of errors witnessed on this connection
+ errCount int
+
+ session Session
+ locker sync.Mutex
+ binarymime bool
+
+ lineLimitReader *lineLimitReader
+ bdatPipe *io.PipeWriter
+ bdatStatus *statusCollector // used for BDAT on LMTP
+ dataResult chan error
+ bytesReceived int // counts total size of chunks when BDAT is used
+
+ fromReceived bool
+ recipients []string
+ didAuth bool
+}
+
+func newConn(c net.Conn, s *Server) *Conn {
+ sc := &Conn{
+ server: s,
+ conn: c,
+ }
+
+ sc.init()
+ return sc
+}
+
+func (c *Conn) init() {
+ c.lineLimitReader = &lineLimitReader{
+ R: c.conn,
+ LineLimit: c.server.MaxLineLength,
+ }
+ rwc := struct {
+ io.Reader
+ io.Writer
+ io.Closer
+ }{
+ Reader: c.lineLimitReader,
+ Writer: c.conn,
+ Closer: c.conn,
+ }
+
+ if c.server.Debug != nil {
+ rwc = struct {
+ io.Reader
+ io.Writer
+ io.Closer
+ }{
+ io.TeeReader(rwc.Reader, c.server.Debug),
+ io.MultiWriter(rwc.Writer, c.server.Debug),
+ rwc.Closer,
+ }
+ }
+
+ c.text = textproto.NewConn(rwc)
+}
+
+// Commands are dispatched to the appropriate handler functions.
+func (c *Conn) handle(cmd string, arg string) {
+ // If panic happens during command handling - send 421 response
+ // and close connection.
+ defer func() {
+ if err := recover(); err != nil {
+ c.writeResponse(421, EnhancedCode{4, 0, 0}, "Internal server error")
+ c.Close()
+
+ stack := debug.Stack()
+ c.server.ErrorLog.Printf("panic serving %v: %v\n%s", c.conn.RemoteAddr(), err, stack)
+ }
+ }()
+
+ if cmd == "" {
+ c.protocolError(500, EnhancedCode{5, 5, 2}, "Error: bad syntax")
+ return
+ }
+
+ cmd = strings.ToUpper(cmd)
+ switch cmd {
+ case "SEND", "SOML", "SAML", "EXPN", "HELP", "TURN":
+ // These commands are not implemented in any state
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, fmt.Sprintf("%v command not implemented", cmd))
+ case "HELO", "EHLO", "LHLO":
+ lmtp := cmd == "LHLO"
+ enhanced := lmtp || cmd == "EHLO"
+ if c.server.LMTP && !lmtp {
+ c.writeResponse(500, EnhancedCode{5, 5, 1}, "This is a LMTP server, use LHLO")
+ return
+ }
+ if !c.server.LMTP && lmtp {
+ c.writeResponse(500, EnhancedCode{5, 5, 1}, "This is not a LMTP server")
+ return
+ }
+ c.handleGreet(enhanced, arg)
+ case "MAIL":
+ c.handleMail(arg)
+ case "RCPT":
+ c.handleRcpt(arg)
+ case "VRFY":
+ c.writeResponse(252, EnhancedCode{2, 5, 0}, "Cannot VRFY user, but will accept message")
+ case "NOOP":
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, "I have sucessfully done nothing")
+ case "RSET": // Reset session
+ c.reset()
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, "Session reset")
+ case "BDAT":
+ c.handleBdat(arg)
+ case "DATA":
+ c.handleData(arg)
+ case "QUIT":
+ c.writeResponse(221, EnhancedCode{2, 0, 0}, "Bye")
+ c.Close()
+ case "AUTH":
+ if c.server.AuthDisabled {
+ c.protocolError(500, EnhancedCode{5, 5, 2}, "Syntax error, AUTH command unrecognized")
+ } else {
+ c.handleAuth(arg)
+ }
+ case "STARTTLS":
+ c.handleStartTLS()
+ default:
+ msg := fmt.Sprintf("Syntax errors, %v command unrecognized", cmd)
+ c.protocolError(500, EnhancedCode{5, 5, 2}, msg)
+ }
+}
+
+func (c *Conn) Server() *Server {
+ return c.server
+}
+
+func (c *Conn) Session() Session {
+ c.locker.Lock()
+ defer c.locker.Unlock()
+ return c.session
+}
+
+func (c *Conn) setSession(session Session) {
+ c.locker.Lock()
+ defer c.locker.Unlock()
+ c.session = session
+}
+
+func (c *Conn) Close() error {
+ c.locker.Lock()
+ defer c.locker.Unlock()
+
+ if c.bdatPipe != nil {
+ c.bdatPipe.CloseWithError(ErrDataReset)
+ c.bdatPipe = nil
+ }
+
+ if c.session != nil {
+ c.session.Logout()
+ c.session = nil
+ }
+
+ return c.conn.Close()
+}
+
+// TLSConnectionState returns the connection's TLS connection state.
+// Zero values are returned if the connection doesn't use TLS.
+func (c *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool) {
+ tc, ok := c.conn.(*tls.Conn)
+ if !ok {
+ return
+ }
+ return tc.ConnectionState(), true
+}
+
+func (c *Conn) Hostname() string {
+ return c.helo
+}
+
+func (c *Conn) Conn() net.Conn {
+ return c.conn
+}
+
+func (c *Conn) authAllowed() bool {
+ _, isTLS := c.TLSConnectionState()
+ return !c.server.AuthDisabled && (isTLS || c.server.AllowInsecureAuth)
+}
+
+// protocolError writes errors responses and closes the connection once too many
+// have occurred.
+func (c *Conn) protocolError(code int, ec EnhancedCode, msg string) {
+ c.writeResponse(code, ec, msg)
+
+ c.errCount++
+ if c.errCount > errThreshold {
+ c.writeResponse(500, EnhancedCode{5, 5, 1}, "Too many errors. Quiting now")
+ c.Close()
+ }
+}
+
+// GREET state -> waiting for HELO
+func (c *Conn) handleGreet(enhanced bool, arg string) {
+ domain, err := parseHelloArgument(arg)
+ if err != nil {
+ c.writeResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for HELO")
+ return
+ }
+ c.helo = domain
+
+ sess, err := c.server.Backend.NewSession(c)
+ if err != nil {
+ if smtpErr, ok := err.(*SMTPError); ok {
+ c.writeResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
+ return
+ }
+ c.writeResponse(451, EnhancedCode{4, 0, 0}, err.Error())
+ return
+ }
+ c.setSession(sess)
+
+ if !enhanced {
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
+ return
+ }
+
+ caps := []string{}
+ caps = append(caps, c.server.caps...)
+ if _, isTLS := c.TLSConnectionState(); c.server.TLSConfig != nil && !isTLS {
+ caps = append(caps, "STARTTLS")
+ }
+ if c.authAllowed() {
+ authCap := "AUTH"
+ for name := range c.server.auths {
+ authCap += " " + name
+ }
+
+ caps = append(caps, authCap)
+ }
+ if c.server.EnableSMTPUTF8 {
+ caps = append(caps, "SMTPUTF8")
+ }
+ if _, isTLS := c.TLSConnectionState(); isTLS && c.server.EnableREQUIRETLS {
+ caps = append(caps, "REQUIRETLS")
+ }
+ if c.server.EnableBINARYMIME {
+ caps = append(caps, "BINARYMIME")
+ }
+ if c.server.MaxMessageBytes > 0 {
+ caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes))
+ } else {
+ caps = append(caps, "SIZE")
+ }
+
+ args := []string{"Hello " + domain}
+ args = append(args, caps...)
+ c.writeResponse(250, NoEnhancedCode, args...)
+}
+
+// READY state -> waiting for MAIL
+func (c *Conn) handleMail(arg string) {
+ if c.helo == "" {
+ c.writeResponse(502, EnhancedCode{2, 5, 1}, "Please introduce yourself first.")
+ return
+ }
+ if c.bdatPipe != nil {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "MAIL not allowed during message transfer")
+ return
+ }
+
+ if len(arg) < 6 || strings.ToUpper(arg[0:5]) != "FROM:" {
+ c.writeResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
+ return
+ }
+ fromArgs := strings.Split(strings.Trim(arg[5:], " "), " ")
+ if c.server.Strict {
+ if !strings.HasPrefix(fromArgs[0], "<") || !strings.HasSuffix(fromArgs[0], ">") {
+ c.writeResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
+ return
+ }
+ }
+ from := fromArgs[0]
+ if from == "" {
+ c.writeResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
+ return
+ }
+ from = strings.Trim(from, "<>")
+
+ opts := &MailOptions{}
+
+ c.binarymime = false
+ // This is where the Conn may put BODY=8BITMIME, but we already
+ // read the DATA as bytes, so it does not effect our processing.
+ if len(fromArgs) > 1 {
+ args, err := parseArgs(fromArgs[1:])
+ if err != nil {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Unable to parse MAIL ESMTP parameters")
+ return
+ }
+
+ for key, value := range args {
+ switch key {
+ case "SIZE":
+ size, err := strconv.ParseInt(value, 10, 32)
+ if err != nil {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Unable to parse SIZE as an integer")
+ return
+ }
+
+ if c.server.MaxMessageBytes > 0 && int(size) > c.server.MaxMessageBytes {
+ c.writeResponse(552, EnhancedCode{5, 3, 4}, "Max message size exceeded")
+ return
+ }
+
+ opts.Size = int(size)
+ case "SMTPUTF8":
+ if !c.server.EnableSMTPUTF8 {
+ c.writeResponse(504, EnhancedCode{5, 5, 4}, "SMTPUTF8 is not implemented")
+ return
+ }
+ opts.UTF8 = true
+ case "REQUIRETLS":
+ if !c.server.EnableREQUIRETLS {
+ c.writeResponse(504, EnhancedCode{5, 5, 4}, "REQUIRETLS is not implemented")
+ return
+ }
+ opts.RequireTLS = true
+ case "BODY":
+ switch value {
+ case "BINARYMIME":
+ if !c.server.EnableBINARYMIME {
+ c.writeResponse(504, EnhancedCode{5, 5, 4}, "BINARYMIME is not implemented")
+ return
+ }
+ c.binarymime = true
+ case "7BIT", "8BITMIME":
+ default:
+ c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown BODY value")
+ return
+ }
+ opts.Body = BodyType(value)
+ case "AUTH":
+ value, err := decodeXtext(value)
+ if err != nil {
+ c.writeResponse(500, EnhancedCode{5, 5, 4}, "Malformed AUTH parameter value")
+ return
+ }
+ if !strings.HasPrefix(value, "<") {
+ c.writeResponse(500, EnhancedCode{5, 5, 4}, "Missing opening angle bracket")
+ return
+ }
+ if !strings.HasSuffix(value, ">") {
+ c.writeResponse(500, EnhancedCode{5, 5, 4}, "Missing closing angle bracket")
+ return
+ }
+ decodedMbox := value[1 : len(value)-1]
+ opts.Auth = &decodedMbox
+ default:
+ c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown MAIL FROM argument")
+ return
+ }
+ }
+ }
+
+ if err := c.Session().Mail(from, opts); err != nil {
+ if smtpErr, ok := err.(*SMTPError); ok {
+ c.writeResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
+ return
+ }
+ c.writeResponse(451, EnhancedCode{4, 0, 0}, err.Error())
+ return
+ }
+
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Roger, accepting mail from <%v>", from))
+ c.fromReceived = true
+}
+
+// This regexp matches 'hexchar' token defined in
+// https://tools.ietf.org/html/rfc4954#section-8 however it is intentionally
+// relaxed by requiring only '+' to be present. It allows us to detect
+// malformed values such as +A or +HH and report them appropriately.
+var hexcharRe = regexp.MustCompile(`\+[0-9A-F]?[0-9A-F]?`)
+
+func decodeXtext(val string) (string, error) {
+ if !strings.Contains(val, "+") {
+ return val, nil
+ }
+
+ var replaceErr error
+ decoded := hexcharRe.ReplaceAllStringFunc(val, func(match string) string {
+ if len(match) != 3 {
+ replaceErr = errors.New("incomplete hexchar")
+ return ""
+ }
+ char, err := strconv.ParseInt(match, 16, 8)
+ if err != nil {
+ replaceErr = err
+ return ""
+ }
+
+ return string(rune(char))
+ })
+ if replaceErr != nil {
+ return "", replaceErr
+ }
+
+ return decoded, nil
+}
+
+func encodeXtext(raw string) string {
+ var out strings.Builder
+ out.Grow(len(raw))
+
+ for _, ch := range raw {
+ if ch == '+' || ch == '=' {
+ out.WriteRune('+')
+ out.WriteString(strings.ToUpper(strconv.FormatInt(int64(ch), 16)))
+ }
+ if ch > '!' && ch < '~' { // printable non-space US-ASCII
+ out.WriteRune(ch)
+ }
+ // Non-ASCII.
+ out.WriteRune('+')
+ out.WriteString(strings.ToUpper(strconv.FormatInt(int64(ch), 16)))
+ }
+ return out.String()
+}
+
+// MAIL state -> waiting for RCPTs followed by DATA
+func (c *Conn) handleRcpt(arg string) {
+ if !c.fromReceived {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "Missing MAIL FROM command.")
+ return
+ }
+ if c.bdatPipe != nil {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "RCPT not allowed during message transfer")
+ return
+ }
+
+ if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
+ c.writeResponse(501, EnhancedCode{5, 5, 2}, "Was expecting RCPT arg syntax of TO:<address>")
+ return
+ }
+
+ // TODO: This trim is probably too forgiving
+ recipient := strings.Trim(arg[3:], "<> ")
+
+ if c.server.MaxRecipients > 0 && len(c.recipients) >= c.server.MaxRecipients {
+ c.writeResponse(552, EnhancedCode{5, 5, 3}, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
+ return
+ }
+
+ if err := c.Session().Rcpt(recipient); err != nil {
+ if smtpErr, ok := err.(*SMTPError); ok {
+ c.writeResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
+ return
+ }
+ c.writeResponse(451, EnhancedCode{4, 0, 0}, err.Error())
+ return
+ }
+ c.recipients = append(c.recipients, recipient)
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("I'll make sure <%v> gets this", recipient))
+}
+
+func (c *Conn) handleAuth(arg string) {
+ if c.helo == "" {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "Please introduce yourself first.")
+ return
+ }
+ if c.didAuth {
+ c.writeResponse(503, EnhancedCode{5, 5, 1}, "Already authenticated")
+ return
+ }
+
+ parts := strings.Fields(arg)
+ if len(parts) == 0 {
+ c.writeResponse(502, EnhancedCode{5, 5, 4}, "Missing parameter")
+ return
+ }
+
+ if _, isTLS := c.TLSConnectionState(); !isTLS && !c.server.AllowInsecureAuth {
+ c.writeResponse(523, EnhancedCode{5, 7, 10}, "TLS is required")
+ return
+ }
+
+ mechanism := strings.ToUpper(parts[0])
+
+ // Parse client initial response if there is one
+ var ir []byte
+ if len(parts) > 1 {
+ var err error
+ ir, err = base64.StdEncoding.DecodeString(parts[1])
+ if err != nil {
+ return
+ }
+ }
+
+ newSasl, ok := c.server.auths[mechanism]
+ if !ok {
+ c.writeResponse(504, EnhancedCode{5, 7, 4}, "Unsupported authentication mechanism")
+ return
+ }
+
+ sasl := newSasl(c)
+
+ response := ir
+ for {
+ challenge, done, err := sasl.Next(response)
+ if err != nil {
+ if smtpErr, ok := err.(*SMTPError); ok {
+ c.writeResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
+ return
+ }
+ c.writeResponse(454, EnhancedCode{4, 7, 0}, err.Error())
+ return
+ }
+
+ if done {
+ break
+ }
+
+ encoded := ""
+ if len(challenge) > 0 {
+ encoded = base64.StdEncoding.EncodeToString(challenge)
+ }
+ c.writeResponse(334, NoEnhancedCode, encoded)
+
+ encoded, err = c.readLine()
+ if err != nil {
+ return // TODO: error handling
+ }
+
+ if encoded == "*" {
+ // https://tools.ietf.org/html/rfc4954#page-4
+ c.writeResponse(501, EnhancedCode{5, 0, 0}, "Negotiation cancelled")
+ return
+ }
+
+ response, err = base64.StdEncoding.DecodeString(encoded)
+ if err != nil {
+ c.writeResponse(454, EnhancedCode{4, 7, 0}, "Invalid base64 data")
+ return
+ }
+ }
+
+ c.writeResponse(235, EnhancedCode{2, 0, 0}, "Authentication succeeded")
+ c.didAuth = true
+}
+
+func (c *Conn) handleStartTLS() {
+ if _, isTLS := c.TLSConnectionState(); isTLS {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "Already running in TLS")
+ return
+ }
+
+ if c.server.TLSConfig == nil {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "TLS not supported")
+ return
+ }
+
+ c.writeResponse(220, EnhancedCode{2, 0, 0}, "Ready to start TLS")
+
+ // Upgrade to TLS
+ tlsConn := tls.Server(c.conn, c.server.TLSConfig)
+
+ if err := tlsConn.Handshake(); err != nil {
+ c.writeResponse(550, EnhancedCode{5, 0, 0}, "Handshake error")
+ return
+ }
+
+ c.conn = tlsConn
+ c.init()
+
+ // Reset all state and close the previous Session.
+ // This is different from just calling reset() since we want the Backend to
+ // be able to see the information about TLS connection in the
+ // ConnectionState object passed to it.
+ if session := c.Session(); session != nil {
+ session.Logout()
+ c.setSession(nil)
+ }
+ c.helo = ""
+ c.didAuth = false
+ c.reset()
+}
+
+// DATA
+func (c *Conn) handleData(arg string) {
+ if arg != "" {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "DATA command should not have any arguments")
+ return
+ }
+ if c.bdatPipe != nil {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "DATA not allowed during message transfer")
+ return
+ }
+ if c.binarymime {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "DATA not allowed for BINARYMIME messages")
+ return
+ }
+
+ if !c.fromReceived || len(c.recipients) == 0 {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "Missing RCPT TO command.")
+ return
+ }
+
+ // We have recipients, go to accept data
+ c.writeResponse(354, EnhancedCode{2, 0, 0}, "Go ahead. End your data with <CR><LF>.<CR><LF>")
+
+ defer c.reset()
+
+ if c.server.LMTP {
+ c.handleDataLMTP()
+ return
+ }
+
+ r := newDataReader(c)
+ code, enhancedCode, msg := toSMTPStatus(c.Session().Data(r))
+ r.limited = false
+ io.Copy(ioutil.Discard, r) // Make sure all the data has been consumed
+ c.writeResponse(code, enhancedCode, msg)
+}
+
+func (c *Conn) handleBdat(arg string) {
+ args := strings.Fields(arg)
+ if len(args) == 0 {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Missing chunk size argument")
+ return
+ }
+ if len(args) > 2 {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Too many arguments")
+ return
+ }
+
+ if !c.fromReceived || len(c.recipients) == 0 {
+ c.writeResponse(502, EnhancedCode{5, 5, 1}, "Missing RCPT TO command.")
+ return
+ }
+
+ last := false
+ if len(args) == 2 {
+ if !strings.EqualFold(args[1], "LAST") {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Unknown BDAT argument")
+ return
+ }
+ last = true
+ }
+
+ // ParseUint instead of Atoi so we will not accept negative values.
+ size, err := strconv.ParseUint(args[0], 10, 32)
+ if err != nil {
+ c.writeResponse(501, EnhancedCode{5, 5, 4}, "Malformed size argument")
+ return
+ }
+
+ if c.server.MaxMessageBytes != 0 && c.bytesReceived+int(size) > c.server.MaxMessageBytes {
+ c.writeResponse(552, EnhancedCode{5, 3, 4}, "Max message size exceeded")
+
+ // Discard chunk itself without passing it to backend.
+ io.Copy(ioutil.Discard, io.LimitReader(c.text.R, int64(size)))
+
+ c.reset()
+ return
+ }
+
+ if c.bdatStatus == nil && c.server.LMTP {
+ c.bdatStatus = c.createStatusCollector()
+ }
+
+ if c.bdatPipe == nil {
+ var r *io.PipeReader
+ r, c.bdatPipe = io.Pipe()
+
+ c.dataResult = make(chan error, 1)
+
+ go func() {
+ defer func() {
+ if err := recover(); err != nil {
+ c.handlePanic(err, c.bdatStatus)
+
+ c.dataResult <- errPanic
+ r.CloseWithError(errPanic)
+ }
+ }()
+
+ var err error
+ if !c.server.LMTP {
+ err = c.Session().Data(r)
+ } else {
+ lmtpSession, ok := c.Session().(LMTPSession)
+ if !ok {
+ err = c.Session().Data(r)
+ for _, rcpt := range c.recipients {
+ c.bdatStatus.SetStatus(rcpt, err)
+ }
+ } else {
+ err = lmtpSession.LMTPData(r, c.bdatStatus)
+ }
+ }
+
+ c.dataResult <- err
+ r.CloseWithError(err)
+ }()
+ }
+
+ c.lineLimitReader.LineLimit = 0
+
+ chunk := io.LimitReader(c.text.R, int64(size))
+ _, err = io.Copy(c.bdatPipe, chunk)
+ if err != nil {
+ // Backend might return an error early using CloseWithError without consuming
+ // the whole chunk.
+ io.Copy(ioutil.Discard, chunk)
+
+ c.writeResponse(toSMTPStatus(err))
+
+ if err == errPanic {
+ c.Close()
+ }
+
+ c.reset()
+ c.lineLimitReader.LineLimit = c.server.MaxLineLength
+ return
+ }
+
+ c.bytesReceived += int(size)
+
+ if last {
+ c.lineLimitReader.LineLimit = c.server.MaxLineLength
+
+ c.bdatPipe.Close()
+
+ err := <-c.dataResult
+
+ if c.server.LMTP {
+ c.bdatStatus.fillRemaining(err)
+ for i, rcpt := range c.recipients {
+ code, enchCode, msg := toSMTPStatus(<-c.bdatStatus.status[i])
+ c.writeResponse(code, enchCode, "<"+rcpt+"> "+msg)
+ }
+ } else {
+ c.writeResponse(toSMTPStatus(err))
+ }
+
+ if err == errPanic {
+ c.Close()
+ return
+ }
+
+ c.reset()
+ } else {
+ c.writeResponse(250, EnhancedCode{2, 0, 0}, "Continue")
+ }
+}
+
+// ErrDataReset is returned by Reader pased to Data function if client does not
+// send another BDAT command and instead closes connection or issues RSET command.
+var ErrDataReset = errors.New("smtp: message transmission aborted")
+
+var errPanic = &SMTPError{
+ Code: 421,
+ EnhancedCode: EnhancedCode{4, 0, 0},
+ Message: "Internal server error",
+}
+
+func (c *Conn) handlePanic(err interface{}, status *statusCollector) {
+ if status != nil {
+ status.fillRemaining(errPanic)
+ }
+
+ stack := debug.Stack()
+ c.server.ErrorLog.Printf("panic serving %v: %v\n%s", c.conn.RemoteAddr(), err, stack)
+}
+
+func (c *Conn) createStatusCollector() *statusCollector {
+ rcptCounts := make(map[string]int, len(c.recipients))
+
+ status := &statusCollector{
+ statusMap: make(map[string]chan error, len(c.recipients)),
+ status: make([]chan error, 0, len(c.recipients)),
+ }
+ for _, rcpt := range c.recipients {
+ rcptCounts[rcpt]++
+ }
+ // Create channels with buffer sizes necessary to fit all
+ // statuses for a single recipient to avoid deadlocks.
+ for rcpt, count := range rcptCounts {
+ status.statusMap[rcpt] = make(chan error, count)
+ }
+ for _, rcpt := range c.recipients {
+ status.status = append(status.status, status.statusMap[rcpt])
+ }
+
+ return status
+}
+
+type statusCollector struct {
+ // Contains map from recipient to list of channels that are used for that
+ // recipient.
+ statusMap map[string]chan error
+
+ // Contains channels from statusMap, in the same
+ // order as Conn.recipients.
+ status []chan error
+}
+
+// fillRemaining sets status for all recipients SetStatus was not called for before.
+func (s *statusCollector) fillRemaining(err error) {
+ // Amount of times certain recipient was specified is indicated by the channel
+ // buffer size, so once we fill it, we can be confident that we sent
+ // at least as much statuses as needed. Extra statuses will be ignored anyway.
+chLoop:
+ for _, ch := range s.statusMap {
+ for {
+ select {
+ case ch <- err:
+ default:
+ continue chLoop
+ }
+ }
+ }
+}
+
+func (s *statusCollector) SetStatus(rcptTo string, err error) {
+ ch := s.statusMap[rcptTo]
+ if ch == nil {
+ panic("SetStatus is called for recipient that was not specified before")
+ }
+
+ select {
+ case ch <- err:
+ default:
+ // There enough buffer space to fit all statuses at once, if this is
+ // not the case - backend is doing something wrong.
+ panic("SetStatus is called more times than particular recipient was specified")
+ }
+}
+
+func (c *Conn) handleDataLMTP() {
+ r := newDataReader(c)
+ status := c.createStatusCollector()
+
+ done := make(chan bool, 1)
+
+ lmtpSession, ok := c.Session().(LMTPSession)
+ if !ok {
+ // Fallback to using a single status for all recipients.
+ err := c.Session().Data(r)
+ io.Copy(ioutil.Discard, r) // Make sure all the data has been consumed
+ for _, rcpt := range c.recipients {
+ status.SetStatus(rcpt, err)
+ }
+ done <- true
+ } else {
+ go func() {
+ defer func() {
+ if err := recover(); err != nil {
+ status.fillRemaining(&SMTPError{
+ Code: 421,
+ EnhancedCode: EnhancedCode{4, 0, 0},
+ Message: "Internal server error",
+ })
+
+ stack := debug.Stack()
+ c.server.ErrorLog.Printf("panic serving %v: %v\n%s", c.conn.RemoteAddr(), err, stack)
+ done <- false
+ }
+ }()
+
+ status.fillRemaining(lmtpSession.LMTPData(r, status))
+ io.Copy(ioutil.Discard, r) // Make sure all the data has been consumed
+ done <- true
+ }()
+ }
+
+ for i, rcpt := range c.recipients {
+ code, enchCode, msg := toSMTPStatus(<-status.status[i])
+ c.writeResponse(code, enchCode, "<"+rcpt+"> "+msg)
+ }
+
+ // If done gets false, the panic occured in LMTPData and the connection
+ // should be closed.
+ if !<-done {
+ c.Close()
+ }
+}
+
+func toSMTPStatus(err error) (code int, enchCode EnhancedCode, msg string) {
+ if err != nil {
+ if smtperr, ok := err.(*SMTPError); ok {
+ return smtperr.Code, smtperr.EnhancedCode, smtperr.Message
+ } else {
+ return 554, EnhancedCode{5, 0, 0}, "Error: transaction failed, blame it on the weather: " + err.Error()
+ }
+ }
+
+ return 250, EnhancedCode{2, 0, 0}, "OK: queued"
+}
+
+func (c *Conn) Reject() {
+ c.writeResponse(421, EnhancedCode{4, 4, 5}, "Too busy. Try again later.")
+ c.Close()
+}
+
+func (c *Conn) greet() {
+ c.writeResponse(220, NoEnhancedCode, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain))
+}
+
+func (c *Conn) writeResponse(code int, enhCode EnhancedCode, text ...string) {
+ // TODO: error handling
+ if c.server.WriteTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.server.WriteTimeout))
+ }
+
+ // All responses must include an enhanced code, if it is missing - use
+ // a generic code X.0.0.
+ if enhCode == EnhancedCodeNotSet {
+ cat := code / 100
+ switch cat {
+ case 2, 4, 5:
+ enhCode = EnhancedCode{cat, 0, 0}
+ default:
+ enhCode = NoEnhancedCode
+ }
+ }
+
+ for i := 0; i < len(text)-1; i++ {
+ c.text.PrintfLine("%d-%v", code, text[i])
+ }
+ if enhCode == NoEnhancedCode {
+ c.text.PrintfLine("%d %v", code, text[len(text)-1])
+ } else {
+ c.text.PrintfLine("%d %v.%v.%v %v", code, enhCode[0], enhCode[1], enhCode[2], text[len(text)-1])
+ }
+}
+
+// Reads a line of input
+func (c *Conn) readLine() (string, error) {
+ if c.server.ReadTimeout != 0 {
+ if err := c.conn.SetReadDeadline(time.Now().Add(c.server.ReadTimeout)); err != nil {
+ return "", err
+ }
+ }
+
+ return c.text.ReadLine()
+}
+
+func (c *Conn) reset() {
+ c.locker.Lock()
+ defer c.locker.Unlock()
+
+ if c.bdatPipe != nil {
+ c.bdatPipe.CloseWithError(ErrDataReset)
+ c.bdatPipe = nil
+ }
+ c.bdatStatus = nil
+ c.bytesReceived = 0
+
+ if c.session != nil {
+ c.session.Reset()
+ }
+
+ c.fromReceived = false
+ c.recipients = nil
+}
diff --git a/vendor/github.com/emersion/go-smtp/data.go b/vendor/github.com/emersion/go-smtp/data.go
new file mode 100644
index 0000000..c338455
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/data.go
@@ -0,0 +1,147 @@
+package smtp
+
+import (
+ "bufio"
+ "io"
+)
+
+type EnhancedCode [3]int
+
+// SMTPError specifies the error code, enhanced error code (if any) and
+// message returned by the server.
+type SMTPError struct {
+ Code int
+ EnhancedCode EnhancedCode
+ Message string
+}
+
+// NoEnhancedCode is used to indicate that enhanced error code should not be
+// included in response.
+//
+// Note that RFC 2034 requires an enhanced code to be included in all 2xx, 4xx
+// and 5xx responses. This constant is exported for use by extensions, you
+// should probably use EnhancedCodeNotSet instead.
+var NoEnhancedCode = EnhancedCode{-1, -1, -1}
+
+// EnhancedCodeNotSet is a nil value of EnhancedCode field in SMTPError, used
+// to indicate that backend failed to provide enhanced status code. X.0.0 will
+// be used (X is derived from error code).
+var EnhancedCodeNotSet = EnhancedCode{0, 0, 0}
+
+func (err *SMTPError) Error() string {
+ return err.Message
+}
+
+func (err *SMTPError) Temporary() bool {
+ return err.Code/100 == 4
+}
+
+var ErrDataTooLarge = &SMTPError{
+ Code: 552,
+ EnhancedCode: EnhancedCode{5, 3, 4},
+ Message: "Maximum message size exceeded",
+}
+
+type dataReader struct {
+ r *bufio.Reader
+ state int
+
+ limited bool
+ n int64 // Maximum bytes remaining
+}
+
+func newDataReader(c *Conn) *dataReader {
+ dr := &dataReader{
+ r: c.text.R,
+ }
+
+ if c.server.MaxMessageBytes > 0 {
+ dr.limited = true
+ dr.n = int64(c.server.MaxMessageBytes)
+ }
+
+ return dr
+}
+
+func (r *dataReader) Read(b []byte) (n int, err error) {
+ if r.limited {
+ if r.n <= 0 {
+ return 0, ErrDataTooLarge
+ }
+ if int64(len(b)) > r.n {
+ b = b[0:r.n]
+ }
+ }
+
+ // Code below is taken from net/textproto with only one modification to
+ // not rewrite CRLF -> LF.
+
+ // Run data through a simple state machine to
+ // elide leading dots and detect ending .\r\n line.
+ const (
+ stateBeginLine = iota // beginning of line; initial state; must be zero
+ stateDot // read . at beginning of line
+ stateDotCR // read .\r at beginning of line
+ stateCR // read \r (possibly at end of line)
+ stateData // reading data in middle of line
+ stateEOF // reached .\r\n end marker line
+ )
+ for n < len(b) && r.state != stateEOF {
+ var c byte
+ c, err = r.r.ReadByte()
+ if err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ break
+ }
+ switch r.state {
+ case stateBeginLine:
+ if c == '.' {
+ r.state = stateDot
+ continue
+ }
+ r.state = stateData
+ case stateDot:
+ if c == '\r' {
+ r.state = stateDotCR
+ continue
+ }
+ if c == '\n' {
+ r.state = stateEOF
+ continue
+ }
+
+ r.state = stateData
+ case stateDotCR:
+ if c == '\n' {
+ r.state = stateEOF
+ continue
+ }
+ r.state = stateData
+ case stateCR:
+ if c == '\n' {
+ r.state = stateBeginLine
+ break
+ }
+ r.state = stateData
+ case stateData:
+ if c == '\r' {
+ r.state = stateCR
+ }
+ if c == '\n' {
+ r.state = stateBeginLine
+ }
+ }
+ b[n] = c
+ n++
+ }
+ if err == nil && r.state == stateEOF {
+ err = io.EOF
+ }
+
+ if r.limited {
+ r.n -= int64(n)
+ }
+ return
+}
diff --git a/vendor/github.com/emersion/go-smtp/lengthlimit_reader.go b/vendor/github.com/emersion/go-smtp/lengthlimit_reader.go
new file mode 100644
index 0000000..1513e56
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/lengthlimit_reader.go
@@ -0,0 +1,47 @@
+package smtp
+
+import (
+ "errors"
+ "io"
+)
+
+var ErrTooLongLine = errors.New("smtp: too long a line in input stream")
+
+// lineLimitReader reads from the underlying Reader but restricts
+// line length of lines in input stream to a certain length.
+//
+// If line length exceeds the limit - Read returns ErrTooLongLine
+type lineLimitReader struct {
+ R io.Reader
+ LineLimit int
+
+ curLineLength int
+}
+
+func (r *lineLimitReader) Read(b []byte) (int, error) {
+ if r.curLineLength > r.LineLimit && r.LineLimit > 0 {
+ return 0, ErrTooLongLine
+ }
+
+ n, err := r.R.Read(b)
+ if err != nil {
+ return n, err
+ }
+
+ if r.LineLimit == 0 {
+ return n, nil
+ }
+
+ for _, chr := range b[:n] {
+ if chr == '\n' {
+ r.curLineLength = 0
+ }
+ r.curLineLength++
+
+ if r.curLineLength > r.LineLimit {
+ return 0, ErrTooLongLine
+ }
+ }
+
+ return n, nil
+}
diff --git a/vendor/github.com/emersion/go-smtp/parse.go b/vendor/github.com/emersion/go-smtp/parse.go
new file mode 100644
index 0000000..dc7e77f
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/parse.go
@@ -0,0 +1,72 @@
+package smtp
+
+import (
+ "fmt"
+ "strings"
+)
+
+func parseCmd(line string) (cmd string, arg string, err error) {
+ line = strings.TrimRight(line, "\r\n")
+
+ l := len(line)
+ switch {
+ case strings.HasPrefix(strings.ToUpper(line), "STARTTLS"):
+ return "STARTTLS", "", nil
+ case l == 0:
+ return "", "", nil
+ case l < 4:
+ return "", "", fmt.Errorf("Command too short: %q", line)
+ case l == 4:
+ return strings.ToUpper(line), "", nil
+ case l == 5:
+ // Too long to be only command, too short to have args
+ return "", "", fmt.Errorf("Mangled command: %q", line)
+ }
+
+ // If we made it here, command is long enough to have args
+ if line[4] != ' ' {
+ // There wasn't a space after the command?
+ return "", "", fmt.Errorf("Mangled command: %q", line)
+ }
+
+ // I'm not sure if we should trim the args or not, but we will for now
+ //return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " "), nil
+ return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " \n\r"), nil
+}
+
+// Takes the arguments proceeding a command and files them
+// into a map[string]string after uppercasing each key. Sample arg
+// string:
+//
+// " BODY=8BITMIME SIZE=1024 SMTPUTF8"
+//
+// The leading space is mandatory.
+func parseArgs(args []string) (map[string]string, error) {
+ argMap := map[string]string{}
+ for _, arg := range args {
+ if arg == "" {
+ continue
+ }
+ m := strings.Split(arg, "=")
+ switch len(m) {
+ case 2:
+ argMap[strings.ToUpper(m[0])] = m[1]
+ case 1:
+ argMap[strings.ToUpper(m[0])] = ""
+ default:
+ return nil, fmt.Errorf("Failed to parse arg string: %q", arg)
+ }
+ }
+ return argMap, nil
+}
+
+func parseHelloArgument(arg string) (string, error) {
+ domain := arg
+ if idx := strings.IndexRune(arg, ' '); idx >= 0 {
+ domain = arg[:idx]
+ }
+ if domain == "" {
+ return "", fmt.Errorf("Invalid domain")
+ }
+ return domain, nil
+}
diff --git a/vendor/github.com/emersion/go-smtp/server.go b/vendor/github.com/emersion/go-smtp/server.go
new file mode 100644
index 0000000..82cc422
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/server.go
@@ -0,0 +1,292 @@
+package smtp
+
+import (
+ "crypto/tls"
+ "errors"
+ "io"
+ "log"
+ "net"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/emersion/go-sasl"
+)
+
+var errTCPAndLMTP = errors.New("smtp: cannot start LMTP server listening on a TCP socket")
+
+// A function that creates SASL servers.
+type SaslServerFactory func(conn *Conn) sasl.Server
+
+// Logger interface is used by Server to report unexpected internal errors.
+type Logger interface {
+ Printf(format string, v ...interface{})
+ Println(v ...interface{})
+}
+
+// A SMTP server.
+type Server struct {
+ // TCP or Unix address to listen on.
+ Addr string
+ // The server TLS configuration.
+ TLSConfig *tls.Config
+ // Enable LMTP mode, as defined in RFC 2033. LMTP mode cannot be used with a
+ // TCP listener.
+ LMTP bool
+
+ Domain string
+ MaxRecipients int
+ MaxMessageBytes int
+ MaxLineLength int
+ AllowInsecureAuth bool
+ Strict bool
+ Debug io.Writer
+ ErrorLog Logger
+ ReadTimeout time.Duration
+ WriteTimeout time.Duration
+
+ // Advertise SMTPUTF8 (RFC 6531) capability.
+ // Should be used only if backend supports it.
+ EnableSMTPUTF8 bool
+
+ // Advertise REQUIRETLS (RFC 8689) capability.
+ // Should be used only if backend supports it.
+ EnableREQUIRETLS bool
+
+ // Advertise BINARYMIME (RFC 3030) capability.
+ // Should be used only if backend supports it.
+ EnableBINARYMIME bool
+
+ // If set, the AUTH command will not be advertised and authentication
+ // attempts will be rejected. This setting overrides AllowInsecureAuth.
+ AuthDisabled bool
+
+ // The server backend.
+ Backend Backend
+
+ caps []string
+ auths map[string]SaslServerFactory
+ done chan struct{}
+
+ locker sync.Mutex
+ listeners []net.Listener
+ conns map[*Conn]struct{}
+}
+
+// New creates a new SMTP server.
+func NewServer(be Backend) *Server {
+ return &Server{
+ // Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
+ MaxLineLength: 2000,
+
+ Backend: be,
+ done: make(chan struct{}, 1),
+ ErrorLog: log.New(os.Stderr, "smtp/server ", log.LstdFlags),
+ caps: []string{"PIPELINING", "8BITMIME", "ENHANCEDSTATUSCODES", "CHUNKING"},
+ auths: map[string]SaslServerFactory{
+ sasl.Plain: func(conn *Conn) sasl.Server {
+ return sasl.NewPlainServer(func(identity, username, password string) error {
+ if identity != "" && identity != username {
+ return errors.New("Identities not supported")
+ }
+
+ sess := conn.Session()
+ if sess == nil {
+ panic("No session when AUTH is called")
+ }
+
+ return sess.AuthPlain(username, password)
+ })
+ },
+ },
+ conns: make(map[*Conn]struct{}),
+ }
+}
+
+// Serve accepts incoming connections on the Listener l.
+func (s *Server) Serve(l net.Listener) error {
+ s.locker.Lock()
+ s.listeners = append(s.listeners, l)
+ s.locker.Unlock()
+
+ var tempDelay time.Duration // how long to sleep on accept failure
+
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ select {
+ case <-s.done:
+ // we called Close()
+ return nil
+ default:
+ }
+ if ne, ok := err.(net.Error); ok && ne.Temporary() {
+ if tempDelay == 0 {
+ tempDelay = 5 * time.Millisecond
+ } else {
+ tempDelay *= 2
+ }
+ if max := 1 * time.Second; tempDelay > max {
+ tempDelay = max
+ }
+ s.ErrorLog.Printf("accept error: %s; retrying in %s", err, tempDelay)
+ time.Sleep(tempDelay)
+ continue
+ }
+ return err
+ }
+ go func() {
+ err := s.handleConn(newConn(c, s))
+ if err != nil {
+ s.ErrorLog.Printf("handler error: %s", err)
+ }
+ }()
+ }
+}
+
+func (s *Server) handleConn(c *Conn) error {
+ s.locker.Lock()
+ s.conns[c] = struct{}{}
+ s.locker.Unlock()
+
+ defer func() {
+ c.Close()
+
+ s.locker.Lock()
+ delete(s.conns, c)
+ s.locker.Unlock()
+ }()
+
+ if tlsConn, ok := c.conn.(*tls.Conn); ok {
+ if d := s.ReadTimeout; d != 0 {
+ c.conn.SetReadDeadline(time.Now().Add(d))
+ }
+ if d := s.WriteTimeout; d != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(d))
+ }
+ if err := tlsConn.Handshake(); err != nil {
+ return err
+ }
+ }
+
+ c.greet()
+
+ for {
+ line, err := c.readLine()
+ if err == nil {
+ cmd, arg, err := parseCmd(line)
+ if err != nil {
+ c.protocolError(501, EnhancedCode{5, 5, 2}, "Bad command")
+ continue
+ }
+
+ c.handle(cmd, arg)
+ } else {
+ if err == io.EOF || errors.Is(err, net.ErrClosed) {
+ return nil
+ }
+ if err == ErrTooLongLine {
+ c.writeResponse(500, EnhancedCode{5, 4, 0}, "Too long line, closing connection")
+ return nil
+ }
+
+ if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
+ c.writeResponse(221, EnhancedCode{2, 4, 2}, "Idle timeout, bye bye")
+ return nil
+ }
+
+ c.writeResponse(221, EnhancedCode{2, 4, 0}, "Connection error, sorry")
+ return err
+ }
+ }
+}
+
+// ListenAndServe listens on the network address s.Addr and then calls Serve
+// to handle requests on incoming connections.
+//
+// If s.Addr is blank and LMTP is disabled, ":smtp" is used.
+func (s *Server) ListenAndServe() error {
+ network := "tcp"
+ if s.LMTP {
+ network = "unix"
+ }
+
+ addr := s.Addr
+ if !s.LMTP && addr == "" {
+ addr = ":smtp"
+ }
+
+ l, err := net.Listen(network, addr)
+ if err != nil {
+ return err
+ }
+
+ return s.Serve(l)
+}
+
+// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
+// Serve to handle requests on incoming TLS connections.
+//
+// If s.Addr is blank, ":smtps" is used.
+func (s *Server) ListenAndServeTLS() error {
+ if s.LMTP {
+ return errTCPAndLMTP
+ }
+
+ addr := s.Addr
+ if addr == "" {
+ addr = ":smtps"
+ }
+
+ l, err := tls.Listen("tcp", addr, s.TLSConfig)
+ if err != nil {
+ return err
+ }
+
+ return s.Serve(l)
+}
+
+// Close immediately closes all active listeners and connections.
+//
+// Close returns any error returned from closing the server's underlying
+// listener(s).
+func (s *Server) Close() error {
+ select {
+ case <-s.done:
+ return errors.New("smtp: server already closed")
+ default:
+ close(s.done)
+ }
+
+ var err error
+ s.locker.Lock()
+ for _, l := range s.listeners {
+ if lerr := l.Close(); lerr != nil && err == nil {
+ err = lerr
+ }
+ }
+
+ for conn := range s.conns {
+ conn.Close()
+ }
+ s.locker.Unlock()
+
+ return err
+}
+
+// EnableAuth enables an authentication mechanism on this server.
+//
+// This function should not be called directly, it must only be used by
+// libraries implementing extensions of the SMTP protocol.
+func (s *Server) EnableAuth(name string, f SaslServerFactory) {
+ s.auths[name] = f
+}
+
+// ForEachConn iterates through all opened connections.
+func (s *Server) ForEachConn(f func(*Conn)) {
+ s.locker.Lock()
+ defer s.locker.Unlock()
+ for conn := range s.conns {
+ f(conn)
+ }
+}
diff --git a/vendor/github.com/emersion/go-smtp/smtp.go b/vendor/github.com/emersion/go-smtp/smtp.go
new file mode 100644
index 0000000..36963cb
--- /dev/null
+++ b/vendor/github.com/emersion/go-smtp/smtp.go
@@ -0,0 +1,30 @@
+// Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321.
+//
+// It also implements the following extensions:
+//
+// 8BITMIME: RFC 1652
+// AUTH: RFC 2554
+// STARTTLS: RFC 3207
+// ENHANCEDSTATUSCODES: RFC 2034
+// SMTPUTF8: RFC 6531
+// REQUIRETLS: RFC 8689
+// CHUNKING: RFC 3030
+// BINARYMIME: RFC 3030
+//
+// LMTP (RFC 2033) is also supported.
+//
+// Additional extensions may be handled by other packages.
+package smtp
+
+import (
+ "errors"
+ "strings"
+)
+
+// validateLine checks to see if a line has CR or LF as per RFC 5321
+func validateLine(line string) error {
+ if strings.ContainsAny(line, "\n\r") {
+ return errors.New("smtp: A line must not contain CR or LF")
+ }
+ return nil
+}
diff --git a/vendor/golang.org/x/text/AUTHORS b/vendor/golang.org/x/text/AUTHORS
new file mode 100644
index 0000000..15167cd
--- /dev/null
+++ b/vendor/golang.org/x/text/AUTHORS
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/text/CONTRIBUTORS b/vendor/golang.org/x/text/CONTRIBUTORS
new file mode 100644
index 0000000..1c4577e
--- /dev/null
+++ b/vendor/golang.org/x/text/CONTRIBUTORS
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/text/LICENSE b/vendor/golang.org/x/text/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/vendor/golang.org/x/text/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/text/PATENTS b/vendor/golang.org/x/text/PATENTS
new file mode 100644
index 0000000..7330990
--- /dev/null
+++ b/vendor/golang.org/x/text/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/text/encoding/encoding.go b/vendor/golang.org/x/text/encoding/encoding.go
new file mode 100644
index 0000000..a0bd7cd
--- /dev/null
+++ b/vendor/golang.org/x/text/encoding/encoding.go
@@ -0,0 +1,335 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package encoding defines an interface for character encodings, such as Shift
+// JIS and Windows 1252, that can convert to and from UTF-8.
+//
+// Encoding implementations are provided in other packages, such as
+// golang.org/x/text/encoding/charmap and
+// golang.org/x/text/encoding/japanese.
+package encoding // import "golang.org/x/text/encoding"
+
+import (
+ "errors"
+ "io"
+ "strconv"
+ "unicode/utf8"
+
+ "golang.org/x/text/encoding/internal/identifier"
+ "golang.org/x/text/transform"
+)
+
+// TODO:
+// - There seems to be some inconsistency in when decoders return errors
+// and when not. Also documentation seems to suggest they shouldn't return
+// errors at all (except for UTF-16).
+// - Encoders seem to rely on or at least benefit from the input being in NFC
+// normal form. Perhaps add an example how users could prepare their output.
+
+// Encoding is a character set encoding that can be transformed to and from
+// UTF-8.
+type Encoding interface {
+ // NewDecoder returns a Decoder.
+ NewDecoder() *Decoder
+
+ // NewEncoder returns an Encoder.
+ NewEncoder() *Encoder
+}
+
+// A Decoder converts bytes to UTF-8. It implements transform.Transformer.
+//
+// Transforming source bytes that are not of that encoding will not result in an
+// error per se. Each byte that cannot be transcoded will be represented in the
+// output by the UTF-8 encoding of '\uFFFD', the replacement rune.
+type Decoder struct {
+ transform.Transformer
+
+ // This forces external creators of Decoders to use names in struct
+ // initializers, allowing for future extendibility without having to break
+ // code.
+ _ struct{}
+}
+
+// Bytes converts the given encoded bytes to UTF-8. It returns the converted
+// bytes or nil, err if any error occurred.
+func (d *Decoder) Bytes(b []byte) ([]byte, error) {
+ b, _, err := transform.Bytes(d, b)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+// String converts the given encoded string to UTF-8. It returns the converted
+// string or "", err if any error occurred.
+func (d *Decoder) String(s string) (string, error) {
+ s, _, err := transform.String(d, s)
+ if err != nil {
+ return "", err
+ }
+ return s, nil
+}
+
+// Reader wraps another Reader to decode its bytes.
+//
+// The Decoder may not be used for any other operation as long as the returned
+// Reader is in use.
+func (d *Decoder) Reader(r io.Reader) io.Reader {
+ return transform.NewReader(r, d)
+}
+
+// An Encoder converts bytes from UTF-8. It implements transform.Transformer.
+//
+// Each rune that cannot be transcoded will result in an error. In this case,
+// the transform will consume all source byte up to, not including the offending
+// rune. Transforming source bytes that are not valid UTF-8 will be replaced by
+// `\uFFFD`. To return early with an error instead, use transform.Chain to
+// preprocess the data with a UTF8Validator.
+type Encoder struct {
+ transform.Transformer
+
+ // This forces external creators of Encoders to use names in struct
+ // initializers, allowing for future extendibility without having to break
+ // code.
+ _ struct{}
+}
+
+// Bytes converts bytes from UTF-8. It returns the converted bytes or nil, err if
+// any error occurred.
+func (e *Encoder) Bytes(b []byte) ([]byte, error) {
+ b, _, err := transform.Bytes(e, b)
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+// String converts a string from UTF-8. It returns the converted string or
+// "", err if any error occurred.
+func (e *Encoder) String(s string) (string, error) {
+ s, _, err := transform.String(e, s)
+ if err != nil {
+ return "", err
+ }
+ return s, nil
+}
+
+// Writer wraps another Writer to encode its UTF-8 output.
+//
+// The Encoder may not be used for any other operation as long as the returned
+// Writer is in use.
+func (e *Encoder) Writer(w io.Writer) io.Writer {
+ return transform.NewWriter(w, e)
+}
+
+// ASCIISub is the ASCII substitute character, as recommended by
+// https://unicode.org/reports/tr36/#Text_Comparison
+const ASCIISub = '\x1a'
+
+// Nop is the nop encoding. Its transformed bytes are the same as the source
+// bytes; it does not replace invalid UTF-8 sequences.
+var Nop Encoding = nop{}
+
+type nop struct{}
+
+func (nop) NewDecoder() *Decoder {
+ return &Decoder{Transformer: transform.Nop}
+}
+func (nop) NewEncoder() *Encoder {
+ return &Encoder{Transformer: transform.Nop}
+}
+
+// Replacement is the replacement encoding. Decoding from the replacement
+// encoding yields a single '\uFFFD' replacement rune. Encoding from UTF-8 to
+// the replacement encoding yields the same as the source bytes except that
+// invalid UTF-8 is converted to '\uFFFD'.
+//
+// It is defined at http://encoding.spec.whatwg.org/#replacement
+var Replacement Encoding = replacement{}
+
+type replacement struct{}
+
+func (replacement) NewDecoder() *Decoder {
+ return &Decoder{Transformer: replacementDecoder{}}
+}
+
+func (replacement) NewEncoder() *Encoder {
+ return &Encoder{Transformer: replacementEncoder{}}
+}
+
+func (replacement) ID() (mib identifier.MIB, other string) {
+ return identifier.Replacement, ""
+}
+
+type replacementDecoder struct{ transform.NopResetter }
+
+func (replacementDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ if len(dst) < 3 {
+ return 0, 0, transform.ErrShortDst
+ }
+ if atEOF {
+ const fffd = "\ufffd"
+ dst[0] = fffd[0]
+ dst[1] = fffd[1]
+ dst[2] = fffd[2]
+ nDst = 3
+ }
+ return nDst, len(src), nil
+}
+
+type replacementEncoder struct{ transform.NopResetter }
+
+func (replacementEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ r, size := rune(0), 0
+
+ for ; nSrc < len(src); nSrc += size {
+ r = rune(src[nSrc])
+
+ // Decode a 1-byte rune.
+ if r < utf8.RuneSelf {
+ size = 1
+
+ } else {
+ // Decode a multi-byte rune.
+ r, size = utf8.DecodeRune(src[nSrc:])
+ if size == 1 {
+ // All valid runes of size 1 (those below utf8.RuneSelf) were
+ // handled above. We have invalid UTF-8 or we haven't seen the
+ // full character yet.
+ if !atEOF && !utf8.FullRune(src[nSrc:]) {
+ err = transform.ErrShortSrc
+ break
+ }
+ r = '\ufffd'
+ }
+ }
+
+ if nDst+utf8.RuneLen(r) > len(dst) {
+ err = transform.ErrShortDst
+ break
+ }
+ nDst += utf8.EncodeRune(dst[nDst:], r)
+ }
+ return nDst, nSrc, err
+}
+
+// HTMLEscapeUnsupported wraps encoders to replace source runes outside the
+// repertoire of the destination encoding with HTML escape sequences.
+//
+// This wrapper exists to comply to URL and HTML forms requiring a
+// non-terminating legacy encoder. The produced sequences may lead to data
+// loss as they are indistinguishable from legitimate input. To avoid this
+// issue, use UTF-8 encodings whenever possible.
+func HTMLEscapeUnsupported(e *Encoder) *Encoder {
+ return &Encoder{Transformer: &errorHandler{e, errorToHTML}}
+}
+
+// ReplaceUnsupported wraps encoders to replace source runes outside the
+// repertoire of the destination encoding with an encoding-specific
+// replacement.
+//
+// This wrapper is only provided for backwards compatibility and legacy
+// handling. Its use is strongly discouraged. Use UTF-8 whenever possible.
+func ReplaceUnsupported(e *Encoder) *Encoder {
+ return &Encoder{Transformer: &errorHandler{e, errorToReplacement}}
+}
+
+type errorHandler struct {
+ *Encoder
+ handler func(dst []byte, r rune, err repertoireError) (n int, ok bool)
+}
+
+// TODO: consider making this error public in some form.
+type repertoireError interface {
+ Replacement() byte
+}
+
+func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF)
+ for err != nil {
+ rerr, ok := err.(repertoireError)
+ if !ok {
+ return nDst, nSrc, err
+ }
+ r, sz := utf8.DecodeRune(src[nSrc:])
+ n, ok := h.handler(dst[nDst:], r, rerr)
+ if !ok {
+ return nDst, nSrc, transform.ErrShortDst
+ }
+ err = nil
+ nDst += n
+ if nSrc += sz; nSrc < len(src) {
+ var dn, sn int
+ dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF)
+ nDst += dn
+ nSrc += sn
+ }
+ }
+ return nDst, nSrc, err
+}
+
+func errorToHTML(dst []byte, r rune, err repertoireError) (n int, ok bool) {
+ buf := [8]byte{}
+ b := strconv.AppendUint(buf[:0], uint64(r), 10)
+ if n = len(b) + len("&#;"); n >= len(dst) {
+ return 0, false
+ }
+ dst[0] = '&'
+ dst[1] = '#'
+ dst[copy(dst[2:], b)+2] = ';'
+ return n, true
+}
+
+func errorToReplacement(dst []byte, r rune, err repertoireError) (n int, ok bool) {
+ if len(dst) == 0 {
+ return 0, false
+ }
+ dst[0] = err.Replacement()
+ return 1, true
+}
+
+// ErrInvalidUTF8 means that a transformer encountered invalid UTF-8.
+var ErrInvalidUTF8 = errors.New("encoding: invalid UTF-8")
+
+// UTF8Validator is a transformer that returns ErrInvalidUTF8 on the first
+// input byte that is not valid UTF-8.
+var UTF8Validator transform.Transformer = utf8Validator{}
+
+type utf8Validator struct{ transform.NopResetter }
+
+func (utf8Validator) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ n := len(src)
+ if n > len(dst) {
+ n = len(dst)
+ }
+ for i := 0; i < n; {
+ if c := src[i]; c < utf8.RuneSelf {
+ dst[i] = c
+ i++
+ continue
+ }
+ _, size := utf8.DecodeRune(src[i:])
+ if size == 1 {
+ // All valid runes of size 1 (those below utf8.RuneSelf) were
+ // handled above. We have invalid UTF-8 or we haven't seen the
+ // full character yet.
+ err = ErrInvalidUTF8
+ if !atEOF && !utf8.FullRune(src[i:]) {
+ err = transform.ErrShortSrc
+ }
+ return i, i, err
+ }
+ if i+size > len(dst) {
+ return i, i, transform.ErrShortDst
+ }
+ for ; size > 0; size-- {
+ dst[i] = src[i]
+ i++
+ }
+ }
+ if len(src) > len(dst) {
+ err = transform.ErrShortDst
+ }
+ return n, n, err
+}
diff --git a/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go b/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
new file mode 100644
index 0000000..5c9b85c
--- /dev/null
+++ b/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go
@@ -0,0 +1,81 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run gen.go
+
+// Package identifier defines the contract between implementations of Encoding
+// and Index by defining identifiers that uniquely identify standardized coded
+// character sets (CCS) and character encoding schemes (CES), which we will
+// together refer to as encodings, for which Encoding implementations provide
+// converters to and from UTF-8. This package is typically only of concern to
+// implementers of Indexes and Encodings.
+//
+// One part of the identifier is the MIB code, which is defined by IANA and
+// uniquely identifies a CCS or CES. Each code is associated with data that
+// references authorities, official documentation as well as aliases and MIME
+// names.
+//
+// Not all CESs are covered by the IANA registry. The "other" string that is
+// returned by ID can be used to identify other character sets or versions of
+// existing ones.
+//
+// It is recommended that each package that provides a set of Encodings provide
+// the All and Common variables to reference all supported encodings and
+// commonly used subset. This allows Index implementations to include all
+// available encodings without explicitly referencing or knowing about them.
+package identifier
+
+// Note: this package is internal, but could be made public if there is a need
+// for writing third-party Indexes and Encodings.
+
+// References:
+// - http://source.icu-project.org/repos/icu/icu/trunk/source/data/mappings/convrtrs.txt
+// - http://www.iana.org/assignments/character-sets/character-sets.xhtml
+// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
+// - http://www.ietf.org/rfc/rfc2978.txt
+// - https://www.unicode.org/reports/tr22/
+// - http://www.w3.org/TR/encoding/
+// - https://encoding.spec.whatwg.org/
+// - https://encoding.spec.whatwg.org/encodings.json
+// - https://tools.ietf.org/html/rfc6657#section-5
+
+// Interface can be implemented by Encodings to define the CCS or CES for which
+// it implements conversions.
+type Interface interface {
+ // ID returns an encoding identifier. Exactly one of the mib and other
+ // values should be non-zero.
+ //
+ // In the usual case it is only necessary to indicate the MIB code. The
+ // other string can be used to specify encodings for which there is no MIB,
+ // such as "x-mac-dingbat".
+ //
+ // The other string may only contain the characters a-z, A-Z, 0-9, - and _.
+ ID() (mib MIB, other string)
+
+ // NOTE: the restrictions on the encoding are to allow extending the syntax
+ // with additional information such as versions, vendors and other variants.
+}
+
+// A MIB identifies an encoding. It is derived from the IANA MIB codes and adds
+// some identifiers for some encodings that are not covered by the IANA
+// standard.
+//
+// See http://www.iana.org/assignments/ianacharset-mib.
+type MIB uint16
+
+// These additional MIB types are not defined in IANA. They are added because
+// they are common and defined within the text repo.
+const (
+ // Unofficial marks the start of encodings not registered by IANA.
+ Unofficial MIB = 10000 + iota
+
+ // Replacement is the WhatWG replacement encoding.
+ Replacement
+
+ // XUserDefined is the code for x-user-defined.
+ XUserDefined
+
+ // MacintoshCyrillic is the code for x-mac-cyrillic.
+ MacintoshCyrillic
+)
diff --git a/vendor/golang.org/x/text/encoding/internal/identifier/mib.go b/vendor/golang.org/x/text/encoding/internal/identifier/mib.go
new file mode 100644
index 0000000..fc7df1b
--- /dev/null
+++ b/vendor/golang.org/x/text/encoding/internal/identifier/mib.go
@@ -0,0 +1,1619 @@
+// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
+
+package identifier
+
+const (
+ // ASCII is the MIB identifier with IANA name US-ASCII (MIME: US-ASCII).
+ //
+ // ANSI X3.4-1986
+ // Reference: RFC2046
+ ASCII MIB = 3
+
+ // ISOLatin1 is the MIB identifier with IANA name ISO_8859-1:1987 (MIME: ISO-8859-1).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin1 MIB = 4
+
+ // ISOLatin2 is the MIB identifier with IANA name ISO_8859-2:1987 (MIME: ISO-8859-2).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin2 MIB = 5
+
+ // ISOLatin3 is the MIB identifier with IANA name ISO_8859-3:1988 (MIME: ISO-8859-3).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin3 MIB = 6
+
+ // ISOLatin4 is the MIB identifier with IANA name ISO_8859-4:1988 (MIME: ISO-8859-4).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin4 MIB = 7
+
+ // ISOLatinCyrillic is the MIB identifier with IANA name ISO_8859-5:1988 (MIME: ISO-8859-5).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatinCyrillic MIB = 8
+
+ // ISOLatinArabic is the MIB identifier with IANA name ISO_8859-6:1987 (MIME: ISO-8859-6).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatinArabic MIB = 9
+
+ // ISOLatinGreek is the MIB identifier with IANA name ISO_8859-7:1987 (MIME: ISO-8859-7).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1947
+ // Reference: RFC1345
+ ISOLatinGreek MIB = 10
+
+ // ISOLatinHebrew is the MIB identifier with IANA name ISO_8859-8:1988 (MIME: ISO-8859-8).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatinHebrew MIB = 11
+
+ // ISOLatin5 is the MIB identifier with IANA name ISO_8859-9:1989 (MIME: ISO-8859-9).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin5 MIB = 12
+
+ // ISOLatin6 is the MIB identifier with IANA name ISO-8859-10 (MIME: ISO-8859-10).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOLatin6 MIB = 13
+
+ // ISOTextComm is the MIB identifier with IANA name ISO_6937-2-add.
+ //
+ // ISO-IR: International Register of Escape Sequences and ISO 6937-2:1983
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISOTextComm MIB = 14
+
+ // HalfWidthKatakana is the MIB identifier with IANA name JIS_X0201.
+ //
+ // JIS X 0201-1976. One byte only, this is equivalent to
+ // JIS/Roman (similar to ASCII) plus eight-bit half-width
+ // Katakana
+ // Reference: RFC1345
+ HalfWidthKatakana MIB = 15
+
+ // JISEncoding is the MIB identifier with IANA name JIS_Encoding.
+ //
+ // JIS X 0202-1991. Uses ISO 2022 escape sequences to
+ // shift code sets as documented in JIS X 0202-1991.
+ JISEncoding MIB = 16
+
+ // ShiftJIS is the MIB identifier with IANA name Shift_JIS (MIME: Shift_JIS).
+ //
+ // This charset is an extension of csHalfWidthKatakana by
+ // adding graphic characters in JIS X 0208. The CCS's are
+ // JIS X0201:1997 and JIS X0208:1997. The
+ // complete definition is shown in Appendix 1 of JIS
+ // X0208:1997.
+ // This charset can be used for the top-level media type "text".
+ ShiftJIS MIB = 17
+
+ // EUCPkdFmtJapanese is the MIB identifier with IANA name Extended_UNIX_Code_Packed_Format_for_Japanese (MIME: EUC-JP).
+ //
+ // Standardized by OSF, UNIX International, and UNIX Systems
+ // Laboratories Pacific. Uses ISO 2022 rules to select
+ // code set 0: US-ASCII (a single 7-bit byte set)
+ // code set 1: JIS X0208-1990 (a double 8-bit byte set)
+ // restricted to A0-FF in both bytes
+ // code set 2: Half Width Katakana (a single 7-bit byte set)
+ // requiring SS2 as the character prefix
+ // code set 3: JIS X0212-1990 (a double 7-bit byte set)
+ // restricted to A0-FF in both bytes
+ // requiring SS3 as the character prefix
+ EUCPkdFmtJapanese MIB = 18
+
+ // EUCFixWidJapanese is the MIB identifier with IANA name Extended_UNIX_Code_Fixed_Width_for_Japanese.
+ //
+ // Used in Japan. Each character is 2 octets.
+ // code set 0: US-ASCII (a single 7-bit byte set)
+ // 1st byte = 00
+ // 2nd byte = 20-7E
+ // code set 1: JIS X0208-1990 (a double 7-bit byte set)
+ // restricted to A0-FF in both bytes
+ // code set 2: Half Width Katakana (a single 7-bit byte set)
+ // 1st byte = 00
+ // 2nd byte = A0-FF
+ // code set 3: JIS X0212-1990 (a double 7-bit byte set)
+ // restricted to A0-FF in
+ // the first byte
+ // and 21-7E in the second byte
+ EUCFixWidJapanese MIB = 19
+
+ // ISO4UnitedKingdom is the MIB identifier with IANA name BS_4730.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO4UnitedKingdom MIB = 20
+
+ // ISO11SwedishForNames is the MIB identifier with IANA name SEN_850200_C.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO11SwedishForNames MIB = 21
+
+ // ISO15Italian is the MIB identifier with IANA name IT.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO15Italian MIB = 22
+
+ // ISO17Spanish is the MIB identifier with IANA name ES.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO17Spanish MIB = 23
+
+ // ISO21German is the MIB identifier with IANA name DIN_66003.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO21German MIB = 24
+
+ // ISO60Norwegian1 is the MIB identifier with IANA name NS_4551-1.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO60Norwegian1 MIB = 25
+
+ // ISO69French is the MIB identifier with IANA name NF_Z_62-010.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO69French MIB = 26
+
+ // ISO10646UTF1 is the MIB identifier with IANA name ISO-10646-UTF-1.
+ //
+ // Universal Transfer Format (1), this is the multibyte
+ // encoding, that subsets ASCII-7. It does not have byte
+ // ordering issues.
+ ISO10646UTF1 MIB = 27
+
+ // ISO646basic1983 is the MIB identifier with IANA name ISO_646.basic:1983.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO646basic1983 MIB = 28
+
+ // INVARIANT is the MIB identifier with IANA name INVARIANT.
+ //
+ // Reference: RFC1345
+ INVARIANT MIB = 29
+
+ // ISO2IntlRefVersion is the MIB identifier with IANA name ISO_646.irv:1983.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO2IntlRefVersion MIB = 30
+
+ // NATSSEFI is the MIB identifier with IANA name NATS-SEFI.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ NATSSEFI MIB = 31
+
+ // NATSSEFIADD is the MIB identifier with IANA name NATS-SEFI-ADD.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ NATSSEFIADD MIB = 32
+
+ // NATSDANO is the MIB identifier with IANA name NATS-DANO.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ NATSDANO MIB = 33
+
+ // NATSDANOADD is the MIB identifier with IANA name NATS-DANO-ADD.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ NATSDANOADD MIB = 34
+
+ // ISO10Swedish is the MIB identifier with IANA name SEN_850200_B.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO10Swedish MIB = 35
+
+ // KSC56011987 is the MIB identifier with IANA name KS_C_5601-1987.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ KSC56011987 MIB = 36
+
+ // ISO2022KR is the MIB identifier with IANA name ISO-2022-KR (MIME: ISO-2022-KR).
+ //
+ // rfc1557 (see also KS_C_5601-1987)
+ // Reference: RFC1557
+ ISO2022KR MIB = 37
+
+ // EUCKR is the MIB identifier with IANA name EUC-KR (MIME: EUC-KR).
+ //
+ // rfc1557 (see also KS_C_5861-1992)
+ // Reference: RFC1557
+ EUCKR MIB = 38
+
+ // ISO2022JP is the MIB identifier with IANA name ISO-2022-JP (MIME: ISO-2022-JP).
+ //
+ // rfc1468 (see also rfc2237 )
+ // Reference: RFC1468
+ ISO2022JP MIB = 39
+
+ // ISO2022JP2 is the MIB identifier with IANA name ISO-2022-JP-2 (MIME: ISO-2022-JP-2).
+ //
+ // rfc1554
+ // Reference: RFC1554
+ ISO2022JP2 MIB = 40
+
+ // ISO13JISC6220jp is the MIB identifier with IANA name JIS_C6220-1969-jp.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO13JISC6220jp MIB = 41
+
+ // ISO14JISC6220ro is the MIB identifier with IANA name JIS_C6220-1969-ro.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO14JISC6220ro MIB = 42
+
+ // ISO16Portuguese is the MIB identifier with IANA name PT.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO16Portuguese MIB = 43
+
+ // ISO18Greek7Old is the MIB identifier with IANA name greek7-old.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO18Greek7Old MIB = 44
+
+ // ISO19LatinGreek is the MIB identifier with IANA name latin-greek.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO19LatinGreek MIB = 45
+
+ // ISO25French is the MIB identifier with IANA name NF_Z_62-010_(1973).
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO25French MIB = 46
+
+ // ISO27LatinGreek1 is the MIB identifier with IANA name Latin-greek-1.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO27LatinGreek1 MIB = 47
+
+ // ISO5427Cyrillic is the MIB identifier with IANA name ISO_5427.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO5427Cyrillic MIB = 48
+
+ // ISO42JISC62261978 is the MIB identifier with IANA name JIS_C6226-1978.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO42JISC62261978 MIB = 49
+
+ // ISO47BSViewdata is the MIB identifier with IANA name BS_viewdata.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO47BSViewdata MIB = 50
+
+ // ISO49INIS is the MIB identifier with IANA name INIS.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO49INIS MIB = 51
+
+ // ISO50INIS8 is the MIB identifier with IANA name INIS-8.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO50INIS8 MIB = 52
+
+ // ISO51INISCyrillic is the MIB identifier with IANA name INIS-cyrillic.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO51INISCyrillic MIB = 53
+
+ // ISO54271981 is the MIB identifier with IANA name ISO_5427:1981.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO54271981 MIB = 54
+
+ // ISO5428Greek is the MIB identifier with IANA name ISO_5428:1980.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO5428Greek MIB = 55
+
+ // ISO57GB1988 is the MIB identifier with IANA name GB_1988-80.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO57GB1988 MIB = 56
+
+ // ISO58GB231280 is the MIB identifier with IANA name GB_2312-80.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO58GB231280 MIB = 57
+
+ // ISO61Norwegian2 is the MIB identifier with IANA name NS_4551-2.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO61Norwegian2 MIB = 58
+
+ // ISO70VideotexSupp1 is the MIB identifier with IANA name videotex-suppl.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO70VideotexSupp1 MIB = 59
+
+ // ISO84Portuguese2 is the MIB identifier with IANA name PT2.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO84Portuguese2 MIB = 60
+
+ // ISO85Spanish2 is the MIB identifier with IANA name ES2.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO85Spanish2 MIB = 61
+
+ // ISO86Hungarian is the MIB identifier with IANA name MSZ_7795.3.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO86Hungarian MIB = 62
+
+ // ISO87JISX0208 is the MIB identifier with IANA name JIS_C6226-1983.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO87JISX0208 MIB = 63
+
+ // ISO88Greek7 is the MIB identifier with IANA name greek7.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO88Greek7 MIB = 64
+
+ // ISO89ASMO449 is the MIB identifier with IANA name ASMO_449.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO89ASMO449 MIB = 65
+
+ // ISO90 is the MIB identifier with IANA name iso-ir-90.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO90 MIB = 66
+
+ // ISO91JISC62291984a is the MIB identifier with IANA name JIS_C6229-1984-a.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO91JISC62291984a MIB = 67
+
+ // ISO92JISC62991984b is the MIB identifier with IANA name JIS_C6229-1984-b.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO92JISC62991984b MIB = 68
+
+ // ISO93JIS62291984badd is the MIB identifier with IANA name JIS_C6229-1984-b-add.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO93JIS62291984badd MIB = 69
+
+ // ISO94JIS62291984hand is the MIB identifier with IANA name JIS_C6229-1984-hand.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO94JIS62291984hand MIB = 70
+
+ // ISO95JIS62291984handadd is the MIB identifier with IANA name JIS_C6229-1984-hand-add.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO95JIS62291984handadd MIB = 71
+
+ // ISO96JISC62291984kana is the MIB identifier with IANA name JIS_C6229-1984-kana.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO96JISC62291984kana MIB = 72
+
+ // ISO2033 is the MIB identifier with IANA name ISO_2033-1983.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO2033 MIB = 73
+
+ // ISO99NAPLPS is the MIB identifier with IANA name ANSI_X3.110-1983.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO99NAPLPS MIB = 74
+
+ // ISO102T617bit is the MIB identifier with IANA name T.61-7bit.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO102T617bit MIB = 75
+
+ // ISO103T618bit is the MIB identifier with IANA name T.61-8bit.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO103T618bit MIB = 76
+
+ // ISO111ECMACyrillic is the MIB identifier with IANA name ECMA-cyrillic.
+ //
+ // ISO registry
+ ISO111ECMACyrillic MIB = 77
+
+ // ISO121Canadian1 is the MIB identifier with IANA name CSA_Z243.4-1985-1.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO121Canadian1 MIB = 78
+
+ // ISO122Canadian2 is the MIB identifier with IANA name CSA_Z243.4-1985-2.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO122Canadian2 MIB = 79
+
+ // ISO123CSAZ24341985gr is the MIB identifier with IANA name CSA_Z243.4-1985-gr.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO123CSAZ24341985gr MIB = 80
+
+ // ISO88596E is the MIB identifier with IANA name ISO_8859-6-E (MIME: ISO-8859-6-E).
+ //
+ // rfc1556
+ // Reference: RFC1556
+ ISO88596E MIB = 81
+
+ // ISO88596I is the MIB identifier with IANA name ISO_8859-6-I (MIME: ISO-8859-6-I).
+ //
+ // rfc1556
+ // Reference: RFC1556
+ ISO88596I MIB = 82
+
+ // ISO128T101G2 is the MIB identifier with IANA name T.101-G2.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO128T101G2 MIB = 83
+
+ // ISO88598E is the MIB identifier with IANA name ISO_8859-8-E (MIME: ISO-8859-8-E).
+ //
+ // rfc1556
+ // Reference: RFC1556
+ ISO88598E MIB = 84
+
+ // ISO88598I is the MIB identifier with IANA name ISO_8859-8-I (MIME: ISO-8859-8-I).
+ //
+ // rfc1556
+ // Reference: RFC1556
+ ISO88598I MIB = 85
+
+ // ISO139CSN369103 is the MIB identifier with IANA name CSN_369103.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO139CSN369103 MIB = 86
+
+ // ISO141JUSIB1002 is the MIB identifier with IANA name JUS_I.B1.002.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO141JUSIB1002 MIB = 87
+
+ // ISO143IECP271 is the MIB identifier with IANA name IEC_P27-1.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO143IECP271 MIB = 88
+
+ // ISO146Serbian is the MIB identifier with IANA name JUS_I.B1.003-serb.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO146Serbian MIB = 89
+
+ // ISO147Macedonian is the MIB identifier with IANA name JUS_I.B1.003-mac.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO147Macedonian MIB = 90
+
+ // ISO150GreekCCITT is the MIB identifier with IANA name greek-ccitt.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO150GreekCCITT MIB = 91
+
+ // ISO151Cuba is the MIB identifier with IANA name NC_NC00-10:81.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO151Cuba MIB = 92
+
+ // ISO6937Add is the MIB identifier with IANA name ISO_6937-2-25.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO6937Add MIB = 93
+
+ // ISO153GOST1976874 is the MIB identifier with IANA name GOST_19768-74.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO153GOST1976874 MIB = 94
+
+ // ISO8859Supp is the MIB identifier with IANA name ISO_8859-supp.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO8859Supp MIB = 95
+
+ // ISO10367Box is the MIB identifier with IANA name ISO_10367-box.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO10367Box MIB = 96
+
+ // ISO158Lap is the MIB identifier with IANA name latin-lap.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO158Lap MIB = 97
+
+ // ISO159JISX02121990 is the MIB identifier with IANA name JIS_X0212-1990.
+ //
+ // ISO-IR: International Register of Escape Sequences
+ // Note: The current registration authority is IPSJ/ITSCJ, Japan.
+ // Reference: RFC1345
+ ISO159JISX02121990 MIB = 98
+
+ // ISO646Danish is the MIB identifier with IANA name DS_2089.
+ //
+ // Danish Standard, DS 2089, February 1974
+ // Reference: RFC1345
+ ISO646Danish MIB = 99
+
+ // USDK is the MIB identifier with IANA name us-dk.
+ //
+ // Reference: RFC1345
+ USDK MIB = 100
+
+ // DKUS is the MIB identifier with IANA name dk-us.
+ //
+ // Reference: RFC1345
+ DKUS MIB = 101
+
+ // KSC5636 is the MIB identifier with IANA name KSC5636.
+ //
+ // Reference: RFC1345
+ KSC5636 MIB = 102
+
+ // Unicode11UTF7 is the MIB identifier with IANA name UNICODE-1-1-UTF-7.
+ //
+ // rfc1642
+ // Reference: RFC1642
+ Unicode11UTF7 MIB = 103
+
+ // ISO2022CN is the MIB identifier with IANA name ISO-2022-CN.
+ //
+ // rfc1922
+ // Reference: RFC1922
+ ISO2022CN MIB = 104
+
+ // ISO2022CNEXT is the MIB identifier with IANA name ISO-2022-CN-EXT.
+ //
+ // rfc1922
+ // Reference: RFC1922
+ ISO2022CNEXT MIB = 105
+
+ // UTF8 is the MIB identifier with IANA name UTF-8.
+ //
+ // rfc3629
+ // Reference: RFC3629
+ UTF8 MIB = 106
+
+ // ISO885913 is the MIB identifier with IANA name ISO-8859-13.
+ //
+ // ISO See https://www.iana.org/assignments/charset-reg/ISO-8859-13 https://www.iana.org/assignments/charset-reg/ISO-8859-13
+ ISO885913 MIB = 109
+
+ // ISO885914 is the MIB identifier with IANA name ISO-8859-14.
+ //
+ // ISO See https://www.iana.org/assignments/charset-reg/ISO-8859-14
+ ISO885914 MIB = 110
+
+ // ISO885915 is the MIB identifier with IANA name ISO-8859-15.
+ //
+ // ISO
+ // Please see: https://www.iana.org/assignments/charset-reg/ISO-8859-15
+ ISO885915 MIB = 111
+
+ // ISO885916 is the MIB identifier with IANA name ISO-8859-16.
+ //
+ // ISO
+ ISO885916 MIB = 112
+
+ // GBK is the MIB identifier with IANA name GBK.
+ //
+ // Chinese IT Standardization Technical Committee
+ // Please see: https://www.iana.org/assignments/charset-reg/GBK
+ GBK MIB = 113
+
+ // GB18030 is the MIB identifier with IANA name GB18030.
+ //
+ // Chinese IT Standardization Technical Committee
+ // Please see: https://www.iana.org/assignments/charset-reg/GB18030
+ GB18030 MIB = 114
+
+ // OSDEBCDICDF0415 is the MIB identifier with IANA name OSD_EBCDIC_DF04_15.
+ //
+ // Fujitsu-Siemens standard mainframe EBCDIC encoding
+ // Please see: https://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-15
+ OSDEBCDICDF0415 MIB = 115
+
+ // OSDEBCDICDF03IRV is the MIB identifier with IANA name OSD_EBCDIC_DF03_IRV.
+ //
+ // Fujitsu-Siemens standard mainframe EBCDIC encoding
+ // Please see: https://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF03-IRV
+ OSDEBCDICDF03IRV MIB = 116
+
+ // OSDEBCDICDF041 is the MIB identifier with IANA name OSD_EBCDIC_DF04_1.
+ //
+ // Fujitsu-Siemens standard mainframe EBCDIC encoding
+ // Please see: https://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-1
+ OSDEBCDICDF041 MIB = 117
+
+ // ISO115481 is the MIB identifier with IANA name ISO-11548-1.
+ //
+ // See https://www.iana.org/assignments/charset-reg/ISO-11548-1
+ ISO115481 MIB = 118
+
+ // KZ1048 is the MIB identifier with IANA name KZ-1048.
+ //
+ // See https://www.iana.org/assignments/charset-reg/KZ-1048
+ KZ1048 MIB = 119
+
+ // Unicode is the MIB identifier with IANA name ISO-10646-UCS-2.
+ //
+ // the 2-octet Basic Multilingual Plane, aka Unicode
+ // this needs to specify network byte order: the standard
+ // does not specify (it is a 16-bit integer space)
+ Unicode MIB = 1000
+
+ // UCS4 is the MIB identifier with IANA name ISO-10646-UCS-4.
+ //
+ // the full code space. (same comment about byte order,
+ // these are 31-bit numbers.
+ UCS4 MIB = 1001
+
+ // UnicodeASCII is the MIB identifier with IANA name ISO-10646-UCS-Basic.
+ //
+ // ASCII subset of Unicode. Basic Latin = collection 1
+ // See ISO 10646, Appendix A
+ UnicodeASCII MIB = 1002
+
+ // UnicodeLatin1 is the MIB identifier with IANA name ISO-10646-Unicode-Latin1.
+ //
+ // ISO Latin-1 subset of Unicode. Basic Latin and Latin-1
+ // Supplement = collections 1 and 2. See ISO 10646,
+ // Appendix A. See rfc1815 .
+ UnicodeLatin1 MIB = 1003
+
+ // UnicodeJapanese is the MIB identifier with IANA name ISO-10646-J-1.
+ //
+ // ISO 10646 Japanese, see rfc1815 .
+ UnicodeJapanese MIB = 1004
+
+ // UnicodeIBM1261 is the MIB identifier with IANA name ISO-Unicode-IBM-1261.
+ //
+ // IBM Latin-2, -3, -5, Extended Presentation Set, GCSGID: 1261
+ UnicodeIBM1261 MIB = 1005
+
+ // UnicodeIBM1268 is the MIB identifier with IANA name ISO-Unicode-IBM-1268.
+ //
+ // IBM Latin-4 Extended Presentation Set, GCSGID: 1268
+ UnicodeIBM1268 MIB = 1006
+
+ // UnicodeIBM1276 is the MIB identifier with IANA name ISO-Unicode-IBM-1276.
+ //
+ // IBM Cyrillic Greek Extended Presentation Set, GCSGID: 1276
+ UnicodeIBM1276 MIB = 1007
+
+ // UnicodeIBM1264 is the MIB identifier with IANA name ISO-Unicode-IBM-1264.
+ //
+ // IBM Arabic Presentation Set, GCSGID: 1264
+ UnicodeIBM1264 MIB = 1008
+
+ // UnicodeIBM1265 is the MIB identifier with IANA name ISO-Unicode-IBM-1265.
+ //
+ // IBM Hebrew Presentation Set, GCSGID: 1265
+ UnicodeIBM1265 MIB = 1009
+
+ // Unicode11 is the MIB identifier with IANA name UNICODE-1-1.
+ //
+ // rfc1641
+ // Reference: RFC1641
+ Unicode11 MIB = 1010
+
+ // SCSU is the MIB identifier with IANA name SCSU.
+ //
+ // SCSU See https://www.iana.org/assignments/charset-reg/SCSU
+ SCSU MIB = 1011
+
+ // UTF7 is the MIB identifier with IANA name UTF-7.
+ //
+ // rfc2152
+ // Reference: RFC2152
+ UTF7 MIB = 1012
+
+ // UTF16BE is the MIB identifier with IANA name UTF-16BE.
+ //
+ // rfc2781
+ // Reference: RFC2781
+ UTF16BE MIB = 1013
+
+ // UTF16LE is the MIB identifier with IANA name UTF-16LE.
+ //
+ // rfc2781
+ // Reference: RFC2781
+ UTF16LE MIB = 1014
+
+ // UTF16 is the MIB identifier with IANA name UTF-16.
+ //
+ // rfc2781
+ // Reference: RFC2781
+ UTF16 MIB = 1015
+
+ // CESU8 is the MIB identifier with IANA name CESU-8.
+ //
+ // https://www.unicode.org/reports/tr26
+ CESU8 MIB = 1016
+
+ // UTF32 is the MIB identifier with IANA name UTF-32.
+ //
+ // https://www.unicode.org/reports/tr19/
+ UTF32 MIB = 1017
+
+ // UTF32BE is the MIB identifier with IANA name UTF-32BE.
+ //
+ // https://www.unicode.org/reports/tr19/
+ UTF32BE MIB = 1018
+
+ // UTF32LE is the MIB identifier with IANA name UTF-32LE.
+ //
+ // https://www.unicode.org/reports/tr19/
+ UTF32LE MIB = 1019
+
+ // BOCU1 is the MIB identifier with IANA name BOCU-1.
+ //
+ // https://www.unicode.org/notes/tn6/
+ BOCU1 MIB = 1020
+
+ // Windows30Latin1 is the MIB identifier with IANA name ISO-8859-1-Windows-3.0-Latin-1.
+ //
+ // Extended ISO 8859-1 Latin-1 for Windows 3.0.
+ // PCL Symbol Set id: 9U
+ Windows30Latin1 MIB = 2000
+
+ // Windows31Latin1 is the MIB identifier with IANA name ISO-8859-1-Windows-3.1-Latin-1.
+ //
+ // Extended ISO 8859-1 Latin-1 for Windows 3.1.
+ // PCL Symbol Set id: 19U
+ Windows31Latin1 MIB = 2001
+
+ // Windows31Latin2 is the MIB identifier with IANA name ISO-8859-2-Windows-Latin-2.
+ //
+ // Extended ISO 8859-2. Latin-2 for Windows 3.1.
+ // PCL Symbol Set id: 9E
+ Windows31Latin2 MIB = 2002
+
+ // Windows31Latin5 is the MIB identifier with IANA name ISO-8859-9-Windows-Latin-5.
+ //
+ // Extended ISO 8859-9. Latin-5 for Windows 3.1
+ // PCL Symbol Set id: 5T
+ Windows31Latin5 MIB = 2003
+
+ // HPRoman8 is the MIB identifier with IANA name hp-roman8.
+ //
+ // LaserJet IIP Printer User's Manual,
+ // HP part no 33471-90901, Hewlet-Packard, June 1989.
+ // Reference: RFC1345
+ HPRoman8 MIB = 2004
+
+ // AdobeStandardEncoding is the MIB identifier with IANA name Adobe-Standard-Encoding.
+ //
+ // PostScript Language Reference Manual
+ // PCL Symbol Set id: 10J
+ AdobeStandardEncoding MIB = 2005
+
+ // VenturaUS is the MIB identifier with IANA name Ventura-US.
+ //
+ // Ventura US. ASCII plus characters typically used in
+ // publishing, like pilcrow, copyright, registered, trade mark,
+ // section, dagger, and double dagger in the range A0 (hex)
+ // to FF (hex).
+ // PCL Symbol Set id: 14J
+ VenturaUS MIB = 2006
+
+ // VenturaInternational is the MIB identifier with IANA name Ventura-International.
+ //
+ // Ventura International. ASCII plus coded characters similar
+ // to Roman8.
+ // PCL Symbol Set id: 13J
+ VenturaInternational MIB = 2007
+
+ // DECMCS is the MIB identifier with IANA name DEC-MCS.
+ //
+ // VAX/VMS User's Manual,
+ // Order Number: AI-Y517A-TE, April 1986.
+ // Reference: RFC1345
+ DECMCS MIB = 2008
+
+ // PC850Multilingual is the MIB identifier with IANA name IBM850.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ PC850Multilingual MIB = 2009
+
+ // PC8DanishNorwegian is the MIB identifier with IANA name PC8-Danish-Norwegian.
+ //
+ // PC Danish Norwegian
+ // 8-bit PC set for Danish Norwegian
+ // PCL Symbol Set id: 11U
+ PC8DanishNorwegian MIB = 2012
+
+ // PC862LatinHebrew is the MIB identifier with IANA name IBM862.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ PC862LatinHebrew MIB = 2013
+
+ // PC8Turkish is the MIB identifier with IANA name PC8-Turkish.
+ //
+ // PC Latin Turkish. PCL Symbol Set id: 9T
+ PC8Turkish MIB = 2014
+
+ // IBMSymbols is the MIB identifier with IANA name IBM-Symbols.
+ //
+ // Presentation Set, CPGID: 259
+ IBMSymbols MIB = 2015
+
+ // IBMThai is the MIB identifier with IANA name IBM-Thai.
+ //
+ // Presentation Set, CPGID: 838
+ IBMThai MIB = 2016
+
+ // HPLegal is the MIB identifier with IANA name HP-Legal.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 1U
+ HPLegal MIB = 2017
+
+ // HPPiFont is the MIB identifier with IANA name HP-Pi-font.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 15U
+ HPPiFont MIB = 2018
+
+ // HPMath8 is the MIB identifier with IANA name HP-Math8.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 8M
+ HPMath8 MIB = 2019
+
+ // HPPSMath is the MIB identifier with IANA name Adobe-Symbol-Encoding.
+ //
+ // PostScript Language Reference Manual
+ // PCL Symbol Set id: 5M
+ HPPSMath MIB = 2020
+
+ // HPDesktop is the MIB identifier with IANA name HP-DeskTop.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 7J
+ HPDesktop MIB = 2021
+
+ // VenturaMath is the MIB identifier with IANA name Ventura-Math.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 6M
+ VenturaMath MIB = 2022
+
+ // MicrosoftPublishing is the MIB identifier with IANA name Microsoft-Publishing.
+ //
+ // PCL 5 Comparison Guide, Hewlett-Packard,
+ // HP part number 5961-0510, October 1992
+ // PCL Symbol Set id: 6J
+ MicrosoftPublishing MIB = 2023
+
+ // Windows31J is the MIB identifier with IANA name Windows-31J.
+ //
+ // Windows Japanese. A further extension of Shift_JIS
+ // to include NEC special characters (Row 13), NEC
+ // selection of IBM extensions (Rows 89 to 92), and IBM
+ // extensions (Rows 115 to 119). The CCS's are
+ // JIS X0201:1997, JIS X0208:1997, and these extensions.
+ // This charset can be used for the top-level media type "text",
+ // but it is of limited or specialized use (see rfc2278 ).
+ // PCL Symbol Set id: 19K
+ Windows31J MIB = 2024
+
+ // GB2312 is the MIB identifier with IANA name GB2312 (MIME: GB2312).
+ //
+ // Chinese for People's Republic of China (PRC) mixed one byte,
+ // two byte set:
+ // 20-7E = one byte ASCII
+ // A1-FE = two byte PRC Kanji
+ // See GB 2312-80
+ // PCL Symbol Set Id: 18C
+ GB2312 MIB = 2025
+
+ // Big5 is the MIB identifier with IANA name Big5 (MIME: Big5).
+ //
+ // Chinese for Taiwan Multi-byte set.
+ // PCL Symbol Set Id: 18T
+ Big5 MIB = 2026
+
+ // Macintosh is the MIB identifier with IANA name macintosh.
+ //
+ // The Unicode Standard ver1.0, ISBN 0-201-56788-1, Oct 1991
+ // Reference: RFC1345
+ Macintosh MIB = 2027
+
+ // IBM037 is the MIB identifier with IANA name IBM037.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM037 MIB = 2028
+
+ // IBM038 is the MIB identifier with IANA name IBM038.
+ //
+ // IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+ // Reference: RFC1345
+ IBM038 MIB = 2029
+
+ // IBM273 is the MIB identifier with IANA name IBM273.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM273 MIB = 2030
+
+ // IBM274 is the MIB identifier with IANA name IBM274.
+ //
+ // IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+ // Reference: RFC1345
+ IBM274 MIB = 2031
+
+ // IBM275 is the MIB identifier with IANA name IBM275.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM275 MIB = 2032
+
+ // IBM277 is the MIB identifier with IANA name IBM277.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM277 MIB = 2033
+
+ // IBM278 is the MIB identifier with IANA name IBM278.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM278 MIB = 2034
+
+ // IBM280 is the MIB identifier with IANA name IBM280.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM280 MIB = 2035
+
+ // IBM281 is the MIB identifier with IANA name IBM281.
+ //
+ // IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+ // Reference: RFC1345
+ IBM281 MIB = 2036
+
+ // IBM284 is the MIB identifier with IANA name IBM284.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM284 MIB = 2037
+
+ // IBM285 is the MIB identifier with IANA name IBM285.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM285 MIB = 2038
+
+ // IBM290 is the MIB identifier with IANA name IBM290.
+ //
+ // IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+ // Reference: RFC1345
+ IBM290 MIB = 2039
+
+ // IBM297 is the MIB identifier with IANA name IBM297.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM297 MIB = 2040
+
+ // IBM420 is the MIB identifier with IANA name IBM420.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990,
+ // IBM NLS RM p 11-11
+ // Reference: RFC1345
+ IBM420 MIB = 2041
+
+ // IBM423 is the MIB identifier with IANA name IBM423.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM423 MIB = 2042
+
+ // IBM424 is the MIB identifier with IANA name IBM424.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM424 MIB = 2043
+
+ // PC8CodePage437 is the MIB identifier with IANA name IBM437.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ PC8CodePage437 MIB = 2011
+
+ // IBM500 is the MIB identifier with IANA name IBM500.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM500 MIB = 2044
+
+ // IBM851 is the MIB identifier with IANA name IBM851.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM851 MIB = 2045
+
+ // PCp852 is the MIB identifier with IANA name IBM852.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ PCp852 MIB = 2010
+
+ // IBM855 is the MIB identifier with IANA name IBM855.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM855 MIB = 2046
+
+ // IBM857 is the MIB identifier with IANA name IBM857.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM857 MIB = 2047
+
+ // IBM860 is the MIB identifier with IANA name IBM860.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM860 MIB = 2048
+
+ // IBM861 is the MIB identifier with IANA name IBM861.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM861 MIB = 2049
+
+ // IBM863 is the MIB identifier with IANA name IBM863.
+ //
+ // IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+ // Reference: RFC1345
+ IBM863 MIB = 2050
+
+ // IBM864 is the MIB identifier with IANA name IBM864.
+ //
+ // IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+ // Reference: RFC1345
+ IBM864 MIB = 2051
+
+ // IBM865 is the MIB identifier with IANA name IBM865.
+ //
+ // IBM DOS 3.3 Ref (Abridged), 94X9575 (Feb 1987)
+ // Reference: RFC1345
+ IBM865 MIB = 2052
+
+ // IBM868 is the MIB identifier with IANA name IBM868.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM868 MIB = 2053
+
+ // IBM869 is the MIB identifier with IANA name IBM869.
+ //
+ // IBM Keyboard layouts and code pages, PN 07G4586 June 1991
+ // Reference: RFC1345
+ IBM869 MIB = 2054
+
+ // IBM870 is the MIB identifier with IANA name IBM870.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM870 MIB = 2055
+
+ // IBM871 is the MIB identifier with IANA name IBM871.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM871 MIB = 2056
+
+ // IBM880 is the MIB identifier with IANA name IBM880.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM880 MIB = 2057
+
+ // IBM891 is the MIB identifier with IANA name IBM891.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM891 MIB = 2058
+
+ // IBM903 is the MIB identifier with IANA name IBM903.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM903 MIB = 2059
+
+ // IBBM904 is the MIB identifier with IANA name IBM904.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBBM904 MIB = 2060
+
+ // IBM905 is the MIB identifier with IANA name IBM905.
+ //
+ // IBM 3174 Character Set Ref, GA27-3831-02, March 1990
+ // Reference: RFC1345
+ IBM905 MIB = 2061
+
+ // IBM918 is the MIB identifier with IANA name IBM918.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM918 MIB = 2062
+
+ // IBM1026 is the MIB identifier with IANA name IBM1026.
+ //
+ // IBM NLS RM Vol2 SE09-8002-01, March 1990
+ // Reference: RFC1345
+ IBM1026 MIB = 2063
+
+ // IBMEBCDICATDE is the MIB identifier with IANA name EBCDIC-AT-DE.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ IBMEBCDICATDE MIB = 2064
+
+ // EBCDICATDEA is the MIB identifier with IANA name EBCDIC-AT-DE-A.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICATDEA MIB = 2065
+
+ // EBCDICCAFR is the MIB identifier with IANA name EBCDIC-CA-FR.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICCAFR MIB = 2066
+
+ // EBCDICDKNO is the MIB identifier with IANA name EBCDIC-DK-NO.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICDKNO MIB = 2067
+
+ // EBCDICDKNOA is the MIB identifier with IANA name EBCDIC-DK-NO-A.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICDKNOA MIB = 2068
+
+ // EBCDICFISE is the MIB identifier with IANA name EBCDIC-FI-SE.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICFISE MIB = 2069
+
+ // EBCDICFISEA is the MIB identifier with IANA name EBCDIC-FI-SE-A.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICFISEA MIB = 2070
+
+ // EBCDICFR is the MIB identifier with IANA name EBCDIC-FR.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICFR MIB = 2071
+
+ // EBCDICIT is the MIB identifier with IANA name EBCDIC-IT.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICIT MIB = 2072
+
+ // EBCDICPT is the MIB identifier with IANA name EBCDIC-PT.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICPT MIB = 2073
+
+ // EBCDICES is the MIB identifier with IANA name EBCDIC-ES.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICES MIB = 2074
+
+ // EBCDICESA is the MIB identifier with IANA name EBCDIC-ES-A.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICESA MIB = 2075
+
+ // EBCDICESS is the MIB identifier with IANA name EBCDIC-ES-S.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICESS MIB = 2076
+
+ // EBCDICUK is the MIB identifier with IANA name EBCDIC-UK.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICUK MIB = 2077
+
+ // EBCDICUS is the MIB identifier with IANA name EBCDIC-US.
+ //
+ // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987
+ // Reference: RFC1345
+ EBCDICUS MIB = 2078
+
+ // Unknown8BiT is the MIB identifier with IANA name UNKNOWN-8BIT.
+ //
+ // Reference: RFC1428
+ Unknown8BiT MIB = 2079
+
+ // Mnemonic is the MIB identifier with IANA name MNEMONIC.
+ //
+ // rfc1345 , also known as "mnemonic+ascii+38"
+ // Reference: RFC1345
+ Mnemonic MIB = 2080
+
+ // Mnem is the MIB identifier with IANA name MNEM.
+ //
+ // rfc1345 , also known as "mnemonic+ascii+8200"
+ // Reference: RFC1345
+ Mnem MIB = 2081
+
+ // VISCII is the MIB identifier with IANA name VISCII.
+ //
+ // rfc1456
+ // Reference: RFC1456
+ VISCII MIB = 2082
+
+ // VIQR is the MIB identifier with IANA name VIQR.
+ //
+ // rfc1456
+ // Reference: RFC1456
+ VIQR MIB = 2083
+
+ // KOI8R is the MIB identifier with IANA name KOI8-R (MIME: KOI8-R).
+ //
+ // rfc1489 , based on GOST-19768-74, ISO-6937/8,
+ // INIS-Cyrillic, ISO-5427.
+ // Reference: RFC1489
+ KOI8R MIB = 2084
+
+ // HZGB2312 is the MIB identifier with IANA name HZ-GB-2312.
+ //
+ // rfc1842 , rfc1843 rfc1843 rfc1842
+ HZGB2312 MIB = 2085
+
+ // IBM866 is the MIB identifier with IANA name IBM866.
+ //
+ // IBM NLDG Volume 2 (SE09-8002-03) August 1994
+ IBM866 MIB = 2086
+
+ // PC775Baltic is the MIB identifier with IANA name IBM775.
+ //
+ // HP PCL 5 Comparison Guide (P/N 5021-0329) pp B-13, 1996
+ PC775Baltic MIB = 2087
+
+ // KOI8U is the MIB identifier with IANA name KOI8-U.
+ //
+ // rfc2319
+ // Reference: RFC2319
+ KOI8U MIB = 2088
+
+ // IBM00858 is the MIB identifier with IANA name IBM00858.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM00858
+ IBM00858 MIB = 2089
+
+ // IBM00924 is the MIB identifier with IANA name IBM00924.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM00924
+ IBM00924 MIB = 2090
+
+ // IBM01140 is the MIB identifier with IANA name IBM01140.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01140
+ IBM01140 MIB = 2091
+
+ // IBM01141 is the MIB identifier with IANA name IBM01141.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01141
+ IBM01141 MIB = 2092
+
+ // IBM01142 is the MIB identifier with IANA name IBM01142.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01142
+ IBM01142 MIB = 2093
+
+ // IBM01143 is the MIB identifier with IANA name IBM01143.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01143
+ IBM01143 MIB = 2094
+
+ // IBM01144 is the MIB identifier with IANA name IBM01144.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01144
+ IBM01144 MIB = 2095
+
+ // IBM01145 is the MIB identifier with IANA name IBM01145.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01145
+ IBM01145 MIB = 2096
+
+ // IBM01146 is the MIB identifier with IANA name IBM01146.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01146
+ IBM01146 MIB = 2097
+
+ // IBM01147 is the MIB identifier with IANA name IBM01147.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01147
+ IBM01147 MIB = 2098
+
+ // IBM01148 is the MIB identifier with IANA name IBM01148.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01148
+ IBM01148 MIB = 2099
+
+ // IBM01149 is the MIB identifier with IANA name IBM01149.
+ //
+ // IBM See https://www.iana.org/assignments/charset-reg/IBM01149
+ IBM01149 MIB = 2100
+
+ // Big5HKSCS is the MIB identifier with IANA name Big5-HKSCS.
+ //
+ // See https://www.iana.org/assignments/charset-reg/Big5-HKSCS
+ Big5HKSCS MIB = 2101
+
+ // IBM1047 is the MIB identifier with IANA name IBM1047.
+ //
+ // IBM1047 (EBCDIC Latin 1/Open Systems) https://www-1.ibm.com/servers/eserver/iseries/software/globalization/pdf/cp01047z.pdf
+ IBM1047 MIB = 2102
+
+ // PTCP154 is the MIB identifier with IANA name PTCP154.
+ //
+ // See https://www.iana.org/assignments/charset-reg/PTCP154
+ PTCP154 MIB = 2103
+
+ // Amiga1251 is the MIB identifier with IANA name Amiga-1251.
+ //
+ // See https://www.amiga.ultranet.ru/Amiga-1251.html
+ Amiga1251 MIB = 2104
+
+ // KOI7switched is the MIB identifier with IANA name KOI7-switched.
+ //
+ // See https://www.iana.org/assignments/charset-reg/KOI7-switched
+ KOI7switched MIB = 2105
+
+ // BRF is the MIB identifier with IANA name BRF.
+ //
+ // See https://www.iana.org/assignments/charset-reg/BRF
+ BRF MIB = 2106
+
+ // TSCII is the MIB identifier with IANA name TSCII.
+ //
+ // See https://www.iana.org/assignments/charset-reg/TSCII
+ TSCII MIB = 2107
+
+ // CP51932 is the MIB identifier with IANA name CP51932.
+ //
+ // See https://www.iana.org/assignments/charset-reg/CP51932
+ CP51932 MIB = 2108
+
+ // Windows874 is the MIB identifier with IANA name windows-874.
+ //
+ // See https://www.iana.org/assignments/charset-reg/windows-874
+ Windows874 MIB = 2109
+
+ // Windows1250 is the MIB identifier with IANA name windows-1250.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1250
+ Windows1250 MIB = 2250
+
+ // Windows1251 is the MIB identifier with IANA name windows-1251.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1251
+ Windows1251 MIB = 2251
+
+ // Windows1252 is the MIB identifier with IANA name windows-1252.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1252
+ Windows1252 MIB = 2252
+
+ // Windows1253 is the MIB identifier with IANA name windows-1253.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1253
+ Windows1253 MIB = 2253
+
+ // Windows1254 is the MIB identifier with IANA name windows-1254.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1254
+ Windows1254 MIB = 2254
+
+ // Windows1255 is the MIB identifier with IANA name windows-1255.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1255
+ Windows1255 MIB = 2255
+
+ // Windows1256 is the MIB identifier with IANA name windows-1256.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1256
+ Windows1256 MIB = 2256
+
+ // Windows1257 is the MIB identifier with IANA name windows-1257.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1257
+ Windows1257 MIB = 2257
+
+ // Windows1258 is the MIB identifier with IANA name windows-1258.
+ //
+ // Microsoft https://www.iana.org/assignments/charset-reg/windows-1258
+ Windows1258 MIB = 2258
+
+ // TIS620 is the MIB identifier with IANA name TIS-620.
+ //
+ // Thai Industrial Standards Institute (TISI)
+ TIS620 MIB = 2259
+
+ // CP50220 is the MIB identifier with IANA name CP50220.
+ //
+ // See https://www.iana.org/assignments/charset-reg/CP50220
+ CP50220 MIB = 2260
+)
diff --git a/vendor/golang.org/x/text/transform/transform.go b/vendor/golang.org/x/text/transform/transform.go
new file mode 100644
index 0000000..48ec64b
--- /dev/null
+++ b/vendor/golang.org/x/text/transform/transform.go
@@ -0,0 +1,709 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package transform provides reader and writer wrappers that transform the
+// bytes passing through as well as various transformations. Example
+// transformations provided by other packages include normalization and
+// conversion between character sets.
+package transform // import "golang.org/x/text/transform"
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "unicode/utf8"
+)
+
+var (
+ // ErrShortDst means that the destination buffer was too short to
+ // receive all of the transformed bytes.
+ ErrShortDst = errors.New("transform: short destination buffer")
+
+ // ErrShortSrc means that the source buffer has insufficient data to
+ // complete the transformation.
+ ErrShortSrc = errors.New("transform: short source buffer")
+
+ // ErrEndOfSpan means that the input and output (the transformed input)
+ // are not identical.
+ ErrEndOfSpan = errors.New("transform: input and output are not identical")
+
+ // errInconsistentByteCount means that Transform returned success (nil
+ // error) but also returned nSrc inconsistent with the src argument.
+ errInconsistentByteCount = errors.New("transform: inconsistent byte count returned")
+
+ // errShortInternal means that an internal buffer is not large enough
+ // to make progress and the Transform operation must be aborted.
+ errShortInternal = errors.New("transform: short internal buffer")
+)
+
+// Transformer transforms bytes.
+type Transformer interface {
+ // Transform writes to dst the transformed bytes read from src, and
+ // returns the number of dst bytes written and src bytes read. The
+ // atEOF argument tells whether src represents the last bytes of the
+ // input.
+ //
+ // Callers should always process the nDst bytes produced and account
+ // for the nSrc bytes consumed before considering the error err.
+ //
+ // A nil error means that all of the transformed bytes (whether freshly
+ // transformed from src or left over from previous Transform calls)
+ // were written to dst. A nil error can be returned regardless of
+ // whether atEOF is true. If err is nil then nSrc must equal len(src);
+ // the converse is not necessarily true.
+ //
+ // ErrShortDst means that dst was too short to receive all of the
+ // transformed bytes. ErrShortSrc means that src had insufficient data
+ // to complete the transformation. If both conditions apply, then
+ // either error may be returned. Other than the error conditions listed
+ // here, implementations are free to report other errors that arise.
+ Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error)
+
+ // Reset resets the state and allows a Transformer to be reused.
+ Reset()
+}
+
+// SpanningTransformer extends the Transformer interface with a Span method
+// that determines how much of the input already conforms to the Transformer.
+type SpanningTransformer interface {
+ Transformer
+
+ // Span returns a position in src such that transforming src[:n] results in
+ // identical output src[:n] for these bytes. It does not necessarily return
+ // the largest such n. The atEOF argument tells whether src represents the
+ // last bytes of the input.
+ //
+ // Callers should always account for the n bytes consumed before
+ // considering the error err.
+ //
+ // A nil error means that all input bytes are known to be identical to the
+ // output produced by the Transformer. A nil error can be returned
+ // regardless of whether atEOF is true. If err is nil, then n must
+ // equal len(src); the converse is not necessarily true.
+ //
+ // ErrEndOfSpan means that the Transformer output may differ from the
+ // input after n bytes. Note that n may be len(src), meaning that the output
+ // would contain additional bytes after otherwise identical output.
+ // ErrShortSrc means that src had insufficient data to determine whether the
+ // remaining bytes would change. Other than the error conditions listed
+ // here, implementations are free to report other errors that arise.
+ //
+ // Calling Span can modify the Transformer state as a side effect. In
+ // effect, it does the transformation just as calling Transform would, only
+ // without copying to a destination buffer and only up to a point it can
+ // determine the input and output bytes are the same. This is obviously more
+ // limited than calling Transform, but can be more efficient in terms of
+ // copying and allocating buffers. Calls to Span and Transform may be
+ // interleaved.
+ Span(src []byte, atEOF bool) (n int, err error)
+}
+
+// NopResetter can be embedded by implementations of Transformer to add a nop
+// Reset method.
+type NopResetter struct{}
+
+// Reset implements the Reset method of the Transformer interface.
+func (NopResetter) Reset() {}
+
+// Reader wraps another io.Reader by transforming the bytes read.
+type Reader struct {
+ r io.Reader
+ t Transformer
+ err error
+
+ // dst[dst0:dst1] contains bytes that have been transformed by t but
+ // not yet copied out via Read.
+ dst []byte
+ dst0, dst1 int
+
+ // src[src0:src1] contains bytes that have been read from r but not
+ // yet transformed through t.
+ src []byte
+ src0, src1 int
+
+ // transformComplete is whether the transformation is complete,
+ // regardless of whether or not it was successful.
+ transformComplete bool
+}
+
+const defaultBufSize = 4096
+
+// NewReader returns a new Reader that wraps r by transforming the bytes read
+// via t. It calls Reset on t.
+func NewReader(r io.Reader, t Transformer) *Reader {
+ t.Reset()
+ return &Reader{
+ r: r,
+ t: t,
+ dst: make([]byte, defaultBufSize),
+ src: make([]byte, defaultBufSize),
+ }
+}
+
+// Read implements the io.Reader interface.
+func (r *Reader) Read(p []byte) (int, error) {
+ n, err := 0, error(nil)
+ for {
+ // Copy out any transformed bytes and return the final error if we are done.
+ if r.dst0 != r.dst1 {
+ n = copy(p, r.dst[r.dst0:r.dst1])
+ r.dst0 += n
+ if r.dst0 == r.dst1 && r.transformComplete {
+ return n, r.err
+ }
+ return n, nil
+ } else if r.transformComplete {
+ return 0, r.err
+ }
+
+ // Try to transform some source bytes, or to flush the transformer if we
+ // are out of source bytes. We do this even if r.r.Read returned an error.
+ // As the io.Reader documentation says, "process the n > 0 bytes returned
+ // before considering the error".
+ if r.src0 != r.src1 || r.err != nil {
+ r.dst0 = 0
+ r.dst1, n, err = r.t.Transform(r.dst, r.src[r.src0:r.src1], r.err == io.EOF)
+ r.src0 += n
+
+ switch {
+ case err == nil:
+ if r.src0 != r.src1 {
+ r.err = errInconsistentByteCount
+ }
+ // The Transform call was successful; we are complete if we
+ // cannot read more bytes into src.
+ r.transformComplete = r.err != nil
+ continue
+ case err == ErrShortDst && (r.dst1 != 0 || n != 0):
+ // Make room in dst by copying out, and try again.
+ continue
+ case err == ErrShortSrc && r.src1-r.src0 != len(r.src) && r.err == nil:
+ // Read more bytes into src via the code below, and try again.
+ default:
+ r.transformComplete = true
+ // The reader error (r.err) takes precedence over the
+ // transformer error (err) unless r.err is nil or io.EOF.
+ if r.err == nil || r.err == io.EOF {
+ r.err = err
+ }
+ continue
+ }
+ }
+
+ // Move any untransformed source bytes to the start of the buffer
+ // and read more bytes.
+ if r.src0 != 0 {
+ r.src0, r.src1 = 0, copy(r.src, r.src[r.src0:r.src1])
+ }
+ n, r.err = r.r.Read(r.src[r.src1:])
+ r.src1 += n
+ }
+}
+
+// TODO: implement ReadByte (and ReadRune??).
+
+// Writer wraps another io.Writer by transforming the bytes read.
+// The user needs to call Close to flush unwritten bytes that may
+// be buffered.
+type Writer struct {
+ w io.Writer
+ t Transformer
+ dst []byte
+
+ // src[:n] contains bytes that have not yet passed through t.
+ src []byte
+ n int
+}
+
+// NewWriter returns a new Writer that wraps w by transforming the bytes written
+// via t. It calls Reset on t.
+func NewWriter(w io.Writer, t Transformer) *Writer {
+ t.Reset()
+ return &Writer{
+ w: w,
+ t: t,
+ dst: make([]byte, defaultBufSize),
+ src: make([]byte, defaultBufSize),
+ }
+}
+
+// Write implements the io.Writer interface. If there are not enough
+// bytes available to complete a Transform, the bytes will be buffered
+// for the next write. Call Close to convert the remaining bytes.
+func (w *Writer) Write(data []byte) (n int, err error) {
+ src := data
+ if w.n > 0 {
+ // Append bytes from data to the last remainder.
+ // TODO: limit the amount copied on first try.
+ n = copy(w.src[w.n:], data)
+ w.n += n
+ src = w.src[:w.n]
+ }
+ for {
+ nDst, nSrc, err := w.t.Transform(w.dst, src, false)
+ if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
+ return n, werr
+ }
+ src = src[nSrc:]
+ if w.n == 0 {
+ n += nSrc
+ } else if len(src) <= n {
+ // Enough bytes from w.src have been consumed. We make src point
+ // to data instead to reduce the copying.
+ w.n = 0
+ n -= len(src)
+ src = data[n:]
+ if n < len(data) && (err == nil || err == ErrShortSrc) {
+ continue
+ }
+ }
+ switch err {
+ case ErrShortDst:
+ // This error is okay as long as we are making progress.
+ if nDst > 0 || nSrc > 0 {
+ continue
+ }
+ case ErrShortSrc:
+ if len(src) < len(w.src) {
+ m := copy(w.src, src)
+ // If w.n > 0, bytes from data were already copied to w.src and n
+ // was already set to the number of bytes consumed.
+ if w.n == 0 {
+ n += m
+ }
+ w.n = m
+ err = nil
+ } else if nDst > 0 || nSrc > 0 {
+ // Not enough buffer to store the remainder. Keep processing as
+ // long as there is progress. Without this case, transforms that
+ // require a lookahead larger than the buffer may result in an
+ // error. This is not something one may expect to be common in
+ // practice, but it may occur when buffers are set to small
+ // sizes during testing.
+ continue
+ }
+ case nil:
+ if w.n > 0 {
+ err = errInconsistentByteCount
+ }
+ }
+ return n, err
+ }
+}
+
+// Close implements the io.Closer interface.
+func (w *Writer) Close() error {
+ src := w.src[:w.n]
+ for {
+ nDst, nSrc, err := w.t.Transform(w.dst, src, true)
+ if _, werr := w.w.Write(w.dst[:nDst]); werr != nil {
+ return werr
+ }
+ if err != ErrShortDst {
+ return err
+ }
+ src = src[nSrc:]
+ }
+}
+
+type nop struct{ NopResetter }
+
+func (nop) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ n := copy(dst, src)
+ if n < len(src) {
+ err = ErrShortDst
+ }
+ return n, n, err
+}
+
+func (nop) Span(src []byte, atEOF bool) (n int, err error) {
+ return len(src), nil
+}
+
+type discard struct{ NopResetter }
+
+func (discard) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ return 0, len(src), nil
+}
+
+var (
+ // Discard is a Transformer for which all Transform calls succeed
+ // by consuming all bytes and writing nothing.
+ Discard Transformer = discard{}
+
+ // Nop is a SpanningTransformer that copies src to dst.
+ Nop SpanningTransformer = nop{}
+)
+
+// chain is a sequence of links. A chain with N Transformers has N+1 links and
+// N+1 buffers. Of those N+1 buffers, the first and last are the src and dst
+// buffers given to chain.Transform and the middle N-1 buffers are intermediate
+// buffers owned by the chain. The i'th link transforms bytes from the i'th
+// buffer chain.link[i].b at read offset chain.link[i].p to the i+1'th buffer
+// chain.link[i+1].b at write offset chain.link[i+1].n, for i in [0, N).
+type chain struct {
+ link []link
+ err error
+ // errStart is the index at which the error occurred plus 1. Processing
+ // errStart at this level at the next call to Transform. As long as
+ // errStart > 0, chain will not consume any more source bytes.
+ errStart int
+}
+
+func (c *chain) fatalError(errIndex int, err error) {
+ if i := errIndex + 1; i > c.errStart {
+ c.errStart = i
+ c.err = err
+ }
+}
+
+type link struct {
+ t Transformer
+ // b[p:n] holds the bytes to be transformed by t.
+ b []byte
+ p int
+ n int
+}
+
+func (l *link) src() []byte {
+ return l.b[l.p:l.n]
+}
+
+func (l *link) dst() []byte {
+ return l.b[l.n:]
+}
+
+// Chain returns a Transformer that applies t in sequence.
+func Chain(t ...Transformer) Transformer {
+ if len(t) == 0 {
+ return nop{}
+ }
+ c := &chain{link: make([]link, len(t)+1)}
+ for i, tt := range t {
+ c.link[i].t = tt
+ }
+ // Allocate intermediate buffers.
+ b := make([][defaultBufSize]byte, len(t)-1)
+ for i := range b {
+ c.link[i+1].b = b[i][:]
+ }
+ return c
+}
+
+// Reset resets the state of Chain. It calls Reset on all the Transformers.
+func (c *chain) Reset() {
+ for i, l := range c.link {
+ if l.t != nil {
+ l.t.Reset()
+ }
+ c.link[i].p, c.link[i].n = 0, 0
+ }
+}
+
+// TODO: make chain use Span (is going to be fun to implement!)
+
+// Transform applies the transformers of c in sequence.
+func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ // Set up src and dst in the chain.
+ srcL := &c.link[0]
+ dstL := &c.link[len(c.link)-1]
+ srcL.b, srcL.p, srcL.n = src, 0, len(src)
+ dstL.b, dstL.n = dst, 0
+ var lastFull, needProgress bool // for detecting progress
+
+ // i is the index of the next Transformer to apply, for i in [low, high].
+ // low is the lowest index for which c.link[low] may still produce bytes.
+ // high is the highest index for which c.link[high] has a Transformer.
+ // The error returned by Transform determines whether to increase or
+ // decrease i. We try to completely fill a buffer before converting it.
+ for low, i, high := c.errStart, c.errStart, len(c.link)-2; low <= i && i <= high; {
+ in, out := &c.link[i], &c.link[i+1]
+ nDst, nSrc, err0 := in.t.Transform(out.dst(), in.src(), atEOF && low == i)
+ out.n += nDst
+ in.p += nSrc
+ if i > 0 && in.p == in.n {
+ in.p, in.n = 0, 0
+ }
+ needProgress, lastFull = lastFull, false
+ switch err0 {
+ case ErrShortDst:
+ // Process the destination buffer next. Return if we are already
+ // at the high index.
+ if i == high {
+ return dstL.n, srcL.p, ErrShortDst
+ }
+ if out.n != 0 {
+ i++
+ // If the Transformer at the next index is not able to process any
+ // source bytes there is nothing that can be done to make progress
+ // and the bytes will remain unprocessed. lastFull is used to
+ // detect this and break out of the loop with a fatal error.
+ lastFull = true
+ continue
+ }
+ // The destination buffer was too small, but is completely empty.
+ // Return a fatal error as this transformation can never complete.
+ c.fatalError(i, errShortInternal)
+ case ErrShortSrc:
+ if i == 0 {
+ // Save ErrShortSrc in err. All other errors take precedence.
+ err = ErrShortSrc
+ break
+ }
+ // Source bytes were depleted before filling up the destination buffer.
+ // Verify we made some progress, move the remaining bytes to the errStart
+ // and try to get more source bytes.
+ if needProgress && nSrc == 0 || in.n-in.p == len(in.b) {
+ // There were not enough source bytes to proceed while the source
+ // buffer cannot hold any more bytes. Return a fatal error as this
+ // transformation can never complete.
+ c.fatalError(i, errShortInternal)
+ break
+ }
+ // in.b is an internal buffer and we can make progress.
+ in.p, in.n = 0, copy(in.b, in.src())
+ fallthrough
+ case nil:
+ // if i == low, we have depleted the bytes at index i or any lower levels.
+ // In that case we increase low and i. In all other cases we decrease i to
+ // fetch more bytes before proceeding to the next index.
+ if i > low {
+ i--
+ continue
+ }
+ default:
+ c.fatalError(i, err0)
+ }
+ // Exhausted level low or fatal error: increase low and continue
+ // to process the bytes accepted so far.
+ i++
+ low = i
+ }
+
+ // If c.errStart > 0, this means we found a fatal error. We will clear
+ // all upstream buffers. At this point, no more progress can be made
+ // downstream, as Transform would have bailed while handling ErrShortDst.
+ if c.errStart > 0 {
+ for i := 1; i < c.errStart; i++ {
+ c.link[i].p, c.link[i].n = 0, 0
+ }
+ err, c.errStart, c.err = c.err, 0, nil
+ }
+ return dstL.n, srcL.p, err
+}
+
+// Deprecated: Use runes.Remove instead.
+func RemoveFunc(f func(r rune) bool) Transformer {
+ return removeF(f)
+}
+
+type removeF func(r rune) bool
+
+func (removeF) Reset() {}
+
+// Transform implements the Transformer interface.
+func (t removeF) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
+ for r, sz := rune(0), 0; len(src) > 0; src = src[sz:] {
+
+ if r = rune(src[0]); r < utf8.RuneSelf {
+ sz = 1
+ } else {
+ r, sz = utf8.DecodeRune(src)
+
+ if sz == 1 {
+ // Invalid rune.
+ if !atEOF && !utf8.FullRune(src) {
+ err = ErrShortSrc
+ break
+ }
+ // We replace illegal bytes with RuneError. Not doing so might
+ // otherwise turn a sequence of invalid UTF-8 into valid UTF-8.
+ // The resulting byte sequence may subsequently contain runes
+ // for which t(r) is true that were passed unnoticed.
+ if !t(r) {
+ if nDst+3 > len(dst) {
+ err = ErrShortDst
+ break
+ }
+ nDst += copy(dst[nDst:], "\uFFFD")
+ }
+ nSrc++
+ continue
+ }
+ }
+
+ if !t(r) {
+ if nDst+sz > len(dst) {
+ err = ErrShortDst
+ break
+ }
+ nDst += copy(dst[nDst:], src[:sz])
+ }
+ nSrc += sz
+ }
+ return
+}
+
+// grow returns a new []byte that is longer than b, and copies the first n bytes
+// of b to the start of the new slice.
+func grow(b []byte, n int) []byte {
+ m := len(b)
+ if m <= 32 {
+ m = 64
+ } else if m <= 256 {
+ m *= 2
+ } else {
+ m += m >> 1
+ }
+ buf := make([]byte, m)
+ copy(buf, b[:n])
+ return buf
+}
+
+const initialBufSize = 128
+
+// String returns a string with the result of converting s[:n] using t, where
+// n <= len(s). If err == nil, n will be len(s). It calls Reset on t.
+func String(t Transformer, s string) (result string, n int, err error) {
+ t.Reset()
+ if s == "" {
+ // Fast path for the common case for empty input. Results in about a
+ // 86% reduction of running time for BenchmarkStringLowerEmpty.
+ if _, _, err := t.Transform(nil, nil, true); err == nil {
+ return "", 0, nil
+ }
+ }
+
+ // Allocate only once. Note that both dst and src escape when passed to
+ // Transform.
+ buf := [2 * initialBufSize]byte{}
+ dst := buf[:initialBufSize:initialBufSize]
+ src := buf[initialBufSize : 2*initialBufSize]
+
+ // The input string s is transformed in multiple chunks (starting with a
+ // chunk size of initialBufSize). nDst and nSrc are per-chunk (or
+ // per-Transform-call) indexes, pDst and pSrc are overall indexes.
+ nDst, nSrc := 0, 0
+ pDst, pSrc := 0, 0
+
+ // pPrefix is the length of a common prefix: the first pPrefix bytes of the
+ // result will equal the first pPrefix bytes of s. It is not guaranteed to
+ // be the largest such value, but if pPrefix, len(result) and len(s) are
+ // all equal after the final transform (i.e. calling Transform with atEOF
+ // being true returned nil error) then we don't need to allocate a new
+ // result string.
+ pPrefix := 0
+ for {
+ // Invariant: pDst == pPrefix && pSrc == pPrefix.
+
+ n := copy(src, s[pSrc:])
+ nDst, nSrc, err = t.Transform(dst, src[:n], pSrc+n == len(s))
+ pDst += nDst
+ pSrc += nSrc
+
+ // TODO: let transformers implement an optional Spanner interface, akin
+ // to norm's QuickSpan. This would even allow us to avoid any allocation.
+ if !bytes.Equal(dst[:nDst], src[:nSrc]) {
+ break
+ }
+ pPrefix = pSrc
+ if err == ErrShortDst {
+ // A buffer can only be short if a transformer modifies its input.
+ break
+ } else if err == ErrShortSrc {
+ if nSrc == 0 {
+ // No progress was made.
+ break
+ }
+ // Equal so far and !atEOF, so continue checking.
+ } else if err != nil || pPrefix == len(s) {
+ return string(s[:pPrefix]), pPrefix, err
+ }
+ }
+ // Post-condition: pDst == pPrefix + nDst && pSrc == pPrefix + nSrc.
+
+ // We have transformed the first pSrc bytes of the input s to become pDst
+ // transformed bytes. Those transformed bytes are discontiguous: the first
+ // pPrefix of them equal s[:pPrefix] and the last nDst of them equal
+ // dst[:nDst]. We copy them around, into a new dst buffer if necessary, so
+ // that they become one contiguous slice: dst[:pDst].
+ if pPrefix != 0 {
+ newDst := dst
+ if pDst > len(newDst) {
+ newDst = make([]byte, len(s)+nDst-nSrc)
+ }
+ copy(newDst[pPrefix:pDst], dst[:nDst])
+ copy(newDst[:pPrefix], s[:pPrefix])
+ dst = newDst
+ }
+
+ // Prevent duplicate Transform calls with atEOF being true at the end of
+ // the input. Also return if we have an unrecoverable error.
+ if (err == nil && pSrc == len(s)) ||
+ (err != nil && err != ErrShortDst && err != ErrShortSrc) {
+ return string(dst[:pDst]), pSrc, err
+ }
+
+ // Transform the remaining input, growing dst and src buffers as necessary.
+ for {
+ n := copy(src, s[pSrc:])
+ atEOF := pSrc+n == len(s)
+ nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], atEOF)
+ pDst += nDst
+ pSrc += nSrc
+
+ // If we got ErrShortDst or ErrShortSrc, do not grow as long as we can
+ // make progress. This may avoid excessive allocations.
+ if err == ErrShortDst {
+ if nDst == 0 {
+ dst = grow(dst, pDst)
+ }
+ } else if err == ErrShortSrc {
+ if atEOF {
+ return string(dst[:pDst]), pSrc, err
+ }
+ if nSrc == 0 {
+ src = grow(src, 0)
+ }
+ } else if err != nil || pSrc == len(s) {
+ return string(dst[:pDst]), pSrc, err
+ }
+ }
+}
+
+// Bytes returns a new byte slice with the result of converting b[:n] using t,
+// where n <= len(b). If err == nil, n will be len(b). It calls Reset on t.
+func Bytes(t Transformer, b []byte) (result []byte, n int, err error) {
+ return doAppend(t, 0, make([]byte, len(b)), b)
+}
+
+// Append appends the result of converting src[:n] using t to dst, where
+// n <= len(src), If err == nil, n will be len(src). It calls Reset on t.
+func Append(t Transformer, dst, src []byte) (result []byte, n int, err error) {
+ if len(dst) == cap(dst) {
+ n := len(src) + len(dst) // It is okay for this to be 0.
+ b := make([]byte, n)
+ dst = b[:copy(b, dst)]
+ }
+ return doAppend(t, len(dst), dst[:cap(dst)], src)
+}
+
+func doAppend(t Transformer, pDst int, dst, src []byte) (result []byte, n int, err error) {
+ t.Reset()
+ pSrc := 0
+ for {
+ nDst, nSrc, err := t.Transform(dst[pDst:], src[pSrc:], true)
+ pDst += nDst
+ pSrc += nSrc
+ if err != ErrShortDst {
+ return dst[:pDst], pSrc, err
+ }
+
+ // Grow the destination buffer, but do not grow as long as we can make
+ // progress. This may avoid excessive allocations.
+ if nDst == 0 {
+ dst = grow(dst, pDst)
+ }
+ }
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
new file mode 100644
index 0000000..d81c542
--- /dev/null
+++ b/vendor/modules.txt
@@ -0,0 +1,18 @@
+# github.com/emersion/go-imap v1.2.1
+## explicit; go 1.13
+github.com/emersion/go-imap
+github.com/emersion/go-imap/client
+github.com/emersion/go-imap/commands
+github.com/emersion/go-imap/responses
+github.com/emersion/go-imap/utf7
+# github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
+## explicit; go 1.12
+github.com/emersion/go-sasl
+# github.com/emersion/go-smtp v0.16.0
+## explicit; go 1.13
+github.com/emersion/go-smtp
+# golang.org/x/text v0.3.7
+## explicit; go 1.17
+golang.org/x/text/encoding
+golang.org/x/text/encoding/internal/identifier
+golang.org/x/text/transform