commit 811b90224f8d402cff53b5ecb959bf69e6e54e5b Author: Nick Thomas Date: Mon Mar 5 12:19:04 2018 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dc844e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/crockery +/crockery.db diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..f060c3a --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,21 @@ +# Crockery design + +We need: + +* SMTP server + * Receive emails -> middlewares -> storage + * https://github.com/emersion/go-smtp ? +* IMAP server + * Receive logins +* HTTP server + * Serve autodiscovery bumpf + * ActiveSync protocol +* Storage + * Accounts + * Passwords! + * Emails + * TLS keys + certificates + * Search emails - some sort of inverted index necessary + * https://github.com/blevesearch/bleve ? + * Embedded database best. Ideally we have a single file to work with + diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..99ff65d --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,46 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/emersion/go-imap" + packages = [ + ".", + "backend", + "commands", + "responses", + "server", + "utf7" + ] + revision = "840b16b212bff35b595e708937c6d60861cfab49" + version = "v0.9" + +[[projects]] + branch = "master" + name = "github.com/emersion/go-sasl" + packages = ["."] + revision = "7e096a0a6197b89989e8cc31016daa67c8c62051" + +[[projects]] + branch = "master" + name = "github.com/emersion/go-smtp" + packages = ["."] + revision = "a63104657743890cb7c2fd54f15a2725291f6a9f" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "encoding", + "encoding/internal/identifier", + "internal/gen", + "transform", + "unicode/cldr" + ] + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "a8a093c114e9fd6d1de5e43c061f3cf1d08fa5279da70af9c0cd030030fe7737" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..37d27c0 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "github.com/emersion/go-smtp" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..375228d --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# Crockery - simple email hosting + +## What + +* SMTP + IMAP email, plus bundled HTTP webmail client +* Devoted to the task of serving email for a single domain +* Can get its own valid TLS certificates via LE, or use supplied ones +* Built-in anti-spam & anti-virus measures +* Built-in DKIM support +* Built-in DMARC support +* Can tell you what DNS records you need, and check them for validity +* Multiple email accounts for your domain +* A "master" wildcard account (if desired) +* Simple import from GMail, etc +* Simple export to GMail, etc +* Nice fast search +* Interact with crockery with simple, natural-ish emails to do things + * SMTP TLS verification - enabled by default + * Try to email foo@shady.net - verification fails, email marked pending, fail in 7 days + * Crockery emails user, "Yo. We can't send this email securely. Here are your options:" + * User hits reply-to (goes to unique email address on same domain) saying what to do: + * Cancel + * Retry + * Send just this email + * Whitelist shady.net (pins certificate) + * What other interactions might we like? + +## What not + +* JMAP. Not today. +* Maildir/mbox support. Imports and exports via IMAP, backups by "copy this file" +* Bring your own X (where X is MTA, IMAP server, database, etc, etc) +* Multiple-domain support (maybe later) +* CalDAV/Carddav (yet) +* Externally sourced accounts (yet) +* Sending email without having an account +* Managesieve (for now) + +## Why + +Email architecture is really complicated, and setting up a mail server of your +own is painful. Various projects exist to try to make it easier, eg: + +* iRedMail +* docker-mailserver +* symbiosis +* ... + +(Personally, I had some ansible recipes: ) + +Even among people who run their own websites, it's rare to run your own email. +It's just too painful. Much of this pain is caused by ultra-configurable +components that need to be orchestrated. Each has its own (remarkably obtuse) +configuration language, and each can do far more than the common case for a +small, single-domain site. + +So, let's make a single binary where all you have to do is: + +* Register a domain +* Upload some DNS records +* Run the binary + +and it gives you a sensible email setup without any additional configuration! + +If it has to share the domain with a HTTP server (quite common), allow the +HTTP-specific parts of mail to be served via reverse proxying. This includes: + +* activesync push notifications +* autodiscovery + +Probably other stuff. Email is big, and just keeps getting bigger. + +## How + +### Initialize a new database + +``` +crockery init -domain example.com +``` + +You'll be prompted to enter a password for the `postmaster@example.com` master +account, or you can provide on the command line: `-adminPassword x`, or in an +environment variable: `CROCKERY_PASSWORD=x`. + +You can provide a custom database name with `-db ` + +### Run the server + +``` +./crockery +``` + +Again, you can use `-db ` if the default of `./crockery.db` doesn't +suit. + +### Configuration + +Mostly non-existent, aside from that listed above. + +We'll need to have a few *offline* commands baked into the `crockery` binary, +apart from `init` as detailed above. Some ideas: + +``` +$ crockery change x y z (domain, user password, etc) +$ crockery reindex (throw away existing indexes, regenerate) +$ crockery whitelist tls (allow the domain to be sent to with insecure/no TLS) +$ crockery whitelist receipt (bypass antispam for this domain) +$ crockery blacklist receipt (no emails to be permitted from this domain +``` + +It's inconvenient for some of these functions to require the server to be +offline, so we'll need to provide another configuration interface too. + +I like sending emails to it. + +Since we're going to need a HTTP server anyway, we could expose admin functions +using that, if logged in as postmaster. It could also expose an API that the +`crockery X` commands could connect to, rather than hitting the store directly. +We can use the `.well-known` integration to avoid the need to configure there, +too. diff --git a/cmd/crockery/main.go b/cmd/crockery/main.go new file mode 100644 index 0000000..49c1379 --- /dev/null +++ b/cmd/crockery/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + + "ur.gs/crockery/internal/services" + "ur.gs/crockery/internal/store" +) + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + + datastore, err := store.New(ctx, "crockery.db") + if err != nil { + log.Fatal("Couldn't open crockery.db:", err) + } + + srv, err := services.New(ctx, datastore) + if err != nil { + log.Fatal("Couldn't start services:", err) + } + + sig := make(chan os.Signal, 1) + done := make(chan bool) + + signal.Notify(sig, os.Interrupt) + + go func() { + s := <-sig + log.Print("Got signal: ", s) + cancel() + }() + + go func() { + srv.Run() + close(done) + }() + + <-done + log.Println("All services finished, exiting") + + os.Exit(0) +} diff --git a/internal/imap/server.go b/internal/imap/server.go new file mode 100644 index 0000000..b94ddf4 --- /dev/null +++ b/internal/imap/server.go @@ -0,0 +1,62 @@ +package imap + +import ( + "context" + "io" + "log" + + imapbackend "github.com/emersion/go-imap/backend" + imapserver "github.com/emersion/go-imap/server" + + "ur.gs/crockery/internal/store" +) + +type Server interface { + Run() + + io.Closer +} + +func NewServer(cancel context.CancelFunc, datastore store.Interface, starttls bool) Server { + out := &concrete{ + cancel: cancel, + store: datastore, + } + + out.server = imapserver.New(out) + + if starttls { + out.server.Addr = ":143" + } else { + out.server.Addr = ":993" + } + + return out +} + +type concrete struct { + cancel context.CancelFunc + store store.Interface + server *imapserver.Server +} + +func (c *concrete) Run() { + if err := c.server.ListenAndServe(); err != nil { + log.Printf("Error serving IMAP %s: %v", c.server.Addr, err) + } else { + log.Printf("Stopped listening on IMAP %s", c.server.Addr) + } + + c.cancel() +} + +// backend implementation for go-smtp +func (c *concrete) Login(string, string) (imapbackend.User, error) { + return nil, nil +} + +func (c *concrete) Close() error { + c.cancel() // FIXME: this doesn't touch the server + + return nil +} diff --git a/internal/services/services.go b/internal/services/services.go new file mode 100644 index 0000000..7d7e8a4 --- /dev/null +++ b/internal/services/services.go @@ -0,0 +1,69 @@ +package services + +import ( + "context" + "io" + "log" + + "ur.gs/crockery/internal/imap" + "ur.gs/crockery/internal/smtp" + "ur.gs/crockery/internal/store" +) + +type Interface interface { + Run() // Run the services +} + +func New(ctx context.Context, datastore store.Interface) (Interface, error) { + ctx, cancelFunc := context.WithCancel(ctx) + + return &concrete{ + ctx: ctx, + cancel: cancelFunc, + store: datastore, + }, nil +} + +type genServer interface { + Run() + + io.Closer +} + +type concrete struct { + ctx context.Context + cancel context.CancelFunc + store store.Interface + + // This is only mutated in the Run() method + servers []genServer +} + +// For now, bind unconditionally to these ports, on IPv4 + IPv6: +// 25 - incoming SMTP (STARTTLS) +// 587 - outgoing SMTP (STARTTLS) +// 143 - IMAP (STARTTLS) +// 993 - IMAP (TLS) +// +// Each listener gets incoming connections serviced against the passed-in store. +func (c *concrete) Run() { + c.servers = []genServer{ + smtp.NewServer(c.cancel, c.store, false), + smtp.NewServer(c.cancel, c.store, true), + + imap.NewServer(c.cancel, c.store, false), + imap.NewServer(c.cancel, c.store, true), + } + + log.Print("Running all services") + for _, srv := range c.servers { + go srv.Run() + } + + <-c.ctx.Done() + + log.Print("Closing all services") + for _, srv := range c.servers { + srv.Close() + } +} diff --git a/internal/smtp/server.go b/internal/smtp/server.go new file mode 100644 index 0000000..f87c4e3 --- /dev/null +++ b/internal/smtp/server.go @@ -0,0 +1,62 @@ +package smtp + +import ( + "context" + "io" + "log" + + "github.com/emersion/go-smtp" + + "ur.gs/crockery/internal/store" +) + +type Server interface { + Run() + + io.Closer +} + +func NewServer(cancel context.CancelFunc, datastore store.Interface, submission bool) Server { + out := &concrete{ + cancel: cancel, + store: datastore, + } + + out.server = smtp.NewServer(out) + out.server.Domain = datastore.Domain() + + if submission { + out.server.Addr = ":587" + } else { + out.server.Addr = ":25" + } + + return out +} + +type concrete struct { + cancel context.CancelFunc + store store.Interface + server *smtp.Server +} + +func (c *concrete) Run() { + if err := c.server.ListenAndServe(); err != nil { + log.Printf("Error serving SMTP %s: %v", c.server.Addr, err) + } else { + log.Printf("Stopped listening on SMTP %s", c.server.Addr) + } + + c.cancel() +} + +// backend implementation for go-smtp +func (c *concrete) Login(string, string) (smtp.User, error) { + return nil, nil +} + +func (c *concrete) Close() error { + c.cancel() // FIXME: this doesn't touch the server + + return nil +} diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 0000000..9412e65 --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,21 @@ +package store + +import ( + "context" +) + +type Interface interface { + Domain() string +} + +func New(ctx context.Context, filename string) (Interface, error) { + return &concrete{domain: "example.com"}, nil +} + +type concrete struct { + domain string +} + +func (c *concrete) Domain() string { + return c.domain +} 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/.travis.yml b/vendor/github.com/emersion/go-imap/.travis.yml new file mode 100644 index 0000000..bedf3b2 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.8 +script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh) +after_script: bash <(curl -s https://codecov.io/bash) 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..1a5a3fe --- /dev/null +++ b/vendor/github.com/emersion/go-imap/README.md @@ -0,0 +1,183 @@ +# go-imap + +[![GoDoc](https://godoc.org/github.com/emersion/go-imap?status.svg)](https://godoc.org/github.com/emersion/go-imap) +[![Build Status](https://travis-ci.org/emersion/go-imap.svg?branch=master)](https://travis-ci.org/emersion/go-imap) +[![Codecov](https://codecov.io/gh/emersion/go-imap/branch/master/graph/badge.svg)](https://codecov.io/gh/emersion/go-imap) +[![Go Report +Card](https://goreportcard.com/badge/github.com/emersion/go-imap)](https://goreportcard.com/report/github.com/emersion/go-imap) +[![Unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable) +[![Gitter chat](https://badges.gitter.im/goimap/Lobby.svg)](https://gitter.im/goimap/Lobby) + +An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It +can be used to build a client and/or a server. + +```bash +go get github.com/emersion/go-imap/... +``` + +## Why? + +Other IMAP implementations in Go: +* Require to make [many type assertions or conversions](https://github.com/emersion/neutron/blob/ca635850e2223d6cfe818664ef901fa6e3c1d859/backend/imap/util.go#L110) +* Are not idiomatic or are [ugly](https://github.com/jordwest/imap-server/blob/master/conn/commands.go#L53) +* Are [not pleasant to use](https://github.com/emersion/neutron/blob/ca635850e2223d6cfe818664ef901fa6e3c1d859/backend/imap/messages.go#L228) +* Implement a server _xor_ a client, not both +* Don't implement unilateral updates (i.e. the server can't notify clients for + new messages) +* Do not have a good test coverage +* Don't handle encoding and charset automatically + +## Usage + +### Client [![GoDoc](https://godoc.org/github.com/emersion/go-imap/client?status.svg)](https://godoc.org/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 substract 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, []string{imap.EnvelopeMsgAttr}, 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 [![GoDoc](https://godoc.org/github.com/emersion/go-imap/server?status.svg)](https://godoc.org/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. + +## 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. + +* [APPENDLIMIT](https://github.com/emersion/go-imap-appendlimit) +* [COMPRESS](https://github.com/emersion/go-imap-compress) +* [ENABLE](https://github.com/emersion/go-imap-enable) +* [ID](https://github.com/ProtonMail/go-imap-id) +* [IDLE](https://github.com/emersion/go-imap-idle) +* [MOVE](https://github.com/emersion/go-imap-move) +* [QUOTA](https://github.com/emersion/go-imap-quota) +* [SPECIAL-USE](https://github.com/emersion/go-imap-specialuse) +* [UNSELECT](https://github.com/emersion/go-imap-unselect) +* [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) + +### Related projects + +* [go-message](https://github.com/emersion/go-message) - parsing and formatting MIME and mail messages +* [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 +* [go-dkim](https://github.com/emersion/go-dkim) - creating and verifying DKIM signatures + +## License + +MIT diff --git a/vendor/github.com/emersion/go-imap/backend/backend.go b/vendor/github.com/emersion/go-imap/backend/backend.go new file mode 100644 index 0000000..031f427 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/backend/backend.go @@ -0,0 +1,16 @@ +// Package backend defines an IMAP server backend interface. +package backend + +import "errors" + +// ErrInvalidCredentials is returned by Backend.Login when a username or a +// password is incorrect. +var ErrInvalidCredentials = errors.New("Invalid credentials") + +// Backend is an IMAP server backend. A backend operation always deals with +// users. +type Backend interface { + // Login authenticates a user. If the username or the password is incorrect, + // it returns ErrInvalidCredentials. + Login(username, password string) (User, error) +} diff --git a/vendor/github.com/emersion/go-imap/backend/mailbox.go b/vendor/github.com/emersion/go-imap/backend/mailbox.go new file mode 100644 index 0000000..72af9ab --- /dev/null +++ b/vendor/github.com/emersion/go-imap/backend/mailbox.go @@ -0,0 +1,78 @@ +package backend + +import ( + "time" + + "github.com/emersion/go-imap" +) + +// Mailbox represents a mailbox belonging to a user in the mail storage system. +// A mailbox operation always deals with messages. +type Mailbox interface { + // Name returns this mailbox name. + Name() string + + // Info returns this mailbox info. + Info() (*imap.MailboxInfo, error) + + // Status returns this mailbox status. The fields Name, Flags and + // PermanentFlags in the returned MailboxStatus must be always populated. This + // function does not affect the state of any messages in the mailbox. See RFC + // 3501 section 6.3.10 for a list of items that can be requested. + Status(items []string) (*imap.MailboxStatus, error) + + // SetSubscribed adds or removes the mailbox to the server's set of "active" + // or "subscribed" mailboxes. + SetSubscribed(subscribed bool) error + + // Check requests a checkpoint of the currently selected mailbox. A checkpoint + // refers to any implementation-dependent housekeeping associated with the + // mailbox (e.g., resolving the server's in-memory state of the mailbox with + // the state on its disk). A checkpoint MAY take a non-instantaneous amount of + // real time to complete. If a server implementation has no such housekeeping + // considerations, CHECK is equivalent to NOOP. + Check() error + + // ListMessages returns a list of messages. seqset must be interpreted as UIDs + // if uid is set to true and as message sequence numbers otherwise. See RFC + // 3501 section 6.4.5 for a list of items that can be requested. + // + // Messages must be sent to ch. When the function returns, ch must be closed. + ListMessages(uid bool, seqset *imap.SeqSet, items []string, ch chan<- *imap.Message) error + + // SearchMessages searches messages. The returned list must contain UIDs if + // uid is set to true, or sequence numbers otherwise. + SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) + + // CreateMessage appends a new message to this mailbox. The \Recent flag will + // be added no matter flags is empty or not. If date is nil, the current time + // will be used. + // + // If the Backend implements Updater, it must notify the client immediately + // via a mailbox update. + CreateMessage(flags []string, date time.Time, body imap.Literal) error + + // UpdateMessagesFlags alters flags for the specified message(s). + // + // If the Backend implements Updater, it must notify the client immediately + // via a message update. + UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error + + // CopyMessages copies the specified message(s) to the end of the specified + // destination mailbox. The flags and internal date of the message(s) SHOULD + // be preserved, and the Recent flag SHOULD be set, in the copy. + // + // If the destination mailbox does not exist, a server SHOULD return an error. + // It SHOULD NOT automatically create the mailbox. + // + // If the Backend implements Updater, it must notify the client immediately + // via a mailbox update. + CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error + + // Expunge permanently removes all messages that have the \Deleted flag set + // from the currently selected mailbox. + // + // If the Backend implements Updater, it must notify the client immediately + // via an expunge update. + Expunge() error +} diff --git a/vendor/github.com/emersion/go-imap/backend/updates.go b/vendor/github.com/emersion/go-imap/backend/updates.go new file mode 100644 index 0000000..8310dac --- /dev/null +++ b/vendor/github.com/emersion/go-imap/backend/updates.go @@ -0,0 +1,109 @@ +package backend + +import ( + "github.com/emersion/go-imap" +) + +// Update contains user and mailbox information about an unilateral backend +// update. +type Update struct { + // The user targeted by this update. If empty, all connected users will + // be notified. + Username string + // The mailbox targeted by this update. If empty, the update targets all + // mailboxes. + Mailbox string + + // A channel that will be closed once the update has been processed. + done chan struct{} +} + +// Done returns a channel that is closed when the update has been broadcast to +// all clients. +func (u *Update) Done() <-chan struct{} { + if u.done == nil { + u.done = make(chan struct{}) + } + return u.done +} + +// DoneUpdate marks an update as done. +// TODO: remove this function +func DoneUpdate(u *Update) { + if u.done != nil { + close(u.done) + } +} + +// StatusUpdate is a status update. See RFC 3501 section 7.1 for a list of +// status responses. +type StatusUpdate struct { + Update + *imap.StatusResp +} + +// MailboxUpdate is a mailbox update. +type MailboxUpdate struct { + Update + *imap.MailboxStatus +} + +// MessageUpdate is a message update. +type MessageUpdate struct { + Update + *imap.Message +} + +// ExpungeUpdate is an expunge update. +type ExpungeUpdate struct { + Update + SeqNum uint32 +} + +// Updater is a Backend that implements Updater is able to send unilateral +// backend updates. Backends not implementing this interface don't correctly +// send unilateral updates, for instance if a user logs in from two connections +// and deletes a message from one of them, the over is not aware that such a +// mesage has been deleted. More importantly, backends implementing Updater can +// notify the user for external updates such as new message notifications. +type Updater interface { + // Updates returns a set of channels where updates are sent to. + Updates() <-chan interface{} +} + +// UpdaterMailbox is a Mailbox that implements UpdaterMailbox is able to poll +// updates for new messages or message status updates during a period of +// inactivity. +type UpdaterMailbox interface { + // Poll requests mailbox updates. + Poll() error +} + +// WaitUpdates returns a channel that's closed when all provided updates have +// been dispatched to all clients. It panics if one of the provided value is +// not an update. +func WaitUpdates(updates ...interface{}) <-chan struct{} { + done := make(chan struct{}) + + var chs []<-chan struct{} + for _, u := range updates { + uu, ok := u.(interface { + Done() <-chan struct{} + }) + if !ok { + panic("imap: cannot wait for update: provided value is not a valid update") + } + + chs = append(chs, uu.Done()) + } + + go func() { + // Wait for all updates to be sent + for _, ch := range chs { + <-ch + } + close(done) + }() + + return done +} diff --git a/vendor/github.com/emersion/go-imap/backend/user.go b/vendor/github.com/emersion/go-imap/backend/user.go new file mode 100644 index 0000000..afcd014 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/backend/user.go @@ -0,0 +1,92 @@ +package backend + +import "errors" + +var ( + // ErrNoSuchMailbox is returned by User.GetMailbox, User.DeleteMailbox and + // User.RenameMailbox when retrieving, deleting or renaming a mailbox that + // doesn't exist. + ErrNoSuchMailbox = errors.New("No such mailbox") + // ErrMailboxAlreadyExists is returned by User.CreateMailbox and + // User.RenameMailbox when creating or renaming mailbox that already exists. + ErrMailboxAlreadyExists = errors.New("Mailbox already exists") +) + +// User represents a user in the mail storage system. A user operation always +// deals with mailboxes. +type User interface { + // Username returns this user's username. + Username() string + + // ListMailboxes returns a list of mailboxes belonging to this user. If + // subscribed is set to true, only returns subscribed mailboxes. + ListMailboxes(subscribed bool) ([]Mailbox, error) + + // GetMailbox returns a mailbox. If it doesn't exist, it returns + // ErrNoSuchMailbox. + GetMailbox(name string) (Mailbox, error) + + // CreateMailbox creates a new mailbox. + // + // If the mailbox already exists, an error must be returned. If the mailbox + // name is suffixed with the server's hierarchy separator character, this is a + // declaration that the client intends to create mailbox names under this name + // in the hierarchy. + // + // If the server's hierarchy separator character appears elsewhere in the + // name, the server SHOULD create any superior hierarchical names that are + // needed for the CREATE command to be successfully completed. In other + // words, an attempt to create "foo/bar/zap" on a server in which "/" is the + // hierarchy separator character SHOULD create foo/ and foo/bar/ if they do + // not already exist. + // + // If a new mailbox is created with the same name as a mailbox which was + // deleted, its unique identifiers MUST be greater than any unique identifiers + // used in the previous incarnation of the mailbox UNLESS the new incarnation + // has a different unique identifier validity value. + CreateMailbox(name string) error + + // DeleteMailbox permanently remove the mailbox with the given name. It is an + // error to // attempt to delete INBOX or a mailbox name that does not exist. + // + // The DELETE command MUST NOT remove inferior hierarchical names. For + // example, if a mailbox "foo" has an inferior "foo.bar" (assuming "." is the + // hierarchy delimiter character), removing "foo" MUST NOT remove "foo.bar". + // + // The value of the highest-used unique identifier of the deleted mailbox MUST + // be preserved so that a new mailbox created with the same name will not + // reuse the identifiers of the former incarnation, UNLESS the new incarnation + // has a different unique identifier validity value. + DeleteMailbox(name string) error + + // RenameMailbox changes the name of a mailbox. It is an error to attempt to + // rename from a mailbox name that does not exist or to a mailbox name that + // already exists. + // + // If the name has inferior hierarchical names, then the inferior hierarchical + // names MUST also be renamed. For example, a rename of "foo" to "zap" will + // rename "foo/bar" (assuming "/" is the hierarchy delimiter character) to + // "zap/bar". + // + // If the server's hierarchy separator character appears in the name, the + // server SHOULD create any superior hierarchical names that are needed for + // the RENAME command to complete successfully. In other words, an attempt to + // rename "foo/bar/zap" to baz/rag/zowie on a server in which "/" is the + // hierarchy separator character SHOULD create baz/ and baz/rag/ if they do + // not already exist. + // + // The value of the highest-used unique identifier of the old mailbox name + // MUST be preserved so that a new mailbox created with the same name will not + // reuse the identifiers of the former incarnation, UNLESS the new incarnation + // has a different unique identifier validity value. + // + // Renaming INBOX is permitted, and has special behavior. It moves all + // messages in INBOX to a new mailbox with the given name, leaving INBOX + // empty. If the server implementation supports inferior hierarchical names + // of INBOX, these are unaffected by a rename of INBOX. + RenameMailbox(existingName, newName string) error + + // Logout is called when this User will no longer be used, likely because the + // client closed the connection. + Logout() error +} 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..b48df86 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/command.go @@ -0,0 +1,89 @@ +package imap + +import ( + "errors" + "strings" +) + +// IMAP4rev1 commands. +const ( + Capability string = "CAPABILITY" + Noop = "NOOP" + Logout = "LOGOUT" + StartTLS = "STARTTLS" + + Authenticate = "AUTHENTICATE" + Login = "LOGIN" + + Select = "SELECT" + Examine = "EXAMINE" + Create = "CREATE" + Delete = "DELETE" + Rename = "RENAME" + Subscribe = "SUBSCRIBE" + Unsubscribe = "UNSUBSCRIBE" + List = "LIST" + Lsub = "LSUB" + Status = "STATUS" + Append = "APPEND" + + Check = "CHECK" + Close = "CLOSE" + Expunge = "EXPUNGE" + Search = "SEARCH" + Fetch = "FETCH" + Store = "STORE" + Copy = "COPY" + Uid = "UID" +) + +// A value that can be converted to a command. +type Commander interface { + Command() *Command +} + +// A command. +type Command struct { + // The command tag. It acts as a unique identifier for this command. 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{}{tag, 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..35f0607 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/append.go @@ -0,0 +1,95 @@ +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.Encoder.String(cmd.Mailbox) + args = append(args, mailbox) + + if cmd.Flags != nil { + flags := make([]interface{}, len(cmd.Flags)) + for i, flag := range cmd.Flags { + flags[i] = flag + } + args = append(args, flags) + } + + if !cmd.Date.IsZero() { + args = append(args, cmd.Date) + } + + args = append(args, cmd.Message) + + return &imap.Command{ + Name: imap.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, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err = utf7.Decoder.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 { + date, ok := fields[0].(string) + if !ok { + return errors.New("Date must be a string") + } + 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..3ac9ee5 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/authenticate.go @@ -0,0 +1,83 @@ +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 +} + +func (cmd *Authenticate) Command() *imap.Command { + return &imap.Command{ + Name: imap.Authenticate, + Arguments: []interface{}{cmd.Mechanism}, + } +} + +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) + 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) + + var response []byte + for { + challenge, done, err := sasl.Next(response) + if err != nil || done { + return err + } + + encoded := base64.StdEncoding.EncodeToString(challenge) + cont := &imap.ContinuationResp{Info: encoded} + if err := conn.WriteResp(cont); err != nil { + return err + } + + scanner.Scan() + if err := scanner.Err(); err != nil { + return err + } + + encoded = scanner.Text() + if encoded != "" { + 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..6d385a1 --- /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: imap.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..716ed3f --- /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: imap.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..a66898b --- /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: imap.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..aa5e3c2 --- /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.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: imap.Copy, + Arguments: []interface{}{cmd.SeqSet, 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.NewSeqSet(seqSet); err != nil { + return err + } else { + cmd.SeqSet = seqSet + } + + if mailbox, ok := fields[1].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.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..e2590ef --- /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.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: imap.Create, + Arguments: []interface{}{mailbox}, + } +} + +func (cmd *Create) Parse(fields []interface{}) error { + if len(fields) < 1 { + return errors.New("No enough arguments") + } + + if mailbox, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.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..9fa94cb --- /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.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: imap.Delete, + Arguments: []interface{}{mailbox}, + } +} + +func (cmd *Delete) Parse(fields []interface{}) error { + if len(fields) < 1 { + return errors.New("No enough arguments") + } + + if mailbox, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { + return err + } else { + cmd.Mailbox = imap.CanonicalMailboxName(mailbox) + } + + return nil +} 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..acf88f3 --- /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: imap.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..45a4fd9 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/fetch.go @@ -0,0 +1,70 @@ +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 []string +} + +func (cmd *Fetch) Command() *imap.Command { + items := make([]interface{}, len(cmd.Items)) + for i, item := range cmd.Items { + if section, err := imap.NewBodySectionName(item); err == nil { + items[i] = section + } else { + items[i] = item + } + } + + return &imap.Command{ + Name: imap.Fetch, + Arguments: []interface{}{cmd.SeqSet, items}, + } +} + +func (cmd *Fetch) Parse(fields []interface{}) error { + if len(fields) < 2 { + return errors.New("No enough arguments") + } + + seqset, ok := fields[0].(string) + if !ok { + return errors.New("Sequence set must be a string") + } + + var err error + if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil { + return err + } + + switch items := fields[1].(type) { + case string: // A macro or a single item + switch strings.ToUpper(items) { + case "ALL": + cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE"} + case "FAST": + cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE"} + case "FULL": + cmd.Items = []string{"FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY"} + default: + cmd.Items = []string{strings.ToUpper(items)} + } + case []interface{}: // A list of items + cmd.Items = make([]string, len(items)) + for i, v := range items { + item, _ := v.(string) + cmd.Items[i] = strings.ToUpper(item) + } + 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/list.go b/vendor/github.com/emersion/go-imap/commands/list.go new file mode 100644 index 0000000..fad71c2 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/list.go @@ -0,0 +1,57 @@ +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 := imap.List + if cmd.Subscribed { + name = imap.Lsub + } + + ref, _ := utf7.Encoder.String(cmd.Reference) + mailbox, _ := utf7.Encoder.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") + } + + if mailbox, ok := fields[0].(string); !ok { + return errors.New("Reference name must be a string") + } else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { + return err + } else { + // TODO: canonical mailbox path + cmd.Reference = imap.CanonicalMailboxName(mailbox) + } + + if mailbox, ok := fields[1].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.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..3d9d674 --- /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: imap.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 ok bool + if cmd.Username, ok = fields[0].(string); !ok { + return errors.New("Username is not a string") + } + if cmd.Password, ok = fields[1].(string); !ok { + return errors.New("Password is not a string") + } + + 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..a467fa1 --- /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: imap.Logout, + } +} + +func (c *Logout) Parse(fields []interface{}) error { + return nil +} 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..af580f0 --- /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: imap.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..010535c --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/rename.go @@ -0,0 +1,48 @@ +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 { + existingName, _ := utf7.Encoder.String(cmd.Existing) + newName, _ := utf7.Encoder.String(cmd.New) + + return &imap.Command{ + Name: imap.Rename, + Arguments: []interface{}{existingName, newName}, + } +} + +func (cmd *Rename) Parse(fields []interface{}) error { + if len(fields) < 2 { + return errors.New("No enough arguments") + } + + if existingName, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if existingName, err := utf7.Decoder.String(existingName); err != nil { + return err + } else { + cmd.Existing = imap.CanonicalMailboxName(existingName) + } + + if newName, ok := fields[1].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if newName, err := utf7.Decoder.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..e01ef0e --- /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, "CHARSET", cmd.Charset) + } + args = append(args, cmd.Criteria.Format()...) + + return &imap.Command{ + Name: imap.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..2c34146 --- /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 := imap.Select + if cmd.ReadOnly { + name = imap.Examine + } + + mailbox, _ := utf7.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: name, + Arguments: []interface{}{mailbox}, + } +} + +func (cmd *Select) Parse(fields []interface{}) error { + if len(fields) < 1 { + return errors.New("No enough arguments") + } + + if mailbox, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.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..fa36f66 --- /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: imap.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..4d000c3 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/status.go @@ -0,0 +1,55 @@ +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 []string +} + +func (cmd *Status) Command() *imap.Command { + mailbox, _ := utf7.Encoder.String(cmd.Mailbox) + + items := make([]interface{}, len(cmd.Items)) + for i, f := range cmd.Items { + items[i] = f + } + + return &imap.Command{ + Name: imap.Status, + Arguments: []interface{}{mailbox, items}, + } +} + +func (cmd *Status) Parse(fields []interface{}) error { + if len(fields) < 2 { + return errors.New("No enough arguments") + } + + if mailbox, ok := fields[0].(string); !ok { + return errors.New("Mailbox name must be a string") + } else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { + return err + } else { + cmd.Mailbox = imap.CanonicalMailboxName(mailbox) + } + + if items, ok := fields[1].([]interface{}); !ok { + return errors.New("Items must be a list") + } else { + cmd.Items = make([]string, len(items)) + for i, v := range items { + item, _ := v.(string) + cmd.Items[i] = strings.ToUpper(item) + } + } + + 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..a8f6944 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/store.go @@ -0,0 +1,45 @@ +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 string + Value interface{} +} + +func (cmd *Store) Command() *imap.Command { + return &imap.Command{ + Name: imap.Store, + Arguments: []interface{}{cmd.SeqSet, cmd.Item, cmd.Value}, + } +} + +func (cmd *Store) Parse(fields []interface{}) (err error) { + if len(fields) < 3 { + return errors.New("No enough arguments") + } + + seqset, ok := fields[0].(string) + if !ok { + return errors.New("Invalid sequence set") + } + if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil { + return err + } + + if cmd.Item, ok = fields[1].(string); !ok { + return errors.New("Item name must be a string") + } + cmd.Item = strings.ToUpper(cmd.Item) + + cmd.Value = fields[2] + + return +} 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..b740f34 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/commands/subscribe.go @@ -0,0 +1,71 @@ +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.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: imap.Subscribe, + Arguments: []interface{}{mailbox}, + } +} + +func (cmd *Subscribe) Parse(fields []interface{}) (err error) { + if len(fields) < 0 { + return errors.New("No enogh arguments") + } + + mailbox, ok := fields[0].(string) + if !ok { + return errors.New("Mailbox name must be a string") + } + + if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil { + return err + } + + return +} + +// An UNSUBSCRIBE command. +// See RFC 3501 section 6.3.7 +type Unsubscribe struct { + Mailbox string +} + +func (cmd *Unsubscribe) Command() *imap.Command { + mailbox, _ := utf7.Encoder.String(cmd.Mailbox) + + return &imap.Command{ + Name: imap.Unsubscribe, + Arguments: []interface{}{mailbox}, + } +} + +func (cmd *Unsubscribe) Parse(fields []interface{}) (err error) { + if len(fields) < 0 { + return errors.New("No enogh arguments") + } + + mailbox, ok := fields[0].(string) + if !ok { + return errors.New("Mailbox name must be a string") + } + + if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil { + return err + } + + return +} 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..75b97c4 --- /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{}{inner.Name} + args = append(args, inner.Arguments...) + + return &imap.Command{ + Name: imap.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/conn.go b/vendor/github.com/emersion/go-imap/conn.go new file mode 100644 index 0000000..1d9469f --- /dev/null +++ b/vendor/github.com/emersion/go-imap/conn.go @@ -0,0 +1,197 @@ +package imap + +import ( + "bufio" + "io" + "net" +) + +// 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 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} +} + +// An IMAP connection. +type Conn struct { + net.Conn + *Reader + *Writer + + br *bufio.Reader + bw *bufio.Writer + + waits chan struct{} + + // 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) 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 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), + } + } +} + +// 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 { + if err := c.Writer.Flush(); err != nil { + return err + } + return nil +} + +// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted +// tunnel. +func (c *Conn) Upgrade(upgrader ConnUpgrader) error { + // Flush all buffered data + if err := c.Flush(); err != nil { + return err + } + + // Block reads and writes during the upgrading process + c.waits = make(chan struct{}) + defer close(c.waits) + + upgraded, err := upgrader(c.Conn) + if err != nil { + return err + } + + c.Conn = upgraded + c.init() + return nil +} + +// Wait waits for the connection to be ready for reads and writes. +func (c *Conn) Wait() { + if c.waits != nil { + <-c.waits + } +} + +// 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..176be73 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/date.go @@ -0,0 +1,72 @@ +package imap + +import ( + "fmt" + "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:05 -0700 (MST)", + "_2 Jan 2006 15:04 -0700", + "_2 Jan 2006 15:04 MST", + "_2 Jan 2006 15:04 -0700 (MST)", + "_2 Jan 06 15:04:05 -0700", + "_2 Jan 06 15:04:05 MST", + "_2 Jan 06 15:04:05 -0700 (MST)", + "_2 Jan 06 15:04 -0700", + "_2 Jan 06 15:04 MST", + "_2 Jan 06 15:04 -0700 (MST)", + "Mon, _2 Jan 2006 15:04:05 -0700", + "Mon, _2 Jan 2006 15:04:05 MST", + "Mon, _2 Jan 2006 15:04:05 -0700 (MST)", + "Mon, _2 Jan 2006 15:04 -0700", + "Mon, _2 Jan 2006 15:04 MST", + "Mon, _2 Jan 2006 15:04 -0700 (MST)", + "Mon, _2 Jan 06 15:04:05 -0700", + "Mon, _2 Jan 06 15:04:05 MST", + "Mon, _2 Jan 06 15:04:05 -0700 (MST)", + "Mon, _2 Jan 06 15:04 -0700", + "Mon, _2 Jan 06 15:04 MST", + "Mon, _2 Jan 06 15:04 -0700 (MST)", +} + +// 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) { + 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/handle.go b/vendor/github.com/emersion/go-imap/handle.go new file mode 100644 index 0000000..3cf4a0d --- /dev/null +++ b/vendor/github.com/emersion/go-imap/handle.go @@ -0,0 +1,121 @@ +package imap + +import ( + "sync" +) + +// A response that can be either accepted or rejected by a handler. +type RespHandle struct { + Resp interface{} + Accepts chan bool +} + +// Accept this response. This means that the handler will process it. +func (h *RespHandle) Accept() { + h.Accepts <- true +} + +// Reject this response. The handler cannot process it. +func (h *RespHandle) Reject() { + h.Accepts <- false +} + +// Accept this response if it has the specified name. If not, reject it. +func (h *RespHandle) AcceptNamedResp(name string) (fields []interface{}, accepted bool) { + res, ok := h.Resp.(*Resp) + if !ok || len(res.Fields) == 0 { + h.Reject() + return + } + + n, ok := res.Fields[0].(string) + if !ok || n != name { + h.Reject() + return + } + + h.Accept() + + fields = res.Fields[1:] + accepted = true + return +} + +// Delivers responses to handlers. +type RespHandler chan *RespHandle + +// Handles responses from a handler. +type RespHandlerFrom interface { + HandleFrom(hdlr RespHandler) error +} + +// A RespHandlerFrom that forwards responses to multiple RespHandler. +type MultiRespHandler struct { + handlers []RespHandler + locker sync.Locker +} + +func NewMultiRespHandler() *MultiRespHandler { + return &MultiRespHandler{ + locker: &sync.Mutex{}, + } +} + +func (mh *MultiRespHandler) HandleFrom(ch RespHandler) error { + for rh := range ch { + mh.locker.Lock() + + accepted := false + for i := len(mh.handlers) - 1; i >= 0; i-- { + hdlr := mh.handlers[i] + + rh := &RespHandle{ + Resp: rh.Resp, + Accepts: make(chan bool), + } + + hdlr <- rh + if accepted = <-rh.Accepts; accepted { + break + } + } + + mh.locker.Unlock() + + if accepted { + rh.Accept() + } else { + rh.Reject() + } + } + + mh.locker.Lock() + for _, hdlr := range mh.handlers { + close(hdlr) + } + mh.handlers = nil + mh.locker.Unlock() + + return nil +} + +func (mh *MultiRespHandler) Add(hdlr RespHandler) { + if hdlr == nil { + return + } + + mh.locker.Lock() + mh.handlers = append(mh.handlers, hdlr) + mh.locker.Unlock() +} + +func (mh *MultiRespHandler) Del(hdlr RespHandler) { + mh.locker.Lock() + for i, h := range mh.handlers { + if h == hdlr { + close(hdlr) + mh.handlers = append(mh.handlers[:i], mh.handlers[i+1:]...) + } + } + mh.locker.Unlock() +} 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..8292f88 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/imap.go @@ -0,0 +1,28 @@ +// Package imap implements IMAP4rev1 (RFC 3501). +package imap + +import ( + "io" +) + +// 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" + +// 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..6e3963d --- /dev/null +++ b/vendor/github.com/emersion/go-imap/mailbox.go @@ -0,0 +1,246 @@ +package imap + +import ( + "errors" + "strings" + "sync" + + "github.com/emersion/go-imap/utf7" +) + +// The primary mailbox, as defined in RFC 3501 section 5.1. +const InboxName = "INBOX" + +// 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.ToUpper(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" +) + +// 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") + } + + info.Attributes, _ = ParseStringList(fields[0]) + + info.Delimiter, _ = fields[1].(string) + + name, _ := fields[2].(string) + info.Name, _ = utf7.Decoder.String(name) + info.Name = CanonicalMailboxName(info.Name) + + return nil +} + +// Format mailbox info to fields. +func (info *MailboxInfo) Format() []interface{} { + name, _ := utf7.Encoder.String(info.Name) + // Thunderbird doesn't understand delimiters if not quoted + return []interface{}{FormatStringList(info.Attributes), Quoted(info.Delimiter), 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 strings.HasPrefix(pattern, info.Delimiter) { + reference = "" + pattern = strings.TrimPrefix(pattern, info.Delimiter) + } + if reference != "" { + if !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) +} + +// Mailbox status items. +const ( + MailboxFlags = "FLAGS" + MailboxPermanentFlags = "PERMANENTFLAGS" + + // Defined in RFC 3501 section 6.3.10. + MailboxMessages = "MESSAGES" + MailboxRecent = "RECENT" + MailboxUnseen = "UNSEEN" + MailboxUidNext = "UIDNEXT" + MailboxUidValidity = "UIDVALIDITY" +) + +// 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[string]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 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 +} + +// Create a new mailbox status that will contain the specified items. +func NewMailboxStatus(name string, items []string) *MailboxStatus { + status := &MailboxStatus{ + Name: name, + Items: make(map[string]interface{}), + } + + for _, k := range items { + status.Items[k] = nil + } + + return status +} + +func (status *MailboxStatus) Parse(fields []interface{}) error { + status.Items = make(map[string]interface{}) + + var k string + for i, f := range fields { + if i%2 == 0 { + var ok bool + if k, ok = f.(string); !ok { + return errors.New("Key is not a string") + } + k = strings.ToUpper(k) + } else { + status.Items[k] = nil + + var err error + switch k { + case MailboxMessages: + status.Messages, err = ParseNumber(f) + case MailboxRecent: + status.Recent, err = ParseNumber(f) + case MailboxUnseen: + status.Unseen, err = ParseNumber(f) + case MailboxUidNext: + status.UidNext, err = ParseNumber(f) + case MailboxUidValidity: + status.UidValidity, 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 MailboxMessages: + v = status.Messages + case MailboxRecent: + v = status.Recent + case MailboxUnseen: + v = status.Unseen + case MailboxUidNext: + v = status.UidNext + case MailboxUidValidity: + v = status.UidValidity + } + + fields = append(fields, k, v) + } + return fields +} 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..f89a152 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/message.go @@ -0,0 +1,1048 @@ +package imap + +import ( + "bytes" + "errors" + "fmt" + "io" + "mime" + "strconv" + "strings" + "time" +) + +// Message flags, defined in RFC 3501 section 2.3.2. +const ( + SeenFlag = "\\Seen" + AnsweredFlag = "\\Answered" + FlaggedFlag = "\\Flagged" + DeletedFlag = "\\Deleted" + DraftFlag = "\\Draft" + RecentFlag = "\\Recent" +) + +var flags = []string{ + SeenFlag, + AnsweredFlag, + FlaggedFlag, + DeletedFlag, + DraftFlag, + RecentFlag, +} + +// Message attributes that can be fetched, defined in RFC 3501 section 6.4.5. +// Attributes that fetches the message contents are defined with +// BodySectionName. +const ( + // Non-extensible form of BODYSTRUCTURE. + BodyMsgAttr = "BODY" + // MIME body structure of the message. + BodyStructureMsgAttr = "BODYSTRUCTURE" + // The envelope structure of the message. + EnvelopeMsgAttr = "ENVELOPE" + // The flags that are set for the message. + FlagsMsgAttr = "FLAGS" + // The internal date of the message. + InternalDateMsgAttr = "INTERNALDATE" + // The RFC 822 size of the message. + SizeMsgAttr = "RFC822.SIZE" + // The unique identifier for the message. + UidMsgAttr = "UID" +) + +// Part specifiers described in RFC 3501 page 55. +const ( + // Refers to the entire part, including headers. + EntireSpecifier = "" + // 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" +) + +// 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 { + flag = strings.ToLower(flag) + for _, f := range flags { + if strings.ToLower(f) == flag { + return f + } + } + return flag +} + +func ParseParamList(fields []interface{}) (map[string]string, error) { + params := make(map[string]string) + + var k string + for i, f := range fields { + p, ok := f.(string) + if !ok { + return nil, errors.New("Parameter list contains a non-string") + } + + 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 parseHeaderParamList(fields []interface{}) (map[string]string, error) { + params, err := ParseParamList(fields) + if err != nil { + return nil, err + } + + for k, v := range params { + 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[string]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 []string +} + +// Create a new empty message that will contain the specified items. +func NewMessage(seqNum uint32, items []string) *Message { + msg := &Message{ + SeqNum: seqNum, + Items: make(map[string]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[string]interface{}) + m.Body = map[*BodySectionName]Literal{} + m.itemsOrder = nil + + var k string + for i, f := range fields { + if i%2 == 0 { // It's a key + var ok bool + if k, ok = f.(string); !ok { + return errors.New("Key is not a string") + } + k = strings.ToUpper(k) + } else { // It's a value + m.Items[k] = nil + m.itemsOrder = append(m.itemsOrder, k) + + switch k { + case BodyMsgAttr, BodyStructureMsgAttr: + bs, ok := f.([]interface{}) + if !ok { + return errors.New("BODYSTRUCTURE is not a list") + } + + m.BodyStructure = &BodyStructure{Extended: k == BodyStructureMsgAttr} + if err := m.BodyStructure.Parse(bs); err != nil { + return err + } + case EnvelopeMsgAttr: + env, ok := f.([]interface{}) + if !ok { + return errors.New("ENVELOPE is not a list") + } + + m.Envelope = &Envelope{} + if err := m.Envelope.Parse(env); err != nil { + return err + } + case FlagsMsgAttr: + flags, ok := f.([]interface{}) + if !ok { + return errors.New("FLAGS is not a list") + } + + m.Flags = make([]string, len(flags)) + for i, flag := range flags { + s, _ := flag.(string) + m.Flags[i] = CanonicalFlag(s) + } + case InternalDateMsgAttr: + date, _ := f.(string) + m.InternalDate, _ = time.Parse(DateTimeLayout, date) + case SizeMsgAttr: + m.Size, _ = ParseNumber(f) + case UidMsgAttr: + m.Uid, _ = ParseNumber(f) + default: + // Likely to be a section of the body + // First check that the section name is correct + if section, err := NewBodySectionName(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 string) []interface{} { + v := m.Items[k] + var kk interface{} = k + + switch strings.ToUpper(k) { + case BodyMsgAttr, BodyStructureMsgAttr: + // Extension data is only returned with the BODYSTRUCTURE fetch + m.BodyStructure.Extended = k == BodyStructureMsgAttr + v = m.BodyStructure.Format() + case EnvelopeMsgAttr: + v = m.Envelope.Format() + case FlagsMsgAttr: + v = FormatStringList(m.Flags) + case InternalDateMsgAttr: + v = m.InternalDate + case SizeMsgAttr: + v = m.Size + case UidMsgAttr: + 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[string]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 +} + +// Get the body section with the specified name. Returns nil if it's not found. +func (m *Message) GetBody(s string) Literal { + for section, body := range m.Body { + if section.value == s { + 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 string +} + +func (section *BodySectionName) parse(s string) (err error) { + section.value = 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) + var fields []interface{} + if fields, err = r.ReadFields(); err != nil { + return + } + + section.BodyPartName = &BodyPartName{} + if err = section.BodyPartName.parse(fields); err != nil { + return + } + + 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) String() (s string) { + 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 +} + +func (section *BodySectionName) resp() *BodySectionName { + var reset bool + + if section.Peek != false { + section.Peek = false + reset = true + } + + if len(section.Partial) == 2 { + section.Partial = []int{section.Partial[0]} + reset = true + } + + if reset && !strings.HasPrefix(section.value, "RFC822") { + section.value = "" // Reset cached value + } + + return section +} + +// 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] +} + +// Parse a body section name. +func NewBodySectionName(s string) (section *BodySectionName, err error) { + section = &BodySectionName{} + err = section.parse(s) + return +} + +// A body part name. +type BodyPartName struct { + // The specifier of the requested part. + Specifier string + // 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 + for i, node := range path { + if node == "" || node == HeaderSpecifier || node == MimeSpecifier || node == TextSpecifier { + part.Specifier = node + end = i + 1 + break + } + + 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() (s string) { + path := make([]string, len(part.Path)) + for i, index := range part.Path { + path[i] = strconv.Itoa(index) + } + + if part.Specifier != "" { + path = append(path, 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 +} + +// 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 +} + +// 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 f, ok := fields[0].(string); ok { + addr.PersonalName, _ = decodeHeader(f) + } + if f, ok := fields[1].(string); ok { + addr.AtDomainList = f + } + if f, ok := fields[2].(string); ok { + addr.MailboxName = f + } + if f, ok := fields[3].(string); ok { + addr.HostName = f + } + + 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) { + addrs = make([]*Address, len(fields)) + + for i, f := range fields { + if addrFields, ok := f.([]interface{}); ok { + addr := &Address{} + if err := addr.Parse(addrFields); err == nil { + addrs[i] = addr + } + } + } + + return +} + +// Format an address list to fields. +func FormatAddressList(addrs []*Address) (fields []interface{}) { + fields = make([]interface{}, len(addrs)) + + for i, addr := range addrs { + fields[i] = addr.Format() + } + + return +} + +// 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, ok := fields[1].(string); ok { + 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{}) { + return []interface{}{ + envelopeDateTime(e.Date), + encodeHeader(e.Subject), + FormatAddressList(e.From), + FormatAddressList(e.Sender), + FormatAddressList(e.ReplyTo), + FormatAddressList(e.To), + FormatAddressList(e.Cc), + FormatAddressList(e.Bcc), + e.InReplyTo, + e.MessageId, + } +} + +// A body structure. +// See RFC 3501 page 74. +type BodyStructure struct { + // Basic fields + + // The MIME type. + MimeType string + // The MIME subtype. + 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) + } + 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, _ = fields[0].(string) + bs.MimeSubType, _ = fields[1].(string) + + params, _ := fields[2].([]interface{}) + bs.Params, _ = parseHeaderParamList(params) + + bs.Id, _ = fields[3].(string) + if desc, ok := fields[4].(string); ok { + bs.Description, _ = decodeHeader(desc) + } + bs.Encoding, _ = fields[5].(string) + bs.Size, _ = ParseNumber(fields[6]) + + end := 7 + + // Type-specific fields + if bs.MimeType == "message" && 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 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) + } + 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 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 bs.MimeType == "message" && 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 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 +} 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..1187ff8 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/read.go @@ -0,0 +1,430 @@ +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 +} + +// Convert a field to a number. +func ParseNumber(f interface{}) (uint32, error) { + // Useful for tests + if n, ok := f.(uint32); ok { + return n, nil + } + + s, ok := f.(string) + if !ok { + return 0, newParseError("number is not a string") + } + + nbr, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return 0, &parseError{err} + } + + return uint32(nbr), nil +} + +// Convert a field list to a string list. +func ParseStringList(f interface{}) ([]string, error) { + fields, ok := f.([]interface{}) + if !ok { + return nil, newParseError("string list is not a list") + } + + list := make([]string, len(fields)) + for i, f := range fields { + var ok bool + if list[i], ok = f.(string); !ok { + return nil, newParseError("string list contains a non-string") + } + } + 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("not a space") + } + return nil +} + +func (r *Reader) ReadCrlf() (err error) { + var char rune + + if char, _, err = r.ReadRune(); err != nil { + return + } + if char != cr { + err = newParseError("line doesn't end with a CR") + } + + 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 { + 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 == "NIL" { + 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) + 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 { + 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 == listEnd || char == respCodeEnd { + 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 string, 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 + } + + code, ok := fields[0].(string) + if !ok { + err = newParseError("response code doesn't start with a string atom") + return + } + if code == "" { + err = newParseError("response code is empty") + return + } + + 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(cr)) + if err != nil { + return + } + info = strings.TrimSuffix(info, string(cr)) + info = strings.TrimLeft(info, " ") + + var char rune + if char, _, err = r.ReadRune(); err != nil { + return + } + if char != lf { + err = newParseError("line doesn't end with a LF") + } + 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..092d5b9 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/response.go @@ -0,0 +1,156 @@ +package imap + +import ( + "errors" +) + +// A value that can be converted to a Resp. +type Responser interface { + Response() *Resp +} + +// A response. +// See RFC 3501 section 2.2.2 +type Resp struct { + // The response tag. Can be either "" for untagged responses, "+" for continuation + // requests or a previous command's tag. + Tag string + // The parsed response fields. + Fields []interface{} +} + +func (r *Resp) WriteTo(w *Writer) error { + tag := r.Tag + if tag == "" { + tag = "*" + } + + fields := []interface{}{tag} + fields = append(fields, r.Fields...) + return w.writeLine(fields...) +} + +// Create a new untagged response. +func NewUntaggedResp(fields []interface{}) *Resp { + return &Resp{ + Tag: "*", + Fields: fields, + } +} + +// A continuation request. +type ContinuationResp struct { + // The info message sent with the continuation request. + Info string +} + +func (r *ContinuationResp) 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() +} + +// Read a single response from a Reader. Returns either a continuation request, +// a status response or a raw response. +func ReadResp(r *Reader) (out interface{}, err error) { + atom, err := r.ReadAtom() + if err != nil { + return + } + tag, ok := atom.(string) + if !ok { + err = errors.New("Response tag is not an atom") + return + } + + if tag == "+" { + if err := r.ReadSp(); err != nil { + r.UnreadRune() + } + + res := &ContinuationResp{} + res.Info, err = r.ReadInfo() + if err != nil { + return + } + + out = res + return + } + + if err = r.ReadSp(); err != nil { + return + } + + // Can be either data or status + // Try to parse a status + isStatus := false + 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) + if status == StatusOk || status == StatusNo || status == StatusBad || status == StatusPreauth || status == StatusBye { + isStatus = true + + res := &StatusResp{ + Tag: tag, + Type: status, + } + + var char rune + if char, _, err = r.ReadRune(); err != nil { + return + } + r.UnreadRune() + + if char == '[' { + // Contains code & arguments + res.Code, res.Arguments, err = r.ReadRespCode() + if err != nil { + return + } + } + + res.Info, err = r.ReadInfo() + if err != nil { + return + } + + out = res + } + } + } else { + r.UnreadRune() + } + } else { + r.UnreadRune() + } + + if !isStatus { + // Not a status so it's data + res := &Resp{Tag: tag} + + var remaining []interface{} + remaining, err = r.ReadLine() + if err != nil { + return + } + + res.Fields = append(fields, remaining...) + out = res + } + + return +} 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..33fa730 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/authenticate.go @@ -0,0 +1,72 @@ +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 + Writer *imap.Writer +} + +func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) { + w := r.Writer + + // Cancel auth if an error occurs + defer (func() { + if err != nil { + w.Write([]byte("*\r\n")) + w.Flush() + } + })() + + for h := range hdlr { + cont, ok := h.Resp.(*imap.ContinuationResp) + if !ok { + h.Reject() + continue + } + h.Accept() + + // Empty challenge, send initial response as stated in RFC 2222 section 5.1 + if cont.Info == "" && r.InitialResponse != nil { + encoded := base64.StdEncoding.EncodeToString(r.InitialResponse) + if _, err = w.Write([]byte(encoded + "\r\n")); err != nil { + return + } + if err = w.Flush(); err != nil { + return + } + + r.InitialResponse = nil + continue + } + + var challenge []byte + challenge, err = base64.StdEncoding.DecodeString(cont.Info) + if err != nil { + return + } + + var res []byte + res, err = r.Mechanism.Next(challenge) + if err != nil { + return + } + + encoded := base64.StdEncoding.EncodeToString(res) + if _, err = w.Write([]byte(encoded + "\r\n")); err != nil { + return + } + if err = w.Flush(); err != nil { + return + } + } + + return +} 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..9290223 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/capability.go @@ -0,0 +1,39 @@ +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) HandleFrom(hdlr imap.RespHandler) (err error) { + for h := range hdlr { + caps, ok := h.AcceptNamedResp(imap.Capability) + if !ok { + continue + } + + r.Caps = make([]string, len(caps)) + for i, c := range caps { + r.Caps[i], _ = c.(string) + } + + return + } + + return +} + +func (r *Capability) WriteTo(w *imap.Writer) error { + fields := []interface{}{imap.Capability} + for _, cap := range r.Caps { + fields = append(fields, cap) + } + + res := &imap.Resp{Fields: fields} + return res.WriteTo(w) +} diff --git a/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..541b9dd --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/expunge.go @@ -0,0 +1,45 @@ +package responses + +import ( + "github.com/emersion/go-imap" +) + +// An EXPUNGE response. +// See RFC 3501 section 7.4.1 +type Expunge struct { + SeqNums chan uint32 +} + +func (r *Expunge) HandleFrom(hdlr imap.RespHandler) error { + defer close(r.SeqNums) + + for h := range hdlr { + res, ok := h.Resp.(*imap.Resp) + if !ok || len(res.Fields) < 3 { + h.Reject() + continue + } + if name, ok := res.Fields[1].(string); !ok || name != imap.Expunge { + h.Reject() + continue + } + h.Accept() + + seqNum, _ := imap.ParseNumber(res.Fields[0]) + r.SeqNums <- seqNum + } + + return nil +} + +func (r *Expunge) WriteTo(w *imap.Writer) error { + for seqNum := range r.SeqNums { + res := imap.NewUntaggedResp([]interface{}{seqNum, imap.Expunge}) + + if err := res.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..83d3b15 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/fetch.go @@ -0,0 +1,55 @@ +package responses + +import ( + "github.com/emersion/go-imap" +) + +// A FETCH response. +// See RFC 3501 section 7.4.2 +type Fetch struct { + Messages chan *imap.Message +} + +func (r *Fetch) HandleFrom(hdlr imap.RespHandler) error { + defer close(r.Messages) + + for h := range hdlr { + res, ok := h.Resp.(*imap.Resp) + if !ok || len(res.Fields) < 3 { + h.Reject() + continue + } + if name, ok := res.Fields[1].(string); !ok || name != imap.Fetch { + h.Reject() + continue + } + h.Accept() + + seqNum, _ := imap.ParseNumber(res.Fields[0]) + fields, _ := res.Fields[2].([]interface{}) + + msg := &imap.Message{ + SeqNum: seqNum, + } + + if err := msg.Parse(fields); err != nil { + return err + } + + r.Messages <- msg + } + + return nil +} + +func (r *Fetch) WriteTo(w *imap.Writer) error { + for msg := range r.Messages { + res := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.Fetch, msg.Format()}) + + if err := res.WriteTo(w); err != nil { + return err + } + } + + return nil +} 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..1fad86b --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/list.go @@ -0,0 +1,59 @@ +package responses + +import ( + "github.com/emersion/go-imap" +) + +// 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 imap.Lsub + } else { + return imap.List + } +} + +func (r *List) HandleFrom(hdlr imap.RespHandler) error { + defer close(r.Mailboxes) + + name := r.Name() + + for h := range hdlr { + fields, ok := h.AcceptNamedResp(name) + if !ok { + continue + } + + 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 { + name := r.Name() + + for mbox := range r.Mailboxes { + fields := []interface{}{name} + fields = append(fields, mbox.Format()...) + + res := imap.NewUntaggedResp(fields) + if err := res.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..e02ea96 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/responses.go @@ -0,0 +1,2 @@ +// IMAP responses defined in RFC 3501. +package responses 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..b7115d3 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/search.go @@ -0,0 +1,37 @@ +package responses + +import ( + "github.com/emersion/go-imap" +) + +// A SEARCH response. +// See RFC 3501 section 7.2.5 +type Search struct { + Ids []uint32 +} + +func (r *Search) HandleFrom(hdlr imap.RespHandler) (err error) { + for h := range hdlr { + fields, ok := h.AcceptNamedResp(imap.Search) + if !ok { + continue + } + + for _, f := range fields { + id, _ := imap.ParseNumber(f) + r.Ids = append(r.Ids, id) + } + } + + return +} + +func (r *Search) WriteTo(w *imap.Writer) (err error) { + fields := []interface{}{imap.Search} + for _, id := range r.Ids { + fields = append(fields, id) + } + + res := imap.NewUntaggedResp(fields) + return res.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..72a0ef1 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/select.go @@ -0,0 +1,145 @@ +package responses + +import ( + "fmt" + + "github.com/emersion/go-imap" +) + +// A SELECT response. +type Select struct { + Mailbox *imap.MailboxStatus +} + +func (r *Select) HandleFrom(hdlr imap.RespHandler) (err error) { + if r.Mailbox == nil { + r.Mailbox = &imap.MailboxStatus{} + } + mbox := r.Mailbox + + mbox.Items = make(map[string]interface{}) + for h := range hdlr { + switch res := h.Resp.(type) { + case *imap.Resp: + fields, ok := h.AcceptNamedResp(imap.MailboxFlags) + if !ok { + break + } + + flags, _ := fields[0].([]interface{}) + mbox.Flags, _ = imap.ParseStringList(flags) + mbox.ItemsLocker.Lock() + mbox.Items[imap.MailboxFlags] = nil + mbox.ItemsLocker.Unlock() + case *imap.StatusResp: + if len(res.Arguments) < 1 { + h.Accepts <- false + break + } + + accepted := true + switch res.Code { + case imap.MailboxUnseen: + mbox.Unseen, _ = imap.ParseNumber(res.Arguments[0]) + mbox.ItemsLocker.Lock() + mbox.Items[imap.MailboxUnseen] = nil + mbox.ItemsLocker.Unlock() + case imap.MailboxPermanentFlags: + flags, _ := res.Arguments[0].([]interface{}) + mbox.PermanentFlags, _ = imap.ParseStringList(flags) + mbox.ItemsLocker.Lock() + mbox.Items[imap.MailboxPermanentFlags] = nil + mbox.ItemsLocker.Unlock() + case imap.MailboxUidNext: + mbox.UidNext, _ = imap.ParseNumber(res.Arguments[0]) + mbox.ItemsLocker.Lock() + mbox.Items[imap.MailboxUidNext] = nil + mbox.ItemsLocker.Unlock() + case imap.MailboxUidValidity: + mbox.UidValidity, _ = imap.ParseNumber(res.Arguments[0]) + mbox.ItemsLocker.Lock() + mbox.Items[imap.MailboxUidValidity] = nil + mbox.ItemsLocker.Unlock() + default: + accepted = false + } + h.Accepts <- accepted + } + } + + return +} + +func (r *Select) WriteTo(w *imap.Writer) (err error) { + status := r.Mailbox + + for k := range r.Mailbox.Items { + switch k { + case imap.MailboxFlags: + flags := make([]interface{}, len(status.Flags)) + for i, f := range status.Flags { + flags[i] = f + } + res := imap.NewUntaggedResp([]interface{}{"FLAGS", flags}) + if err = res.WriteTo(w); err != nil { + return + } + case imap.MailboxPermanentFlags: + flags := make([]interface{}, len(status.PermanentFlags)) + for i, f := range status.PermanentFlags { + flags[i] = f + } + statusRes := &imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodePermanentFlags, + Arguments: []interface{}{flags}, + Info: "Flags permitted.", + } + if err = statusRes.WriteTo(w); err != nil { + return + } + case imap.MailboxMessages: + res := imap.NewUntaggedResp([]interface{}{status.Messages, "EXISTS"}) + if err = res.WriteTo(w); err != nil { + return + } + case imap.MailboxRecent: + res := imap.NewUntaggedResp([]interface{}{status.Recent, "RECENT"}) + if err = res.WriteTo(w); err != nil { + return + } + case imap.MailboxUnseen: + statusRes := &imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodeUnseen, + Arguments: []interface{}{status.Unseen}, + Info: fmt.Sprintf("Message %d is first unseen", status.Unseen), + } + if err = statusRes.WriteTo(w); err != nil { + return + } + case imap.MailboxUidNext: + statusRes := &imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodeUidNext, + Arguments: []interface{}{status.UidNext}, + Info: "Predicted next UID", + } + if err = statusRes.WriteTo(w); err != nil { + return + } + case imap.MailboxUidValidity: + statusRes := &imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodeUidValidity, + Arguments: []interface{}{status.UidValidity}, + Info: "UIDs valid", + } + if err = statusRes.WriteTo(w); err != nil { + return + } + } + } + + return +} 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..a20c94c --- /dev/null +++ b/vendor/github.com/emersion/go-imap/responses/status.go @@ -0,0 +1,63 @@ +package responses + +import ( + "errors" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/utf7" +) + +// A STATUS response. +// See RFC 3501 section 7.2.4 +type Status struct { + Mailbox *imap.MailboxStatus +} + +func (r *Status) HandleFrom(hdlr imap.RespHandler) error { + if r.Mailbox == nil { + r.Mailbox = &imap.MailboxStatus{} + } + mbox := r.Mailbox + mbox.Items = nil + + for h := range hdlr { + fields, ok := h.AcceptNamedResp(imap.Status) + if !ok { + continue + } + if len(fields) < 2 { + return errors.New("STATUS response expects two fields") + } + + name, ok := fields[0].(string) + if !ok { + return errors.New("STATUS response expects a string as first argument") + } + mbox.Name, _ = utf7.Decoder.String(name) + mbox.Name = imap.CanonicalMailboxName(mbox.Name) + + var items []interface{} + if items, ok = fields[1].([]interface{}); !ok { + return errors.New("STATUS response expects a list as second argument") + } + + if err := mbox.Parse(items); err != nil { + return err + } + } + + return nil +} + +func (r *Status) WriteTo(w *imap.Writer) error { + mbox := r.Mailbox + + fields := []interface{}{imap.Status, mbox.Name, mbox.Format()} + + res := imap.NewUntaggedResp(fields) + if err := res.WriteTo(w); err != nil { + return err + } + + return nil +} 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..efa8c46 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/search.go @@ -0,0 +1,367 @@ +package imap + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "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, err := ioutil.ReadAll(r) + if 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 = NewSeqSet(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 = NewSeqSet(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, "UID", c.Uid) + } + + if !c.Since.IsZero() && !c.Before.IsZero() && c.Before.Sub(c.Since) == 24*time.Hour { + fields = append(fields, "ON", searchDate(c.Since)) + } else { + if !c.Since.IsZero() { + fields = append(fields, "SINCE", searchDate(c.Since)) + } + if !c.Before.IsZero() { + fields = append(fields, "BEFORE", searchDate(c.Before)) + } + } + if !c.SentSince.IsZero() && !c.SentBefore.IsZero() && c.SentBefore.Sub(c.SentSince) == 24*time.Hour { + fields = append(fields, "SENTON", searchDate(c.SentSince)) + } else { + if !c.SentSince.IsZero() { + fields = append(fields, "SENTSINCE", searchDate(c.SentSince)) + } + if !c.SentBefore.IsZero() { + fields = append(fields, "SENTBEFORE", searchDate(c.SentBefore)) + } + } + + for key, values := range c.Header { + var prefields []interface{} + switch key { + case "Bcc", "Cc", "From", "Subject", "To": + prefields = []interface{}{strings.ToUpper(key)} + default: + prefields = []interface{}{"HEADER", key} + } + for _, value := range values { + fields = append(fields, prefields...) + fields = append(fields, value) + } + } + + for _, value := range c.Body { + fields = append(fields, "BODY", value) + } + for _, value := range c.Text { + fields = append(fields, "TEXT", value) + } + + for _, flag := range c.WithFlags { + var subfields []interface{} + switch flag { + case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, RecentFlag, SeenFlag: + subfields = []interface{}{strings.ToUpper(strings.TrimPrefix(flag, "\\"))} + default: + subfields = []interface{}{"KEYWORD", flag} + } + fields = append(fields, subfields...) + } + for _, flag := range c.WithoutFlags { + var subfields []interface{} + switch flag { + case AnsweredFlag, DeletedFlag, DraftFlag, FlaggedFlag, SeenFlag: + subfields = []interface{}{"UN" + strings.ToUpper(strings.TrimPrefix(flag, "\\"))} + case RecentFlag: + subfields = []interface{}{"OLD"} + default: + subfields = []interface{}{"UNKEYWORD", flag} + } + fields = append(fields, subfields...) + } + + if c.Larger > 0 { + fields = append(fields, "LARGER", c.Larger) + } + if c.Smaller > 0 { + fields = append(fields, "SMALLER", c.Smaller) + } + + for _, not := range c.Not { + fields = append(fields, "NOT", not.Format()) + } + + for _, or := range c.Or { + fields = append(fields, "OR", or[0].Format(), or[1].Format()) + } + + 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..7313bb1 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/seqset.go @@ -0,0 +1,290 @@ +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 +} + +// NewSeqSet returns a new SeqSet instance after parsing the set string. +func NewSeqSet(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 + return +} + +// 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/server/cmd_any.go b/vendor/github.com/emersion/go-imap/server/cmd_any.go new file mode 100644 index 0000000..cdfb300 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/cmd_any.go @@ -0,0 +1,52 @@ +package server + +import ( + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/backend" + "github.com/emersion/go-imap/commands" + "github.com/emersion/go-imap/responses" +) + +type Capability struct { + commands.Capability +} + +func (cmd *Capability) Handle(conn Conn) error { + res := &responses.Capability{Caps: conn.Capabilities()} + return conn.WriteResp(res) +} + +type Noop struct { + commands.Noop +} + +func (cmd *Noop) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox != nil { + // If a mailbox is selected, NOOP can be used to poll for server updates + if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok { + return mbox.Poll() + } + } + + return nil +} + +type Logout struct { + commands.Logout +} + +func (cmd *Logout) Handle(conn Conn) error { + res := &imap.StatusResp{ + Type: imap.StatusBye, + Info: "Closing connection", + } + + if err := conn.WriteResp(res); err != nil { + return err + } + + // Request to close the connection + conn.Context().State = imap.LogoutState + return nil +} diff --git a/vendor/github.com/emersion/go-imap/server/cmd_auth.go b/vendor/github.com/emersion/go-imap/server/cmd_auth.go new file mode 100644 index 0000000..b7e82a0 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/cmd_auth.go @@ -0,0 +1,261 @@ +package server + +import ( + "errors" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/backend" + "github.com/emersion/go-imap/commands" + "github.com/emersion/go-imap/responses" +) + +// imap errors in Authenticated state. +var ( + ErrNotAuthenticated = errors.New("Not authenticated") +) + +type Select struct { + commands.Select +} + +func (cmd *Select) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + mbox, err := ctx.User.GetMailbox(cmd.Mailbox) + if err != nil { + return err + } + + items := []string{ + imap.MailboxFlags, imap.MailboxPermanentFlags, + imap.MailboxMessages, imap.MailboxRecent, imap.MailboxUnseen, + imap.MailboxUidNext, imap.MailboxUidValidity, + } + + status, err := mbox.Status(items) + if err != nil { + return err + } + + ctx.Mailbox = mbox + ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly + + res := &responses.Select{Mailbox: status} + if err := conn.WriteResp(res); err != nil { + return err + } + + code := imap.CodeReadWrite + if ctx.MailboxReadOnly { + code = imap.CodeReadOnly + } + return ErrStatusResp(&imap.StatusResp{ + Type: imap.StatusOk, + Code: code, + }) +} + +type Create struct { + commands.Create +} + +func (cmd *Create) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + return ctx.User.CreateMailbox(cmd.Mailbox) +} + +type Delete struct { + commands.Delete +} + +func (cmd *Delete) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + return ctx.User.DeleteMailbox(cmd.Mailbox) +} + +type Rename struct { + commands.Rename +} + +func (cmd *Rename) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + return ctx.User.RenameMailbox(cmd.Existing, cmd.New) +} + +type Subscribe struct { + commands.Subscribe +} + +func (cmd *Subscribe) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + mbox, err := ctx.User.GetMailbox(cmd.Mailbox) + if err != nil { + return err + } + + return mbox.SetSubscribed(true) +} + +type Unsubscribe struct { + commands.Unsubscribe +} + +func (cmd *Unsubscribe) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + mbox, err := ctx.User.GetMailbox(cmd.Mailbox) + if err != nil { + return err + } + + return mbox.SetSubscribed(false) +} + +type List struct { + commands.List +} + +func (cmd *List) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + ch := make(chan *imap.MailboxInfo) + res := &responses.List{Mailboxes: ch, Subscribed: cmd.Subscribed} + + done := make(chan error, 1) + go (func() { + done <- conn.WriteResp(res) + close(done) + })() + + mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed) + if err != nil { + close(ch) + return err + } + + for _, mbox := range mailboxes { + info, err := mbox.Info() + if err != nil { + close(ch) + return err + } + + // An empty ("" string) mailbox name argument is a special request to return + // the hierarchy delimiter and the root name of the name given in the + // reference. + if cmd.Mailbox == "" { + ch <- &imap.MailboxInfo{ + Attributes: []string{imap.NoSelectAttr}, + Delimiter: info.Delimiter, + Name: info.Delimiter, + } + break + } + + if info.Match(cmd.Reference, cmd.Mailbox) { + ch <- info + } + } + + close(ch) + + return <-done +} + +type Status struct { + commands.Status +} + +func (cmd *Status) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + mbox, err := ctx.User.GetMailbox(cmd.Mailbox) + if err != nil { + return err + } + + status, err := mbox.Status(cmd.Items) + if err != nil { + return err + } + + // Only keep items thqat have been requested + items := make(map[string]interface{}) + for _, k := range cmd.Items { + items[k] = status.Items[k] + } + status.Items = items + + res := &responses.Status{Mailbox: status} + return conn.WriteResp(res) +} + +type Append struct { + commands.Append +} + +func (cmd *Append) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.User == nil { + return ErrNotAuthenticated + } + + mbox, err := ctx.User.GetMailbox(cmd.Mailbox) + if err == backend.ErrNoSuchMailbox { + return ErrStatusResp(&imap.StatusResp{ + Type: imap.StatusNo, + Code: imap.CodeTryCreate, + Info: err.Error(), + }) + } else if err != nil { + return err + } + + if err := mbox.CreateMessage(cmd.Flags, cmd.Date, cmd.Message); err != nil { + return err + } + + // If APPEND targets the currently selected mailbox, send an untagged EXISTS + // Do this only if the backend doesn't send updates itself + if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() { + status, err := mbox.Status([]string{imap.MailboxMessages}) + if err != nil { + return err + } + + res := &responses.Select{Mailbox: status} + if err := conn.WriteResp(res); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/emersion/go-imap/server/cmd_noauth.go b/vendor/github.com/emersion/go-imap/server/cmd_noauth.go new file mode 100644 index 0000000..5db26c6 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/cmd_noauth.go @@ -0,0 +1,127 @@ +package server + +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" +) + +// IMAP errors in Not Authenticated state. +var ( + ErrAlreadyAuthenticated = errors.New("Already authenticated") + ErrAuthDisabled = errors.New("Authentication disabled") +) + +type StartTLS struct { + commands.StartTLS +} + +func (cmd *StartTLS) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.State != imap.NotAuthenticatedState { + return ErrAlreadyAuthenticated + } + if conn.IsTLS() { + return errors.New("TLS is already enabled") + } + if conn.Server().TLSConfig == nil { + return errors.New("TLS support not enabled") + } + + // Send an OK status response to let the client know that the TLS handshake + // can begin + return ErrStatusResp(&imap.StatusResp{ + Type: imap.StatusOk, + Info: "Begin TLS negotiation now", + }) +} + +func (cmd *StartTLS) Upgrade(conn Conn) error { + tlsConfig := conn.Server().TLSConfig + + var tlsConn *tls.Conn + err := conn.Upgrade(func(conn net.Conn) (net.Conn, error) { + tlsConn = tls.Server(conn, tlsConfig) + err := tlsConn.Handshake() + return tlsConn, err + }) + if err != nil { + return err + } + + conn.setTLSConn(tlsConn) + + res := &responses.Capability{Caps: conn.Capabilities()} + return conn.WriteResp(res) +} + +func afterAuthStatus(conn Conn) error { + return ErrStatusResp(&imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodeCapability, + Arguments: imap.FormatStringList(conn.Capabilities()), + }) +} + +func canAuth(conn Conn) bool { + for _, cap := range conn.Capabilities() { + if cap == "AUTH=PLAIN" { + return true + } + } + return false +} + +type Login struct { + commands.Login +} + +func (cmd *Login) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.State != imap.NotAuthenticatedState { + return ErrAlreadyAuthenticated + } + if !canAuth(conn) { + return ErrAuthDisabled + } + + user, err := conn.Server().Backend.Login(cmd.Username, cmd.Password) + if err != nil { + return err + } + + ctx.State = imap.AuthenticatedState + ctx.User = user + return afterAuthStatus(conn) +} + +type Authenticate struct { + commands.Authenticate +} + +func (cmd *Authenticate) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.State != imap.NotAuthenticatedState { + return ErrAlreadyAuthenticated + } + if !canAuth(conn) { + return ErrAuthDisabled + } + + mechanisms := map[string]sasl.Server{} + for name, newSasl := range conn.Server().auths { + mechanisms[name] = newSasl(conn) + } + + err := cmd.Authenticate.Handle(mechanisms, conn) + if err != nil { + return err + } + + return afterAuthStatus(conn) +} diff --git a/vendor/github.com/emersion/go-imap/server/cmd_selected.go b/vendor/github.com/emersion/go-imap/server/cmd_selected.go new file mode 100644 index 0000000..67c6e4b --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/cmd_selected.go @@ -0,0 +1,313 @@ +package server + +import ( + "errors" + "strings" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/commands" + "github.com/emersion/go-imap/responses" +) + +// imap errors in Selected state. +var ( + ErrNoMailboxSelected = errors.New("No mailbox selected") + ErrMailboxReadOnly = errors.New("Mailbox opened in read-only mode") +) + +// A command handler that supports UIDs. +type UidHandler interface { + Handler + + // Handle this command using UIDs for a given connection. + UidHandle(conn Conn) error +} + +type Check struct { + commands.Check +} + +func (cmd *Check) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + if ctx.MailboxReadOnly { + return ErrMailboxReadOnly + } + + return ctx.Mailbox.Check() +} + +type Close struct { + commands.Close +} + +func (cmd *Close) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + + mailbox := ctx.Mailbox + ctx.Mailbox = nil + ctx.MailboxReadOnly = false + + if err := mailbox.Expunge(); err != nil { + return err + } + + // No need to send expunge updates here, since the mailbox is already unselected + return nil +} + +type Expunge struct { + commands.Expunge +} + +func (cmd *Expunge) Handle(conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + if ctx.MailboxReadOnly { + return ErrMailboxReadOnly + } + + // Get a list of messages that will be deleted + // That will allow us to send expunge updates if the backend doesn't support it + var seqnums []uint32 + if conn.Server().Updates == nil { + criteria := &imap.SearchCriteria{ + WithFlags: []string{imap.DeletedFlag}, + } + + var err error + seqnums, err = ctx.Mailbox.SearchMessages(false, criteria) + if err != nil { + return err + } + } + + if err := ctx.Mailbox.Expunge(); err != nil { + return err + } + + // If the backend doesn't support expunge updates, let's do it ourselves + if conn.Server().Updates == nil { + done := make(chan error) + defer close(done) + + ch := make(chan uint32) + res := &responses.Expunge{SeqNums: ch} + + go (func() { + done <- conn.WriteResp(res) + })() + + // Iterate sequence numbers from the last one to the first one, as deleting + // messages changes their respective numbers + for i := len(seqnums) - 1; i >= 0; i-- { + ch <- seqnums[i] + } + close(ch) + + if err := <-done; err != nil { + return err + } + } + + return nil +} + +type Search struct { + commands.Search +} + +func (cmd *Search) handle(uid bool, conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + + ids, err := ctx.Mailbox.SearchMessages(uid, cmd.Criteria) + if err != nil { + return err + } + + res := &responses.Search{Ids: ids} + return conn.WriteResp(res) +} + +func (cmd *Search) Handle(conn Conn) error { + return cmd.handle(false, conn) +} + +func (cmd *Search) UidHandle(conn Conn) error { + return cmd.handle(true, conn) +} + +type Fetch struct { + commands.Fetch +} + +func (cmd *Fetch) handle(uid bool, conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + + ch := make(chan *imap.Message) + res := &responses.Fetch{Messages: ch} + + done := make(chan error, 1) + go (func() { + done <- conn.WriteResp(res) + })() + + err := ctx.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch) + if err != nil { + return err + } + + return <-done +} + +func (cmd *Fetch) Handle(conn Conn) error { + return cmd.handle(false, conn) +} + +func (cmd *Fetch) UidHandle(conn Conn) error { + // Append UID to the list of requested items if it isn't already present + hasUid := false + for _, item := range cmd.Items { + if item == "UID" { + hasUid = true + break + } + } + if !hasUid { + cmd.Items = append(cmd.Items, "UID") + } + + return cmd.handle(true, conn) +} + +type Store struct { + commands.Store +} + +func (cmd *Store) handle(uid bool, conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + if ctx.MailboxReadOnly { + return ErrMailboxReadOnly + } + + itemStr := cmd.Item + silent := strings.HasSuffix(itemStr, imap.SilentOp) + if silent { + itemStr = strings.TrimSuffix(itemStr, imap.SilentOp) + } + item := imap.FlagsOp(itemStr) + + if item != imap.SetFlags && item != imap.AddFlags && item != imap.RemoveFlags { + return errors.New("Unsupported STORE operation") + } + + flagsList, ok := cmd.Value.([]interface{}) + if !ok { + return errors.New("Flags must be a list") + } + flags, err := imap.ParseStringList(flagsList) + if err != nil { + return err + } + for i, flag := range flags { + flags[i] = imap.CanonicalFlag(flag) + } + + // If the backend supports message updates, this will prevent this connection + // from receiving them + // TODO: find a better way to do this, without conn.silent + *conn.silent() = silent + err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags) + *conn.silent() = false + if err != nil { + return err + } + + // Not silent: send FETCH updates if the backend doesn't support message + // updates + if conn.Server().Updates == nil && !silent { + inner := &Fetch{} + inner.SeqSet = cmd.SeqSet + inner.Items = []string{"FLAGS"} + if uid { + inner.Items = append(inner.Items, "UID") + } + + if err := inner.handle(uid, conn); err != nil { + return err + } + } + + return nil +} + +func (cmd *Store) Handle(conn Conn) error { + return cmd.handle(false, conn) +} + +func (cmd *Store) UidHandle(conn Conn) error { + return cmd.handle(true, conn) +} + +type Copy struct { + commands.Copy +} + +func (cmd *Copy) handle(uid bool, conn Conn) error { + ctx := conn.Context() + if ctx.Mailbox == nil { + return ErrNoMailboxSelected + } + + return ctx.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox) +} + +func (cmd *Copy) Handle(conn Conn) error { + return cmd.handle(false, conn) +} + +func (cmd *Copy) UidHandle(conn Conn) error { + return cmd.handle(true, conn) +} + +type Uid struct { + commands.Uid +} + +func (cmd *Uid) Handle(conn Conn) error { + inner := cmd.Cmd.Command() + hdlr, err := conn.commandHandler(inner) + if err != nil { + return err + } + + uidHdlr, ok := hdlr.(UidHandler) + if !ok { + return errors.New("Command unsupported with UID") + } + + if err := uidHdlr.UidHandle(conn); err != nil { + return err + } + + return ErrStatusResp(&imap.StatusResp{ + Type: imap.StatusOk, + Info: imap.Uid + " " + inner.Name + " completed", + }) +} diff --git a/vendor/github.com/emersion/go-imap/server/conn.go b/vendor/github.com/emersion/go-imap/server/conn.go new file mode 100644 index 0000000..e4cb921 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/conn.go @@ -0,0 +1,383 @@ +package server + +import ( + "crypto/tls" + "errors" + "io" + "net" + "sync" + "time" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/backend" +) + +// Conn is a connection to a client. +type Conn interface { + io.Reader + + // Server returns this connection's server. + Server() *Server + // Context returns this connection's context. + Context() *Context + // Capabilities returns a list of capabilities enabled for this connection. + Capabilities() []string + // WriteResp writes a response to this connection. + WriteResp(res imap.WriterTo) error + // IsTLS returns true if TLS is enabled. + IsTLS() bool + // TLSState returns the TLS connection state if TLS is enabled, nil otherwise. + TLSState() *tls.ConnectionState + // Upgrade upgrades a connection, e.g. wrap an unencrypted connection with an + // encrypted tunnel. + Upgrade(upgrader imap.ConnUpgrader) error + // Close closes this connection. + Close() error + + setTLSConn(*tls.Conn) + silent() *bool // TODO: remove this + serve() error + commandHandler(cmd *imap.Command) (hdlr Handler, err error) +} + +// Context stores a connection's metadata. +type Context struct { + // This connection's current state. + State imap.ConnState + // If the client is logged in, the user. + User backend.User + // If the client has selected a mailbox, the mailbox. + Mailbox backend.Mailbox + // True if the currently selected mailbox has been opened in read-only mode. + MailboxReadOnly bool + // Responses to send to the client. + Responses chan<- imap.WriterTo +} + +type conn struct { + *imap.Conn + + s *Server + ctx *Context + l sync.Locker + tlsConn *tls.Conn + continues chan bool + responses chan imap.WriterTo + silentVal bool +} + +func newConn(s *Server, c net.Conn) *conn { + // Create an imap.Reader and an imap.Writer + continues := make(chan bool) + r := imap.NewServerReader(nil, continues) + w := imap.NewWriter(nil) + + responses := make(chan imap.WriterTo) + + tlsConn, _ := c.(*tls.Conn) + + conn := &conn{ + Conn: imap.NewConn(c, r, w), + + s: s, + l: &sync.Mutex{}, + ctx: &Context{ + State: imap.ConnectingState, + Responses: responses, + }, + tlsConn: tlsConn, + continues: continues, + responses: responses, + } + + if s.Debug != nil { + conn.Conn.SetDebug(s.Debug) + } + if s.MaxLiteralSize > 0 { + conn.Conn.MaxLiteralSize = s.MaxLiteralSize + } + + conn.l.Lock() + go conn.send() + + return conn +} + +func (c *conn) Server() *Server { + return c.s +} + +func (c *conn) Context() *Context { + return c.ctx +} + +type response struct { + response imap.WriterTo + done chan struct{} +} + +func (r *response) WriteTo(w *imap.Writer) error { + err := r.response.WriteTo(w) + close(r.done) + return err +} + +func (c *conn) setDeadline() { + if c.s.AutoLogout == 0 { + return + } + + dur := c.s.AutoLogout + if dur < MinAutoLogout { + dur = MinAutoLogout + } + t := time.Now().Add(dur) + + c.Conn.SetDeadline(t) +} + +func (c *conn) WriteResp(r imap.WriterTo) error { + done := make(chan struct{}) + c.responses <- &response{r, done} + <-done + c.setDeadline() + return nil +} + +func (c *conn) Close() error { + if c.ctx.User != nil { + c.ctx.User.Logout() + } + + if err := c.Conn.Close(); err != nil { + return err + } + + close(c.continues) + + c.ctx.State = imap.LogoutState + return nil +} + +func (c *conn) Capabilities() []string { + caps := []string{"IMAP4rev1"} + + if c.ctx.State == imap.NotAuthenticatedState { + if !c.IsTLS() && c.s.TLSConfig != nil { + caps = append(caps, "STARTTLS") + } + + if !c.canAuth() { + caps = append(caps, "LOGINDISABLED") + } else { + for name := range c.s.auths { + caps = append(caps, "AUTH="+name) + } + } + } + + for _, ext := range c.s.extensions { + caps = append(caps, ext.Capabilities(c)...) + } + + return caps +} + +func (c *conn) send() { + // Send continuation requests + go func() { + for range c.continues { + res := &imap.ContinuationResp{Info: "send literal"} + if err := res.WriteTo(c.Writer); err != nil { + c.Server().ErrorLog.Println("cannot send continuation request: ", err) + } else if err := c.Writer.Flush(); err != nil { + c.Server().ErrorLog.Println("cannot flush connection: ", err) + } + } + }() + + // Send responses + for { + // Get a response that needs to be sent + res := <-c.responses + + // Request to send the response + c.l.Lock() + + // Send the response + if err := res.WriteTo(c.Writer); err != nil { + c.Server().ErrorLog.Println("cannot send response: ", err) + } else if err := c.Writer.Flush(); err != nil { + c.Server().ErrorLog.Println("cannot flush connection: ", err) + } + + c.l.Unlock() + } +} + +func (c *conn) greet() error { + c.ctx.State = imap.NotAuthenticatedState + + caps := c.Capabilities() + args := make([]interface{}, len(caps)) + for i, cap := range caps { + args[i] = cap + } + + greeting := &imap.StatusResp{ + Type: imap.StatusOk, + Code: imap.CodeCapability, + Arguments: args, + Info: "IMAP4rev1 Service Ready", + } + + c.l.Unlock() + defer c.l.Lock() + + return c.WriteResp(greeting) +} + +func (c *conn) setTLSConn(tlsConn *tls.Conn) { + c.tlsConn = tlsConn +} + +func (c *conn) IsTLS() bool { + return c.tlsConn != nil +} + +func (c *conn) TLSState() *tls.ConnectionState { + if c.tlsConn != nil { + state := c.tlsConn.ConnectionState() + return &state + } + return nil +} + +// canAuth checks if the client can use plain text authentication. +func (c *conn) canAuth() bool { + return c.IsTLS() || c.s.AllowInsecureAuth +} + +func (c *conn) silent() *bool { + return &c.silentVal +} + +func (c *conn) serve() error { + // Send greeting + if err := c.greet(); err != nil { + return err + } + + for { + if c.ctx.State == imap.LogoutState { + return nil + } + + var res *imap.StatusResp + var up Upgrader + + c.Wait() + fields, err := c.ReadLine() + if err == io.EOF || c.ctx.State == imap.LogoutState { + return nil + } + c.setDeadline() + + if err != nil { + if imap.IsParseError(err) { + res = &imap.StatusResp{ + Type: imap.StatusBad, + Info: err.Error(), + } + } else { + c.s.ErrorLog.Println("cannot read command:", err) + return err + } + } else { + cmd := &imap.Command{} + if err := cmd.Parse(fields); err != nil { + res = &imap.StatusResp{ + Tag: cmd.Tag, + Type: imap.StatusBad, + Info: err.Error(), + } + } else { + var err error + res, up, err = c.handleCommand(cmd) + if err != nil { + res = &imap.StatusResp{ + Tag: cmd.Tag, + Type: imap.StatusBad, + Info: err.Error(), + } + } + } + } + + if res != nil { + c.l.Unlock() + + if err := c.WriteResp(res); err != nil { + c.s.ErrorLog.Println("cannot write response:", err) + c.l.Lock() + continue + } + + if up != nil && res.Type == imap.StatusOk { + if err := up.Upgrade(c); err != nil { + c.s.ErrorLog.Println("cannot upgrade connection:", err) + return err + } + } + + c.l.Lock() + } + } +} + +func (c *conn) commandHandler(cmd *imap.Command) (hdlr Handler, err error) { + newHandler := c.s.Command(cmd.Name) + if newHandler == nil { + err = errors.New("Unknown command") + return + } + + hdlr = newHandler() + err = hdlr.Parse(cmd.Arguments) + return +} + +func (c *conn) handleCommand(cmd *imap.Command) (res *imap.StatusResp, up Upgrader, err error) { + hdlr, err := c.commandHandler(cmd) + if err != nil { + return + } + + c.l.Unlock() + defer c.l.Lock() + + hdlrErr := hdlr.Handle(c) + if statusErr, ok := hdlrErr.(*errStatusResp); ok { + res = statusErr.resp + } else if hdlrErr != nil { + res = &imap.StatusResp{ + Type: imap.StatusNo, + Info: hdlrErr.Error(), + } + } else { + res = &imap.StatusResp{ + Type: imap.StatusOk, + } + } + + if res != nil { + res.Tag = cmd.Tag + + if res.Type == imap.StatusOk && res.Info == "" { + res.Info = cmd.Name + " completed" + } + } + + up, _ = hdlr.(Upgrader) + return +} diff --git a/vendor/github.com/emersion/go-imap/server/server.go b/vendor/github.com/emersion/go-imap/server/server.go new file mode 100644 index 0000000..3f0511d --- /dev/null +++ b/vendor/github.com/emersion/go-imap/server/server.go @@ -0,0 +1,426 @@ +// Package server provides an IMAP server. +package server + +import ( + "crypto/tls" + "errors" + "io" + "log" + "net" + "os" + "sync" + "time" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/backend" + "github.com/emersion/go-imap/responses" + "github.com/emersion/go-sasl" +) + +// The minimum autologout duration defined in RFC 3501 section 5.4. +const MinAutoLogout = 30 * time.Minute + +// A command handler. +type Handler interface { + imap.Parser + + // Handle this command for a given connection. + // + // By default, after this function has returned a status response is sent. To + // prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp. + Handle(conn Conn) error +} + +// A connection upgrader. If a Handler is also an Upgrader, the connection will +// be upgraded after the Handler succeeds. +// +// This should only be used by libraries implementing an IMAP extension (e.g. +// COMPRESS). +type Upgrader interface { + // Upgrade the connection. This method should call conn.Upgrade(). + Upgrade(conn Conn) error +} + +// A function that creates handlers. +type HandlerFactory func() Handler + +// A function that creates SASL servers. +type SaslServerFactory func(conn Conn) sasl.Server + +// An IMAP extension. +type Extension interface { + // Get capabilities provided by this extension for a given connection. + Capabilities(c Conn) []string + // Get the command handler factory for the provided command name. + Command(name string) HandlerFactory +} + +// An extension that provides additional features to each connection. +type ConnExtension interface { + Extension + + // This function will be called when a client connects to the server. It can + // be used to add new features to the default Conn interface by implementing + // new methods. + NewConn(c Conn) Conn +} + +type errStatusResp struct { + resp *imap.StatusResp +} + +func (err *errStatusResp) Error() string { + return "" +} + +// ErrStatusResp can be returned by a Handler to replace the default status +// response. The response tag must be empty. +// +// To disable the default status response, use ErrNoStatusResp instead. +func ErrStatusResp(res *imap.StatusResp) error { + return &errStatusResp{res} +} + +// ErrNoStatusResp can be returned by a Handler to prevent the default status +// response from being sent. +func ErrNoStatusResp() error { + return &errStatusResp{nil} +} + +// An IMAP server. +type Server struct { + locker sync.Mutex + listeners map[net.Listener]struct{} + conns map[Conn]struct{} + + commands map[string]HandlerFactory + auths map[string]SaslServerFactory + extensions []Extension + + // TCP address to listen on. + Addr string + // This server's TLS configuration. + TLSConfig *tls.Config + // This server's backend. + Backend backend.Backend + // Backend updates that will be sent to connected clients. + Updates <-chan interface{} + // Automatically logout clients after a duration. To do not logout users + // automatically, set this to zero. The duration MUST be at least + // MinAutoLogout (as stated in RFC 3501 section 5.4). + AutoLogout time.Duration + // Allow authentication over unencrypted connections. + AllowInsecureAuth bool + // An io.Writer to which all network activity will be mirrored. + Debug io.Writer + // ErrorLog specifies an optional logger for errors accepting + // connections and unexpected behavior from handlers. + // If nil, logging goes to os.Stderr via the log package's + // standard logger. + ErrorLog imap.Logger + // The maximum literal size, in bytes. Literals exceeding this size will be + // rejected. A value of zero disables the limit (this is the default). + MaxLiteralSize uint32 +} + +// Create a new IMAP server from an existing listener. +func New(bkd backend.Backend) *Server { + s := &Server{ + listeners: make(map[net.Listener]struct{}), + conns: make(map[Conn]struct{}), + Backend: bkd, + ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags), + } + + s.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") + } + + user, err := bkd.Login(username, password) + if err != nil { + return err + } + + ctx := conn.Context() + ctx.State = imap.AuthenticatedState + ctx.User = user + return nil + }) + }, + } + + s.commands = map[string]HandlerFactory{ + imap.Noop: func() Handler { return &Noop{} }, + imap.Capability: func() Handler { return &Capability{} }, + imap.Logout: func() Handler { return &Logout{} }, + + imap.StartTLS: func() Handler { return &StartTLS{} }, + imap.Login: func() Handler { return &Login{} }, + imap.Authenticate: func() Handler { return &Authenticate{} }, + + imap.Select: func() Handler { return &Select{} }, + imap.Examine: func() Handler { + hdlr := &Select{} + hdlr.ReadOnly = true + return hdlr + }, + imap.Create: func() Handler { return &Create{} }, + imap.Delete: func() Handler { return &Delete{} }, + imap.Rename: func() Handler { return &Rename{} }, + imap.Subscribe: func() Handler { return &Subscribe{} }, + imap.Unsubscribe: func() Handler { return &Unsubscribe{} }, + imap.List: func() Handler { return &List{} }, + imap.Lsub: func() Handler { + hdlr := &List{} + hdlr.Subscribed = true + return hdlr + }, + imap.Status: func() Handler { return &Status{} }, + imap.Append: func() Handler { return &Append{} }, + + imap.Check: func() Handler { return &Check{} }, + imap.Close: func() Handler { return &Close{} }, + imap.Expunge: func() Handler { return &Expunge{} }, + imap.Search: func() Handler { return &Search{} }, + imap.Fetch: func() Handler { return &Fetch{} }, + imap.Store: func() Handler { return &Store{} }, + imap.Copy: func() Handler { return &Copy{} }, + imap.Uid: func() Handler { return &Uid{} }, + } + + return s +} + +// Serve accepts incoming connections on the Listener l. +func (s *Server) Serve(l net.Listener) error { + s.locker.Lock() + s.listeners[l] = struct{}{} + s.locker.Unlock() + + defer func() { + s.locker.Lock() + defer s.locker.Unlock() + l.Close() + delete(s.listeners, l) + }() + + go s.listenUpdates() + + for { + c, err := l.Accept() + if err != nil { + return err + } + + var conn Conn = newConn(s, c) + for _, ext := range s.extensions { + if ext, ok := ext.(ConnExtension); ok { + conn = ext.NewConn(conn) + } + } + + go s.serveConn(conn) + } +} + +// ListenAndServe listens on the TCP network address s.Addr and then calls Serve +// to handle requests on incoming connections. +// +// If s.Addr is blank, ":imap" is used. +func (s *Server) ListenAndServe() error { + addr := s.Addr + if addr == "" { + addr = ":imap" + } + + l, err := net.Listen("tcp", 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, ":imaps" is used. +func (s *Server) ListenAndServeTLS() error { + addr := s.Addr + if addr == "" { + addr = ":imaps" + } + + l, err := tls.Listen("tcp", addr, s.TLSConfig) + if err != nil { + return err + } + + return s.Serve(l) +} + +func (s *Server) serveConn(conn Conn) error { + s.locker.Lock() + s.conns[conn] = struct{}{} + s.locker.Unlock() + + defer func() { + s.locker.Lock() + defer s.locker.Unlock() + conn.Close() + delete(s.conns, conn) + }() + + return conn.serve() +} + +// Get a command handler factory for the provided command name. +func (s *Server) Command(name string) HandlerFactory { + // Extensions can override builtin commands + for _, ext := range s.extensions { + if h := ext.Command(name); h != nil { + return h + } + } + + return s.commands[name] +} + +func (s *Server) listenUpdates() (err error) { + updater, ok := s.Backend.(backend.Updater) + if !ok { + return + } + s.Updates = updater.Updates() + + for { + item := <-s.Updates + + var ( + update *backend.Update + res imap.WriterTo + ) + + switch item := item.(type) { + case *backend.StatusUpdate: + update = &item.Update + res = item.StatusResp + case *backend.MailboxUpdate: + update = &item.Update + res = &responses.Select{Mailbox: item.MailboxStatus} + case *backend.MessageUpdate: + update = &item.Update + + ch := make(chan *imap.Message, 1) + ch <- item.Message + close(ch) + + res = &responses.Fetch{Messages: ch} + case *backend.ExpungeUpdate: + update = &item.Update + + ch := make(chan uint32, 1) + ch <- item.SeqNum + close(ch) + + res = &responses.Expunge{SeqNums: ch} + default: + s.ErrorLog.Printf("unhandled update: %T\n", item) + } + + if update == nil || res == nil { + continue + } + + sends := make(chan struct{}) + wait := 0 + s.locker.Lock() + for conn := range s.conns { + ctx := conn.Context() + + if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) { + continue + } + if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) { + continue + } + if *conn.silent() { + // If silent is set, do not send message updates + if _, ok := res.(*responses.Fetch); ok { + continue + } + } + + conn := conn // Copy conn to a local variable + go func() { + done := make(chan struct{}) + conn.Context().Responses <- &response{ + response: res, + done: done, + } + <-done + sends <- struct{}{} + }() + + wait++ + } + s.locker.Unlock() + + if wait > 0 { + go func() { + for done := 0; done < wait; done++ { + <-sends + } + close(sends) + + backend.DoneUpdate(update) + }() + } else { + backend.DoneUpdate(update) + } + } +} + +// 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) + } +} + +// Stops listening and closes all current connections. +func (s *Server) Close() error { + s.locker.Lock() + defer s.locker.Unlock() + + for l := range s.listeners { + l.Close() + } + + for conn := range s.conns { + conn.Close() + } + + return nil +} + +// Enable some IMAP extensions on this server. +// +// This function should not be called directly, it must only be used by +// libraries implementing extensions of the IMAP protocol. +func (s *Server) Enable(extensions ...Extension) { + s.extensions = append(s.extensions, extensions...) +} + +// Enable an authentication mechanism on this server. +// +// This function should not be called directly, it must only be used by +// libraries implementing extensions of the IMAP protocol. +func (s *Server) EnableAuth(name string, f SaslServerFactory) { + s.auths[name] = f +} 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..6600dd1 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/status.go @@ -0,0 +1,120 @@ +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. + StatusOk 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. + StatusNo = "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. + StatusBad = "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. + StatusPreauth = "PREAUTH" + + // The BYE response is always untagged, and indicates that the server + // is about to close the connection. + StatusBye = "BYE" +) + +// Status response codes defined in RFC 3501 section 7.1. +const ( + CodeAlert = "ALERT" + CodeBadCharset = "BADCHARSET" + CodeCapability = "CAPABILITY" + CodeParse = "PARSE" + CodePermanentFlags = "PERMANENTFLAGS" + CodeReadOnly = "READ-ONLY" + CodeReadWrite = "READ-WRITE" + CodeTryCreate = "TRYCREATE" + CodeUidNext = "UIDNEXT" + CodeUidValidity = "UIDVALIDITY" + CodeUnseen = "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 string + + // Arguments provided with the status code. + Arguments []interface{} + + // The status info. + Info string +} + +// 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 == StatusNo || r.Type == StatusBad { + return errors.New(r.Info) + } + return nil +} + +func (r *StatusResp) WriteTo(w *Writer) error { + tag := r.Tag + if tag == "" { + tag = "*" + } + + if err := w.writeFields([]interface{}{tag, string(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() +} 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..0989212 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/utf7/decoder.go @@ -0,0 +1,152 @@ +package utf7 + +import ( + "errors" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/encoding" + "golang.org/x/text/transform" +) + +var ErrBadUtf7 = errors.New("bad utf-7 encoding") + +var Decoder = &encoding.Decoder{ + Transformer: &decoder{true}, +} + +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 = ErrBadUtf7 + 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 = ErrBadUtf7 + return + } + } + + if i == len(src) { // Implicit shift ("&...") + if atEOF { + err = ErrBadUtf7 + } 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 = ErrBadUtf7 + return + } + + b = decode(src[start:i]) + d.ascii = false + } + + if len(b) == 0 { // Bad encoding + err = ErrBadUtf7 + return + } + + if nDst+len(b) > len(dst) { + 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, enc.DecodedLen(n)*3) + } else { + n += 4 - n&3 + b = make([]byte, n+enc.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 := enc.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..da20f6d --- /dev/null +++ b/vendor/github.com/emersion/go-imap/utf7/encoder.go @@ -0,0 +1,96 @@ +package utf7 + +import ( + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/encoding" + "golang.org/x/text/transform" +) + +var Encoder = &encoding.Encoder{ + Transformer: &encoder{}, +} + +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 := enc.EncodedLen(len(b)) + 2 + b64 := make([]byte, n) + enc.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..b9acf84 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/utf7/utf7.go @@ -0,0 +1,15 @@ +// Modified UTF-7 encoding defined in RFC 3501 section 5.1.3 +package utf7 + +import ( + "encoding/base64" +) + +const ( + min = 0x20 // Minimum self-representing UTF-7 value + max = 0x7E // Maximum self-representing UTF-7 value + + repl = '\uFFFD' // Unicode replacement code point +) + +var enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,") 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..1d08239 --- /dev/null +++ b/vendor/github.com/emersion/go-imap/write.go @@ -0,0 +1,229 @@ +package imap + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" + "time" + "unicode" +) + +type flusher interface { + Flush() error +} + +// A string that will be quoted. +type Quoted 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 + + 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) writeAtom(s string) error { + return w.writeString(s) +} + +func (w *Writer) writeAstring(s string) error { + if !isAscii(s) { + // IMAP doesn't allow 8-bit data outside literals + return w.writeLiteral(bytes.NewBufferString(s)) + } + + specials := string([]rune{dquote, listStart, listEnd, literalStart, sp}) + if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, specials) { + return w.writeQuoted(s) + } + + return w.writeAtom(s) +} + +func (w *Writer) writeDateTime(t time.Time, layout string) error { + if t.IsZero() { + return w.writeAtom(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)) +} + +func (w *Writer) writeLiteral(l Literal) error { + if l == nil { + return w.writeString(nilAtom) + } + + header := string(literalStart) + strconv.Itoa(l.Len()) + string(literalEnd) + crlf + if err := w.writeString(header); err != nil { + return err + } + + // If a channel is available, wait for a continuation request before sending data + if w.continues != nil { + // Make sure to flush the writer, otherwise we may never receive a continuation request + if err := w.Flush(); err != nil { + return err + } + + if !<-w.continues { + return fmt.Errorf("imap: cannot send literal: no continuation request received") + } + } + + _, err := io.Copy(w, l) + return err +} + +func (w *Writer) writeField(field interface{}) error { + if field == nil { + return w.writeAtom(nilAtom) + } + + switch field := field.(type) { + case string: + return w.writeAstring(field) + case Quoted: + return w.writeQuoted(string(field)) + case int: + return w.writeNumber(uint32(field)) + case uint32: + return w.writeNumber(field) + case Literal: + return w.writeLiteral(field) + case []interface{}: + return w.writeList(field) + case 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(field.String()) + } + + return fmt.Errorf("imap: cannot format field: %v", field) +} + +func (w *Writer) writeRespCode(code string, args []interface{}) error { + if err := w.writeString(string(respCodeStart)); err != nil { + return err + } + + fields := []interface{}{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/.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/.travis.yml b/vendor/github.com/emersion/go-sasl/.travis.yml new file mode 100644 index 0000000..92823df --- /dev/null +++ b/vendor/github.com/emersion/go-sasl/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - 1.5 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..3975c30 --- /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) +* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (only server, obsolete, use PLAIN instead) +* [PLAIN](https://tools.ietf.org/html/rfc4616) +* [XOAUTH2](https://developers.google.com/gmail/xoauth2_protocol) + +## 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..7ee10de --- /dev/null +++ b/vendor/github.com/emersion/go-sasl/login.go @@ -0,0 +1,47 @@ +package sasl + +// The LOGIN mechanism name. +const Login = "LOGIN" + +// Authenticates users with an username and a password. +type LoginAuthenticator func(username, password string) error + +type loginState int + +const ( + loginNotStarted loginState = iota + loginWaitingUsername + loginWaitingPassword + loginCompleted +) + +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. +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: + challenge = []byte("Username:") + 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/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-sasl/xoauth2.go b/vendor/github.com/emersion/go-sasl/xoauth2.go new file mode 100644 index 0000000..9e5d03e --- /dev/null +++ b/vendor/github.com/emersion/go-sasl/xoauth2.go @@ -0,0 +1,48 @@ +package sasl + +import ( + "encoding/json" + "fmt" +) + +// The XOAUTH2 mechanism name. +const Xoauth2 = "XOAUTH2" + +// An XOAUTH2 error. +type Xoauth2Error struct { + Status string `json:"status"` + Schemes string `json:"schemes"` + Scope string `json:"scope"` +} + +// Implements error. +func (err *Xoauth2Error) Error() string { + return fmt.Sprintf("XOAUTH2 authentication error (%v)", err.Status) +} + +type xoauth2Client struct { + Username string + Token string +} + +func (a *xoauth2Client) Start() (mech string, ir []byte, err error) { + mech = Xoauth2 + ir = []byte("user=" + a.Username + "\x01auth=Bearer " + a.Token + "\x01\x01") + return +} + +func (a *xoauth2Client) Next(challenge []byte) ([]byte, error) { + // Server sent an error response + xoauth2Err := &Xoauth2Error{} + if err := json.Unmarshal(challenge, xoauth2Err); err != nil { + return nil, err + } else { + return nil, xoauth2Err + } +} + +// An implementation of the XOAUTH2 authentication mechanism, as +// described in https://developers.google.com/gmail/xoauth2_protocol. +func NewXoauth2Client(username, token string) Client { + return &xoauth2Client{username, token} +} 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/.travis.yml b/vendor/github.com/emersion/go-smtp/.travis.yml new file mode 100644 index 0000000..5c2689c --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.7 +script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh) +after_script: bash <(curl -s https://codecov.io/bash) 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 100755 index 0000000..958fd00 --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/README.md @@ -0,0 +1,125 @@ +# go-smtp + +[![GoDoc](https://godoc.org/github.com/emersion/go-smtp?status.svg)](https://godoc.org/github.com/emersion/go-smtp) +[![Build Status](https://travis-ci.org/emersion/go-smtp.svg?branch=master)](https://travis-ci.org/emersion/go-smtp) +[![codecov](https://codecov.io/gh/emersion/go-smtp/branch/master/graph/badge.svg)](https://codecov.io/gh/emersion/go-smtp) +[![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable) +[![Go Report Card](https://goreportcard.com/badge/github.com/emersion/go-smtp)](https://goreportcard.com/report/github.com/emersion/go-smtp) + +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 + +## Usage + +### Client + +```go +package main + +import ( + "log" + "strings" + + "github.com/emersion/go-sasl" + "github.com/emersion/go-smtp" +) + +func main() { + // Set up 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. + +### Server + +```go +package main + +import ( + "errors" + "io/ioutil" + "log" + + "github.com/emersion/go-smtp" +) + +type Backend struct{} + +func (bkd *Backend) Login(username, password string) (smtp.User, error) { + if username != "username" || password != "password" { + return nil, errors.New("Invalid username or password") + } + return &User{}, nil +} + +type User struct{} + +func (u *User) Send(from string, to []string, r io.Reader) error { + log.Println("Sending message:", from, to) + + if b, err := ioutil.ReadAll(r); err != nil { + return err + } else { + log.Println("Data:", string(b)) + } + return nil +} + +func (u *User) Logout() error { + return nil +} + +func main() { + be := &Backend{} + + s := smtp.NewServer(be) + + s.Addr = ":1025" + s.Domain = "localhost" + s.MaxIdleSeconds = 300 + 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: +RCPT TO: +DATA +Hey <3 +. +``` + +## 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..d813880 --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/backend.go @@ -0,0 +1,19 @@ +package smtp + +import ( + "io" +) + +// A SMTP server backend. +type Backend interface { + // Authenticate a user. + Login(username, password string) (User, error) +} + +// An authenticated user. +type User interface { + // Send an e-mail. + Send(from string, to []string, r io.Reader) error + // Logout is called when this User will no longer be used. + Logout() 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..33510fd --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/client.go @@ -0,0 +1,384 @@ +// 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" + "io" + "net" + "net/textproto" + "strings" + + "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 + // map of supported extensions + ext map[string]string + // supported auth mechanisms + auth []string + localName string // the name to use in HELO/EHLO + didHello bool // whether we've said HELO/EHLO + helloError error // the error from the hello +} + +// 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.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) { + text := textproto.NewConn(conn) + _, _, err := text.ReadResponse(220) + if err != nil { + text.Close() + return nil, err + } + c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"} + return c, nil +} + +// 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. +func (c *Client) Hello(localName string) error { + 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 +func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) { + 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) + return code, msg, err +} + +// 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 { + _, msg, err := c.cmd(250, "EHLO %s", 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. +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 + } + c.conn = tls.Client(c.conn, config) + c.Text = textproto.NewConn(c.conn) + c.tls = true + 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. +func (c *Client) Verify(addr string) error { + 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. +// A failed authentication closes the connection. +// Only servers that advertise the AUTH extension support this function. +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 { + c.Quit() + return err + } + resp64 := make([]byte, encoding.EncodedLen(len(resp))) + encoding.Encode(resp64, resp) + code, msg64, err := c.cmd(0, "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 = &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, "*") + c.Quit() + 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. +func (c *Client) Mail(from string) error { + if err := c.hello(); err != nil { + return err + } + cmdStr := "MAIL FROM:<%s>" + if c.ext != nil { + if _, ok := c.ext["8BITMIME"]; ok { + cmdStr += " BODY=8BITMIME" + } + } + _, _, 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. +func (c *Client) Rcpt(to string) error { + _, _, err := c.cmd(25, "RCPT TO:<%s>", to) + return err +} + +type dataCloser struct { + c *Client + io.WriteCloser +} + +func (d *dataCloser) Close() error { + d.WriteCloser.Close() + _, _, err := d.c.Text.ReadResponse(250) + return err +} + +// 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. +func (c *Client) Data() (io.WriteCloser, error) { + _, _, err := c.cmd(354, "DATA") + if err != nil { + return nil, err + } + return &dataCloser{c, c.Text.DotWriter()}, nil +} + +var testHookStartTLS func(*tls.Config) // nil, except for tests + +// SendMail connects to the server at addr, switches to TLS if +// possible, authenticates with the optional mechanism a if possible, +// 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. +// +// The SendMail function and the the net/smtp package are low-level +// mechanisms and provide no support for DKIM signing, MIME +// attachments (see the mime/multipart package), or other mail +// functionality. Higher-level packages exist outside of the standard +// library. +func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error { + 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 { + config := &tls.Config{ServerName: c.serverName} + if testHookStartTLS != nil { + testHookStartTLS(config) + } + if err = c.StartTLS(config); err != nil { + return err + } + } + if a != nil && c.ext != nil { + if _, ok := c.ext["AUTH"]; ok { + if err = c.Auth(a); err != nil { + return err + } + } + } + if err = c.Mail(from); 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() +} + +// 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 + } + _, _, err := c.cmd(250, "RSET") + return err +} + +// Quit sends the QUIT command and closes the connection to the server. +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() +} 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..e6225e0 --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/conn.go @@ -0,0 +1,432 @@ +package smtp + +import ( + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net" + "net/textproto" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +// A SMTP message. +type message struct { + // The message contents. + io.Reader + + // The sender e-mail address. + From string + // The recipients e-mail addresses. + To []string +} + +type Conn struct { + conn net.Conn + text *textproto.Conn + server *Server + helo string + msg *message + nbrErrors int + user User + locker sync.Mutex +} + +func newConn(c net.Conn, s *Server) *Conn { + sc := &Conn{ + server: s, + conn: c, + } + + sc.init() + return sc +} + +func (c *Conn) init() { + var rwc io.ReadWriteCloser = c.conn + if c.server.Debug != nil { + rwc = struct { + io.Reader + io.Writer + io.Closer + }{ + io.TeeReader(c.conn, c.server.Debug), + io.MultiWriter(c.conn, c.server.Debug), + c.conn, + } + } + + c.text = textproto.NewConn(rwc) +} + +// Commands are dispatched to the appropriate handler functions. +func (c *Conn) handle(cmd string, arg string) { + if cmd == "" { + c.WriteResponse(500, "Speak up") + return + } + + switch cmd { + case "SEND", "SOML", "SAML", "EXPN", "HELP", "TURN": + // These commands are not implemented in any state + c.WriteResponse(502, fmt.Sprintf("%v command not implemented", cmd)) + case "HELO", "EHLO": + c.handleGreet((cmd == "EHLO"), arg) + case "MAIL": + c.handleMail(arg) + case "RCPT": + c.handleRcpt(arg) + case "VRFY": + c.WriteResponse(252, "Cannot VRFY user, but will accept message") + case "NOOP": + c.WriteResponse(250, "I have sucessfully done nothing") + case "RSET": // Reset session + c.reset() + c.WriteResponse(250, "Session reset") + case "DATA": + c.handleData(arg) + case "QUIT": + c.WriteResponse(221, "Goodnight and good luck") + c.Close() + case "AUTH": + c.handleAuth(arg) + case "STARTTLS": + c.handleStartTLS() + default: + c.WriteResponse(500, fmt.Sprintf("Syntax error, %v command unrecognized", cmd)) + + c.nbrErrors++ + if c.nbrErrors > 3 { + c.WriteResponse(500, "Too many unrecognized commands") + c.Close() + } + } +} + +func (c *Conn) Server() *Server { + return c.server +} + +func (c *Conn) User() User { + c.locker.Lock() + defer c.locker.Unlock() + return c.user +} + +func (c *Conn) SetUser(user User) { + c.locker.Lock() + defer c.locker.Unlock() + c.user = user +} + +func (c *Conn) Close() error { + if user := c.User(); user != nil { + user.Logout() + } + + return c.conn.Close() +} + +// Check if this connection is encrypted. +func (c *Conn) IsTLS() bool { + _, ok := c.conn.(*tls.Conn) + return ok +} + +// GREET state -> waiting for HELO +func (c *Conn) handleGreet(enhanced bool, arg string) { + if !enhanced { + domain, err := parseHelloArgument(arg) + if err != nil { + c.WriteResponse(501, "Domain/address argument required for HELO") + return + } + c.helo = domain + + c.WriteResponse(250, fmt.Sprintf("Hello %s", domain)) + } else { + domain, err := parseHelloArgument(arg) + if err != nil { + c.WriteResponse(501, "Domain/address argument required for EHLO") + return + } + + c.helo = domain + + caps := []string{} + caps = append(caps, c.server.caps...) + if c.server.TLSConfig != nil && !c.IsTLS() { + caps = append(caps, "STARTTLS") + } + if c.IsTLS() || c.server.AllowInsecureAuth { + authCap := "AUTH" + for name, _ := range c.server.auths { + authCap += " " + name + } + + caps = append(caps, authCap) + } + if c.server.MaxMessageBytes > 0 { + caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes)) + } + + args := []string{"Hello " + domain} + args = append(args, caps...) + c.WriteResponse(250, args...) + } +} + +// READY state -> waiting for MAIL +func (c *Conn) handleMail(arg string) { + if c.helo == "" { + c.WriteResponse(502, "Please introduce yourself first.") + return + } + if c.msg == nil { + c.WriteResponse(502, "Please authenticate first.") + return + } + + // Match FROM, while accepting '>' as quoted pair and in double quoted strings + // (?i) makes the regex case insensitive, (?:) is non-grouping sub-match + re := regexp.MustCompile("(?i)^FROM:\\s*<((?:\\\\>|[^>])+|\"[^\"]+\"@[^>]+)>( [\\w= ]+)?$") + m := re.FindStringSubmatch(arg) + if m == nil { + c.WriteResponse(501, "Was expecting MAIL arg syntax of FROM:
") + return + } + + from := m[1] + + // 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 m[2] != "" { + args, err := parseArgs(m[2]) + if err != nil { + c.WriteResponse(501, "Unable to parse MAIL ESMTP parameters") + return + } + + if args["SIZE"] != "" { + size, err := strconv.ParseInt(args["SIZE"], 10, 32) + if err != nil { + c.WriteResponse(501, "Unable to parse SIZE as an integer") + return + } + + if c.server.MaxMessageBytes > 0 && int(size) > c.server.MaxMessageBytes { + c.WriteResponse(552, "Max message size exceeded") + return + } + } + } + + c.msg.From = from + c.WriteResponse(250, fmt.Sprintf("Roger, accepting mail from <%v>", from)) +} + +// MAIL state -> waiting for RCPTs followed by DATA +func (c *Conn) handleRcpt(arg string) { + if c.msg == nil || c.msg.From == "" { + c.WriteResponse(502, "Missing MAIL FROM command.") + return + } + + if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") { + c.WriteResponse(501, "Was expecting RCPT arg syntax of TO:
") + return + } + + // TODO: This trim is probably too forgiving + recipient := strings.Trim(arg[3:], "<> ") + + if c.server.MaxRecipients > 0 && len(c.msg.To) >= c.server.MaxRecipients { + c.WriteResponse(552, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients)) + return + } + + c.msg.To = append(c.msg.To, recipient) + c.WriteResponse(250, fmt.Sprintf("I'll make sure <%v> gets this", recipient)) +} + +func (c *Conn) handleAuth(arg string) { + if c.helo == "" { + c.WriteResponse(502, "Please introduce yourself first.") + return + } + + if arg == "" { + c.WriteResponse(502, "Missing parameter") + return + } + + parts := strings.Fields(arg) + 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, "Unsupported authentication mechanism") + return + } + + sasl := newSasl(c) + + response := ir + for { + challenge, done, err := sasl.Next(response) + if err != nil { + c.WriteResponse(454, err.Error()) + return + } + + if done { + break + } + + encoded := "" + if len(challenge) > 0 { + encoded = base64.StdEncoding.EncodeToString(challenge) + } + c.WriteResponse(334, encoded) + + encoded, err = c.ReadLine() + if err != nil { + return // TODO: error handling + } + + response, err = base64.StdEncoding.DecodeString(encoded) + if err != nil { + c.WriteResponse(454, "Invalid base64 data") + return + } + } + + if c.User() != nil { + c.WriteResponse(235, "Authentication succeeded") + + c.msg = &message{} + } +} + +func (c *Conn) handleStartTLS() { + if c.IsTLS() { + c.WriteResponse(502, "Already running in TLS") + return + } + + if c.server.TLSConfig == nil { + c.WriteResponse(502, "TLS not supported") + return + } + + c.WriteResponse(220, "Ready to start TLS") + + // Upgrade to TLS + var tlsConn *tls.Conn + tlsConn = tls.Server(c.conn, c.server.TLSConfig) + + if err := tlsConn.Handshake(); err != nil { + c.WriteResponse(550, "Handshake error") + } + + c.conn = tlsConn + c.init() + + // Reset envelope as a new EHLO/HELO is required after STARTTLS + c.reset() +} + +// DATA +func (c *Conn) handleData(arg string) { + if arg != "" { + c.WriteResponse(501, "DATA command should not have any arguments") + return + } + + if c.msg == nil || c.msg.From == "" || len(c.msg.To) == 0 { + c.WriteResponse(502, "Missing RCPT TO command.") + return + } + + // We have recipients, go to accept data + c.WriteResponse(354, "Go ahead. End your data with .") + + c.msg.Reader = newDataReader(c) + err := c.User().Send(c.msg.From, c.msg.To, c.msg.Reader) + io.Copy(ioutil.Discard, c.msg.Reader) // Make sure all the data has been consumed + if err != nil { + if smtperr, ok := err.(*smtpError); ok { + c.WriteResponse(smtperr.Code, smtperr.Message) + } else { + c.WriteResponse(554, "Error: transaction failed, blame it on the weather: "+err.Error()) + } + } else { + c.WriteResponse(250, "Ok: queued") + } + + c.reset() +} + +func (c *Conn) Reject() { + c.WriteResponse(421, "Too busy. Try again later.") + c.Close() +} + +func (c *Conn) greet() { + c.WriteResponse(220, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain)) +} + +// Calculate the next read or write deadline based on MaxIdleSeconds. +func (c *Conn) nextDeadline() time.Time { + if c.server.MaxIdleSeconds == 0 { + return time.Time{} // No deadline + } + + return time.Now().Add(time.Duration(c.server.MaxIdleSeconds) * time.Second) +} + +func (c *Conn) WriteResponse(code int, text ...string) { + // TODO: error handling + + c.conn.SetDeadline(c.nextDeadline()) + + for i := 0; i < len(text)-1; i++ { + c.text.PrintfLine("%v-%v", code, text[i]) + } + c.text.PrintfLine("%v %v", code, text[len(text)-1]) +} + +// Reads a line of input +func (c *Conn) ReadLine() (string, error) { + if err := c.conn.SetReadDeadline(c.nextDeadline()); err != nil { + return "", err + } + + return c.text.ReadLine() +} + +func (c *Conn) reset() { + if user := c.User(); user != nil { + user.Logout() + } + + c.locker.Lock() + c.user = nil + c.msg = nil + c.locker.Unlock() +} 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..8591e48 --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/data.go @@ -0,0 +1,57 @@ +package smtp + +import ( + "io" +) + +type smtpError struct { + Code int + Message string +} + +func (err *smtpError) Error() string { + return err.Message +} + +var ErrDataTooLarge = &smtpError{ + Code: 552, + Message: "Maximum message size exceeded", +} + +type dataReader struct { + r io.Reader + + limited bool + n int64 // Maximum bytes remaining +} + +func newDataReader(c *Conn) io.Reader { + dr := &dataReader{ + r: c.text.DotReader(), + } + + 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] + } + } + + n, err = r.r.Read(b) + + if r.limited { + r.n -= int64(n) + } + return +} 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..24ac48f --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/parse.go @@ -0,0 +1,66 @@ +package smtp + +import ( + "fmt" + "regexp" + "strings" +) + +func parseCmd(line string) (cmd string, arg string, err error) { + line = strings.TrimRight(line, "\r\n") + + l := len(line) + switch { + case strings.HasPrefix(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" +// The leading space is mandatory. +func parseArgs(arg string) (args map[string]string, err error) { + args = map[string]string{} + re := regexp.MustCompile(" (\\w+)=(\\w+)") + pm := re.FindAllStringSubmatch(arg, -1) + if pm == nil { + return nil, fmt.Errorf("Failed to parse arg string: %q", arg) + } + + for _, m := range pm { + args[strings.ToUpper(m[1])] = m[2] + } + return args, 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 100755 index 0000000..6e20e9e --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/server.go @@ -0,0 +1,187 @@ +package smtp + +import ( + "crypto/tls" + "errors" + "io" + "net" + "sync" + + "github.com/emersion/go-sasl" +) + +// A function that creates SASL servers. +type SaslServerFactory func(conn *Conn) sasl.Server + +// A SMTP server. +type Server struct { + // TCP address to listen on. + Addr string + // The server TLS configuration. + TLSConfig *tls.Config + + Domain string + MaxRecipients int + MaxIdleSeconds int + MaxMessageBytes int + AllowInsecureAuth bool + Debug io.Writer + + // The server backend. + Backend Backend + + listener net.Listener + caps []string + auths map[string]SaslServerFactory + + locker sync.Mutex + conns map[*Conn]struct{} +} + +// New creates a new SMTP server. +func NewServer(be Backend) *Server { + return &Server{ + Backend: be, + caps: []string{"PIPELINING", "8BITMIME"}, + 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") + } + + user, err := be.Login(username, password) + if err != nil { + return err + } + + conn.SetUser(user) + return nil + }) + }, + }, + conns: make(map[*Conn]struct{}), + } +} + +// Serve accepts incoming connections on the Listener l. +func (s *Server) Serve(l net.Listener) error { + s.listener = l + defer s.Close() + + for { + c, err := l.Accept() + if err != nil { + return err + } + + go s.handleConn(newConn(c, s)) + } +} + +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() + }() + + c.greet() + + for { + line, err := c.ReadLine() + if err == nil { + cmd, arg, err := parseCmd(line) + if err != nil { + c.nbrErrors++ + c.WriteResponse(501, "Bad command") + continue + } + + c.handle(cmd, arg) + } else { + if err == io.EOF { + return nil + } + + if neterr, ok := err.(net.Error); ok && neterr.Timeout() { + c.WriteResponse(221, "Idle timeout, bye bye") + return nil + } + + c.WriteResponse(221, "Connection error, sorry") + return err + } + } +} + +// ListenAndServe listens on the TCP network address s.Addr and then calls Serve +// to handle requests on incoming connections. +// +// If s.Addr is blank, ":smtp" is used. +func (s *Server) ListenAndServe() error { + addr := s.Addr + if addr == "" { + addr = ":smtp" + } + + l, err := net.Listen("tcp", 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 { + 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 stops the server. +func (s *Server) Close() { + s.listener.Close() + + s.locker.Lock() + defer s.locker.Unlock() + + for conn := range s.conns { + conn.Close() + } +} + +// 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..e24f205 --- /dev/null +++ b/vendor/github.com/emersion/go-smtp/smtp.go @@ -0,0 +1,7 @@ +// 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 +// Additional extensions may be handled by other packages. +package smtp 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..221f175 --- /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 +// http://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/gen.go b/vendor/golang.org/x/text/encoding/internal/identifier/gen.go new file mode 100644 index 0000000..0c8eba7 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/identifier/gen.go @@ -0,0 +1,137 @@ +// 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. + +// +build ignore + +package main + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "log" + "strings" + + "golang.org/x/text/internal/gen" +) + +type registry struct { + XMLName xml.Name `xml:"registry"` + Updated string `xml:"updated"` + Registry []struct { + ID string `xml:"id,attr"` + Record []struct { + Name string `xml:"name"` + Xref []struct { + Type string `xml:"type,attr"` + Data string `xml:"data,attr"` + } `xml:"xref"` + Desc struct { + Data string `xml:",innerxml"` + // Any []struct { + // Data string `xml:",chardata"` + // } `xml:",any"` + // Data string `xml:",chardata"` + } `xml:"description,"` + MIB string `xml:"value"` + Alias []string `xml:"alias"` + MIME string `xml:"preferred_alias"` + } `xml:"record"` + } `xml:"registry"` +} + +func main() { + r := gen.OpenIANAFile("assignments/character-sets/character-sets.xml") + reg := ®istry{} + if err := xml.NewDecoder(r).Decode(®); err != nil && err != io.EOF { + log.Fatalf("Error decoding charset registry: %v", err) + } + if len(reg.Registry) == 0 || reg.Registry[0].ID != "character-sets-1" { + log.Fatalf("Unexpected ID %s", reg.Registry[0].ID) + } + + w := &bytes.Buffer{} + fmt.Fprintf(w, "const (\n") + for _, rec := range reg.Registry[0].Record { + constName := "" + for _, a := range rec.Alias { + if strings.HasPrefix(a, "cs") && strings.IndexByte(a, '-') == -1 { + // Some of the constant definitions have comments in them. Strip those. + constName = strings.Title(strings.SplitN(a[2:], "\n", 2)[0]) + } + } + if constName == "" { + switch rec.MIB { + case "2085": + constName = "HZGB2312" // Not listed as alias for some reason. + default: + log.Fatalf("No cs alias defined for %s.", rec.MIB) + } + } + if rec.MIME != "" { + rec.MIME = fmt.Sprintf(" (MIME: %s)", rec.MIME) + } + fmt.Fprintf(w, "// %s is the MIB identifier with IANA name %s%s.\n//\n", constName, rec.Name, rec.MIME) + if len(rec.Desc.Data) > 0 { + fmt.Fprint(w, "// ") + d := xml.NewDecoder(strings.NewReader(rec.Desc.Data)) + inElem := true + attr := "" + for { + t, err := d.Token() + if err != nil { + if err != io.EOF { + log.Fatal(err) + } + break + } + switch x := t.(type) { + case xml.CharData: + attr = "" // Don't need attribute info. + a := bytes.Split([]byte(x), []byte("\n")) + for i, b := range a { + if b = bytes.TrimSpace(b); len(b) != 0 { + if !inElem && i > 0 { + fmt.Fprint(w, "\n// ") + } + inElem = false + fmt.Fprintf(w, "%s ", string(b)) + } + } + case xml.StartElement: + if x.Name.Local == "xref" { + inElem = true + use := false + for _, a := range x.Attr { + if a.Name.Local == "type" { + use = use || a.Value != "person" + } + if a.Name.Local == "data" && use { + attr = a.Value + " " + } + } + } + case xml.EndElement: + inElem = false + fmt.Fprint(w, attr) + } + } + fmt.Fprint(w, "\n") + } + for _, x := range rec.Xref { + switch x.Type { + case "rfc": + fmt.Fprintf(w, "// Reference: %s\n", strings.ToUpper(x.Data)) + case "uri": + fmt.Fprintf(w, "// Reference: %s\n", x.Data) + } + } + fmt.Fprintf(w, "%s MIB = %s\n", constName, rec.MIB) + fmt.Fprintln(w) + } + fmt.Fprintln(w, ")") + + gen.WriteGoFile("mib.go", "identifier", w.Bytes()) +} 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..7351b4e --- /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 +// - http://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..768842b --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/identifier/mib.go @@ -0,0 +1,1621 @@ +// 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 + // (formerly ECMA + // 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 http://www.iana.org/assignments/charset-reg/ISO-8859-13 http://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 http://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: http://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: http://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: http://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: http://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: http://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: http://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 http://www.iana.org/assignments/charset-reg/ISO-11548-1 + ISO115481 MIB = 118 + + // KZ1048 is the MIB identifier with IANA name KZ-1048. + // + // See http://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 http://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. + // + // http://www.unicode.org/unicode/reports/tr26 + CESU8 MIB = 1016 + + // UTF32 is the MIB identifier with IANA name UTF-32. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32 MIB = 1017 + + // UTF32BE is the MIB identifier with IANA name UTF-32BE. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32BE MIB = 1018 + + // UTF32LE is the MIB identifier with IANA name UTF-32LE. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32LE MIB = 1019 + + // BOCU1 is the MIB identifier with IANA name BOCU-1. + // + // http://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 http://www.iana.org/assignments/charset-reg/IBM00858 + IBM00858 MIB = 2089 + + // IBM00924 is the MIB identifier with IANA name IBM00924. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM00924 + IBM00924 MIB = 2090 + + // IBM01140 is the MIB identifier with IANA name IBM01140. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01140 + IBM01140 MIB = 2091 + + // IBM01141 is the MIB identifier with IANA name IBM01141. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01141 + IBM01141 MIB = 2092 + + // IBM01142 is the MIB identifier with IANA name IBM01142. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01142 + IBM01142 MIB = 2093 + + // IBM01143 is the MIB identifier with IANA name IBM01143. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01143 + IBM01143 MIB = 2094 + + // IBM01144 is the MIB identifier with IANA name IBM01144. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01144 + IBM01144 MIB = 2095 + + // IBM01145 is the MIB identifier with IANA name IBM01145. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01145 + IBM01145 MIB = 2096 + + // IBM01146 is the MIB identifier with IANA name IBM01146. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01146 + IBM01146 MIB = 2097 + + // IBM01147 is the MIB identifier with IANA name IBM01147. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01147 + IBM01147 MIB = 2098 + + // IBM01148 is the MIB identifier with IANA name IBM01148. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01148 + IBM01148 MIB = 2099 + + // IBM01149 is the MIB identifier with IANA name IBM01149. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01149 + IBM01149 MIB = 2100 + + // Big5HKSCS is the MIB identifier with IANA name Big5-HKSCS. + // + // See http://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) http://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 http://www.iana.org/assignments/charset-reg/PTCP154 + PTCP154 MIB = 2103 + + // Amiga1251 is the MIB identifier with IANA name Amiga-1251. + // + // See http://www.amiga.ultranet.ru/Amiga-1251.html + Amiga1251 MIB = 2104 + + // KOI7switched is the MIB identifier with IANA name KOI7-switched. + // + // See http://www.iana.org/assignments/charset-reg/KOI7-switched + KOI7switched MIB = 2105 + + // BRF is the MIB identifier with IANA name BRF. + // + // See http://www.iana.org/assignments/charset-reg/BRF + BRF MIB = 2106 + + // TSCII is the MIB identifier with IANA name TSCII. + // + // See http://www.iana.org/assignments/charset-reg/TSCII + TSCII MIB = 2107 + + // CP51932 is the MIB identifier with IANA name CP51932. + // + // See http://www.iana.org/assignments/charset-reg/CP51932 + CP51932 MIB = 2108 + + // Windows874 is the MIB identifier with IANA name windows-874. + // + // See http://www.iana.org/assignments/charset-reg/windows-874 + Windows874 MIB = 2109 + + // Windows1250 is the MIB identifier with IANA name windows-1250. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1250 + Windows1250 MIB = 2250 + + // Windows1251 is the MIB identifier with IANA name windows-1251. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1251 + Windows1251 MIB = 2251 + + // Windows1252 is the MIB identifier with IANA name windows-1252. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1252 + Windows1252 MIB = 2252 + + // Windows1253 is the MIB identifier with IANA name windows-1253. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1253 + Windows1253 MIB = 2253 + + // Windows1254 is the MIB identifier with IANA name windows-1254. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1254 + Windows1254 MIB = 2254 + + // Windows1255 is the MIB identifier with IANA name windows-1255. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1255 + Windows1255 MIB = 2255 + + // Windows1256 is the MIB identifier with IANA name windows-1256. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1256 + Windows1256 MIB = 2256 + + // Windows1257 is the MIB identifier with IANA name windows-1257. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1257 + Windows1257 MIB = 2257 + + // Windows1258 is the MIB identifier with IANA name windows-1258. + // + // Microsoft http://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 http://www.iana.org/assignments/charset-reg/CP50220 + CP50220 MIB = 2260 +) diff --git a/vendor/golang.org/x/text/internal/gen/code.go b/vendor/golang.org/x/text/internal/gen/code.go new file mode 100644 index 0000000..0389509 --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/code.go @@ -0,0 +1,369 @@ +// 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. + +package gen + +import ( + "bytes" + "encoding/gob" + "fmt" + "hash" + "hash/fnv" + "io" + "log" + "os" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// This file contains utilities for generating code. + +// TODO: other write methods like: +// - slices, maps, types, etc. + +// CodeWriter is a utility for writing structured code. It computes the content +// hash and size of written content. It ensures there are newlines between +// written code blocks. +type CodeWriter struct { + buf bytes.Buffer + Size int + Hash hash.Hash32 // content hash + gob *gob.Encoder + // For comments we skip the usual one-line separator if they are followed by + // a code block. + skipSep bool +} + +func (w *CodeWriter) Write(p []byte) (n int, err error) { + return w.buf.Write(p) +} + +// NewCodeWriter returns a new CodeWriter. +func NewCodeWriter() *CodeWriter { + h := fnv.New32() + return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)} +} + +// WriteGoFile appends the buffer with the total size of all created structures +// and writes it as a Go file to the the given file with the given package name. +func (w *CodeWriter) WriteGoFile(filename, pkg string) { + f, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer f.Close() + if _, err = w.WriteGo(f, pkg, ""); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteVersionedGoFile appends the buffer with the total size of all created +// structures and writes it as a Go file to the the given file with the given +// package name and build tags for the current Unicode version, +func (w *CodeWriter) WriteVersionedGoFile(filename, pkg string) { + tags := buildTags() + if tags != "" { + filename = insertVersion(filename, UnicodeVersion()) + } + f, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer f.Close() + if _, err = w.WriteGo(f, pkg, tags); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo appends the buffer with the total size of all created structures and +// writes it as a Go file to the the given writer with the given package name. +func (w *CodeWriter) WriteGo(out io.Writer, pkg, tags string) (n int, err error) { + sz := w.Size + w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32()) + defer w.buf.Reset() + return WriteGo(out, pkg, tags, w.buf.Bytes()) +} + +func (w *CodeWriter) printf(f string, x ...interface{}) { + fmt.Fprintf(w, f, x...) +} + +func (w *CodeWriter) insertSep() { + if w.skipSep { + w.skipSep = false + return + } + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n") +} + +// WriteComment writes a comment block. All line starts are prefixed with "//". +// Initial empty lines are gobbled. The indentation for the first line is +// stripped from consecutive lines. +func (w *CodeWriter) WriteComment(comment string, args ...interface{}) { + s := fmt.Sprintf(comment, args...) + s = strings.Trim(s, "\n") + + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n// ") + w.skipSep = true + + // strip first indent level. + sep := "\n" + for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] { + sep += s[:1] + } + + strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s) + + w.printf("\n") +} + +func (w *CodeWriter) writeSizeInfo(size int) { + w.printf("// Size: %d bytes\n", size) +} + +// WriteConst writes a constant of the given name and value. +func (w *CodeWriter) WriteConst(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + + switch v.Type().Kind() { + case reflect.String: + w.printf("const %s %s = ", name, typeName(x)) + w.WriteString(v.String()) + w.printf("\n") + default: + w.printf("const %s = %#v\n", name, x) + } +} + +// WriteVar writes a variable of the given name and value. +func (w *CodeWriter) WriteVar(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + oldSize := w.Size + sz := int(v.Type().Size()) + w.Size += sz + + switch v.Type().Kind() { + case reflect.String: + w.printf("var %s %s = ", name, typeName(x)) + w.WriteString(v.String()) + case reflect.Struct: + w.gob.Encode(x) + fallthrough + case reflect.Slice, reflect.Array: + w.printf("var %s = ", name) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + default: + w.printf("var %s %s = ", name, typeName(x)) + w.gob.Encode(x) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + } + w.printf("\n") +} + +func (w *CodeWriter) writeValue(v reflect.Value) { + x := v.Interface() + switch v.Kind() { + case reflect.String: + w.WriteString(v.String()) + case reflect.Array: + // Don't double count: callers of WriteArray count on the size being + // added, so we need to discount it here. + w.Size -= int(v.Type().Size()) + w.writeSlice(x, true) + case reflect.Slice: + w.writeSlice(x, false) + case reflect.Struct: + w.printf("%s{\n", typeName(v.Interface())) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + w.printf("%s: ", t.Field(i).Name) + w.writeValue(v.Field(i)) + w.printf(",\n") + } + w.printf("}") + default: + w.printf("%#v", x) + } +} + +// WriteString writes a string literal. +func (w *CodeWriter) WriteString(s string) { + s = strings.Replace(s, `\`, `\\`, -1) + io.WriteString(w.Hash, s) // content hash + w.Size += len(s) + + const maxInline = 40 + if len(s) <= maxInline { + w.printf("%q", s) + return + } + + // We will render the string as a multi-line string. + const maxWidth = 80 - 4 - len(`"`) - len(`" +`) + + // When starting on its own line, go fmt indents line 2+ an extra level. + n, max := maxWidth, maxWidth-4 + + // As per https://golang.org/issue/18078, the compiler has trouble + // compiling the concatenation of many strings, s0 + s1 + s2 + ... + sN, + // for large N. We insert redundant, explicit parentheses to work around + // that, lowering the N at any given step: (s0 + s1 + ... + s63) + (s64 + + // ... + s127) + etc + (etc + ... + sN). + explicitParens, extraComment := len(s) > 128*1024, "" + if explicitParens { + w.printf(`(`) + extraComment = "; the redundant, explicit parens are for https://golang.org/issue/18078" + } + + // Print "" +\n, if a string does not start on its own line. + b := w.buf.Bytes() + if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' { + w.printf("\"\" + // Size: %d bytes%s\n", len(s), extraComment) + n, max = maxWidth, maxWidth + } + + w.printf(`"`) + + for sz, p, nLines := 0, 0, 0; p < len(s); { + var r rune + r, sz = utf8.DecodeRuneInString(s[p:]) + out := s[p : p+sz] + chars := 1 + if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' { + switch sz { + case 1: + out = fmt.Sprintf("\\x%02x", s[p]) + case 2, 3: + out = fmt.Sprintf("\\u%04x", r) + case 4: + out = fmt.Sprintf("\\U%08x", r) + } + chars = len(out) + } + if n -= chars; n < 0 { + nLines++ + if explicitParens && nLines&63 == 63 { + w.printf("\") + (\"") + } + w.printf("\" +\n\"") + n = max - len(out) + } + w.printf("%s", out) + p += sz + } + w.printf(`"`) + if explicitParens { + w.printf(`)`) + } +} + +// WriteSlice writes a slice value. +func (w *CodeWriter) WriteSlice(x interface{}) { + w.writeSlice(x, false) +} + +// WriteArray writes an array value. +func (w *CodeWriter) WriteArray(x interface{}) { + w.writeSlice(x, true) +} + +func (w *CodeWriter) writeSlice(x interface{}, isArray bool) { + v := reflect.ValueOf(x) + w.gob.Encode(v.Len()) + w.Size += v.Len() * int(v.Type().Elem().Size()) + name := typeName(x) + if isArray { + name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:]) + } + if isArray { + w.printf("%s{\n", name) + } else { + w.printf("%s{ // %d elements\n", name, v.Len()) + } + + switch kind := v.Type().Elem().Kind(); kind { + case reflect.String: + for _, s := range x.([]string) { + w.WriteString(s) + w.printf(",\n") + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // nLine and nBlock are the number of elements per line and block. + nLine, nBlock, format := 8, 64, "%d," + switch kind { + case reflect.Uint8: + format = "%#02x," + case reflect.Uint16: + format = "%#04x," + case reflect.Uint32: + nLine, nBlock, format = 4, 32, "%#08x," + case reflect.Uint, reflect.Uint64: + nLine, nBlock, format = 4, 32, "%#016x," + case reflect.Int8: + nLine = 16 + } + n := nLine + for i := 0; i < v.Len(); i++ { + if i%nBlock == 0 && v.Len() > nBlock { + w.printf("// Entry %X - %X\n", i, i+nBlock-1) + } + x := v.Index(i).Interface() + w.gob.Encode(x) + w.printf(format, x) + if n--; n == 0 { + n = nLine + w.printf("\n") + } + } + w.printf("\n") + case reflect.Struct: + zero := reflect.Zero(v.Type().Elem()).Interface() + for i := 0; i < v.Len(); i++ { + x := v.Index(i).Interface() + w.gob.EncodeValue(v) + if !reflect.DeepEqual(zero, x) { + line := fmt.Sprintf("%#v,\n", x) + line = line[strings.IndexByte(line, '{'):] + w.printf("%d: ", i) + w.printf(line) + } + } + case reflect.Array: + for i := 0; i < v.Len(); i++ { + w.printf("%d: %#v,\n", i, v.Index(i).Interface()) + } + default: + panic("gen: slice elem type not supported") + } + w.printf("}") +} + +// WriteType writes a definition of the type of the given value and returns the +// type name. +func (w *CodeWriter) WriteType(x interface{}) string { + t := reflect.TypeOf(x) + w.printf("type %s struct {\n", t.Name()) + for i := 0; i < t.NumField(); i++ { + w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type) + } + w.printf("}\n") + return t.Name() +} + +// typeName returns the name of the go type of x. +func typeName(x interface{}) string { + t := reflect.ValueOf(x).Type() + return strings.Replace(fmt.Sprint(t), "main.", "", 1) +} diff --git a/vendor/golang.org/x/text/internal/gen/gen.go b/vendor/golang.org/x/text/internal/gen/gen.go new file mode 100644 index 0000000..4c3f760 --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/gen.go @@ -0,0 +1,333 @@ +// 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. + +// Package gen contains common code for the various code generation tools in the +// text repository. Its usage ensures consistency between tools. +// +// This package defines command line flags that are common to most generation +// tools. The flags allow for specifying specific Unicode and CLDR versions +// in the public Unicode data repository (http://www.unicode.org/Public). +// +// A local Unicode data mirror can be set through the flag -local or the +// environment variable UNICODE_DIR. The former takes precedence. The local +// directory should follow the same structure as the public repository. +// +// IANA data can also optionally be mirrored by putting it in the iana directory +// rooted at the top of the local mirror. Beware, though, that IANA data is not +// versioned. So it is up to the developer to use the right version. +package gen // import "golang.org/x/text/internal/gen" + +import ( + "bytes" + "flag" + "fmt" + "go/build" + "go/format" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "sync" + "unicode" + + "golang.org/x/text/unicode/cldr" +) + +var ( + url = flag.String("url", + "http://www.unicode.org/Public", + "URL of Unicode database directory") + iana = flag.String("iana", + "http://www.iana.org", + "URL of the IANA repository") + unicodeVersion = flag.String("unicode", + getEnv("UNICODE_VERSION", unicode.Version), + "unicode version to use") + cldrVersion = flag.String("cldr", + getEnv("CLDR_VERSION", cldr.Version), + "cldr version to use") +) + +func getEnv(name, def string) string { + if v := os.Getenv(name); v != "" { + return v + } + return def +} + +// Init performs common initialization for a gen command. It parses the flags +// and sets up the standard logging parameters. +func Init() { + log.SetPrefix("") + log.SetFlags(log.Lshortfile) + flag.Parse() +} + +const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +` + +// UnicodeVersion reports the requested Unicode version. +func UnicodeVersion() string { + return *unicodeVersion +} + +// CLDRVersion reports the requested CLDR version. +func CLDRVersion() string { + return *cldrVersion +} + +var tags = []struct{ version, buildTags string }{ + {"10.0.0", "go1.10"}, + {"", "!go1.10"}, +} + +// buildTags reports the build tags used for the current Unicode version. +func buildTags() string { + v := UnicodeVersion() + for _, x := range tags { + // We should do a numeric comparison, but including the collate package + // would create an import cycle. We approximate it by assuming that + // longer version strings are later. + if len(x.version) <= len(v) { + return x.buildTags + } + if len(x.version) == len(v) && x.version <= v { + return x.buildTags + } + } + return tags[0].buildTags +} + +// IsLocal reports whether data files are available locally. +func IsLocal() bool { + dir, err := localReadmeFile() + if err != nil { + return false + } + if _, err = os.Stat(dir); err != nil { + return false + } + return true +} + +// OpenUCDFile opens the requested UCD file. The file is specified relative to +// the public Unicode root directory. It will call log.Fatal if there are any +// errors. +func OpenUCDFile(file string) io.ReadCloser { + return openUnicode(path.Join(*unicodeVersion, "ucd", file)) +} + +// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there +// are any errors. +func OpenCLDRCoreZip() io.ReadCloser { + return OpenUnicodeFile("cldr", *cldrVersion, "core.zip") +} + +// OpenUnicodeFile opens the requested file of the requested category from the +// root of the Unicode data archive. The file is specified relative to the +// public Unicode root directory. If version is "", it will use the default +// Unicode version. It will call log.Fatal if there are any errors. +func OpenUnicodeFile(category, version, file string) io.ReadCloser { + if version == "" { + version = UnicodeVersion() + } + return openUnicode(path.Join(category, version, file)) +} + +// OpenIANAFile opens the requested IANA file. The file is specified relative +// to the IANA root, which is typically either http://www.iana.org or the +// iana directory in the local mirror. It will call log.Fatal if there are any +// errors. +func OpenIANAFile(path string) io.ReadCloser { + return Open(*iana, "iana", path) +} + +var ( + dirMutex sync.Mutex + localDir string +) + +const permissions = 0755 + +func localReadmeFile() (string, error) { + p, err := build.Import("golang.org/x/text", "", build.FindOnly) + if err != nil { + return "", fmt.Errorf("Could not locate package: %v", err) + } + return filepath.Join(p.Dir, "DATA", "README"), nil +} + +func getLocalDir() string { + dirMutex.Lock() + defer dirMutex.Unlock() + + readme, err := localReadmeFile() + if err != nil { + log.Fatal(err) + } + dir := filepath.Dir(readme) + if _, err := os.Stat(readme); err != nil { + if err := os.MkdirAll(dir, permissions); err != nil { + log.Fatalf("Could not create directory: %v", err) + } + ioutil.WriteFile(readme, []byte(readmeTxt), permissions) + } + return dir +} + +const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT. + +This directory contains downloaded files used to generate the various tables +in the golang.org/x/text subrepo. + +Note that the language subtag repo (iana/assignments/language-subtag-registry) +and all other times in the iana subdirectory are not versioned and will need +to be periodically manually updated. The easiest way to do this is to remove +the entire iana directory. This is mostly of concern when updating the language +package. +` + +// Open opens subdir/path if a local directory is specified and the file exists, +// where subdir is a directory relative to the local root, or fetches it from +// urlRoot/path otherwise. It will call log.Fatal if there are any errors. +func Open(urlRoot, subdir, path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path)) + return open(file, urlRoot, path) +} + +func openUnicode(path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), filepath.FromSlash(path)) + return open(file, *url, path) +} + +// TODO: automatically periodically update non-versioned files. + +func open(file, urlRoot, path string) io.ReadCloser { + if f, err := os.Open(file); err == nil { + return f + } + r := get(urlRoot, path) + defer r.Close() + b, err := ioutil.ReadAll(r) + if err != nil { + log.Fatalf("Could not download file: %v", err) + } + os.MkdirAll(filepath.Dir(file), permissions) + if err := ioutil.WriteFile(file, b, permissions); err != nil { + log.Fatalf("Could not create file: %v", err) + } + return ioutil.NopCloser(bytes.NewReader(b)) +} + +func get(root, path string) io.ReadCloser { + url := root + "/" + path + fmt.Printf("Fetching %s...", url) + defer fmt.Println(" done.") + resp, err := http.Get(url) + if err != nil { + log.Fatalf("HTTP GET: %v", err) + } + if resp.StatusCode != 200 { + log.Fatalf("Bad GET status for %q: %q", url, resp.Status) + } + return resp.Body +} + +// TODO: use Write*Version in all applicable packages. + +// WriteUnicodeVersion writes a constant for the Unicode version from which the +// tables are generated. +func WriteUnicodeVersion(w io.Writer) { + fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion()) +} + +// WriteCLDRVersion writes a constant for the CLDR version from which the +// tables are generated. +func WriteCLDRVersion(w io.Writer) { + fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion()) +} + +// WriteGoFile prepends a standard file comment and package statement to the +// given bytes, applies gofmt, and writes them to a file with the given name. +// It will call log.Fatal if there are any errors. +func WriteGoFile(filename, pkg string, b []byte) { + w, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer w.Close() + if _, err = WriteGo(w, pkg, "", b); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +func insertVersion(filename, version string) string { + suffix := ".go" + if strings.HasSuffix(filename, "_test.go") { + suffix = "_test.go" + } + return fmt.Sprint(filename[:len(filename)-len(suffix)], version, suffix) +} + +// WriteVersionedGoFile prepends a standard file comment, adds build tags to +// version the file for the current Unicode version, and package statement to +// the given bytes, applies gofmt, and writes them to a file with the given +// name. It will call log.Fatal if there are any errors. +func WriteVersionedGoFile(filename, pkg string, b []byte) { + tags := buildTags() + if tags != "" { + filename = insertVersion(filename, UnicodeVersion()) + } + w, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer w.Close() + if _, err = WriteGo(w, pkg, tags, b); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo prepends a standard file comment and package statement to the given +// bytes, applies gofmt, and writes them to w. +func WriteGo(w io.Writer, pkg, tags string, b []byte) (n int, err error) { + src := []byte(header) + if tags != "" { + src = append(src, fmt.Sprintf("// +build %s\n\n", tags)...) + } + src = append(src, fmt.Sprintf("package %s\n\n", pkg)...) + src = append(src, b...) + formatted, err := format.Source(src) + if err != nil { + // Print the generated code even in case of an error so that the + // returned error can be meaningfully interpreted. + n, _ = w.Write(src) + return n, err + } + return w.Write(formatted) +} + +// Repackage rewrites a Go file from belonging to package main to belonging to +// the given package. +func Repackage(inFile, outFile, pkg string) { + src, err := ioutil.ReadFile(inFile) + if err != nil { + log.Fatalf("reading %s: %v", inFile, err) + } + const toDelete = "package main\n\n" + i := bytes.Index(src, []byte(toDelete)) + if i < 0 { + log.Fatalf("Could not find %q in %s.", toDelete, inFile) + } + w := &bytes.Buffer{} + w.Write(src[i+len(toDelete):]) + WriteGoFile(outFile, pkg, w.Bytes()) +} 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..fe47b9b --- /dev/null +++ b/vendor/golang.org/x/text/transform/transform.go @@ -0,0 +1,705 @@ +// 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 be returned + // regardless of whether atEOF is true. If err is nil, then 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:]) + nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], pSrc+n == len(s)) + 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 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/golang.org/x/text/unicode/cldr/base.go b/vendor/golang.org/x/text/unicode/cldr/base.go new file mode 100644 index 0000000..63cdc16 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/base.go @@ -0,0 +1,105 @@ +// 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 cldr + +import ( + "encoding/xml" + "regexp" + "strconv" +) + +// Elem is implemented by every XML element. +type Elem interface { + setEnclosing(Elem) + setName(string) + enclosing() Elem + + GetCommon() *Common +} + +type hidden struct { + CharData string `xml:",chardata"` + Alias *struct { + Common + Source string `xml:"source,attr"` + Path string `xml:"path,attr"` + } `xml:"alias"` + Def *struct { + Common + Choice string `xml:"choice,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + } `xml:"default"` +} + +// Common holds several of the most common attributes and sub elements +// of an XML element. +type Common struct { + XMLName xml.Name + name string + enclElem Elem + Type string `xml:"type,attr,omitempty"` + Reference string `xml:"reference,attr,omitempty"` + Alt string `xml:"alt,attr,omitempty"` + ValidSubLocales string `xml:"validSubLocales,attr,omitempty"` + Draft string `xml:"draft,attr,omitempty"` + hidden +} + +// Default returns the default type to select from the enclosed list +// or "" if no default value is specified. +func (e *Common) Default() string { + if e.Def == nil { + return "" + } + if e.Def.Choice != "" { + return e.Def.Choice + } else if e.Def.Type != "" { + // Type is still used by the default element in collation. + return e.Def.Type + } + return "" +} + +// Element returns the XML element name. +func (e *Common) Element() string { + return e.name +} + +// GetCommon returns e. It is provided such that Common implements Elem. +func (e *Common) GetCommon() *Common { + return e +} + +// Data returns the character data accumulated for this element. +func (e *Common) Data() string { + e.CharData = charRe.ReplaceAllStringFunc(e.CharData, replaceUnicode) + return e.CharData +} + +func (e *Common) setName(s string) { + e.name = s +} + +func (e *Common) enclosing() Elem { + return e.enclElem +} + +func (e *Common) setEnclosing(en Elem) { + e.enclElem = en +} + +// Escape characters that can be escaped without further escaping the string. +var charRe = regexp.MustCompile(`&#x[0-9a-fA-F]*;|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\[abtnvfr]`) + +// replaceUnicode converts hexadecimal Unicode codepoint notations to a one-rune string. +// It assumes the input string is correctly formatted. +func replaceUnicode(s string) string { + if s[1] == '#' { + r, _ := strconv.ParseInt(s[3:len(s)-1], 16, 32) + return string(r) + } + r, _, _, _ := strconv.UnquoteChar(s, 0) + return string(r) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/cldr.go b/vendor/golang.org/x/text/unicode/cldr/cldr.go new file mode 100644 index 0000000..2197f8a --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/cldr.go @@ -0,0 +1,130 @@ +// 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. + +//go:generate go run makexml.go -output xml.go + +// Package cldr provides a parser for LDML and related XML formats. +// This package is intended to be used by the table generation tools +// for the various internationalization-related packages. +// As the XML types are generated from the CLDR DTD, and as the CLDR standard +// is periodically amended, this package may change considerably over time. +// This mostly means that data may appear and disappear between versions. +// That is, old code should keep compiling for newer versions, but data +// may have moved or changed. +// CLDR version 22 is the first version supported by this package. +// Older versions may not work. +package cldr // import "golang.org/x/text/unicode/cldr" + +import ( + "fmt" + "sort" +) + +// CLDR provides access to parsed data of the Unicode Common Locale Data Repository. +type CLDR struct { + parent map[string][]string + locale map[string]*LDML + resolved map[string]*LDML + bcp47 *LDMLBCP47 + supp *SupplementalData +} + +func makeCLDR() *CLDR { + return &CLDR{ + parent: make(map[string][]string), + locale: make(map[string]*LDML), + resolved: make(map[string]*LDML), + bcp47: &LDMLBCP47{}, + supp: &SupplementalData{}, + } +} + +// BCP47 returns the parsed BCP47 LDML data. If no such data was parsed, nil is returned. +func (cldr *CLDR) BCP47() *LDMLBCP47 { + return nil +} + +// Draft indicates the draft level of an element. +type Draft int + +const ( + Approved Draft = iota + Contributed + Provisional + Unconfirmed +) + +var drafts = []string{"unconfirmed", "provisional", "contributed", "approved", ""} + +// ParseDraft returns the Draft value corresponding to the given string. The +// empty string corresponds to Approved. +func ParseDraft(level string) (Draft, error) { + if level == "" { + return Approved, nil + } + for i, s := range drafts { + if level == s { + return Unconfirmed - Draft(i), nil + } + } + return Approved, fmt.Errorf("cldr: unknown draft level %q", level) +} + +func (d Draft) String() string { + return drafts[len(drafts)-1-int(d)] +} + +// SetDraftLevel sets which draft levels to include in the evaluated LDML. +// Any draft element for which the draft level is higher than lev will be excluded. +// If multiple draft levels are available for a single element, the one with the +// lowest draft level will be selected, unless preferDraft is true, in which case +// the highest draft will be chosen. +// It is assumed that the underlying LDML is canonicalized. +func (cldr *CLDR) SetDraftLevel(lev Draft, preferDraft bool) { + // TODO: implement + cldr.resolved = make(map[string]*LDML) +} + +// RawLDML returns the LDML XML for id in unresolved form. +// id must be one of the strings returned by Locales. +func (cldr *CLDR) RawLDML(loc string) *LDML { + return cldr.locale[loc] +} + +// LDML returns the fully resolved LDML XML for loc, which must be one of +// the strings returned by Locales. +func (cldr *CLDR) LDML(loc string) (*LDML, error) { + return cldr.resolve(loc) +} + +// Supplemental returns the parsed supplemental data. If no such data was parsed, +// nil is returned. +func (cldr *CLDR) Supplemental() *SupplementalData { + return cldr.supp +} + +// Locales returns the locales for which there exist files. +// Valid sublocales for which there is no file are not included. +// The root locale is always sorted first. +func (cldr *CLDR) Locales() []string { + loc := []string{"root"} + hasRoot := false + for l, _ := range cldr.locale { + if l == "root" { + hasRoot = true + continue + } + loc = append(loc, l) + } + sort.Strings(loc[1:]) + if !hasRoot { + return loc[1:] + } + return loc +} + +// Get fills in the fields of x based on the XPath path. +func Get(e Elem, path string) (res Elem, err error) { + return walkXPath(e, path) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/collate.go b/vendor/golang.org/x/text/unicode/cldr/collate.go new file mode 100644 index 0000000..80ee28d --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/collate.go @@ -0,0 +1,359 @@ +// 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 cldr + +import ( + "bufio" + "encoding/xml" + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// RuleProcessor can be passed to Collator's Process method, which +// parses the rules and calls the respective method for each rule found. +type RuleProcessor interface { + Reset(anchor string, before int) error + Insert(level int, str, context, extend string) error + Index(id string) +} + +const ( + // cldrIndex is a Unicode-reserved sentinel value used to mark the start + // of a grouping within an index. + // We ignore any rule that starts with this rune. + // See http://unicode.org/reports/tr35/#Collation_Elements for details. + cldrIndex = "\uFDD0" + + // specialAnchor is the format in which to represent logical reset positions, + // such as "first tertiary ignorable". + specialAnchor = "<%s/>" +) + +// Process parses the rules for the tailorings of this collation +// and calls the respective methods of p for each rule found. +func (c Collation) Process(p RuleProcessor) (err error) { + if len(c.Cr) > 0 { + if len(c.Cr) > 1 { + return fmt.Errorf("multiple cr elements, want 0 or 1") + } + return processRules(p, c.Cr[0].Data()) + } + if c.Rules.Any != nil { + return c.processXML(p) + } + return errors.New("no tailoring data") +} + +// processRules parses rules in the Collation Rule Syntax defined in +// http://www.unicode.org/reports/tr35/tr35-collation.html#Collation_Tailorings. +func processRules(p RuleProcessor, s string) (err error) { + chk := func(s string, e error) string { + if err == nil { + err = e + } + return s + } + i := 0 // Save the line number for use after the loop. + scanner := bufio.NewScanner(strings.NewReader(s)) + for ; scanner.Scan() && err == nil; i++ { + for s := skipSpace(scanner.Text()); s != "" && s[0] != '#'; s = skipSpace(s) { + level := 5 + var ch byte + switch ch, s = s[0], s[1:]; ch { + case '&': // followed by or '[' ']' + if s = skipSpace(s); consume(&s, '[') { + s = chk(parseSpecialAnchor(p, s)) + } else { + s = chk(parseAnchor(p, 0, s)) + } + case '<': // sort relation '<'{1,4}, optionally followed by '*'. + for level = 1; consume(&s, '<'); level++ { + } + if level > 4 { + err = fmt.Errorf("level %d > 4", level) + } + fallthrough + case '=': // identity relation, optionally followed by *. + if consume(&s, '*') { + s = chk(parseSequence(p, level, s)) + } else { + s = chk(parseOrder(p, level, s)) + } + default: + chk("", fmt.Errorf("illegal operator %q", ch)) + break + } + } + } + if chk("", scanner.Err()); err != nil { + return fmt.Errorf("%d: %v", i, err) + } + return nil +} + +// parseSpecialAnchor parses the anchor syntax which is either of the form +// ['before' ] +// or +// [