Update vendor/

This commit is contained in:
2018-06-28 01:09:56 +01:00
parent 3e5ab5bb0a
commit 21c6e571d8
108 changed files with 121110 additions and 1144 deletions

32
Gopkg.lock generated
View File

@@ -27,17 +27,28 @@
version = "v1.1.0" version = "v1.1.0"
[[projects]] [[projects]]
branch = "v1"
name = "github.com/emersion/go-imap" name = "github.com/emersion/go-imap"
packages = [ packages = [
".", ".",
"backend", "backend",
"backend/backendutil",
"commands", "commands",
"responses", "responses",
"server", "server",
"utf7" "utf7"
] ]
revision = "840b16b212bff35b595e708937c6d60861cfab49" revision = "e402a386e2001febf508c43e85d1a1383dca9e3c"
version = "v0.9"
[[projects]]
name = "github.com/emersion/go-message"
packages = [
".",
"charset",
"mail"
]
revision = "f7e2be8074d097a816a1f5bd9d502adc46275d4a"
version = "v0.9.1"
[[projects]] [[projects]]
branch = "master" branch = "master"
@@ -51,6 +62,12 @@
packages = ["."] packages = ["."]
revision = "592cbda523faa1cbe7089c2997534a4a5a22986a" revision = "592cbda523faa1cbe7089c2997534a4a5a22986a"
[[projects]]
branch = "master"
name = "github.com/emersion/go-textwrapper"
packages = ["."]
revision = "d0e65e56babe3f687ff94c1d764ca0e6aa7723ee"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/luksen/maildir" name = "github.com/luksen/maildir"
@@ -85,7 +102,7 @@
branch = "master" branch = "master"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = ["context"] packages = ["context"]
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" revision = "e514e69ffb8bc3c76a71ae40de0118d794855992"
[[projects]] [[projects]]
branch = "master" branch = "master"
@@ -97,13 +114,18 @@
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix"] packages = ["unix"]
revision = "a200a19cb90b19de298170992778b1fda7217bd6" revision = "7138fd3d9dc8335c567ca206f4333fb75eb05d56"
[[projects]] [[projects]]
name = "golang.org/x/text" name = "golang.org/x/text"
packages = [ packages = [
"encoding", "encoding",
"encoding/charmap",
"encoding/internal",
"encoding/internal/identifier", "encoding/internal/identifier",
"encoding/japanese",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"internal/gen", "internal/gen",
"transform", "transform",
"unicode/cldr" "unicode/cldr"
@@ -120,6 +142,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "3c83cbb5b80f3005d42707c0c6e4f1b301a0429c8b83a837816d41ddeb9dd3ef" inputs-digest = "08b3e1cf0695997a6bb64a34c8f6d941f605f7354a813e70606352318c6cfb5f"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@@ -24,6 +24,10 @@
# go-tests = true # go-tests = true
# unused-packages = true # unused-packages = true
[[constraint]]
name = "github.com/emersion/go-imap"
branch = "v1"
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true

View File

@@ -1,5 +1,6 @@
language: go language: go
sudo: false
go: go:
- 1.8 - 1.9
script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh) script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh)
after_script: bash <(curl -s https://codecov.io/bash) after_script: bash <(curl -s https://codecov.io/bash)

View File

@@ -6,7 +6,6 @@
[![Go Report [![Go Report
Card](https://goreportcard.com/badge/github.com/emersion/go-imap)](https://goreportcard.com/report/github.com/emersion/go-imap) 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) [![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 An [IMAP4rev1](https://tools.ietf.org/html/rfc3501) library written in Go. It
can be used to build a client and/or a server. can be used to build a client and/or a server.
@@ -96,7 +95,7 @@ func main() {
messages := make(chan *imap.Message, 10) messages := make(chan *imap.Message, 10)
done = make(chan error, 1) done = make(chan error, 1)
go func() { go func() {
done <- c.Fetch(seqset, []string{imap.EnvelopeMsgAttr}, messages) done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
}() }()
log.Println("Last 4 messages:") log.Println("Last 4 messages:")

View File

@@ -0,0 +1,2 @@
// Package backendutil provides utility functions to implement IMAP backends.
package backendutil

View File

@@ -0,0 +1,68 @@
package backendutil
import (
"bytes"
"errors"
"io"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
)
var errNoSuchPart = errors.New("backendutil: no such message body part")
// FetchBodySection extracts a body section from a message.
func FetchBodySection(e *message.Entity, section *imap.BodySectionName) (imap.Literal, error) {
// First, find the requested part using the provided path
for i := len(section.Path) - 1; i >= 0; i-- {
n := section.Path[i]
mr := e.MultipartReader()
if mr == nil {
return nil, errNoSuchPart
}
for j := 1; j <= n; j++ {
p, err := mr.NextPart()
if err == io.EOF {
return nil, errNoSuchPart
} else if err != nil {
return nil, err
}
if j == n {
e = p
break
}
}
}
// Then, write the requested data to a buffer
b := new(bytes.Buffer)
// Write the header
mw, err := message.CreateWriter(b, e.Header)
if err != nil {
return nil, err
}
defer mw.Close()
// If the header hasn't been requested, discard it
if section.Specifier == imap.TextSpecifier {
b.Reset()
}
// Write the body, if requested
switch section.Specifier {
case imap.EntireSpecifier, imap.TextSpecifier:
if _, err := io.Copy(mw, e.Body); err != nil {
return nil, err
}
}
var l imap.Literal = b
if section.Partial != nil {
l = bytes.NewReader(section.ExtractPartial(b.Bytes()))
}
return l, nil
}

View File

@@ -0,0 +1,60 @@
package backendutil
import (
"io"
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
)
// FetchBodyStructure computes a message's body structure from its content.
func FetchBodyStructure(e *message.Entity, extended bool) (*imap.BodyStructure, error) {
bs := new(imap.BodyStructure)
mediaType, mediaParams, _ := e.Header.ContentType()
typeParts := strings.SplitN(mediaType, "/", 2)
bs.MIMEType = typeParts[0]
if len(typeParts) == 2 {
bs.MIMESubType = typeParts[1]
}
bs.Params = mediaParams
bs.Id = e.Header.Get("Content-Id")
bs.Description = e.Header.Get("Content-Description")
bs.Encoding = e.Header.Get("Content-Encoding")
// TODO: bs.Size
if mr := e.MultipartReader(); mr != nil {
var parts []*imap.BodyStructure
for {
p, err := mr.NextPart()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
pbs, err := FetchBodyStructure(p, extended)
if err != nil {
return nil, err
}
parts = append(parts, pbs)
}
bs.Parts = parts
}
// TODO: bs.Envelope, bs.BodyStructure
// TODO: bs.Lines
if extended {
bs.Extended = true
bs.Disposition, bs.DispositionParams, _ = e.Header.ContentDisposition()
// TODO: bs.Language, bs.Location
// TODO: bs.MD5
}
return bs, nil
}

View File

@@ -0,0 +1,50 @@
package backendutil
import (
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
)
func headerAddressList(h mail.Header, key string) ([]*imap.Address, error) {
addrs, err := h.AddressList(key)
list := make([]*imap.Address, len(addrs))
for i, a := range addrs {
parts := strings.SplitN(a.Address, "@", 2)
mailbox := parts[0]
var hostname string
if len(parts) == 2 {
hostname = parts[1]
}
list[i] = &imap.Address{
PersonalName: a.Name,
MailboxName: mailbox,
HostName: hostname,
}
}
return list, err
}
// FetchEnvelope returns a message's envelope from its header.
func FetchEnvelope(h message.Header) (*imap.Envelope, error) {
mh := mail.Header{h}
env := new(imap.Envelope)
env.Date, _ = mh.Date()
env.Subject, _ = mh.Subject()
env.From, _ = headerAddressList(mh, "From")
env.Sender, _ = headerAddressList(mh, "Sender")
env.ReplyTo, _ = headerAddressList(mh, "Reply-To")
env.To, _ = headerAddressList(mh, "To")
env.Cc, _ = headerAddressList(mh, "Cc")
env.Bcc, _ = headerAddressList(mh, "Bcc")
env.InReplyTo = mh.Get("In-Reply-To")
env.MessageId = mh.Get("Message-Id")
return env, nil
}

View File

@@ -0,0 +1,40 @@
package backendutil
import (
"github.com/emersion/go-imap"
)
// UpdateFlags executes a flag operation on the flag set current.
func UpdateFlags(current []string, op imap.FlagsOp, flags []string) []string {
switch op {
case imap.SetFlags:
// TODO: keep \Recent if it is present
return flags
case imap.AddFlags:
// Check for duplicates
for _, flag := range current {
for i, addFlag := range flags {
if addFlag == flag {
flags = append(flags[:i], flags[i+1:]...)
break
}
}
}
return append(current, flags...)
case imap.RemoveFlags:
// Iterate through flags from the last one to the first one, to be able to
// delete some of them.
for i := len(current) - 1; i >= 0; i-- {
flag := current[i]
for _, removeFlag := range flags {
if removeFlag == flag {
current = append(current[:i], current[i+1:]...)
break
}
}
}
return current
}
return current
}

View File

@@ -0,0 +1,225 @@
package backendutil
import (
"bytes"
"fmt"
"io"
"strings"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
)
func matchString(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
func bufferBody(e *message.Entity) (*bytes.Buffer, error) {
b := new(bytes.Buffer)
if _, err := io.Copy(b, e.Body); err != nil {
return nil, err
}
e.Body = b
return b, nil
}
func matchBody(e *message.Entity, substr string) (bool, error) {
if s, ok := e.Body.(fmt.Stringer); ok {
return matchString(s.String(), substr), nil
}
b, err := bufferBody(e)
if err != nil {
return false, err
}
return matchString(b.String(), substr), nil
}
type lengther interface {
Len() int
}
func bodyLen(e *message.Entity) (int, error) {
if l, ok := e.Body.(lengther); ok {
return l.Len(), nil
}
b, err := bufferBody(e)
if err != nil {
return 0, err
}
return b.Len(), nil
}
// Match returns true if a message matches the provided criteria. Sequence
// number, UID, flag and internal date contrainsts are not checked.
func Match(e *message.Entity, c *imap.SearchCriteria) (bool, error) {
// TODO: support encoded header fields for Bcc, Cc, From, To
// TODO: add header size for Larger and Smaller
h := mail.Header{e.Header}
if !c.SentBefore.IsZero() || !c.SentSince.IsZero() {
t, err := h.Date()
if err != nil {
return false, err
}
t = t.Round(24 * time.Hour)
if !c.SentBefore.IsZero() && !t.Before(c.SentBefore) {
return false, nil
}
if !c.SentSince.IsZero() && !t.After(c.SentSince) {
return false, nil
}
}
for key, wantValues := range c.Header {
values, ok := e.Header[key]
for _, wantValue := range wantValues {
if wantValue == "" && !ok {
return false, nil
}
if wantValue != "" {
ok := false
for _, v := range values {
if matchString(v, wantValue) {
ok = true
break
}
}
if !ok {
return false, nil
}
}
}
}
for _, body := range c.Body {
if ok, err := matchBody(e, body); err != nil || !ok {
return false, err
}
}
for _, text := range c.Text {
// TODO: also match header fields
if ok, err := matchBody(e, text); err != nil || !ok {
return false, err
}
}
if c.Larger > 0 || c.Smaller > 0 {
n, err := bodyLen(e)
if err != nil {
return false, err
}
if c.Larger > 0 && uint32(n) < c.Larger {
return false, nil
}
if c.Smaller > 0 && uint32(n) > c.Smaller {
return false, nil
}
}
for _, not := range c.Not {
ok, err := Match(e, not)
if err != nil || ok {
return false, err
}
}
for _, or := range c.Or {
ok1, err := Match(e, or[0])
if err != nil {
return ok1, err
}
ok2, err := Match(e, or[1])
if err != nil || (!ok1 && !ok2) {
return false, err
}
}
return true, nil
}
func matchFlags(flags map[string]bool, c *imap.SearchCriteria) bool {
for _, f := range c.WithFlags {
if !flags[f] {
return false
}
}
for _, f := range c.WithoutFlags {
if flags[f] {
return false
}
}
for _, not := range c.Not {
if matchFlags(flags, not) {
return false
}
}
for _, or := range c.Or {
if !matchFlags(flags, or[0]) && !matchFlags(flags, or[1]) {
return false
}
}
return true
}
// MatchFlags returns true if a flag list matches the provided criteria.
func MatchFlags(flags []string, c *imap.SearchCriteria) bool {
flagsMap := make(map[string]bool)
for _, f := range flags {
flagsMap[f] = true
}
return matchFlags(flagsMap, c)
}
// MatchSeqNumAndUid returns true if a sequence number and a UID matches the
// provided criteria.
func MatchSeqNumAndUid(seqNum uint32, uid uint32, c *imap.SearchCriteria) bool {
if c.SeqNum != nil && !c.SeqNum.Contains(seqNum) {
return false
}
if c.Uid != nil && !c.Uid.Contains(uid) {
return false
}
for _, not := range c.Not {
if MatchSeqNumAndUid(seqNum, uid, not) {
return false
}
}
for _, or := range c.Or {
if !MatchSeqNumAndUid(seqNum, uid, or[0]) && !MatchSeqNumAndUid(seqNum, uid, or[1]) {
return false
}
}
return true
}
// MatchDate returns true if a date matches the provided criteria.
func MatchDate(date time.Time, c *imap.SearchCriteria) bool {
date = date.Round(24 * time.Hour)
if !c.Since.IsZero() && !date.After(c.Since) {
return false
}
if !c.Before.IsZero() && !date.Before(c.Before) {
return false
}
for _, not := range c.Not {
if MatchDate(date, not) {
return false
}
}
for _, or := range c.Or {
if !MatchDate(date, or[0]) && !MatchDate(date, or[1]) {
return false
}
}
return true
}

View File

@@ -15,11 +15,11 @@ type Mailbox interface {
// Info returns this mailbox info. // Info returns this mailbox info.
Info() (*imap.MailboxInfo, error) Info() (*imap.MailboxInfo, error)
// Status returns this mailbox status. The fields Name, Flags and // Status returns this mailbox status. The fields Name, Flags, PermanentFlags
// PermanentFlags in the returned MailboxStatus must be always populated. This // and UnseenSeqNum in the returned MailboxStatus must be always populated.
// function does not affect the state of any messages in the mailbox. See RFC // This function does not affect the state of any messages in the mailbox. See
// 3501 section 6.3.10 for a list of items that can be requested. // RFC 3501 section 6.3.10 for a list of items that can be requested.
Status(items []string) (*imap.MailboxStatus, error) Status(items []imap.StatusItem) (*imap.MailboxStatus, error)
// SetSubscribed adds or removes the mailbox to the server's set of "active" // SetSubscribed adds or removes the mailbox to the server's set of "active"
// or "subscribed" mailboxes. // or "subscribed" mailboxes.
@@ -38,7 +38,7 @@ type Mailbox interface {
// 3501 section 6.4.5 for a list of items that can be requested. // 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. // 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 ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error
// SearchMessages searches messages. The returned list must contain UIDs if // SearchMessages searches messages. The returned list must contain UIDs if
// uid is set to true, or sequence numbers otherwise. // uid is set to true, or sequence numbers otherwise.

View File

@@ -6,35 +6,47 @@ import (
// Update contains user and mailbox information about an unilateral backend // Update contains user and mailbox information about an unilateral backend
// update. // update.
type Update struct { type Update interface {
// The user targeted by this update. If empty, all connected users will // The user targeted by this update. If empty, all connected users will
// be notified. // be notified.
Username string Username() string
// The mailbox targeted by this update. If empty, the update targets all // The mailbox targeted by this update. If empty, the update targets all
// mailboxes. // mailboxes.
Mailbox string Mailbox() string
// Done returns a channel that is closed when the update has been broadcast to
// all clients.
Done() chan struct{}
}
// A channel that will be closed once the update has been processed. // NewUpdate creates a new update.
func NewUpdate(username, mailbox string) Update {
return &update{
username: username,
mailbox: mailbox,
}
}
type update struct {
username string
mailbox string
done chan struct{} done chan struct{}
} }
// Done returns a channel that is closed when the update has been broadcast to func (u *update) Username() string {
// all clients. return u.username
func (u *Update) Done() <-chan struct{} { }
func (u *update) Mailbox() string {
return u.mailbox
}
func (u *update) Done() chan struct{} {
if u.done == nil { if u.done == nil {
u.done = make(chan struct{}) u.done = make(chan struct{})
} }
return u.done 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 // StatusUpdate is a status update. See RFC 3501 section 7.1 for a list of
// status responses. // status responses.
type StatusUpdate struct { type StatusUpdate struct {
@@ -60,50 +72,21 @@ type ExpungeUpdate struct {
SeqNum uint32 SeqNum uint32
} }
// Updater is a Backend that implements Updater is able to send unilateral // BackendUpdater is a Backend that implements Updater is able to send
// backend updates. Backends not implementing this interface don't correctly // unilateral backend updates. Backends not implementing this interface don't
// send unilateral updates, for instance if a user logs in from two connections // correctly send unilateral updates, for instance if a user logs in from two
// and deletes a message from one of them, the over is not aware that such a // connections and deletes a message from one of them, the over is not aware
// mesage has been deleted. More importantly, backends implementing Updater can // that such a mesage has been deleted. More importantly, backends implementing
// notify the user for external updates such as new message notifications. // Updater can notify the user for external updates such as new message
type Updater interface { // notifications.
type BackendUpdater interface {
// Updates returns a set of channels where updates are sent to. // Updates returns a set of channels where updates are sent to.
Updates() <-chan interface{} Updates() <-chan Update
} }
// UpdaterMailbox is a Mailbox that implements UpdaterMailbox is able to poll // MailboxPoller is a Mailbox that is able to poll updates for new messages or
// updates for new messages or message status updates during a period of // message status updates during a period of inactivity.
// inactivity. type MailboxPoller interface {
type UpdaterMailbox interface {
// Poll requests mailbox updates. // Poll requests mailbox updates.
Poll() error 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
}

View File

@@ -5,38 +5,6 @@ import (
"strings" "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. // A value that can be converted to a command.
type Commander interface { type Commander interface {
Command() *Command Command() *Command

View File

@@ -19,13 +19,13 @@ type Append struct {
func (cmd *Append) Command() *imap.Command { func (cmd *Append) Command() *imap.Command {
var args []interface{} var args []interface{}
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
args = append(args, mailbox) args = append(args, mailbox)
if cmd.Flags != nil { if cmd.Flags != nil {
flags := make([]interface{}, len(cmd.Flags)) flags := make([]interface{}, len(cmd.Flags))
for i, flag := range cmd.Flags { for i, flag := range cmd.Flags {
flags[i] = flag flags[i] = imap.Atom(flag)
} }
args = append(args, flags) args = append(args, flags)
} }
@@ -37,7 +37,7 @@ func (cmd *Append) Command() *imap.Command {
args = append(args, cmd.Message) args = append(args, cmd.Message)
return &imap.Command{ return &imap.Command{
Name: imap.Append, Name: "APPEND",
Arguments: args, Arguments: args,
} }
} }
@@ -48,9 +48,9 @@ func (cmd *Append) Parse(fields []interface{}) (err error) {
} }
// Parse mailbox name // Parse mailbox name
if mailbox, ok := fields[0].(string); !ok { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err = utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
@@ -81,11 +81,9 @@ func (cmd *Append) Parse(fields []interface{}) (err error) {
// Parse date // Parse date
if len(fields) > 0 { if len(fields) > 0 {
date, ok := fields[0].(string) if date, ok := fields[0].(string); !ok {
if !ok {
return errors.New("Date must be a string") return errors.New("Date must be a string")
} } else if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
return err return err
} }
} }

View File

@@ -27,7 +27,7 @@ type Authenticate struct {
func (cmd *Authenticate) Command() *imap.Command { func (cmd *Authenticate) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Authenticate, Name: "AUTHENTICATE",
Arguments: []interface{}{cmd.Mechanism}, Arguments: []interface{}{cmd.Mechanism},
} }
} }
@@ -62,7 +62,7 @@ func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn Authenti
} }
encoded := base64.StdEncoding.EncodeToString(challenge) encoded := base64.StdEncoding.EncodeToString(challenge)
cont := &imap.ContinuationResp{Info: encoded} cont := &imap.ContinuationReq{Info: encoded}
if err := conn.WriteResp(cont); err != nil { if err := conn.WriteResp(cont); err != nil {
return err return err
} }

View File

@@ -9,7 +9,7 @@ type Capability struct{}
func (c *Capability) Command() *imap.Command { func (c *Capability) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Capability, Name: "CAPABILITY",
} }
} }

View File

@@ -9,7 +9,7 @@ type Check struct{}
func (cmd *Check) Command() *imap.Command { func (cmd *Check) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Check, Name: "CHECK",
} }
} }

View File

@@ -9,7 +9,7 @@ type Close struct{}
func (cmd *Close) Command() *imap.Command { func (cmd *Close) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Close, Name: "CLOSE",
} }
} }

View File

@@ -14,10 +14,10 @@ type Copy struct {
} }
func (cmd *Copy) Command() *imap.Command { func (cmd *Copy) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: imap.Copy, Name: "COPY",
Arguments: []interface{}{cmd.SeqSet, mailbox}, Arguments: []interface{}{cmd.SeqSet, mailbox},
} }
} }
@@ -29,15 +29,15 @@ func (cmd *Copy) Parse(fields []interface{}) error {
if seqSet, ok := fields[0].(string); !ok { if seqSet, ok := fields[0].(string); !ok {
return errors.New("Invalid sequence set") return errors.New("Invalid sequence set")
} else if seqSet, err := imap.NewSeqSet(seqSet); err != nil { } else if seqSet, err := imap.ParseSeqSet(seqSet); err != nil {
return err return err
} else { } else {
cmd.SeqSet = seqSet cmd.SeqSet = seqSet
} }
if mailbox, ok := fields[1].(string); !ok { if mailbox, err := imap.ParseString(fields[1]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)

View File

@@ -13,10 +13,10 @@ type Create struct {
} }
func (cmd *Create) Command() *imap.Command { func (cmd *Create) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: imap.Create, Name: "CREATE",
Arguments: []interface{}{mailbox}, Arguments: []interface{}{mailbox},
} }
} }
@@ -26,9 +26,9 @@ func (cmd *Create) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if mailbox, ok := fields[0].(string); !ok { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)

View File

@@ -13,10 +13,10 @@ type Delete struct {
} }
func (cmd *Delete) Command() *imap.Command { func (cmd *Delete) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: imap.Delete, Name: "DELETE",
Arguments: []interface{}{mailbox}, Arguments: []interface{}{mailbox},
} }
} }
@@ -26,9 +26,9 @@ func (cmd *Delete) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if mailbox, ok := fields[0].(string); !ok { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)

View File

@@ -8,7 +8,7 @@ import (
type Expunge struct{} type Expunge struct{}
func (cmd *Expunge) Command() *imap.Command { func (cmd *Expunge) Command() *imap.Command {
return &imap.Command{Name: imap.Expunge} return &imap.Command{Name: "EXPUNGE"}
} }
func (cmd *Expunge) Parse(fields []interface{}) error { func (cmd *Expunge) Parse(fields []interface{}) error {

View File

@@ -10,21 +10,21 @@ import (
// Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5. // Fetch is a FETCH command, as defined in RFC 3501 section 6.4.5.
type Fetch struct { type Fetch struct {
SeqSet *imap.SeqSet SeqSet *imap.SeqSet
Items []string Items []imap.FetchItem
} }
func (cmd *Fetch) Command() *imap.Command { func (cmd *Fetch) Command() *imap.Command {
items := make([]interface{}, len(cmd.Items)) items := make([]interface{}, len(cmd.Items))
for i, item := range cmd.Items { for i, item := range cmd.Items {
if section, err := imap.NewBodySectionName(item); err == nil { if section, err := imap.ParseBodySectionName(item); err == nil {
items[i] = section items[i] = section
} else { } else {
items[i] = item items[i] = string(item)
} }
} }
return &imap.Command{ return &imap.Command{
Name: imap.Fetch, Name: "FETCH",
Arguments: []interface{}{cmd.SeqSet, items}, Arguments: []interface{}{cmd.SeqSet, items},
} }
} }
@@ -34,33 +34,22 @@ func (cmd *Fetch) Parse(fields []interface{}) error {
return errors.New("No enough arguments") 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 var err error
if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil { if seqset, ok := fields[0].(string); !ok {
return errors.New("Sequence set must be an atom")
} else if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
return err return err
} }
switch items := fields[1].(type) { switch items := fields[1].(type) {
case string: // A macro or a single item case string: // A macro or a single item
switch strings.ToUpper(items) { cmd.Items = imap.FetchItem(strings.ToUpper(items)).Expand()
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 case []interface{}: // A list of items
cmd.Items = make([]string, len(items)) cmd.Items = make([]imap.FetchItem, 0, len(items))
for i, v := range items { for _, v := range items {
item, _ := v.(string) itemStr, _ := v.(string)
cmd.Items[i] = strings.ToUpper(item) item := imap.FetchItem(strings.ToUpper(itemStr))
cmd.Items = append(cmd.Items, item.Expand()...)
} }
default: default:
return errors.New("Items must be either a string or a list") return errors.New("Items must be either a string or a list")

View File

@@ -17,13 +17,14 @@ type List struct {
} }
func (cmd *List) Command() *imap.Command { func (cmd *List) Command() *imap.Command {
name := imap.List name := "LIST"
if cmd.Subscribed { if cmd.Subscribed {
name = imap.Lsub name = "LSUB"
} }
ref, _ := utf7.Encoder.String(cmd.Reference) enc := utf7.Encoding.NewEncoder()
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) ref, _ := enc.String(cmd.Reference)
mailbox, _ := enc.String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: name, Name: name,
@@ -36,18 +37,20 @@ func (cmd *List) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if mailbox, ok := fields[0].(string); !ok { dec := utf7.Encoding.NewDecoder()
return errors.New("Reference name must be a string")
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return err
} else if mailbox, err := dec.String(mailbox); err != nil {
return err return err
} else { } else {
// TODO: canonical mailbox path // TODO: canonical mailbox path
cmd.Reference = imap.CanonicalMailboxName(mailbox) cmd.Reference = imap.CanonicalMailboxName(mailbox)
} }
if mailbox, ok := fields[1].(string); !ok { if mailbox, err := imap.ParseString(fields[1]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := dec.String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)

View File

@@ -14,7 +14,7 @@ type Login struct {
func (cmd *Login) Command() *imap.Command { func (cmd *Login) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Login, Name: "LOGIN",
Arguments: []interface{}{cmd.Username, cmd.Password}, Arguments: []interface{}{cmd.Username, cmd.Password},
} }
} }
@@ -24,12 +24,12 @@ func (cmd *Login) Parse(fields []interface{}) error {
return errors.New("Not enough arguments") return errors.New("Not enough arguments")
} }
var ok bool var err error
if cmd.Username, ok = fields[0].(string); !ok { if cmd.Username, err = imap.ParseString(fields[0]); err != nil {
return errors.New("Username is not a string") return err
} }
if cmd.Password, ok = fields[1].(string); !ok { if cmd.Password, err = imap.ParseString(fields[1]); err != nil {
return errors.New("Password is not a string") return err
} }
return nil return nil

View File

@@ -9,7 +9,7 @@ type Logout struct{}
func (c *Logout) Command() *imap.Command { func (c *Logout) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Logout, Name: "LOGOUT",
} }
} }

View File

@@ -9,7 +9,7 @@ type Noop struct{}
func (c *Noop) Command() *imap.Command { func (c *Noop) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Noop, Name: "NOOP",
} }
} }

View File

@@ -14,11 +14,12 @@ type Rename struct {
} }
func (cmd *Rename) Command() *imap.Command { func (cmd *Rename) Command() *imap.Command {
existingName, _ := utf7.Encoder.String(cmd.Existing) enc := utf7.Encoding.NewEncoder()
newName, _ := utf7.Encoder.String(cmd.New) existingName, _ := enc.String(cmd.Existing)
newName, _ := enc.String(cmd.New)
return &imap.Command{ return &imap.Command{
Name: imap.Rename, Name: "RENAME",
Arguments: []interface{}{existingName, newName}, Arguments: []interface{}{existingName, newName},
} }
} }
@@ -28,17 +29,19 @@ func (cmd *Rename) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if existingName, ok := fields[0].(string); !ok { dec := utf7.Encoding.NewDecoder()
return errors.New("Mailbox name must be a string")
} else if existingName, err := utf7.Decoder.String(existingName); err != nil { if existingName, err := imap.ParseString(fields[0]); err != nil {
return err
} else if existingName, err := dec.String(existingName); err != nil {
return err return err
} else { } else {
cmd.Existing = imap.CanonicalMailboxName(existingName) cmd.Existing = imap.CanonicalMailboxName(existingName)
} }
if newName, ok := fields[1].(string); !ok { if newName, err := imap.ParseString(fields[1]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if newName, err := utf7.Decoder.String(newName); err != nil { } else if newName, err := dec.String(newName); err != nil {
return err return err
} else { } else {
cmd.New = imap.CanonicalMailboxName(newName) cmd.New = imap.CanonicalMailboxName(newName)

View File

@@ -22,7 +22,7 @@ func (cmd *Search) Command() *imap.Command {
args = append(args, cmd.Criteria.Format()...) args = append(args, cmd.Criteria.Format()...)
return &imap.Command{ return &imap.Command{
Name: imap.Search, Name: "SEARCH",
Arguments: args, Arguments: args,
} }
} }

View File

@@ -15,12 +15,12 @@ type Select struct {
} }
func (cmd *Select) Command() *imap.Command { func (cmd *Select) Command() *imap.Command {
name := imap.Select name := "SELECT"
if cmd.ReadOnly { if cmd.ReadOnly {
name = imap.Examine name = "EXAMINE"
} }
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: name, Name: name,
@@ -33,9 +33,9 @@ func (cmd *Select) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if mailbox, ok := fields[0].(string); !ok { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)

View File

@@ -9,7 +9,7 @@ type StartTLS struct{}
func (cmd *StartTLS) Command() *imap.Command { func (cmd *StartTLS) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.StartTLS, Name: "STARTTLS",
} }
} }

View File

@@ -11,19 +11,19 @@ import (
// Status is a STATUS command, as defined in RFC 3501 section 6.3.10. // Status is a STATUS command, as defined in RFC 3501 section 6.3.10.
type Status struct { type Status struct {
Mailbox string Mailbox string
Items []string Items []imap.StatusItem
} }
func (cmd *Status) Command() *imap.Command { func (cmd *Status) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
items := make([]interface{}, len(cmd.Items)) items := make([]interface{}, len(cmd.Items))
for i, f := range cmd.Items { for i, item := range cmd.Items {
items[i] = f items[i] = string(item)
} }
return &imap.Command{ return &imap.Command{
Name: imap.Status, Name: "STATUS",
Arguments: []interface{}{mailbox, items}, Arguments: []interface{}{mailbox, items},
} }
} }
@@ -33,21 +33,24 @@ func (cmd *Status) Parse(fields []interface{}) error {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
if mailbox, ok := fields[0].(string); !ok { if mailbox, err := imap.ParseString(fields[0]); err != nil {
return errors.New("Mailbox name must be a string") return err
} else if mailbox, err := utf7.Decoder.String(mailbox); err != nil { } else if mailbox, err := utf7.Encoding.NewDecoder().String(mailbox); err != nil {
return err return err
} else { } else {
cmd.Mailbox = imap.CanonicalMailboxName(mailbox) cmd.Mailbox = imap.CanonicalMailboxName(mailbox)
} }
if items, ok := fields[1].([]interface{}); !ok { items, ok := fields[1].([]interface{})
return errors.New("Items must be a list") if !ok {
} else { return errors.New("STATUS command parameter is not a list")
cmd.Items = make([]string, len(items)) }
for i, v := range items { cmd.Items = make([]imap.StatusItem, len(items))
item, _ := v.(string) for i, f := range items {
cmd.Items[i] = strings.ToUpper(item) if s, ok := f.(string); !ok {
return errors.New("Got a non-string field in a STATUS command parameter")
} else {
cmd.Items[i] = imap.StatusItem(strings.ToUpper(s))
} }
} }

View File

@@ -10,18 +10,18 @@ import (
// Store is a STORE command, as defined in RFC 3501 section 6.4.6. // Store is a STORE command, as defined in RFC 3501 section 6.4.6.
type Store struct { type Store struct {
SeqSet *imap.SeqSet SeqSet *imap.SeqSet
Item string Item imap.StoreItem
Value interface{} Value interface{}
} }
func (cmd *Store) Command() *imap.Command { func (cmd *Store) Command() *imap.Command {
return &imap.Command{ return &imap.Command{
Name: imap.Store, Name: "STORE",
Arguments: []interface{}{cmd.SeqSet, cmd.Item, cmd.Value}, Arguments: []interface{}{cmd.SeqSet, string(cmd.Item), cmd.Value},
} }
} }
func (cmd *Store) Parse(fields []interface{}) (err error) { func (cmd *Store) Parse(fields []interface{}) error {
if len(fields) < 3 { if len(fields) < 3 {
return errors.New("No enough arguments") return errors.New("No enough arguments")
} }
@@ -30,16 +30,18 @@ func (cmd *Store) Parse(fields []interface{}) (err error) {
if !ok { if !ok {
return errors.New("Invalid sequence set") return errors.New("Invalid sequence set")
} }
if cmd.SeqSet, err = imap.NewSeqSet(seqset); err != nil { var err error
if cmd.SeqSet, err = imap.ParseSeqSet(seqset); err != nil {
return err return err
} }
if cmd.Item, ok = fields[1].(string); !ok { if item, ok := fields[1].(string); !ok {
return errors.New("Item name must be a string") return errors.New("Item name must be a string")
} else {
cmd.Item = imap.StoreItem(strings.ToUpper(item))
} }
cmd.Item = strings.ToUpper(cmd.Item)
// TODO: could be fields[2:] according to RFC 3501 page 91 "store-att-flags"
cmd.Value = fields[2] cmd.Value = fields[2]
return nil
return
} }

View File

@@ -13,29 +13,25 @@ type Subscribe struct {
} }
func (cmd *Subscribe) Command() *imap.Command { func (cmd *Subscribe) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: imap.Subscribe, Name: "SUBSCRIBE",
Arguments: []interface{}{mailbox}, Arguments: []interface{}{mailbox},
} }
} }
func (cmd *Subscribe) Parse(fields []interface{}) (err error) { func (cmd *Subscribe) Parse(fields []interface{}) error {
if len(fields) < 0 { if len(fields) < 0 {
return errors.New("No enogh arguments") return errors.New("No enough arguments")
} }
mailbox, ok := fields[0].(string) if mailbox, err := imap.ParseString(fields[0]); err != nil {
if !ok { return err
return errors.New("Mailbox name must be a string") } else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
}
if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil {
return err return err
} }
return nil
return
} }
// An UNSUBSCRIBE command. // An UNSUBSCRIBE command.
@@ -45,27 +41,23 @@ type Unsubscribe struct {
} }
func (cmd *Unsubscribe) Command() *imap.Command { func (cmd *Unsubscribe) Command() *imap.Command {
mailbox, _ := utf7.Encoder.String(cmd.Mailbox) mailbox, _ := utf7.Encoding.NewEncoder().String(cmd.Mailbox)
return &imap.Command{ return &imap.Command{
Name: imap.Unsubscribe, Name: "UNSUBSCRIBE",
Arguments: []interface{}{mailbox}, Arguments: []interface{}{mailbox},
} }
} }
func (cmd *Unsubscribe) Parse(fields []interface{}) (err error) { func (cmd *Unsubscribe) Parse(fields []interface{}) error {
if len(fields) < 0 { if len(fields) < 0 {
return errors.New("No enogh arguments") return errors.New("No enogh arguments")
} }
mailbox, ok := fields[0].(string) if mailbox, err := imap.ParseString(fields[0]); err != nil {
if !ok { return err
return errors.New("Mailbox name must be a string") } else if cmd.Mailbox, err = utf7.Encoding.NewDecoder().String(mailbox); err != nil {
}
if cmd.Mailbox, err = utf7.Decoder.String(mailbox); err != nil {
return err return err
} }
return nil
return
} }

View File

@@ -20,7 +20,7 @@ func (cmd *Uid) Command() *imap.Command {
args = append(args, inner.Arguments...) args = append(args, inner.Arguments...)
return &imap.Command{ return &imap.Command{
Name: imap.Uid, Name: "UID",
Arguments: args, Arguments: args,
} }
} }

View File

@@ -154,10 +154,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
// Flush writes any buffered data to the underlying connection. // Flush writes any buffered data to the underlying connection.
func (c *Conn) Flush() error { func (c *Conn) Flush() error {
if err := c.Writer.Flush(); err != nil { return c.Writer.Flush()
return err
}
return nil
} }
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted // Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted

View File

@@ -1,121 +0,0 @@
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()
}

View File

@@ -2,9 +2,60 @@
package imap package imap
import ( import (
"errors"
"io" "io"
"strings"
) )
// A StatusItem is a mailbox status data item that can be retrieved with a
// STATUS command. See RFC 3501 section 6.3.10.
type StatusItem string
const (
StatusMessages StatusItem = "MESSAGES"
StatusRecent = "RECENT"
StatusUidNext = "UIDNEXT"
StatusUidValidity = "UIDVALIDITY"
StatusUnseen = "UNSEEN"
)
// A FetchItem is a message data item that can be fetched.
type FetchItem string
// List of items that can be fetched.
const (
// Macros
FetchAll FetchItem = "ALL"
FetchFast = "FAST"
FetchFull = "FULL"
// Items
FetchBody = "BODY"
FetchBodyStructure = "BODYSTRUCTURE"
FetchEnvelope = "ENVELOPE"
FetchFlags = "FLAGS"
FetchInternalDate = "INTERNALDATE"
FetchRFC822 = "RFC822"
FetchRFC822Header = "RFC822.HEADER"
FetchRFC822Size = "RFC822.SIZE"
FetchRFC822Text = "RFC822.TEXT"
FetchUid = "UID"
)
// Expand expands the item if it's a macro.
func (item FetchItem) Expand() []FetchItem {
switch item {
case FetchAll:
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope}
case FetchFast:
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size}
case FetchFull:
return []FetchItem{FetchFlags, FetchInternalDate, FetchRFC822Size, FetchEnvelope, FetchBody}
default:
return []FetchItem{item}
}
}
// FlagsOp is an operation that will be applied on message flags. // FlagsOp is an operation that will be applied on message flags.
type FlagsOp string type FlagsOp string
@@ -17,9 +68,36 @@ const (
RemoveFlags = "-FLAGS" RemoveFlags = "-FLAGS"
) )
// SilentOp can be appended to a FlagsOp to prevent the operation from // silentOp can be appended to a FlagsOp to prevent the operation from
// triggering unilateral message updates. // triggering unilateral message updates.
const SilentOp = ".SILENT" const silentOp = ".SILENT"
// A StoreItem is a message data item that can be updated.
type StoreItem string
// FormatFlagsOp returns the StoreItem that executes the flags operation op.
func FormatFlagsOp(op FlagsOp, silent bool) StoreItem {
s := string(op)
if silent {
s += silentOp
}
return StoreItem(s)
}
// ParseFlagsOp parses a flags operation from StoreItem.
func ParseFlagsOp(item StoreItem) (op FlagsOp, silent bool, err error) {
itemStr := string(item)
silent = strings.HasSuffix(itemStr, silentOp)
if silent {
itemStr = strings.TrimSuffix(itemStr, silentOp)
}
op = FlagsOp(itemStr)
if op != SetFlags && op != AddFlags && op != RemoveFlags {
err = errors.New("Unsupported STORE operation")
}
return
}
// CharsetReader, if non-nil, defines a function to generate charset-conversion // CharsetReader, if non-nil, defines a function to generate charset-conversion
// readers, converting from the provided charset into UTF-8. Charsets are always // readers, converting from the provided charset into UTF-8. Charsets are always

View File

@@ -2,6 +2,7 @@ package imap
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"sync" "sync"
@@ -53,22 +54,36 @@ func (info *MailboxInfo) Parse(fields []interface{}) error {
return errors.New("Mailbox info needs at least 3 fields") return errors.New("Mailbox info needs at least 3 fields")
} }
info.Attributes, _ = ParseStringList(fields[0]) var err error
if info.Attributes, err = ParseStringList(fields[0]); err != nil {
return err
}
info.Delimiter, _ = fields[1].(string) var ok bool
if info.Delimiter, ok = fields[1].(string); !ok {
return errors.New("Mailbox delimiter must be a string")
}
name, _ := fields[2].(string) if name, err := ParseString(fields[2]); err != nil {
info.Name, _ = utf7.Decoder.String(name) return err
info.Name = CanonicalMailboxName(info.Name) } else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
return err
} else {
info.Name = CanonicalMailboxName(name)
}
return nil return nil
} }
// Format mailbox info to fields. // Format mailbox info to fields.
func (info *MailboxInfo) Format() []interface{} { func (info *MailboxInfo) Format() []interface{} {
name, _ := utf7.Encoder.String(info.Name) name, _ := utf7.Encoding.NewEncoder().String(info.Name)
attrs := make([]interface{}, len(info.Attributes))
for i, attr := range info.Attributes {
attrs[i] = Atom(attr)
}
// Thunderbird doesn't understand delimiters if not quoted // Thunderbird doesn't understand delimiters if not quoted
return []interface{}{FormatStringList(info.Attributes), Quoted(info.Delimiter), name} return []interface{}{attrs, Quoted(info.Delimiter), name}
} }
// TODO: optimize this // TODO: optimize this
@@ -125,19 +140,6 @@ func (info *MailboxInfo) Match(reference, pattern string) bool {
return info.match(name, pattern) 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. // A mailbox status.
type MailboxStatus struct { type MailboxStatus struct {
// The mailbox name. // The mailbox name.
@@ -147,7 +149,7 @@ type MailboxStatus struct {
// The mailbox items that are currently filled in. This map's values // The mailbox items that are currently filled in. This map's values
// should not be used directly, they must only be used by libraries // should not be used directly, they must only be used by libraries
// implementing extensions of the IMAP protocol. // implementing extensions of the IMAP protocol.
Items map[string]interface{} Items map[StatusItem]interface{}
// The Items map may be accessed in different goroutines. Protect // The Items map may be accessed in different goroutines. Protect
// concurrent writes. // concurrent writes.
@@ -157,6 +159,8 @@ type MailboxStatus struct {
Flags []string Flags []string
// The mailbox permanent flags. // The mailbox permanent flags.
PermanentFlags []string PermanentFlags []string
// The sequence number of the first unseen message in the mailbox.
UnseenSeqNum uint32
// The number of messages in this mailbox. // The number of messages in this mailbox.
Messages uint32 Messages uint32
@@ -172,10 +176,10 @@ type MailboxStatus struct {
} }
// Create a new mailbox status that will contain the specified items. // Create a new mailbox status that will contain the specified items.
func NewMailboxStatus(name string, items []string) *MailboxStatus { func NewMailboxStatus(name string, items []StatusItem) *MailboxStatus {
status := &MailboxStatus{ status := &MailboxStatus{
Name: name, Name: name,
Items: make(map[string]interface{}), Items: make(map[StatusItem]interface{}),
} }
for _, k := range items { for _, k := range items {
@@ -186,30 +190,30 @@ func NewMailboxStatus(name string, items []string) *MailboxStatus {
} }
func (status *MailboxStatus) Parse(fields []interface{}) error { func (status *MailboxStatus) Parse(fields []interface{}) error {
status.Items = make(map[string]interface{}) status.Items = make(map[StatusItem]interface{})
var k string var k StatusItem
for i, f := range fields { for i, f := range fields {
if i%2 == 0 { if i%2 == 0 {
var ok bool if kstr, ok := f.(string); !ok {
if k, ok = f.(string); !ok { return fmt.Errorf("cannot parse mailbox status: key is not a string, but a %T", f)
return errors.New("Key is not a string") } else {
k = StatusItem(strings.ToUpper(kstr))
} }
k = strings.ToUpper(k)
} else { } else {
status.Items[k] = nil status.Items[k] = nil
var err error var err error
switch k { switch k {
case MailboxMessages: case StatusMessages:
status.Messages, err = ParseNumber(f) status.Messages, err = ParseNumber(f)
case MailboxRecent: case StatusRecent:
status.Recent, err = ParseNumber(f) status.Recent, err = ParseNumber(f)
case MailboxUnseen: case StatusUnseen:
status.Unseen, err = ParseNumber(f) status.Unseen, err = ParseNumber(f)
case MailboxUidNext: case StatusUidNext:
status.UidNext, err = ParseNumber(f) status.UidNext, err = ParseNumber(f)
case MailboxUidValidity: case StatusUidValidity:
status.UidValidity, err = ParseNumber(f) status.UidValidity, err = ParseNumber(f)
default: default:
status.Items[k] = f status.Items[k] = f
@@ -228,19 +232,19 @@ func (status *MailboxStatus) Format() []interface{} {
var fields []interface{} var fields []interface{}
for k, v := range status.Items { for k, v := range status.Items {
switch k { switch k {
case MailboxMessages: case StatusMessages:
v = status.Messages v = status.Messages
case MailboxRecent: case StatusRecent:
v = status.Recent v = status.Recent
case MailboxUnseen: case StatusUnseen:
v = status.Unseen v = status.Unseen
case MailboxUidNext: case StatusUidNext:
v = status.UidNext v = status.UidNext
case MailboxUidValidity: case StatusUidValidity:
v = status.UidValidity v = status.UidValidity
} }
fields = append(fields, k, v) fields = append(fields, string(k), v)
} }
return fields return fields
} }

View File

@@ -30,30 +30,13 @@ var flags = []string{
RecentFlag, RecentFlag,
} }
// Message attributes that can be fetched, defined in RFC 3501 section 6.4.5. // A PartSpecifier specifies which parts of the MIME entity should be returned.
// Attributes that fetches the message contents are defined with type PartSpecifier string
// 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. // Part specifiers described in RFC 3501 page 55.
const ( const (
// Refers to the entire part, including headers. // Refers to the entire part, including headers.
EntireSpecifier = "" EntireSpecifier PartSpecifier = ""
// Refers to the header of the part. Must include the final CRLF delimiting // Refers to the header of the part. Must include the final CRLF delimiting
// the header and the body. // the header and the body.
HeaderSpecifier = "HEADER" HeaderSpecifier = "HEADER"
@@ -61,7 +44,7 @@ const (
TextSpecifier = "TEXT" TextSpecifier = "TEXT"
// Refers to the MIME Internet Message Body header. Must include the final // Refers to the MIME Internet Message Body header. Must include the final
// CRLF delimiting the header and the body. // CRLF delimiting the header and the body.
MimeSpecifier = "MIME" MIMESpecifier = "MIME"
) )
// Returns the canonical form of a flag. Flags are case-insensitive. // Returns the canonical form of a flag. Flags are case-insensitive.
@@ -83,9 +66,9 @@ func ParseParamList(fields []interface{}) (map[string]string, error) {
var k string var k string
for i, f := range fields { for i, f := range fields {
p, ok := f.(string) p, err := ParseString(f)
if !ok { if err != nil {
return nil, errors.New("Parameter list contains a non-string") return nil, errors.New("Parameter list contains a non-string: " + err.Error())
} }
if i%2 == 0 { if i%2 == 0 {
@@ -158,7 +141,7 @@ type Message struct {
// The mailbox items that are currently filled in. This map's values // The mailbox items that are currently filled in. This map's values
// should not be used directly, they must only be used by libraries // should not be used directly, they must only be used by libraries
// implementing extensions of the IMAP protocol. // implementing extensions of the IMAP protocol.
Items map[string]interface{} Items map[FetchItem]interface{}
// The message envelope. // The message envelope.
Envelope *Envelope Envelope *Envelope
@@ -178,14 +161,14 @@ type Message struct {
// The order in which items were requested. This order must be preserved // The order in which items were requested. This order must be preserved
// because some bad IMAP clients (looking at you, Outlook!) refuse responses // because some bad IMAP clients (looking at you, Outlook!) refuse responses
// containing items in a different order. // containing items in a different order.
itemsOrder []string itemsOrder []FetchItem
} }
// Create a new empty message that will contain the specified items. // Create a new empty message that will contain the specified items.
func NewMessage(seqNum uint32, items []string) *Message { func NewMessage(seqNum uint32, items []FetchItem) *Message {
msg := &Message{ msg := &Message{
SeqNum: seqNum, SeqNum: seqNum,
Items: make(map[string]interface{}), Items: make(map[FetchItem]interface{}),
Body: make(map[*BodySectionName]Literal), Body: make(map[*BodySectionName]Literal),
itemsOrder: items, itemsOrder: items,
} }
@@ -199,65 +182,65 @@ func NewMessage(seqNum uint32, items []string) *Message {
// Parse a message from fields. // Parse a message from fields.
func (m *Message) Parse(fields []interface{}) error { func (m *Message) Parse(fields []interface{}) error {
m.Items = make(map[string]interface{}) m.Items = make(map[FetchItem]interface{})
m.Body = map[*BodySectionName]Literal{} m.Body = map[*BodySectionName]Literal{}
m.itemsOrder = nil m.itemsOrder = nil
var k string var k FetchItem
for i, f := range fields { for i, f := range fields {
if i%2 == 0 { // It's a key if i%2 == 0 { // It's a key
var ok bool if kstr, ok := f.(string); !ok {
if k, ok = f.(string); !ok { return fmt.Errorf("cannot parse message: key is not a string, but a %T", f)
return errors.New("Key is not a string") } else {
k = FetchItem(strings.ToUpper(kstr))
} }
k = strings.ToUpper(k)
} else { // It's a value } else { // It's a value
m.Items[k] = nil m.Items[k] = nil
m.itemsOrder = append(m.itemsOrder, k) m.itemsOrder = append(m.itemsOrder, k)
switch k { switch k {
case BodyMsgAttr, BodyStructureMsgAttr: case FetchBody, FetchBodyStructure:
bs, ok := f.([]interface{}) bs, ok := f.([]interface{})
if !ok { if !ok {
return errors.New("BODYSTRUCTURE is not a list") return fmt.Errorf("cannot parse message: BODYSTRUCTURE is not a list, but a %T", f)
} }
m.BodyStructure = &BodyStructure{Extended: k == BodyStructureMsgAttr} m.BodyStructure = &BodyStructure{Extended: k == FetchBodyStructure}
if err := m.BodyStructure.Parse(bs); err != nil { if err := m.BodyStructure.Parse(bs); err != nil {
return err return err
} }
case EnvelopeMsgAttr: case FetchEnvelope:
env, ok := f.([]interface{}) env, ok := f.([]interface{})
if !ok { if !ok {
return errors.New("ENVELOPE is not a list") return fmt.Errorf("cannot parse message: ENVELOPE is not a list, but a %T", f)
} }
m.Envelope = &Envelope{} m.Envelope = &Envelope{}
if err := m.Envelope.Parse(env); err != nil { if err := m.Envelope.Parse(env); err != nil {
return err return err
} }
case FlagsMsgAttr: case FetchFlags:
flags, ok := f.([]interface{}) flags, ok := f.([]interface{})
if !ok { if !ok {
return errors.New("FLAGS is not a list") return fmt.Errorf("cannot parse message: FLAGS is not a list, but a %T", f)
} }
m.Flags = make([]string, len(flags)) m.Flags = make([]string, len(flags))
for i, flag := range flags { for i, flag := range flags {
s, _ := flag.(string) s, _ := ParseString(flag)
m.Flags[i] = CanonicalFlag(s) m.Flags[i] = CanonicalFlag(s)
} }
case InternalDateMsgAttr: case FetchInternalDate:
date, _ := f.(string) date, _ := f.(string)
m.InternalDate, _ = time.Parse(DateTimeLayout, date) m.InternalDate, _ = time.Parse(DateTimeLayout, date)
case SizeMsgAttr: case FetchRFC822Size:
m.Size, _ = ParseNumber(f) m.Size, _ = ParseNumber(f)
case UidMsgAttr: case FetchUid:
m.Uid, _ = ParseNumber(f) m.Uid, _ = ParseNumber(f)
default: default:
// Likely to be a section of the body // Likely to be a section of the body
// First check that the section name is correct // First check that the section name is correct
if section, err := NewBodySectionName(k); err != nil { if section, err := ParseBodySectionName(k); err != nil {
// Not a section name, maybe an attribute defined in an IMAP extension // Not a section name, maybe an attribute defined in an IMAP extension
m.Items[k] = f m.Items[k] = f
} else { } else {
@@ -270,24 +253,28 @@ func (m *Message) Parse(fields []interface{}) error {
return nil return nil
} }
func (m *Message) formatItem(k string) []interface{} { func (m *Message) formatItem(k FetchItem) []interface{} {
v := m.Items[k] v := m.Items[k]
var kk interface{} = k var kk interface{} = string(k)
switch strings.ToUpper(k) { switch k {
case BodyMsgAttr, BodyStructureMsgAttr: case FetchBody, FetchBodyStructure:
// Extension data is only returned with the BODYSTRUCTURE fetch // Extension data is only returned with the BODYSTRUCTURE fetch
m.BodyStructure.Extended = k == BodyStructureMsgAttr m.BodyStructure.Extended = k == FetchBodyStructure
v = m.BodyStructure.Format() v = m.BodyStructure.Format()
case EnvelopeMsgAttr: case FetchEnvelope:
v = m.Envelope.Format() v = m.Envelope.Format()
case FlagsMsgAttr: case FetchFlags:
v = FormatStringList(m.Flags) flags := make([]interface{}, len(m.Flags))
case InternalDateMsgAttr: for i, flag := range m.Flags {
flags[i] = Atom(flag)
}
v = flags
case FetchInternalDate:
v = m.InternalDate v = m.InternalDate
case SizeMsgAttr: case FetchRFC822Size:
v = m.Size v = m.Size
case UidMsgAttr: case FetchUid:
v = m.Uid v = m.Uid
default: default:
for section, literal := range m.Body { for section, literal := range m.Body {
@@ -307,7 +294,7 @@ func (m *Message) Format() []interface{} {
var fields []interface{} var fields []interface{}
// First send ordered items // First send ordered items
processed := make(map[string]bool) processed := make(map[FetchItem]bool)
for _, k := range m.itemsOrder { for _, k := range m.itemsOrder {
if _, ok := m.Items[k]; ok { if _, ok := m.Items[k]; ok {
fields = append(fields, m.formatItem(k)...) fields = append(fields, m.formatItem(k)...)
@@ -326,9 +313,11 @@ func (m *Message) Format() []interface{} {
} }
// Get the body section with the specified name. Returns nil if it's not found. // Get the body section with the specified name. Returns nil if it's not found.
func (m *Message) GetBody(s string) Literal { func (m *Message) GetBody(section *BodySectionName) Literal {
for section, body := range m.Body { section = section.resp()
if section.value == s {
for s, body := range m.Body {
if section.Equal(s) {
return body return body
} }
} }
@@ -338,7 +327,7 @@ func (m *Message) GetBody(s string) Literal {
// A body section name. // A body section name.
// See RFC 3501 page 55. // See RFC 3501 page 55.
type BodySectionName struct { type BodySectionName struct {
*BodyPartName BodyPartName
// If set to true, do not implicitly set the \Seen flag. // If set to true, do not implicitly set the \Seen flag.
Peek bool Peek bool
@@ -347,11 +336,11 @@ type BodySectionName struct {
// octets desired. // octets desired.
Partial []int Partial []int
value string value FetchItem
} }
func (section *BodySectionName) parse(s string) (err error) { func (section *BodySectionName) parse(s string) error {
section.value = s section.value = FetchItem(s)
if s == "RFC822" { if s == "RFC822" {
s = "BODY[]" s = "BODY[]"
@@ -385,14 +374,13 @@ func (section *BodySectionName) parse(s string) (err error) {
b := bytes.NewBufferString(part + string(cr) + string(lf)) b := bytes.NewBufferString(part + string(cr) + string(lf))
r := NewReader(b) r := NewReader(b)
var fields []interface{} fields, err := r.ReadFields()
if fields, err = r.ReadFields(); err != nil { if err != nil {
return return err
} }
section.BodyPartName = &BodyPartName{} if err := section.BodyPartName.parse(fields); err != nil {
if err = section.BodyPartName.parse(fields); err != nil { return err
return
} }
if len(partial) > 0 { if len(partial) > 0 {
@@ -420,17 +408,17 @@ func (section *BodySectionName) parse(s string) (err error) {
return nil return nil
} }
func (section *BodySectionName) String() (s string) { func (section *BodySectionName) FetchItem() FetchItem {
if section.value != "" { if section.value != "" {
return section.value return section.value
} }
s = "BODY" s := "BODY"
if section.Peek { if section.Peek {
s += ".PEEK" s += ".PEEK"
} }
s += "[" + section.BodyPartName.String() + "]" s += "[" + section.BodyPartName.string() + "]"
if len(section.Partial) > 0 { if len(section.Partial) > 0 {
s += "<" s += "<"
@@ -444,30 +432,39 @@ func (section *BodySectionName) String() (s string) {
s += ">" s += ">"
} }
return return FetchItem(s)
}
// Equal checks whether two sections are equal.
func (section *BodySectionName) Equal(other *BodySectionName) bool {
if section.Peek != other.Peek {
return false
}
if len(section.Partial) != len(other.Partial) {
return false
}
if len(section.Partial) > 0 && section.Partial[0] != other.Partial[0] {
return false
}
if len(section.Partial) > 1 && section.Partial[1] != other.Partial[1] {
return false
}
return section.BodyPartName.Equal(&other.BodyPartName)
} }
func (section *BodySectionName) resp() *BodySectionName { func (section *BodySectionName) resp() *BodySectionName {
var reset bool resp := *section // Copy section
if resp.Peek != false {
if section.Peek != false { resp.Peek = false
section.Peek = false
reset = true
} }
if len(resp.Partial) == 2 {
if len(section.Partial) == 2 { resp.Partial = []int{resp.Partial[0]}
section.Partial = []int{section.Partial[0]}
reset = true
} }
resp.value = ""
if reset && !strings.HasPrefix(section.value, "RFC822") { return &resp
section.value = "" // Reset cached value
}
return section
} }
// Returns a subset of the specified bytes matching the partial requested in the // ExtractPartial returns a subset of the specified bytes matching the partial requested in the
// section name. // section name.
func (section *BodySectionName) ExtractPartial(b []byte) []byte { func (section *BodySectionName) ExtractPartial(b []byte) []byte {
if len(section.Partial) != 2 { if len(section.Partial) != 2 {
@@ -486,17 +483,17 @@ func (section *BodySectionName) ExtractPartial(b []byte) []byte {
return b[from:to] return b[from:to]
} }
// Parse a body section name. // ParseBodySectionName parses a body section name.
func NewBodySectionName(s string) (section *BodySectionName, err error) { func ParseBodySectionName(s FetchItem) (*BodySectionName, error) {
section = &BodySectionName{} section := new(BodySectionName)
err = section.parse(s) err := section.parse(string(s))
return return section, err
} }
// A body part name. // A body part name.
type BodyPartName struct { type BodyPartName struct {
// The specifier of the requested part. // The specifier of the requested part.
Specifier string Specifier PartSpecifier
// The part path. Parts indexes start at 1. // The part path. Parts indexes start at 1.
Path []int Path []int
// If Specifier is HEADER, contains header fields that will/won't be returned, // If Specifier is HEADER, contains header fields that will/won't be returned,
@@ -521,11 +518,13 @@ func (part *BodyPartName) parse(fields []interface{}) error {
path := strings.Split(strings.ToUpper(name), ".") path := strings.Split(strings.ToUpper(name), ".")
end := 0 end := 0
loop:
for i, node := range path { for i, node := range path {
if node == "" || node == HeaderSpecifier || node == MimeSpecifier || node == TextSpecifier { switch PartSpecifier(node) {
part.Specifier = node case EntireSpecifier, HeaderSpecifier, MIMESpecifier, TextSpecifier:
part.Specifier = PartSpecifier(node)
end = i + 1 end = i + 1
break break loop
} }
index, err := strconv.Atoi(node) index, err := strconv.Atoi(node)
@@ -560,14 +559,14 @@ func (part *BodyPartName) parse(fields []interface{}) error {
return nil return nil
} }
func (part *BodyPartName) String() (s string) { func (part *BodyPartName) string() string {
path := make([]string, len(part.Path)) path := make([]string, len(part.Path))
for i, index := range part.Path { for i, index := range part.Path {
path[i] = strconv.Itoa(index) path[i] = strconv.Itoa(index)
} }
if part.Specifier != "" { if part.Specifier != EntireSpecifier {
path = append(path, part.Specifier) path = append(path, string(part.Specifier))
} }
if part.Specifier == HeaderSpecifier && len(part.Fields) > 0 { if part.Specifier == HeaderSpecifier && len(part.Fields) > 0 {
@@ -578,13 +577,47 @@ func (part *BodyPartName) String() (s string) {
} }
} }
s = strings.Join(path, ".") s := strings.Join(path, ".")
if len(part.Fields) > 0 { if len(part.Fields) > 0 {
s += " (" + strings.Join(part.Fields, " ") + ")" s += " (" + strings.Join(part.Fields, " ") + ")"
} }
return return s
}
// Equal checks whether two body part names are equal.
func (part *BodyPartName) Equal(other *BodyPartName) bool {
if part.Specifier != other.Specifier {
return false
}
if part.NotFields != other.NotFields {
return false
}
if len(part.Path) != len(other.Path) {
return false
}
for i, node := range part.Path {
if node != other.Path[i] {
return false
}
}
if len(part.Fields) != len(other.Fields) {
return false
}
for _, field := range part.Fields {
found := false
for _, f := range other.Fields {
if strings.EqualFold(field, f) {
found = true
break
}
}
if !found {
return false
}
}
return true
} }
// An address. // An address.
@@ -605,17 +638,17 @@ func (addr *Address) Parse(fields []interface{}) error {
return errors.New("Address doesn't contain 4 fields") return errors.New("Address doesn't contain 4 fields")
} }
if f, ok := fields[0].(string); ok { if s, err := ParseString(fields[0]); err == nil {
addr.PersonalName, _ = decodeHeader(f) addr.PersonalName, _ = decodeHeader(s)
} }
if f, ok := fields[1].(string); ok { if s, err := ParseString(fields[1]); err == nil {
addr.AtDomainList = f addr.AtDomainList, _ = decodeHeader(s)
} }
if f, ok := fields[2].(string); ok { if s, err := ParseString(fields[2]); err == nil {
addr.MailboxName = f addr.MailboxName, _ = decodeHeader(s)
} }
if f, ok := fields[3].(string); ok { if s, err := ParseString(fields[3]); err == nil {
addr.HostName = f addr.HostName, _ = decodeHeader(s)
} }
return nil return nil
@@ -702,7 +735,7 @@ func (e *Envelope) Parse(fields []interface{}) error {
if date, ok := fields[0].(string); ok { if date, ok := fields[0].(string); ok {
e.Date, _ = parseMessageDateTime(date) e.Date, _ = parseMessageDateTime(date)
} }
if subject, ok := fields[1].(string); ok { if subject, err := ParseString(fields[1]); err == nil {
e.Subject, _ = decodeHeader(subject) e.Subject, _ = decodeHeader(subject)
} }
if from, ok := fields[2].([]interface{}); ok { if from, ok := fields[2].([]interface{}); ok {
@@ -755,9 +788,9 @@ type BodyStructure struct {
// Basic fields // Basic fields
// The MIME type. // The MIME type.
MimeType string MIMEType string
// The MIME subtype. // The MIME subtype.
MimeSubType string MIMESubType string
// The MIME parameters. // The MIME parameters.
Params map[string]string Params map[string]string
@@ -796,7 +829,7 @@ type BodyStructure struct {
Location []string Location []string
// The MD5 checksum. // The MD5 checksum.
Md5 string MD5 string
} }
func (bs *BodyStructure) Parse(fields []interface{}) error { func (bs *BodyStructure) Parse(fields []interface{}) error {
@@ -809,7 +842,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
switch fields[0].(type) { switch fields[0].(type) {
case []interface{}: // A multipart body part case []interface{}: // A multipart body part
bs.MimeType = "multipart" bs.MIMEType = "multipart"
end := 0 end := 0
for i, fi := range fields { for i, fi := range fields {
@@ -829,7 +862,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
} }
} }
bs.MimeSubType, _ = fields[end].(string) bs.MIMESubType, _ = fields[end].(string)
end++ end++
// GMail seems to return only 3 extension data fields. Parse as many fields // GMail seems to return only 3 extension data fields. Parse as many fields
@@ -873,14 +906,14 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
return errors.New("Non-multipart body part doesn't have 7 fields") return errors.New("Non-multipart body part doesn't have 7 fields")
} }
bs.MimeType, _ = fields[0].(string) bs.MIMEType, _ = fields[0].(string)
bs.MimeSubType, _ = fields[1].(string) bs.MIMESubType, _ = fields[1].(string)
params, _ := fields[2].([]interface{}) params, _ := fields[2].([]interface{})
bs.Params, _ = parseHeaderParamList(params) bs.Params, _ = parseHeaderParamList(params)
bs.Id, _ = fields[3].(string) bs.Id, _ = fields[3].(string)
if desc, ok := fields[4].(string); ok { if desc, err := ParseString(fields[4]); err == nil {
bs.Description, _ = decodeHeader(desc) bs.Description, _ = decodeHeader(desc)
} }
bs.Encoding, _ = fields[5].(string) bs.Encoding, _ = fields[5].(string)
@@ -889,7 +922,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
end := 7 end := 7
// Type-specific fields // Type-specific fields
if bs.MimeType == "message" && bs.MimeSubType == "rfc822" { if bs.MIMEType == "message" && bs.MIMESubType == "rfc822" {
if len(fields)-end < 3 { if len(fields)-end < 3 {
return errors.New("Missing type-specific fields for message/rfc822") return errors.New("Missing type-specific fields for message/rfc822")
} }
@@ -906,7 +939,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
end += 3 end += 3
} }
if bs.MimeType == "text" { if bs.MIMEType == "text" {
if len(fields)-end < 1 { if len(fields)-end < 1 {
return errors.New("Missing type-specific fields for text/*") return errors.New("Missing type-specific fields for text/*")
} }
@@ -920,7 +953,7 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
if len(fields) > end { if len(fields) > end {
bs.Extended = true // Contains extension data bs.Extended = true // Contains extension data
bs.Md5, _ = fields[end].(string) bs.MD5, _ = fields[end].(string)
end++ end++
} }
if len(fields) > end { if len(fields) > end {
@@ -956,12 +989,12 @@ func (bs *BodyStructure) Parse(fields []interface{}) error {
} }
func (bs *BodyStructure) Format() (fields []interface{}) { func (bs *BodyStructure) Format() (fields []interface{}) {
if bs.MimeType == "multipart" { if bs.MIMEType == "multipart" {
for _, part := range bs.Parts { for _, part := range bs.Parts {
fields = append(fields, part.Format()) fields = append(fields, part.Format())
} }
fields = append(fields, bs.MimeSubType) fields = append(fields, bs.MIMESubType)
if bs.Extended { if bs.Extended {
extended := make([]interface{}, 4) extended := make([]interface{}, 4)
@@ -986,8 +1019,8 @@ func (bs *BodyStructure) Format() (fields []interface{}) {
} }
} else { } else {
fields = make([]interface{}, 7) fields = make([]interface{}, 7)
fields[0] = bs.MimeType fields[0] = bs.MIMEType
fields[1] = bs.MimeSubType fields[1] = bs.MIMESubType
fields[2] = formatHeaderParamList(bs.Params) fields[2] = formatHeaderParamList(bs.Params)
if bs.Id != "" { if bs.Id != "" {
@@ -1003,7 +1036,7 @@ func (bs *BodyStructure) Format() (fields []interface{}) {
fields[6] = bs.Size fields[6] = bs.Size
// Type-specific fields // Type-specific fields
if bs.MimeType == "message" && bs.MimeSubType == "rfc822" { if bs.MIMEType == "message" && bs.MIMESubType == "rfc822" {
var env interface{} var env interface{}
if bs.Envelope != nil { if bs.Envelope != nil {
env = bs.Envelope.Format() env = bs.Envelope.Format()
@@ -1016,7 +1049,7 @@ func (bs *BodyStructure) Format() (fields []interface{}) {
fields = append(fields, env, bsbs, bs.Lines) fields = append(fields, env, bsbs, bs.Lines)
} }
if bs.MimeType == "text" { if bs.MIMEType == "text" {
fields = append(fields, bs.Lines) fields = append(fields, bs.Lines)
} }
@@ -1024,8 +1057,8 @@ func (bs *BodyStructure) Format() (fields []interface{}) {
if bs.Extended { if bs.Extended {
extended := make([]interface{}, 4) extended := make([]interface{}, 4)
if bs.Md5 != "" { if bs.MD5 != "" {
extended[0] = bs.Md5 extended[0] = bs.MD5
} }
if bs.Disposition != "" { if bs.Disposition != "" {
extended[1] = []interface{}{ extended[1] = []interface{}{

View File

@@ -62,7 +62,7 @@ type reader interface {
StringReader StringReader
} }
// Convert a field to a number. // ParseNumber parses a number.
func ParseNumber(f interface{}) (uint32, error) { func ParseNumber(f interface{}) (uint32, error) {
// Useful for tests // Useful for tests
if n, ok := f.(uint32); ok { if n, ok := f.(uint32); ok {
@@ -71,7 +71,7 @@ func ParseNumber(f interface{}) (uint32, error) {
s, ok := f.(string) s, ok := f.(string)
if !ok { if !ok {
return 0, newParseError("number is not a string") return 0, newParseError("expected a number, got a non-atom")
} }
nbr, err := strconv.ParseUint(s, 10, 32) nbr, err := strconv.ParseUint(s, 10, 32)
@@ -82,18 +82,44 @@ func ParseNumber(f interface{}) (uint32, error) {
return uint32(nbr), nil return uint32(nbr), nil
} }
// ParseString parses a string, which is either a literal, a quoted string or an
// atom.
func ParseString(f interface{}) (string, error) {
if s, ok := f.(string); ok {
return s, nil
}
// Useful for tests
if q, ok := f.(Quoted); ok {
return string(q), nil
}
if a, ok := f.(Atom); ok {
return string(a), nil
}
if l, ok := f.(Literal); ok {
b := make([]byte, l.Len())
if _, err := io.ReadFull(l, b); err != nil {
return "", err
}
return string(b), nil
}
return "", newParseError("expected a string")
}
// Convert a field list to a string list. // Convert a field list to a string list.
func ParseStringList(f interface{}) ([]string, error) { func ParseStringList(f interface{}) ([]string, error) {
fields, ok := f.([]interface{}) fields, ok := f.([]interface{})
if !ok { if !ok {
return nil, newParseError("string list is not a list") return nil, newParseError("expected a string list, got a non-list")
} }
list := make([]string, len(fields)) list := make([]string, len(fields))
for i, f := range fields { for i, f := range fields {
var ok bool var err error
if list[i], ok = f.(string); !ok { if list[i], err = ParseString(f); err != nil {
return nil, newParseError("string list contains a non-string") return nil, newParseError("cannot parse string in string list: " + err.Error())
} }
} }
return list, nil return list, nil
@@ -121,7 +147,7 @@ func (r *Reader) ReadSp() error {
return err return err
} }
if char != sp { if char != sp {
return newParseError("not a space") return newParseError("expected a space")
} }
return nil return nil
} }
@@ -134,6 +160,7 @@ func (r *Reader) ReadCrlf() (err error) {
} }
if char != cr { if char != cr {
err = newParseError("line doesn't end with a CR") err = newParseError("line doesn't end with a CR")
return
} }
if char, _, err = r.ReadRune(); err != nil { if char, _, err = r.ReadRune(); err != nil {
@@ -305,6 +332,9 @@ func (r *Reader) ReadFields() (fields []interface{}, err error) {
return return
} }
if char == cr || char == listEnd || char == respCodeEnd { if char == cr || char == listEnd || char == respCodeEnd {
if char == cr {
r.UnreadRune()
}
return return
} }
if char == listStart { if char == listStart {
@@ -354,7 +384,7 @@ func (r *Reader) ReadLine() (fields []interface{}, err error) {
return return
} }
func (r *Reader) ReadRespCode() (code string, fields []interface{}, err error) { func (r *Reader) ReadRespCode() (code StatusRespCode, fields []interface{}, err error) {
char, _, err := r.ReadRune() char, _, err := r.ReadRune()
if err != nil { if err != nil {
return return
@@ -376,15 +406,16 @@ func (r *Reader) ReadRespCode() (code string, fields []interface{}, err error) {
return return
} }
code, ok := fields[0].(string) codeStr, ok := fields[0].(string)
if !ok { if !ok {
err = newParseError("response code doesn't start with a string atom") err = newParseError("response code doesn't start with a string atom")
return return
} }
if code == "" { if codeStr == "" {
err = newParseError("response code is empty") err = newParseError("response code is empty")
return return
} }
code = StatusRespCode(strings.ToUpper(codeStr))
fields = fields[1:] fields = fields[1:]

View File

@@ -1,17 +1,104 @@
package imap package imap
import ( import (
"errors" "strings"
) )
// A value that can be converted to a Resp. // Resp is an IMAP response. It is either a *DataResp, a
type Responser interface { // *ContinuationReq or a *StatusResp.
Response() *Resp type Resp interface {
resp()
} }
// A response. // ReadResp reads a single response from a Reader.
// See RFC 3501 section 2.2.2 func ReadResp(r *Reader) (Resp, error) {
type Resp struct { atom, err := r.ReadAtom()
if err != nil {
return nil, err
}
tag, ok := atom.(string)
if !ok {
return nil, newParseError("response tag is not an atom")
}
if tag == "+" {
if err := r.ReadSp(); err != nil {
r.UnreadRune()
}
resp := &ContinuationReq{}
resp.Info, err = r.ReadInfo()
if err != nil {
return nil, err
}
return resp, nil
}
if err := r.ReadSp(); err != nil {
return nil, err
}
// Can be either data or status
// Try to parse a status
var fields []interface{}
if atom, err := r.ReadAtom(); err == nil {
fields = append(fields, atom)
if err := r.ReadSp(); err == nil {
if name, ok := atom.(string); ok {
status := StatusRespType(name)
switch status {
case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
resp := &StatusResp{
Tag: tag,
Type: status,
}
char, _, err := r.ReadRune()
if err != nil {
return nil, err
}
r.UnreadRune()
if char == '[' {
// Contains code & arguments
resp.Code, resp.Arguments, err = r.ReadRespCode()
if err != nil {
return nil, err
}
}
resp.Info, err = r.ReadInfo()
if err != nil {
return nil, err
}
return resp, nil
}
}
} else {
r.UnreadRune()
}
} else {
r.UnreadRune()
}
// Not a status so it's data
resp := &DataResp{Tag: tag}
var remaining []interface{}
remaining, err = r.ReadLine()
if err != nil {
return nil, err
}
resp.Fields = append(fields, remaining...)
return resp, nil
}
// DataResp is an IMAP response containing data.
type DataResp struct {
// The response tag. Can be either "" for untagged responses, "+" for continuation // The response tag. Can be either "" for untagged responses, "+" for continuation
// requests or a previous command's tag. // requests or a previous command's tag.
Tag string Tag string
@@ -19,10 +106,20 @@ type Resp struct {
Fields []interface{} Fields []interface{}
} }
func (r *Resp) WriteTo(w *Writer) error { // NewUntaggedResp creates a new untagged response.
tag := r.Tag func NewUntaggedResp(fields []interface{}) *DataResp {
return &DataResp{
Tag: "*",
Fields: fields,
}
}
func (r *DataResp) resp() {}
func (r *DataResp) WriteTo(w *Writer) error {
tag := Atom(r.Tag)
if tag == "" { if tag == "" {
tag = "*" tag = Atom("*")
} }
fields := []interface{}{tag} fields := []interface{}{tag}
@@ -30,21 +127,15 @@ func (r *Resp) WriteTo(w *Writer) error {
return w.writeLine(fields...) return w.writeLine(fields...)
} }
// Create a new untagged response. // ContinuationReq is a continuation request response.
func NewUntaggedResp(fields []interface{}) *Resp { type ContinuationReq struct {
return &Resp{
Tag: "*",
Fields: fields,
}
}
// A continuation request.
type ContinuationResp struct {
// The info message sent with the continuation request. // The info message sent with the continuation request.
Info string Info string
} }
func (r *ContinuationResp) WriteTo(w *Writer) error { func (r *ContinuationReq) resp() {}
func (r *ContinuationReq) WriteTo(w *Writer) error {
if err := w.writeString("+"); err != nil { if err := w.writeString("+"); err != nil {
return err return err
} }
@@ -58,99 +149,33 @@ func (r *ContinuationResp) WriteTo(w *Writer) error {
return w.writeCrlf() return w.writeCrlf()
} }
// Read a single response from a Reader. Returns either a continuation request, // ParseNamedResp attempts to parse a named data response.
// a status response or a raw response. func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
func ReadResp(r *Reader) (out interface{}, err error) { data, ok := resp.(*DataResp)
atom, err := r.ReadAtom() if !ok || len(data.Fields) == 0 {
if err != nil {
return
}
tag, ok := atom.(string)
if !ok {
err = errors.New("Response tag is not an atom")
return return
} }
if tag == "+" { // Some responses (namely EXISTS and RECENT) are formatted like so:
if err := r.ReadSp(); err != nil { // [num] [name] [...]
r.UnreadRune() // Which is fucking stupid. But we handle that here by checking if the
} // response name is a number and then rearranging it.
if len(data.Fields) > 1 {
res := &ContinuationResp{} name, ok := data.Fields[1].(string)
res.Info, err = r.ReadInfo() if ok {
if err != nil { if _, err := ParseNumber(data.Fields[0]); err == nil {
return fields := []interface{}{data.Fields[0]}
} fields = append(fields, data.Fields[2:]...)
return strings.ToUpper(name), fields, true
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 { // IMAP commands are formatted like this:
// Not a status so it's data // [name] [...]
res := &Resp{Tag: tag} name, ok = data.Fields[0].(string)
if !ok {
var remaining []interface{} return
remaining, err = r.ReadLine()
if err != nil {
return
}
res.Fields = append(fields, remaining...)
out = res
} }
return strings.ToUpper(name), data.Fields[1:], true
return
} }

View File

@@ -14,59 +14,45 @@ type Authenticate struct {
Writer *imap.Writer Writer *imap.Writer
} }
func (r *Authenticate) HandleFrom(hdlr imap.RespHandler) (err error) { func (r *Authenticate) writeLine(l string) error {
w := r.Writer if _, err := r.Writer.Write([]byte(l + "\r\n")); err != nil {
return err
}
return r.Writer.Flush()
}
// Cancel auth if an error occurs func (r *Authenticate) cancel() error {
defer (func() { return r.writeLine("*")
if err != nil { }
w.Write([]byte("*\r\n"))
w.Flush()
}
})()
for h := range hdlr { func (r *Authenticate) Handle(resp imap.Resp) error {
cont, ok := h.Resp.(*imap.ContinuationResp) cont, ok := resp.(*imap.ContinuationReq)
if !ok { if !ok {
h.Reject() return ErrUnhandled
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 // Empty challenge, send initial response as stated in RFC 2222 section 5.1
if cont.Info == "" && r.InitialResponse != nil {
encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
if err := r.writeLine(encoded); err != nil {
return err
}
r.InitialResponse = nil
return nil
}
challenge, err := base64.StdEncoding.DecodeString(cont.Info)
if err != nil {
r.cancel()
return err
}
reply, err := r.Mechanism.Next(challenge)
if err != nil {
r.cancel()
return err
}
encoded := base64.StdEncoding.EncodeToString(reply)
return r.writeLine(encoded)
} }

View File

@@ -10,30 +10,11 @@ type Capability struct {
Caps []string 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 { func (r *Capability) WriteTo(w *imap.Writer) error {
fields := []interface{}{imap.Capability} fields := []interface{}{"CAPABILITY"}
for _, cap := range r.Caps { for _, cap := range r.Caps {
fields = append(fields, cap) fields = append(fields, cap)
} }
res := &imap.Resp{Fields: fields} return imap.NewUntaggedResp(fields).WriteTo(w)
return res.WriteTo(w)
} }

View File

@@ -4,39 +4,37 @@ import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
) )
const expungeName = "EXPUNGE"
// An EXPUNGE response. // An EXPUNGE response.
// See RFC 3501 section 7.4.1 // See RFC 3501 section 7.4.1
type Expunge struct { type Expunge struct {
SeqNums chan uint32 SeqNums chan uint32
} }
func (r *Expunge) HandleFrom(hdlr imap.RespHandler) error { func (r *Expunge) Handle(resp imap.Resp) error {
defer close(r.SeqNums) name, fields, ok := imap.ParseNamedResp(resp)
if !ok || name != expungeName {
for h := range hdlr { return ErrUnhandled
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
} }
if len(fields) == 0 {
return errNotEnoughFields
}
seqNum, err := imap.ParseNumber(fields[0])
if err != nil {
return err
}
r.SeqNums <- seqNum
return nil return nil
} }
func (r *Expunge) WriteTo(w *imap.Writer) error { func (r *Expunge) WriteTo(w *imap.Writer) error {
for seqNum := range r.SeqNums { for seqNum := range r.SeqNums {
res := imap.NewUntaggedResp([]interface{}{seqNum, imap.Expunge}) resp := imap.NewUntaggedResp([]interface{}{seqNum, expungeName})
if err := resp.WriteTo(w); err != nil {
if err := res.WriteTo(w); err != nil {
return err return err
} }
} }

View File

@@ -4,49 +4,41 @@ import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
) )
const fetchName = "FETCH"
// A FETCH response. // A FETCH response.
// See RFC 3501 section 7.4.2 // See RFC 3501 section 7.4.2
type Fetch struct { type Fetch struct {
Messages chan *imap.Message Messages chan *imap.Message
} }
func (r *Fetch) HandleFrom(hdlr imap.RespHandler) error { func (r *Fetch) Handle(resp imap.Resp) error {
defer close(r.Messages) name, fields, ok := imap.ParseNamedResp(resp)
if !ok || name != fetchName {
for h := range hdlr { return ErrUnhandled
res, ok := h.Resp.(*imap.Resp) } else if len(fields) < 1 {
if !ok || len(res.Fields) < 3 { return errNotEnoughFields
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
} }
seqNum, err := imap.ParseNumber(fields[0])
if err != nil {
return err
}
msgFields, _ := fields[1].([]interface{})
msg := &imap.Message{SeqNum: seqNum}
if err := msg.Parse(msgFields); err != nil {
return err
}
r.Messages <- msg
return nil return nil
} }
func (r *Fetch) WriteTo(w *imap.Writer) error { func (r *Fetch) WriteTo(w *imap.Writer) error {
for msg := range r.Messages { for msg := range r.Messages {
res := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.Fetch, msg.Format()}) resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, fetchName, msg.Format()})
if err := resp.WriteTo(w); err != nil {
if err := res.WriteTo(w); err != nil {
return err return err
} }
} }

View File

@@ -4,6 +4,11 @@ import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
) )
const (
listName = "LIST"
lsubName = "LSUB"
)
// A LIST response. // A LIST response.
// If Subscribed is set to true, LSUB will be used instead. // If Subscribed is set to true, LSUB will be used instead.
// See RFC 3501 section 7.2.2 // See RFC 3501 section 7.2.2
@@ -14,43 +19,36 @@ type List struct {
func (r *List) Name() string { func (r *List) Name() string {
if r.Subscribed { if r.Subscribed {
return imap.Lsub return lsubName
} else { } else {
return imap.List return listName
} }
} }
func (r *List) HandleFrom(hdlr imap.RespHandler) error { func (r *List) Handle(resp imap.Resp) error {
defer close(r.Mailboxes) name, fields, ok := imap.ParseNamedResp(resp)
if !ok || name != r.Name() {
name := r.Name() return ErrUnhandled
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
} }
mbox := &imap.MailboxInfo{}
if err := mbox.Parse(fields); err != nil {
return err
}
r.Mailboxes <- mbox
return nil return nil
} }
func (r *List) WriteTo(w *imap.Writer) error { func (r *List) WriteTo(w *imap.Writer) error {
name := r.Name() respName := r.Name()
for mbox := range r.Mailboxes { for mbox := range r.Mailboxes {
fields := []interface{}{name} fields := []interface{}{respName}
fields = append(fields, mbox.Format()...) fields = append(fields, mbox.Format()...)
res := imap.NewUntaggedResp(fields) resp := imap.NewUntaggedResp(fields)
if err := res.WriteTo(w); err != nil { if err := resp.WriteTo(w); err != nil {
return err return err
} }
} }

View File

@@ -1,2 +1,28 @@
// IMAP responses defined in RFC 3501. // IMAP responses defined in RFC 3501.
package responses package responses
import (
"errors"
"github.com/emersion/go-imap"
)
// ErrUnhandled is used when a response hasn't been handled.
var ErrUnhandled = errors.New("imap: unhandled response")
var errNotEnoughFields = errors.New("imap: not enough fields in response")
// Handler handles responses.
type Handler interface {
// Handle processes a response. If the response cannot be processed,
// ErrUnhandledResp must be returned.
Handle(resp imap.Resp) error
}
// HandlerFunc is a function that handles responses.
type HandlerFunc func(resp imap.Resp) error
// Handle implements Handler.
func (f HandlerFunc) Handle(resp imap.Resp) error {
return f(resp)
}

View File

@@ -4,34 +4,38 @@ import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
) )
const searchName = "SEARCH"
// A SEARCH response. // A SEARCH response.
// See RFC 3501 section 7.2.5 // See RFC 3501 section 7.2.5
type Search struct { type Search struct {
Ids []uint32 Ids []uint32
} }
func (r *Search) HandleFrom(hdlr imap.RespHandler) (err error) { func (r *Search) Handle(resp imap.Resp) error {
for h := range hdlr { name, fields, ok := imap.ParseNamedResp(resp)
fields, ok := h.AcceptNamedResp(imap.Search) if !ok || name != searchName {
if !ok { return ErrUnhandled
continue }
}
for _, f := range fields { r.Ids = make([]uint32, len(fields))
id, _ := imap.ParseNumber(f) for i, f := range fields {
r.Ids = append(r.Ids, id) if id, err := imap.ParseNumber(f); err != nil {
return err
} else {
r.Ids[i] = id
} }
} }
return return nil
} }
func (r *Search) WriteTo(w *imap.Writer) (err error) { func (r *Search) WriteTo(w *imap.Writer) (err error) {
fields := []interface{}{imap.Search} fields := []interface{}{searchName}
for _, id := range r.Ids { for _, id := range r.Ids {
fields = append(fields, id) fields = append(fields, id)
} }
res := imap.NewUntaggedResp(fields) resp := imap.NewUntaggedResp(fields)
return res.WriteTo(w) return resp.WriteTo(w)
} }

View File

@@ -11,135 +11,132 @@ type Select struct {
Mailbox *imap.MailboxStatus Mailbox *imap.MailboxStatus
} }
func (r *Select) HandleFrom(hdlr imap.RespHandler) (err error) { func (r *Select) Handle(resp imap.Resp) error {
if r.Mailbox == nil { if r.Mailbox == nil {
r.Mailbox = &imap.MailboxStatus{} r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
} }
mbox := r.Mailbox mbox := r.Mailbox
mbox.Items = make(map[string]interface{}) switch resp := resp.(type) {
for h := range hdlr { case *imap.DataResp:
switch res := h.Resp.(type) { name, fields, ok := imap.ParseNamedResp(resp)
case *imap.Resp: if !ok || name != "FLAGS" {
fields, ok := h.AcceptNamedResp(imap.MailboxFlags) return ErrUnhandled
if !ok { } else if len(fields) < 1 {
break return errNotEnoughFields
} }
flags, _ := fields[0].([]interface{}) flags, _ := fields[0].([]interface{})
mbox.Flags, _ = imap.ParseStringList(flags) mbox.Flags, _ = imap.ParseStringList(flags)
case *imap.StatusResp:
if len(resp.Arguments) < 1 {
return ErrUnhandled
}
var item imap.StatusItem
switch resp.Code {
case "UNSEEN":
mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
case "PERMANENTFLAGS":
flags, _ := resp.Arguments[0].([]interface{})
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
case "UIDNEXT":
mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
item = imap.StatusUidNext
case "UIDVALIDITY":
mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
item = imap.StatusUidValidity
default:
return ErrUnhandled
}
if item != "" {
mbox.ItemsLocker.Lock() mbox.ItemsLocker.Lock()
mbox.Items[imap.MailboxFlags] = nil mbox.Items[item] = nil
mbox.ItemsLocker.Unlock() mbox.ItemsLocker.Unlock()
case *imap.StatusResp: }
if len(res.Arguments) < 1 { default:
h.Accepts <- false return ErrUnhandled
break }
} return nil
}
accepted := true func (r *Select) WriteTo(w *imap.Writer) error {
switch res.Code { mbox := r.Mailbox
case imap.MailboxUnseen:
mbox.Unseen, _ = imap.ParseNumber(res.Arguments[0]) if mbox.Flags != nil {
mbox.ItemsLocker.Lock() flags := make([]interface{}, len(mbox.Flags))
mbox.Items[imap.MailboxUnseen] = nil for i, f := range mbox.Flags {
mbox.ItemsLocker.Unlock() flags[i] = imap.Atom(f)
case imap.MailboxPermanentFlags: }
flags, _ := res.Arguments[0].([]interface{}) res := imap.NewUntaggedResp([]interface{}{"FLAGS", flags})
mbox.PermanentFlags, _ = imap.ParseStringList(flags) if err := res.WriteTo(w); err != nil {
mbox.ItemsLocker.Lock() return err
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 if mbox.PermanentFlags != nil {
} flags := make([]interface{}, len(mbox.PermanentFlags))
for i, f := range mbox.PermanentFlags {
flags[i] = imap.Atom(f)
}
statusRes := &imap.StatusResp{
Type: imap.StatusRespOk,
Code: imap.CodePermanentFlags,
Arguments: []interface{}{flags},
Info: "Flags permitted.",
}
if err := statusRes.WriteTo(w); err != nil {
return err
}
}
func (r *Select) WriteTo(w *imap.Writer) (err error) { if mbox.UnseenSeqNum > 0 {
status := r.Mailbox statusRes := &imap.StatusResp{
Type: imap.StatusRespOk,
Code: imap.CodeUnseen,
Arguments: []interface{}{mbox.UnseenSeqNum},
Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
}
if err := statusRes.WriteTo(w); err != nil {
return err
}
}
for k := range r.Mailbox.Items { for k := range r.Mailbox.Items {
switch k { switch k {
case imap.MailboxFlags: case imap.StatusMessages:
flags := make([]interface{}, len(status.Flags)) res := imap.NewUntaggedResp([]interface{}{mbox.Messages, "EXISTS"})
for i, f := range status.Flags { if err := res.WriteTo(w); err != nil {
flags[i] = f return err
} }
res := imap.NewUntaggedResp([]interface{}{"FLAGS", flags}) case imap.StatusRecent:
if err = res.WriteTo(w); err != nil { res := imap.NewUntaggedResp([]interface{}{mbox.Recent, "RECENT"})
return if err := res.WriteTo(w); err != nil {
} return err
case imap.MailboxPermanentFlags:
flags := make([]interface{}, len(status.PermanentFlags))
for i, f := range status.PermanentFlags {
flags[i] = f
} }
case imap.StatusUidNext:
statusRes := &imap.StatusResp{ statusRes := &imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
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, Code: imap.CodeUidNext,
Arguments: []interface{}{status.UidNext}, Arguments: []interface{}{mbox.UidNext},
Info: "Predicted next UID", Info: "Predicted next UID",
} }
if err = statusRes.WriteTo(w); err != nil { if err := statusRes.WriteTo(w); err != nil {
return return err
} }
case imap.MailboxUidValidity: case imap.StatusUidValidity:
statusRes := &imap.StatusResp{ statusRes := &imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Code: imap.CodeUidValidity, Code: imap.CodeUidValidity,
Arguments: []interface{}{status.UidValidity}, Arguments: []interface{}{mbox.UidValidity},
Info: "UIDs valid", Info: "UIDs valid",
} }
if err = statusRes.WriteTo(w); err != nil { if err := statusRes.WriteTo(w); err != nil {
return return err
} }
} }
} }
return return nil
} }

View File

@@ -7,57 +7,47 @@ import (
"github.com/emersion/go-imap/utf7" "github.com/emersion/go-imap/utf7"
) )
const statusName = "STATUS"
// A STATUS response. // A STATUS response.
// See RFC 3501 section 7.2.4 // See RFC 3501 section 7.2.4
type Status struct { type Status struct {
Mailbox *imap.MailboxStatus Mailbox *imap.MailboxStatus
} }
func (r *Status) HandleFrom(hdlr imap.RespHandler) error { func (r *Status) Handle(resp imap.Resp) error {
if r.Mailbox == nil { if r.Mailbox == nil {
r.Mailbox = &imap.MailboxStatus{} r.Mailbox = &imap.MailboxStatus{}
} }
mbox := r.Mailbox mbox := r.Mailbox
mbox.Items = nil
for h := range hdlr { name, fields, ok := imap.ParseNamedResp(resp)
fields, ok := h.AcceptNamedResp(imap.Status) if !ok || name != statusName {
if !ok { return ErrUnhandled
continue } else if len(fields) < 2 {
} return errNotEnoughFields
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 if name, err := imap.ParseString(fields[0]); err != nil {
return err
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
return err
} else {
mbox.Name = imap.CanonicalMailboxName(name)
}
var items []interface{}
if items, ok = fields[1].([]interface{}); !ok {
return errors.New("STATUS response expects a list as second argument")
}
mbox.Items = nil
return mbox.Parse(items)
} }
func (r *Status) WriteTo(w *imap.Writer) error { func (r *Status) WriteTo(w *imap.Writer) error {
mbox := r.Mailbox mbox := r.Mailbox
name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
fields := []interface{}{imap.Status, mbox.Name, mbox.Format()} fields := []interface{}{statusName, name, mbox.Format()}
return imap.NewUntaggedResp(fields).WriteTo(w)
res := imap.NewUntaggedResp(fields)
if err := res.WriteTo(w); err != nil {
return err
}
return nil
} }

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/textproto" "net/textproto"
"strings" "strings"
"time" "time"
@@ -43,8 +42,8 @@ func convertField(f interface{}, charsetReader func(io.Reader) io.Reader) string
} }
} }
b, err := ioutil.ReadAll(r) b := make([]byte, l.Len())
if err != nil { if _, err := io.ReadFull(r, b); err != nil {
return "" return ""
} }
return string(b) return string(b)
@@ -238,7 +237,7 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.
case "UID": case "UID":
if f, fields, err = popSearchField(fields); err != nil { if f, fields, err = popSearchField(fields); err != nil {
return nil, err return nil, err
} else if c.Uid, err = NewSeqSet(maybeString(f)); err != nil { } else if c.Uid, err = ParseSeqSet(maybeString(f)); err != nil {
return nil, err return nil, err
} }
case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN": case "UNANSWERED", "UNDELETED", "UNDRAFT", "UNFLAGGED", "UNSEEN":
@@ -251,7 +250,7 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f))) c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
} }
default: // Try to parse a sequence set default: // Try to parse a sequence set
if c.SeqNum, err = NewSeqSet(key); err != nil { if c.SeqNum, err = ParseSeqSet(key); err != nil {
return nil, err return nil, err
} }
} }

View File

@@ -122,8 +122,8 @@ type SeqSet struct {
Set []Seq Set []Seq
} }
// NewSeqSet returns a new SeqSet instance after parsing the set string. // ParseSeqSet returns a new SeqSet instance after parsing the set string.
func NewSeqSet(set string) (s *SeqSet, err error) { func ParseSeqSet(set string) (s *SeqSet, err error) {
s = new(SeqSet) s = new(SeqSet)
return s, s.Add(set) return s, s.Add(set)
} }
@@ -268,7 +268,6 @@ func (s *SeqSet) insertAt(i int, v Seq) {
s.Set = set s.Set = set
} }
s.Set[i] = v s.Set[i] = v
return
} }
// search attempts to find the index of the sequence set value that contains q. // search attempts to find the index of the sequence set value that contains q.

View File

@@ -24,7 +24,7 @@ func (cmd *Noop) Handle(conn Conn) error {
ctx := conn.Context() ctx := conn.Context()
if ctx.Mailbox != nil { if ctx.Mailbox != nil {
// If a mailbox is selected, NOOP can be used to poll for server updates // If a mailbox is selected, NOOP can be used to poll for server updates
if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok { if mbox, ok := ctx.Mailbox.(backend.MailboxPoller); ok {
return mbox.Poll() return mbox.Poll()
} }
} }
@@ -38,7 +38,7 @@ type Logout struct {
func (cmd *Logout) Handle(conn Conn) error { func (cmd *Logout) Handle(conn Conn) error {
res := &imap.StatusResp{ res := &imap.StatusResp{
Type: imap.StatusBye, Type: imap.StatusRespBye,
Info: "Closing connection", Info: "Closing connection",
} }

View File

@@ -29,10 +29,9 @@ func (cmd *Select) Handle(conn Conn) error {
return err return err
} }
items := []string{ items := []imap.StatusItem{
imap.MailboxFlags, imap.MailboxPermanentFlags, imap.StatusMessages, imap.StatusRecent, imap.StatusUnseen,
imap.MailboxMessages, imap.MailboxRecent, imap.MailboxUnseen, imap.StatusUidNext, imap.StatusUidValidity,
imap.MailboxUidNext, imap.MailboxUidValidity,
} }
status, err := mbox.Status(items) status, err := mbox.Status(items)
@@ -48,12 +47,12 @@ func (cmd *Select) Handle(conn Conn) error {
return err return err
} }
code := imap.CodeReadWrite var code imap.StatusRespCode = imap.CodeReadWrite
if ctx.MailboxReadOnly { if ctx.MailboxReadOnly {
code = imap.CodeReadOnly code = imap.CodeReadOnly
} }
return ErrStatusResp(&imap.StatusResp{ return ErrStatusResp(&imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Code: code, Code: code,
}) })
} }
@@ -208,7 +207,7 @@ func (cmd *Status) Handle(conn Conn) error {
} }
// Only keep items thqat have been requested // Only keep items thqat have been requested
items := make(map[string]interface{}) items := make(map[imap.StatusItem]interface{})
for _, k := range cmd.Items { for _, k := range cmd.Items {
items[k] = status.Items[k] items[k] = status.Items[k]
} }
@@ -231,7 +230,7 @@ func (cmd *Append) Handle(conn Conn) error {
mbox, err := ctx.User.GetMailbox(cmd.Mailbox) mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
if err == backend.ErrNoSuchMailbox { if err == backend.ErrNoSuchMailbox {
return ErrStatusResp(&imap.StatusResp{ return ErrStatusResp(&imap.StatusResp{
Type: imap.StatusNo, Type: imap.StatusRespNo,
Code: imap.CodeTryCreate, Code: imap.CodeTryCreate,
Info: err.Error(), Info: err.Error(),
}) })
@@ -246,10 +245,13 @@ func (cmd *Append) Handle(conn Conn) error {
// If APPEND targets the currently selected mailbox, send an untagged EXISTS // If APPEND targets the currently selected mailbox, send an untagged EXISTS
// Do this only if the backend doesn't send updates itself // Do this only if the backend doesn't send updates itself
if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() { if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
status, err := mbox.Status([]string{imap.MailboxMessages}) status, err := mbox.Status([]imap.StatusItem{imap.StatusMessages})
if err != nil { if err != nil {
return err return err
} }
status.Flags = nil
status.PermanentFlags = nil
status.UnseenSeqNum = 0
res := &responses.Select{Mailbox: status} res := &responses.Select{Mailbox: status}
if err := conn.WriteResp(res); err != nil { if err := conn.WriteResp(res); err != nil {

View File

@@ -36,7 +36,7 @@ func (cmd *StartTLS) Handle(conn Conn) error {
// Send an OK status response to let the client know that the TLS handshake // Send an OK status response to let the client know that the TLS handshake
// can begin // can begin
return ErrStatusResp(&imap.StatusResp{ return ErrStatusResp(&imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Info: "Begin TLS negotiation now", Info: "Begin TLS negotiation now",
}) })
} }
@@ -62,7 +62,7 @@ func (cmd *StartTLS) Upgrade(conn Conn) error {
func afterAuthStatus(conn Conn) error { func afterAuthStatus(conn Conn) error {
return ErrStatusResp(&imap.StatusResp{ return ErrStatusResp(&imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Code: imap.CodeCapability, Code: imap.CodeCapability,
Arguments: imap.FormatStringList(conn.Capabilities()), Arguments: imap.FormatStringList(conn.Capabilities()),
}) })

View File

@@ -2,7 +2,6 @@ package server
import ( import (
"errors" "errors"
"strings"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"github.com/emersion/go-imap/commands" "github.com/emersion/go-imap/commands"
@@ -53,12 +52,8 @@ func (cmd *Close) Handle(conn Conn) error {
ctx.Mailbox = nil ctx.Mailbox = nil
ctx.MailboxReadOnly = false ctx.MailboxReadOnly = false
if err := mailbox.Expunge(); err != nil {
return err
}
// No need to send expunge updates here, since the mailbox is already unselected // No need to send expunge updates here, since the mailbox is already unselected
return nil return mailbox.Expunge()
} }
type Expunge struct { type Expunge struct {
@@ -206,15 +201,10 @@ func (cmd *Store) handle(uid bool, conn Conn) error {
return ErrMailboxReadOnly return ErrMailboxReadOnly
} }
itemStr := cmd.Item // Only flags operations are supported
silent := strings.HasSuffix(itemStr, imap.SilentOp) op, silent, err := imap.ParseFlagsOp(cmd.Item)
if silent { if err != nil {
itemStr = strings.TrimSuffix(itemStr, imap.SilentOp) return err
}
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{}) flagsList, ok := cmd.Value.([]interface{})
@@ -233,7 +223,7 @@ func (cmd *Store) handle(uid bool, conn Conn) error {
// from receiving them // from receiving them
// TODO: find a better way to do this, without conn.silent // TODO: find a better way to do this, without conn.silent
*conn.silent() = silent *conn.silent() = silent
err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags) err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, op, flags)
*conn.silent() = false *conn.silent() = false
if err != nil { if err != nil {
return err return err
@@ -244,7 +234,7 @@ func (cmd *Store) handle(uid bool, conn Conn) error {
if conn.Server().Updates == nil && !silent { if conn.Server().Updates == nil && !silent {
inner := &Fetch{} inner := &Fetch{}
inner.SeqSet = cmd.SeqSet inner.SeqSet = cmd.SeqSet
inner.Items = []string{"FLAGS"} inner.Items = []imap.FetchItem{imap.FetchFlags}
if uid { if uid {
inner.Items = append(inner.Items, "UID") inner.Items = append(inner.Items, "UID")
} }
@@ -307,7 +297,7 @@ func (cmd *Uid) Handle(conn Conn) error {
} }
return ErrStatusResp(&imap.StatusResp{ return ErrStatusResp(&imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Info: imap.Uid + " " + inner.Name + " completed", Info: "UID " + inner.Name + " completed",
}) })
} }

View File

@@ -36,7 +36,7 @@ type Conn interface {
setTLSConn(*tls.Conn) setTLSConn(*tls.Conn)
silent() *bool // TODO: remove this silent() *bool // TODO: remove this
serve() error serve(Conn) error
commandHandler(cmd *imap.Command) (hdlr Handler, err error) commandHandler(cmd *imap.Command) (hdlr Handler, err error)
} }
@@ -52,17 +52,21 @@ type Context struct {
MailboxReadOnly bool MailboxReadOnly bool
// Responses to send to the client. // Responses to send to the client.
Responses chan<- imap.WriterTo Responses chan<- imap.WriterTo
// Closed when the client is logged out.
LoggedOut <-chan struct{}
} }
type conn struct { type conn struct {
*imap.Conn *imap.Conn
conn Conn // With extensions overrides
s *Server s *Server
ctx *Context ctx *Context
l sync.Locker l sync.Locker
tlsConn *tls.Conn tlsConn *tls.Conn
continues chan bool continues chan bool
responses chan imap.WriterTo responses chan imap.WriterTo
loggedOut chan struct{}
silentVal bool silentVal bool
} }
@@ -73,6 +77,7 @@ func newConn(s *Server, c net.Conn) *conn {
w := imap.NewWriter(nil) w := imap.NewWriter(nil)
responses := make(chan imap.WriterTo) responses := make(chan imap.WriterTo)
loggedOut := make(chan struct{})
tlsConn, _ := c.(*tls.Conn) tlsConn, _ := c.(*tls.Conn)
@@ -84,10 +89,12 @@ func newConn(s *Server, c net.Conn) *conn {
ctx: &Context{ ctx: &Context{
State: imap.ConnectingState, State: imap.ConnectingState,
Responses: responses, Responses: responses,
LoggedOut: loggedOut,
}, },
tlsConn: tlsConn, tlsConn: tlsConn,
continues: continues, continues: continues,
responses: responses, responses: responses,
loggedOut: loggedOut,
} }
if s.Debug != nil { if s.Debug != nil {
@@ -149,14 +156,7 @@ func (c *conn) Close() error {
c.ctx.User.Logout() c.ctx.User.Logout()
} }
if err := c.Conn.Close(); err != nil { return c.Conn.Close()
return err
}
close(c.continues)
c.ctx.State = imap.LogoutState
return nil
} }
func (c *conn) Capabilities() []string { func (c *conn) Capabilities() []string {
@@ -187,8 +187,8 @@ func (c *conn) send() {
// Send continuation requests // Send continuation requests
go func() { go func() {
for range c.continues { for range c.continues {
res := &imap.ContinuationResp{Info: "send literal"} resp := &imap.ContinuationReq{Info: "send literal"}
if err := res.WriteTo(c.Writer); err != nil { if err := resp.WriteTo(c.Writer); err != nil {
c.Server().ErrorLog.Println("cannot send continuation request: ", err) c.Server().ErrorLog.Println("cannot send continuation request: ", err)
} else if err := c.Writer.Flush(); err != nil { } else if err := c.Writer.Flush(); err != nil {
c.Server().ErrorLog.Println("cannot flush connection: ", err) c.Server().ErrorLog.Println("cannot flush connection: ", err)
@@ -199,19 +199,22 @@ func (c *conn) send() {
// Send responses // Send responses
for { for {
// Get a response that needs to be sent // Get a response that needs to be sent
res := <-c.responses select {
case res := <-c.responses:
// Request to send the response
c.l.Lock()
// Request to send the response // Send the response
c.l.Lock() 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)
}
// Send the response c.l.Unlock()
if err := res.WriteTo(c.Writer); err != nil { case <-c.loggedOut:
c.Server().ErrorLog.Println("cannot send response: ", err) return
} else if err := c.Writer.Flush(); err != nil {
c.Server().ErrorLog.Println("cannot flush connection: ", err)
} }
c.l.Unlock()
} }
} }
@@ -225,7 +228,7 @@ func (c *conn) greet() error {
} }
greeting := &imap.StatusResp{ greeting := &imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
Code: imap.CodeCapability, Code: imap.CodeCapability,
Arguments: args, Arguments: args,
Info: "IMAP4rev1 Service Ready", Info: "IMAP4rev1 Service Ready",
@@ -262,7 +265,15 @@ func (c *conn) silent() *bool {
return &c.silentVal return &c.silentVal
} }
func (c *conn) serve() error { func (c *conn) serve(conn Conn) error {
c.conn = conn
defer func() {
c.ctx.State = imap.LogoutState
close(c.continues)
close(c.loggedOut)
}()
// Send greeting // Send greeting
if err := c.greet(); err != nil { if err := c.greet(); err != nil {
return err return err
@@ -286,7 +297,7 @@ func (c *conn) serve() error {
if err != nil { if err != nil {
if imap.IsParseError(err) { if imap.IsParseError(err) {
res = &imap.StatusResp{ res = &imap.StatusResp{
Type: imap.StatusBad, Type: imap.StatusRespBad,
Info: err.Error(), Info: err.Error(),
} }
} else { } else {
@@ -298,7 +309,7 @@ func (c *conn) serve() error {
if err := cmd.Parse(fields); err != nil { if err := cmd.Parse(fields); err != nil {
res = &imap.StatusResp{ res = &imap.StatusResp{
Tag: cmd.Tag, Tag: cmd.Tag,
Type: imap.StatusBad, Type: imap.StatusRespBad,
Info: err.Error(), Info: err.Error(),
} }
} else { } else {
@@ -307,7 +318,7 @@ func (c *conn) serve() error {
if err != nil { if err != nil {
res = &imap.StatusResp{ res = &imap.StatusResp{
Tag: cmd.Tag, Tag: cmd.Tag,
Type: imap.StatusBad, Type: imap.StatusRespBad,
Info: err.Error(), Info: err.Error(),
} }
} }
@@ -323,8 +334,8 @@ func (c *conn) serve() error {
continue continue
} }
if up != nil && res.Type == imap.StatusOk { if up != nil && res.Type == imap.StatusRespOk {
if err := up.Upgrade(c); err != nil { if err := up.Upgrade(c.conn); err != nil {
c.s.ErrorLog.Println("cannot upgrade connection:", err) c.s.ErrorLog.Println("cannot upgrade connection:", err)
return err return err
} }
@@ -356,24 +367,24 @@ func (c *conn) handleCommand(cmd *imap.Command) (res *imap.StatusResp, up Upgrad
c.l.Unlock() c.l.Unlock()
defer c.l.Lock() defer c.l.Lock()
hdlrErr := hdlr.Handle(c) hdlrErr := hdlr.Handle(c.conn)
if statusErr, ok := hdlrErr.(*errStatusResp); ok { if statusErr, ok := hdlrErr.(*errStatusResp); ok {
res = statusErr.resp res = statusErr.resp
} else if hdlrErr != nil { } else if hdlrErr != nil {
res = &imap.StatusResp{ res = &imap.StatusResp{
Type: imap.StatusNo, Type: imap.StatusRespNo,
Info: hdlrErr.Error(), Info: hdlrErr.Error(),
} }
} else { } else {
res = &imap.StatusResp{ res = &imap.StatusResp{
Type: imap.StatusOk, Type: imap.StatusRespOk,
} }
} }
if res != nil { if res != nil {
res.Tag = cmd.Tag res.Tag = cmd.Tag
if res.Type == imap.StatusOk && res.Info == "" { if res.Type == imap.StatusRespOk && res.Info == "" {
res.Info = cmd.Name + " completed" res.Info = cmd.Name + " completed"
} }
} }

View File

@@ -45,7 +45,7 @@ type Upgrader interface {
type HandlerFactory func() Handler type HandlerFactory func() Handler
// A function that creates SASL servers. // A function that creates SASL servers.
type SaslServerFactory func(conn Conn) sasl.Server type SASLServerFactory func(conn Conn) sasl.Server
// An IMAP extension. // An IMAP extension.
type Extension interface { type Extension interface {
@@ -94,7 +94,7 @@ type Server struct {
conns map[Conn]struct{} conns map[Conn]struct{}
commands map[string]HandlerFactory commands map[string]HandlerFactory
auths map[string]SaslServerFactory auths map[string]SASLServerFactory
extensions []Extension extensions []Extension
// TCP address to listen on. // TCP address to listen on.
@@ -104,7 +104,7 @@ type Server struct {
// This server's backend. // This server's backend.
Backend backend.Backend Backend backend.Backend
// Backend updates that will be sent to connected clients. // Backend updates that will be sent to connected clients.
Updates <-chan interface{} Updates <-chan backend.Update
// Automatically logout clients after a duration. To do not logout users // Automatically logout clients after a duration. To do not logout users
// automatically, set this to zero. The duration MUST be at least // automatically, set this to zero. The duration MUST be at least
// MinAutoLogout (as stated in RFC 3501 section 5.4). // MinAutoLogout (as stated in RFC 3501 section 5.4).
@@ -132,7 +132,7 @@ func New(bkd backend.Backend) *Server {
ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags), ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags),
} }
s.auths = map[string]SaslServerFactory{ s.auths = map[string]SASLServerFactory{
sasl.Plain: func(conn Conn) sasl.Server { sasl.Plain: func(conn Conn) sasl.Server {
return sasl.NewPlainServer(func(identity, username, password string) error { return sasl.NewPlainServer(func(identity, username, password string) error {
if identity != "" && identity != username { if identity != "" && identity != username {
@@ -153,42 +153,42 @@ func New(bkd backend.Backend) *Server {
} }
s.commands = map[string]HandlerFactory{ s.commands = map[string]HandlerFactory{
imap.Noop: func() Handler { return &Noop{} }, "NOOP": func() Handler { return &Noop{} },
imap.Capability: func() Handler { return &Capability{} }, "CAPABILITY": func() Handler { return &Capability{} },
imap.Logout: func() Handler { return &Logout{} }, "LOGOUT": func() Handler { return &Logout{} },
imap.StartTLS: func() Handler { return &StartTLS{} }, "STARTTLS": func() Handler { return &StartTLS{} },
imap.Login: func() Handler { return &Login{} }, "LOGIN": func() Handler { return &Login{} },
imap.Authenticate: func() Handler { return &Authenticate{} }, "AUTHENTICATE": func() Handler { return &Authenticate{} },
imap.Select: func() Handler { return &Select{} }, "SELECT": func() Handler { return &Select{} },
imap.Examine: func() Handler { "EXAMINE": func() Handler {
hdlr := &Select{} hdlr := &Select{}
hdlr.ReadOnly = true hdlr.ReadOnly = true
return hdlr return hdlr
}, },
imap.Create: func() Handler { return &Create{} }, "CREATE": func() Handler { return &Create{} },
imap.Delete: func() Handler { return &Delete{} }, "DELETE": func() Handler { return &Delete{} },
imap.Rename: func() Handler { return &Rename{} }, "RENAME": func() Handler { return &Rename{} },
imap.Subscribe: func() Handler { return &Subscribe{} }, "SUBSCRIBE": func() Handler { return &Subscribe{} },
imap.Unsubscribe: func() Handler { return &Unsubscribe{} }, "UNSUBSCRIBE": func() Handler { return &Unsubscribe{} },
imap.List: func() Handler { return &List{} }, "LIST": func() Handler { return &List{} },
imap.Lsub: func() Handler { "LSUB": func() Handler {
hdlr := &List{} hdlr := &List{}
hdlr.Subscribed = true hdlr.Subscribed = true
return hdlr return hdlr
}, },
imap.Status: func() Handler { return &Status{} }, "STATUS": func() Handler { return &Status{} },
imap.Append: func() Handler { return &Append{} }, "APPEND": func() Handler { return &Append{} },
imap.Check: func() Handler { return &Check{} }, "CHECK": func() Handler { return &Check{} },
imap.Close: func() Handler { return &Close{} }, "CLOSE": func() Handler { return &Close{} },
imap.Expunge: func() Handler { return &Expunge{} }, "EXPUNGE": func() Handler { return &Expunge{} },
imap.Search: func() Handler { return &Search{} }, "SEARCH": func() Handler { return &Search{} },
imap.Fetch: func() Handler { return &Fetch{} }, "FETCH": func() Handler { return &Fetch{} },
imap.Store: func() Handler { return &Store{} }, "STORE": func() Handler { return &Store{} },
imap.Copy: func() Handler { return &Copy{} }, "COPY": func() Handler { return &Copy{} },
imap.Uid: func() Handler { return &Uid{} }, "UID": func() Handler { return &Uid{} },
} }
return s return s
@@ -207,7 +207,11 @@ func (s *Server) Serve(l net.Listener) error {
delete(s.listeners, l) delete(s.listeners, l)
}() }()
go s.listenUpdates() updater, ok := s.Backend.(backend.BackendUpdater)
if ok {
s.Updates = updater.Updates()
go s.listenUpdates()
}
for { for {
c, err := l.Accept() c, err := l.Accept()
@@ -274,7 +278,7 @@ func (s *Server) serveConn(conn Conn) error {
delete(s.conns, conn) delete(s.conns, conn)
}() }()
return conn.serve() return conn.serve(conn)
} }
// Get a command handler factory for the provided command name. // Get a command handler factory for the provided command name.
@@ -289,49 +293,32 @@ func (s *Server) Command(name string) HandlerFactory {
return s.commands[name] return s.commands[name]
} }
func (s *Server) listenUpdates() (err error) { func (s *Server) listenUpdates() {
updater, ok := s.Backend.(backend.Updater)
if !ok {
return
}
s.Updates = updater.Updates()
for { for {
item := <-s.Updates update := <-s.Updates
var ( var res imap.WriterTo
update *backend.Update switch update := update.(type) {
res imap.WriterTo
)
switch item := item.(type) {
case *backend.StatusUpdate: case *backend.StatusUpdate:
update = &item.Update res = update.StatusResp
res = item.StatusResp
case *backend.MailboxUpdate: case *backend.MailboxUpdate:
update = &item.Update res = &responses.Select{Mailbox: update.MailboxStatus}
res = &responses.Select{Mailbox: item.MailboxStatus}
case *backend.MessageUpdate: case *backend.MessageUpdate:
update = &item.Update
ch := make(chan *imap.Message, 1) ch := make(chan *imap.Message, 1)
ch <- item.Message ch <- update.Message
close(ch) close(ch)
res = &responses.Fetch{Messages: ch} res = &responses.Fetch{Messages: ch}
case *backend.ExpungeUpdate: case *backend.ExpungeUpdate:
update = &item.Update
ch := make(chan uint32, 1) ch := make(chan uint32, 1)
ch <- item.SeqNum ch <- update.SeqNum
close(ch) close(ch)
res = &responses.Expunge{SeqNums: ch} res = &responses.Expunge{SeqNums: ch}
default: default:
s.ErrorLog.Printf("unhandled update: %T\n", item) s.ErrorLog.Printf("unhandled update: %T\n", update)
} }
if res == nil {
if update == nil || res == nil {
continue continue
} }
@@ -341,10 +328,10 @@ func (s *Server) listenUpdates() (err error) {
for conn := range s.conns { for conn := range s.conns {
ctx := conn.Context() ctx := conn.Context()
if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) { if update.Username() != "" && (ctx.User == nil || ctx.User.Username() != update.Username()) {
continue continue
} }
if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) { if update.Mailbox() != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox()) {
continue continue
} }
if *conn.silent() { if *conn.silent() {
@@ -374,12 +361,11 @@ func (s *Server) listenUpdates() (err error) {
for done := 0; done < wait; done++ { for done := 0; done < wait; done++ {
<-sends <-sends
} }
close(sends)
backend.DoneUpdate(update) close(update.Done())
}() }()
} else { } else {
backend.DoneUpdate(update) close(update.Done())
} }
} }
} }
@@ -421,6 +407,6 @@ func (s *Server) Enable(extensions ...Extension) {
// //
// This function should not be called directly, it must only be used by // This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol. // libraries implementing extensions of the IMAP protocol.
func (s *Server) EnableAuth(name string, f SaslServerFactory) { func (s *Server) EnableAuth(name string, f SASLServerFactory) {
s.auths[name] = f s.auths[name] = f
} }

View File

@@ -12,13 +12,13 @@ const (
// The OK response indicates an information message from the server. When // The OK response indicates an information message from the server. When
// tagged, it indicates successful completion of the associated command. // tagged, it indicates successful completion of the associated command.
// The untagged form indicates an information-only message. // The untagged form indicates an information-only message.
StatusOk StatusRespType = "OK" StatusRespOk StatusRespType = "OK"
// The NO response indicates an operational error message from the // The NO response indicates an operational error message from the
// server. When tagged, it indicates unsuccessful completion of the // server. When tagged, it indicates unsuccessful completion of the
// associated command. The untagged form indicates a warning; the // associated command. The untagged form indicates a warning; the
// command can still complete successfully. // command can still complete successfully.
StatusNo = "NO" StatusRespNo = "NO"
// The BAD response indicates an error message from the server. When // The BAD response indicates an error message from the server. When
// tagged, it reports a protocol-level error in the client's command; // tagged, it reports a protocol-level error in the client's command;
@@ -26,32 +26,34 @@ const (
// form indicates a protocol-level error for which the associated // form indicates a protocol-level error for which the associated
// command can not be determined; it can also indicate an internal // command can not be determined; it can also indicate an internal
// server failure. // server failure.
StatusBad = "BAD" StatusRespBad = "BAD"
// The PREAUTH response is always untagged, and is one of three // The PREAUTH response is always untagged, and is one of three
// possible greetings at connection startup. It indicates that the // possible greetings at connection startup. It indicates that the
// connection has already been authenticated by external means; thus // connection has already been authenticated by external means; thus
// no LOGIN command is needed. // no LOGIN command is needed.
StatusPreauth = "PREAUTH" StatusRespPreauth = "PREAUTH"
// The BYE response is always untagged, and indicates that the server // The BYE response is always untagged, and indicates that the server
// is about to close the connection. // is about to close the connection.
StatusBye = "BYE" StatusRespBye = "BYE"
) )
type StatusRespCode string
// Status response codes defined in RFC 3501 section 7.1. // Status response codes defined in RFC 3501 section 7.1.
const ( const (
CodeAlert = "ALERT" CodeAlert StatusRespCode = "ALERT"
CodeBadCharset = "BADCHARSET" CodeBadCharset = "BADCHARSET"
CodeCapability = "CAPABILITY" CodeCapability = "CAPABILITY"
CodeParse = "PARSE" CodeParse = "PARSE"
CodePermanentFlags = "PERMANENTFLAGS" CodePermanentFlags = "PERMANENTFLAGS"
CodeReadOnly = "READ-ONLY" CodeReadOnly = "READ-ONLY"
CodeReadWrite = "READ-WRITE" CodeReadWrite = "READ-WRITE"
CodeTryCreate = "TRYCREATE" CodeTryCreate = "TRYCREATE"
CodeUidNext = "UIDNEXT" CodeUidNext = "UIDNEXT"
CodeUidValidity = "UIDVALIDITY" CodeUidValidity = "UIDVALIDITY"
CodeUnseen = "UNSEEN" CodeUnseen = "UNSEEN"
) )
// A status response. // A status response.
@@ -59,21 +61,19 @@ const (
type StatusResp struct { type StatusResp struct {
// The response tag. If empty, it defaults to *. // The response tag. If empty, it defaults to *.
Tag string Tag string
// The status type. // The status type.
Type StatusRespType Type StatusRespType
// The status code. // The status code.
// See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml // See https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml
Code string Code StatusRespCode
// Arguments provided with the status code. // Arguments provided with the status code.
Arguments []interface{} Arguments []interface{}
// The status info. // The status info.
Info string Info string
} }
func (r *StatusResp) resp() {}
// If this status is NO or BAD, returns an error with the status info. // If this status is NO or BAD, returns an error with the status info.
// Otherwise, returns nil. // Otherwise, returns nil.
func (r *StatusResp) Err() error { func (r *StatusResp) Err() error {
@@ -82,14 +82,14 @@ func (r *StatusResp) Err() error {
return errors.New("imap: connection closed during command execution") return errors.New("imap: connection closed during command execution")
} }
if r.Type == StatusNo || r.Type == StatusBad { if r.Type == StatusRespNo || r.Type == StatusRespBad {
return errors.New(r.Info) return errors.New(r.Info)
} }
return nil return nil
} }
func (r *StatusResp) WriteTo(w *Writer) error { func (r *StatusResp) WriteTo(w *Writer) error {
tag := r.Tag tag := Atom(r.Tag)
if tag == "" { if tag == "" {
tag = "*" tag = "*"
} }

View File

@@ -5,15 +5,11 @@ import (
"unicode/utf16" "unicode/utf16"
"unicode/utf8" "unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
var ErrBadUtf7 = errors.New("bad utf-7 encoding") // ErrInvalidUTF7 means that a transformer encountered invalid UTF-7.
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
var Decoder = &encoding.Decoder{
Transformer: &decoder{true},
}
type decoder struct { type decoder struct {
ascii bool ascii bool
@@ -24,7 +20,7 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
ch := src[i] ch := src[i]
if ch < min || ch > max { // Illegal code point in ASCII mode if ch < min || ch > max { // Illegal code point in ASCII mode
err = ErrBadUtf7 err = ErrInvalidUTF7
return return
} }
@@ -47,14 +43,14 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
start := i + 1 start := i + 1
for i++; i < len(src) && src[i] != '-'; i++ { for i++; i < len(src) && src[i] != '-'; i++ {
if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
err = ErrBadUtf7 err = ErrInvalidUTF7
return return
} }
} }
if i == len(src) { // Implicit shift ("&...") if i == len(src) { // Implicit shift ("&...")
if atEOF { if atEOF {
err = ErrBadUtf7 err = ErrInvalidUTF7
} else { } else {
err = transform.ErrShortSrc err = transform.ErrShortSrc
} }
@@ -67,7 +63,7 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
d.ascii = true d.ascii = true
} else { // Control or non-ASCII code points in base64 } else { // Control or non-ASCII code points in base64
if !d.ascii { // Null shift ("&...-&...-") if !d.ascii { // Null shift ("&...-&...-")
err = ErrBadUtf7 err = ErrInvalidUTF7
return return
} }
@@ -76,7 +72,7 @@ func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err er
} }
if len(b) == 0 { // Bad encoding if len(b) == 0 { // Bad encoding
err = ErrBadUtf7 err = ErrInvalidUTF7
return return
} }
@@ -116,16 +112,16 @@ func decode(b64 []byte) []byte {
if n := len(b64); b64[n-1] == '=' { if n := len(b64); b64[n-1] == '=' {
return nil return nil
} else if n&3 == 0 { } else if n&3 == 0 {
b = make([]byte, enc.DecodedLen(n)*3) b = make([]byte, b64Enc.DecodedLen(n)*3)
} else { } else {
n += 4 - n&3 n += 4 - n&3
b = make([]byte, n+enc.DecodedLen(n)*3) b = make([]byte, n+b64Enc.DecodedLen(n)*3)
copy(b[copy(b, b64):n], []byte("==")) copy(b[copy(b, b64):n], []byte("=="))
b64, b = b[:n], b[n:] b64, b = b[:n], b[n:]
} }
// Decode Base64 into the first 1/3rd of b // Decode Base64 into the first 1/3rd of b
n, err := enc.Decode(b, b64) n, err := b64Enc.Decode(b, b64)
if err != nil || n&1 == 1 { if err != nil || n&1 == 1 {
return nil return nil
} }

View File

@@ -4,14 +4,9 @@ import (
"unicode/utf16" "unicode/utf16"
"unicode/utf8" "unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
var Encoder = &encoding.Encoder{
Transformer: &encoder{},
}
type encoder struct{} type encoder struct{}
func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
@@ -81,9 +76,9 @@ func encode(s []byte) []byte {
} }
// Encode as base64 // Encode as base64
n := enc.EncodedLen(len(b)) + 2 n := b64Enc.EncodedLen(len(b)) + 2
b64 := make([]byte, n) b64 := make([]byte, n)
enc.Encode(b64[1:], b) b64Enc.Encode(b64[1:], b)
// Strip padding // Strip padding
n -= 2 - (len(b)+2)%3 n -= 2 - (len(b)+2)%3

View File

@@ -3,6 +3,8 @@ package utf7
import ( import (
"encoding/base64" "encoding/base64"
"golang.org/x/text/encoding"
) )
const ( const (
@@ -12,4 +14,21 @@ const (
repl = '\uFFFD' // Unicode replacement code point repl = '\uFFFD' // Unicode replacement code point
) )
var enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,") var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
type enc struct{}
func (e enc) NewDecoder() *encoding.Decoder {
return &encoding.Decoder{
Transformer: &decoder{true},
}
}
func (e enc) NewEncoder() *encoding.Encoder {
return &encoding.Encoder{
Transformer: &encoder{},
}
}
// Encoding is the modified UTF-7 encoding.
var Encoding encoding.Encoding = enc{}

View File

@@ -14,8 +14,12 @@ type flusher interface {
Flush() error Flush() error
} }
// A string that will be quoted. type (
type Quoted string // A string that will be quoted.
Quoted string
// A raw atom.
Atom string
)
type WriterTo interface { type WriterTo interface {
WriteTo(w *Writer) error WriteTo(w *Writer) error
@@ -83,8 +87,7 @@ func (w *Writer) writeAstring(s string) error {
return w.writeLiteral(bytes.NewBufferString(s)) return w.writeLiteral(bytes.NewBufferString(s))
} }
specials := string([]rune{dquote, listStart, listEnd, literalStart, sp}) if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, atomSpecials) {
if strings.ToUpper(s) == nilAtom || s == "" || strings.ContainsAny(s, specials) {
return w.writeQuoted(s) return w.writeQuoted(s)
} }
@@ -162,6 +165,8 @@ func (w *Writer) writeField(field interface{}) error {
return w.writeAstring(field) return w.writeAstring(field)
case Quoted: case Quoted:
return w.writeQuoted(string(field)) return w.writeQuoted(string(field))
case Atom:
return w.writeAtom(string(field))
case int: case int:
return w.writeNumber(uint32(field)) return w.writeNumber(uint32(field))
case uint32: case uint32:
@@ -184,18 +189,18 @@ func (w *Writer) writeField(field interface{}) error {
return w.writeString(field.String()) return w.writeString(field.String())
case *BodySectionName: case *BodySectionName:
// Can contain spaces - that's why we don't just pass it as a string // Can contain spaces - that's why we don't just pass it as a string
return w.writeString(field.String()) return w.writeString(string(field.FetchItem()))
} }
return fmt.Errorf("imap: cannot format field: %v", field) return fmt.Errorf("imap: cannot format field: %v", field)
} }
func (w *Writer) writeRespCode(code string, args []interface{}) error { func (w *Writer) writeRespCode(code StatusRespCode, args []interface{}) error {
if err := w.writeString(string(respCodeStart)); err != nil { if err := w.writeString(string(respCodeStart)); err != nil {
return err return err
} }
fields := []interface{}{code} fields := []interface{}{string(code)}
fields = append(fields, args...) fields = append(fields, args...)
if err := w.writeFields(fields); err != nil { if err := w.writeFields(fields); err != nil {

24
vendor/github.com/emersion/go-message/.gitignore generated vendored Normal file
View File

@@ -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

5
vendor/github.com/emersion/go-message/.travis.yml generated vendored Normal file
View File

@@ -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)

21
vendor/github.com/emersion/go-message/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
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.

22
vendor/github.com/emersion/go-message/README.md generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# go-message
[![GoDoc](https://godoc.org/github.com/emersion/go-message?status.svg)](https://godoc.org/github.com/emersion/go-message)
[![Build Status](https://travis-ci.org/emersion/go-message.svg?branch=master)](https://travis-ci.org/emersion/go-message)
[![codecov](https://codecov.io/gh/emersion/go-message/branch/master/graph/badge.svg)](https://codecov.io/gh/emersion/go-message)
[![Go Report Card](https://goreportcard.com/badge/github.com/emersion/go-message)](https://goreportcard.com/report/github.com/emersion/go-message)
[![Unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)
A Go library for the Internet Message Format. It implements:
* [RFC 5322](https://tools.ietf.org/html/rfc5322): Internet Message Format
* [RFC 2045](https://tools.ietf.org/html/rfc2045), [RFC 2046](https://tools.ietf.org/html/rfc2046) and [RFC 2047](https://tools.ietf.org/html/rfc2047): Multipurpose Internet Mail Extensions
* [RFC 2183](https://tools.ietf.org/html/rfc2183): Content-Disposition Header Field
## Features
* Streaming API
* Automatic encoding and charset handling
* A [`mail`](https://godoc.org/github.com/emersion/go-message/mail) subpackage to read and write mail messages
## License
MIT

View File

@@ -0,0 +1,57 @@
// Package charset provides functions to decode and encode charsets.
package charset
import (
"fmt"
"io"
"strings"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/encoding/traditionalchinese"
)
var charsets = map[string]encoding.Encoding{
"big5": traditionalchinese.Big5,
"euc-jp": japanese.EUCJP,
"gbk": simplifiedchinese.GBK,
"gb2312": simplifiedchinese.GBK, // as GBK is a superset of HZGB2312, so just use GBK
"gb18030": simplifiedchinese.GB18030, // GB18030 Use for parse QQ business mail message
"iso-2022-jp": japanese.ISO2022JP,
"iso-8859-1": charmap.ISO8859_1,
"iso-8859-2": charmap.ISO8859_2,
"iso-8859-3": charmap.ISO8859_3,
"iso-8859-4": charmap.ISO8859_4,
"iso-8859-9": charmap.ISO8859_9,
"iso-8859-10": charmap.ISO8859_10,
"iso-8859-13": charmap.ISO8859_13,
"iso-8859-14": charmap.ISO8859_14,
"iso-8859-15": charmap.ISO8859_15,
"iso-8859-16": charmap.ISO8859_16,
"koi8-r": charmap.KOI8R,
"shift_jis": japanese.ShiftJIS,
"windows-1250": charmap.Windows1250,
"windows-1251": charmap.Windows1251,
"windows-1252": charmap.Windows1252,
}
// Reader returns an io.Reader that converts the provided charset to UTF-8.
func Reader(charset string, input io.Reader) (io.Reader, error) {
charset = strings.ToLower(charset)
// "ascii" is not in the spec but is common
if charset == "utf-8" || charset == "us-ascii" || charset == "ascii" {
return input, nil
}
if enc, ok := charsets[charset]; ok {
return enc.NewDecoder().Reader(input), nil
}
return nil, fmt.Errorf("unhandled charset %q", charset)
}
// RegisterEncoding registers an encoding. This is intended to be called from
// the init function in packages that want to support additional charsets.
func RegisterEncoding(name string, enc encoding.Encoding) {
charsets[name] = enc
}

View File

@@ -0,0 +1,22 @@
package charset
import (
"mime"
)
var wordDecoder = &mime.WordDecoder{CharsetReader: Reader}
// DecodeHeader decodes an internationalized header field. If it fails, it
// returns the input string and the error.
func DecodeHeader(s string) (string, error) {
dec, err := wordDecoder.DecodeHeader(s)
if err != nil {
return s, err
}
return dec, nil
}
// EncodeHeader encodes an internationalized header field.
func EncodeHeader(s string) string {
return mime.QEncoding.Encode("utf-8", s)
}

47
vendor/github.com/emersion/go-message/encoding.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
package message
import (
"encoding/base64"
"fmt"
"io"
"mime/quotedprintable"
"strings"
"github.com/emersion/go-textwrapper"
)
func encodingReader(enc string, r io.Reader) (io.Reader, error) {
var dec io.Reader
switch strings.ToLower(enc) {
case "quoted-printable":
dec = quotedprintable.NewReader(r)
case "base64":
dec = base64.NewDecoder(base64.StdEncoding, r)
case "7bit", "8bit", "binary", "":
dec = r
default:
return nil, fmt.Errorf("unhandled encoding %q", enc)
}
return dec, nil
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
func encodingWriter(enc string, w io.Writer) io.WriteCloser {
var wc io.WriteCloser
switch strings.ToLower(enc) {
case "quoted-printable":
wc = quotedprintable.NewWriter(w)
case "base64":
wc = base64.NewEncoder(base64.StdEncoding, textwrapper.NewRFC822(w))
case "7bit", "8bit":
wc = nopCloser{textwrapper.New(w, "\r\n", 1000)}
default: // "binary"
wc = nopCloser{w}
}
return wc
}

132
vendor/github.com/emersion/go-message/entity.go generated vendored Normal file
View File

@@ -0,0 +1,132 @@
package message
import (
"bufio"
"io"
"mime/multipart"
"net/textproto"
"strings"
"github.com/emersion/go-message/charset"
)
type unknownEncodingError struct {
error
}
// IsUnknownEncoding returns a boolean indicating whether the error is known to
// report that the transfer encoding or the charset advertised by the entity is
// unknown.
func IsUnknownEncoding(err error) bool {
_, ok := err.(unknownEncodingError)
return ok
}
// An Entity is either a whole message or a one of the parts in the body of a
// multipart entity.
type Entity struct {
Header Header // The entity's header.
Body io.Reader // The decoded entity's body.
mediaType string
mediaParams map[string]string
}
// New makes a new message with the provided header and body. The entity's
// transfer encoding and charset are automatically decoded to UTF-8.
//
// If the message uses an unknown transfer encoding or charset, New returns an
// error that verifies IsUnknownEncoding, but also returns an Entity that can
// be read.
func New(header Header, body io.Reader) (*Entity, error) {
var err error
enc := header.Get("Content-Transfer-Encoding")
if decoded, encErr := encodingReader(enc, body); encErr != nil {
err = unknownEncodingError{encErr}
} else {
body = decoded
}
mediaType, mediaParams, _ := header.ContentType()
if ch, ok := mediaParams["charset"]; ok {
if converted, charsetErr := charset.Reader(ch, body); charsetErr != nil {
err = unknownEncodingError{charsetErr}
} else {
body = converted
}
}
return &Entity{
Header: header,
Body: body,
mediaType: mediaType,
mediaParams: mediaParams,
}, err
}
// NewMultipart makes a new multipart message with the provided header and
// parts. The Content-Type header must begin with "multipart/".
//
// If the message uses an unknown transfer encoding, NewMultipart returns an
// error that verifies IsUnknownEncoding, but also returns an Entity that can
// be read.
func NewMultipart(header Header, parts []*Entity) (*Entity, error) {
r := &multipartBody{
header: header,
parts: parts,
}
return New(header, r)
}
// Read reads a message from r. The message's encoding and charset are
// automatically decoded to UTF-8. Note that this function only reads the
// message header.
//
// If the message uses an unknown transfer encoding or charset, Read returns an
// error that verifies IsUnknownEncoding, but also returns an Entity that can
// be read.
func Read(r io.Reader) (*Entity, error) {
br := bufio.NewReader(r)
h, err := textproto.NewReader(br).ReadMIMEHeader()
if err != nil {
return nil, err
}
return New(Header(h), br)
}
// MultipartReader returns a MultipartReader that reads parts from this entity's
// body. If this entity is not multipart, it returns nil.
func (e *Entity) MultipartReader() MultipartReader {
if !strings.HasPrefix(e.mediaType, "multipart/") {
return nil
}
if mb, ok := e.Body.(*multipartBody); ok {
return mb
}
return &multipartReader{multipart.NewReader(e.Body, e.mediaParams["boundary"])}
}
// writeBodyTo writes this entity's body to w (without the header).
func (e *Entity) writeBodyTo(w *Writer) error {
var err error
if mb, ok := e.Body.(*multipartBody); ok {
err = mb.writeBodyTo(w)
} else {
_, err = io.Copy(w, e.Body)
}
return err
}
// WriteTo writes this entity's header and body to w.
func (e *Entity) WriteTo(w io.Writer) error {
ew, err := CreateWriter(w, e.Header)
if err != nil {
return err
}
defer ew.Close()
return e.writeBodyTo(ew)
}

154
vendor/github.com/emersion/go-message/header.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
package message
import (
"mime"
"net/textproto"
"strings"
"github.com/emersion/go-message/charset"
)
const maxHeaderLen = 76
func parseHeaderWithParams(s string) (f string, params map[string]string, err error) {
f, params, err = mime.ParseMediaType(s)
if err != nil {
return s, nil, err
}
for k, v := range params {
params[k], _ = charset.DecodeHeader(v)
}
return
}
func formatHeaderWithParams(f string, params map[string]string) string {
encParams := make(map[string]string)
for k, v := range params {
encParams[k] = charset.EncodeHeader(v)
}
return mime.FormatMediaType(f, encParams)
}
// formatHeaderField formats a header field, ensuring each line is no longer
// than 76 characters. It tries to fold lines at whitespace characters if
// possible. If the header contains a word longer than this limit, it will be
// split.
func formatHeaderField(k, v string) string {
s := k + ": "
if v == "" {
return s + "\r\n"
}
first := true
for len(v) > 0 {
maxlen := maxHeaderLen
if first {
maxlen -= len(s)
}
// We'll need to fold before i
foldBefore := maxlen + 1
foldAt := len(v)
var folding string
if foldBefore > len(v) {
// We reached the end of the string
if v[len(v)-1] != '\n' {
// If there isn't already a trailing CRLF, insert one
folding = "\r\n"
}
} else {
// Find the closest whitespace before i
foldAt = strings.LastIndexAny(v[:foldBefore], " \t\n")
if foldAt == 0 {
// The whitespace we found was the previous folding WSP
foldAt = foldBefore - 1
} else if foldAt < 0 {
// We didn't find any whitespace, we have to insert one
foldAt = foldBefore - 2
}
switch v[foldAt] {
case ' ', '\t':
if v[foldAt-1] != '\n' {
folding = "\r\n" // The next char will be a WSP, don't need to insert one
}
case '\n':
folding = "" // There is already a CRLF, nothing to do
default:
folding = "\r\n " // Another char, we need to insert CRLF + WSP
}
}
s += v[:foldAt] + folding
v = v[foldAt:]
first = false
}
return s
}
// A Header represents the key-value pairs in a message header.
type Header map[string][]string
// Add adds the key, value pair to the header. It appends to any existing values
// associated with key.
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
// Set sets the header entries associated with key to the single element value.
// It replaces any existing values associated with key.
func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value)
}
// Get gets the first value associated with the given key. If there are no
// values associated with the key, Get returns "".
func (h Header) Get(key string) string {
return textproto.MIMEHeader(h).Get(key)
}
// Del deletes the values associated with key.
func (h Header) Del(key string) {
textproto.MIMEHeader(h).Del(key)
}
// ContentType parses the Content-Type header field.
//
// If no Content-Type is specified, it returns "text/plain".
func (h Header) ContentType() (t string, params map[string]string, err error) {
v := h.Get("Content-Type")
if v == "" {
return "text/plain", nil, nil
}
return parseHeaderWithParams(v)
}
// SetContentType formats the Content-Type header field.
func (h Header) SetContentType(t string, params map[string]string) {
h.Set("Content-Type", formatHeaderWithParams(t, params))
}
// ContentDescription parses the Content-Description header field.
func (h Header) ContentDescription() (string, error) {
return charset.DecodeHeader(h.Get("Content-Description"))
}
// SetContentDescription parses the Content-Description header field.
func (h Header) SetContentDescription(desc string) {
h.Set("Content-Description", charset.EncodeHeader(desc))
}
// ContentDisposition parses the Content-Disposition header field, as defined in
// RFC 2183.
func (h Header) ContentDisposition() (disp string, params map[string]string, err error) {
return parseHeaderWithParams(h.Get("Content-Disposition"))
}
// SetContentDisposition formats the Content-Disposition header field, as
// defined in RFC 2183.
func (h Header) SetContentDisposition(disp string, params map[string]string) {
h.Set("Content-Disposition", formatHeaderWithParams(disp, params))
}

37
vendor/github.com/emersion/go-message/mail/address.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
package mail
import (
"net/mail"
"strings"
)
// Address represents a single mail address.
type Address mail.Address
// String formats the address as a valid RFC 5322 address. If the address's name
// contains non-ASCII characters the name will be rendered according to
// RFC 2047.
func (a *Address) String() string {
return ((*mail.Address)(a)).String()
}
func parseAddressList(s string) ([]*Address, error) {
list, err := mail.ParseAddressList(s)
if err != nil {
return nil, err
}
addrs := make([]*Address, len(list))
for i, a := range list {
addrs[i] = (*Address)(a)
}
return addrs, nil
}
func formatAddressList(l []*Address) string {
formatted := make([]string, len(l))
for i, a := range l {
formatted[i] = a.String()
}
return strings.Join(formatted, ", ")
}

View File

@@ -0,0 +1,38 @@
package mail
import (
"github.com/emersion/go-message"
)
// An AttachmentHeader represents an attachment's header.
type AttachmentHeader struct {
message.Header
}
// NewAttachmentHeader creates a new AttachmentHeader.
func NewAttachmentHeader() AttachmentHeader {
h := AttachmentHeader{make(message.Header)}
h.Set("Content-Disposition", "attachment")
h.Set("Content-Transfer-Encoding", "base64")
return h
}
// Filename parses the attachment's filename.
func (h AttachmentHeader) Filename() (string, error) {
_, params, err := h.ContentDisposition()
filename, ok := params["filename"]
if !ok {
// Using "name" in Content-Type is discouraged
_, params, err = h.ContentType()
filename = params["name"]
}
return filename, err
}
// SetFilename formats the attachment's filename.
func (h AttachmentHeader) SetFilename(filename string) {
dispParams := map[string]string{"filename": filename}
h.SetContentDisposition("attachment", dispParams)
}

57
vendor/github.com/emersion/go-message/mail/header.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package mail
import (
"net/mail"
"time"
"github.com/emersion/go-message"
"github.com/emersion/go-message/charset"
)
const dateLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
// A Header is a mail header.
type Header struct {
message.Header
}
// NewHeader creates a new mail header.
func NewHeader() Header {
return Header{make(message.Header)}
}
// AddressList parses the named header field as a list of addresses. If the
// header is missing, it returns nil.
func (h Header) AddressList(key string) ([]*Address, error) {
v := h.Get(key)
if v == "" {
return nil, nil
}
return parseAddressList(v)
}
// SetAddressList formats the named header to the provided list of addresses.
func (h Header) SetAddressList(key string, addrs []*Address) {
h.Set(key, formatAddressList(addrs))
}
// Date parses the Date header field.
func (h Header) Date() (time.Time, error) {
return mail.Header(h.Header).Date()
}
// SetDate formats the Date header field.
func (h Header) SetDate(t time.Time) {
h.Set("Date", t.Format(dateLayout))
}
// Subject parses the Subject header field. If there is an error, the raw field
// value is returned alongside the error.
func (h Header) Subject() (string, error) {
return charset.DecodeHeader(h.Get("Subject"))
}
// SetSubject formats the Subject header field.
func (h Header) SetSubject(s string) {
h.Set("Subject", charset.EncodeHeader(s))
}

9
vendor/github.com/emersion/go-message/mail/mail.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// Package mail implements reading and writing mail messages.
//
// This package assumes that a mail message contains one or more text parts and
// zero or more attachment parts. Each text part represents a different version
// of the message content (e.g. a different type, a different language and so
// on).
//
// RFC 5322 defines the Internet Message Format.
package mail

130
vendor/github.com/emersion/go-message/mail/reader.go generated vendored Normal file
View File

@@ -0,0 +1,130 @@
package mail
import (
"container/list"
"io"
"strings"
"github.com/emersion/go-message"
)
// A PartHeader is a mail part header. It contains convenience functions to get
// and set header fields.
type PartHeader interface {
// Add adds the key, value pair to the header.
Add(key, value string)
// Del deletes the values associated with key.
Del(key string)
// Get gets the first value associated with the given key. If there are no
// values associated with the key, Get returns "".
Get(key string) string
// Set sets the header entries associated with key to the single element
// value. It replaces any existing values associated with key.
Set(key, value string)
}
// A Part is either a mail text or an attachment. Header is either a TextHeader
// or an AttachmentHeader.
type Part struct {
Header PartHeader
Body io.Reader
}
// A Reader reads a mail message.
type Reader struct {
Header Header
e *message.Entity
readers *list.List
}
// NewReader creates a new mail reader.
func NewReader(e *message.Entity) *Reader {
mr := e.MultipartReader()
if mr == nil {
// Artificially create a multipart entity
// With this header, no error will be returned by message.NewMultipart
h := make(message.Header)
h.Set("Content-Type", "multipart/mixed")
me, _ := message.NewMultipart(h, []*message.Entity{e})
mr = me.MultipartReader()
}
l := list.New()
l.PushBack(mr)
return &Reader{Header{e.Header}, e, l}
}
// CreateReader reads a mail header from r and returns a new mail reader.
//
// If the message uses an unknown transfer encoding or charset, CreateReader
// returns an error that verifies message.IsUnknownEncoding, but also returns a
// Reader that can be used.
func CreateReader(r io.Reader) (*Reader, error) {
e, err := message.Read(r)
if err != nil && !message.IsUnknownEncoding(err) {
return nil, err
}
return NewReader(e), err
}
// NextPart returns the next mail part. If there is no more part, io.EOF is
// returned as error.
//
// The returned Part.Body must be read completely before the next call to
// NextPart, otherwise it will be discarded.
//
// If the part uses an unknown transfer encoding or charset, NextPart returns an
// error that verifies message.IsUnknownEncoding, but also returns a Part that
// can be used.
func (r *Reader) NextPart() (*Part, error) {
for r.readers.Len() > 0 {
e := r.readers.Back()
mr := e.Value.(message.MultipartReader)
p, err := mr.NextPart()
if err == io.EOF {
// This whole multipart entity has been read, continue with the next one
r.readers.Remove(e)
continue
} else if err != nil && !message.IsUnknownEncoding(err) {
return nil, err
}
if pmr := p.MultipartReader(); pmr != nil {
// This is a multipart part, read it
r.readers.PushBack(pmr)
} else {
// This is a non-multipart part, return a mail part
mp := &Part{Body: p.Body}
t, _, _ := p.Header.ContentType()
disp, _, _ := p.Header.ContentDisposition()
if strings.HasPrefix(t, "text/") && disp != "attachment" {
mp.Header = TextHeader{p.Header}
} else {
mp.Header = AttachmentHeader{p.Header}
}
return mp, err
}
}
return nil, io.EOF
}
// Close finishes the reader.
func (r *Reader) Close() error {
for r.readers.Len() > 0 {
e := r.readers.Back()
mr := e.Value.(message.MultipartReader)
if err := mr.Close(); err != nil {
return err
}
r.readers.Remove(e)
}
return nil
}

18
vendor/github.com/emersion/go-message/mail/text.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package mail
import (
"github.com/emersion/go-message"
)
// A TextHeader represents a message text header.
type TextHeader struct {
message.Header
}
// NewTextHeader creates a new message text header.
func NewTextHeader() TextHeader {
h := TextHeader{make(message.Header)}
h.Set("Content-Disposition", "inline")
h.Set("Content-Transfer-Encoding", "quoted-printable")
return h
}

73
vendor/github.com/emersion/go-message/mail/writer.go generated vendored Normal file
View File

@@ -0,0 +1,73 @@
package mail
import (
"io"
"github.com/emersion/go-message"
)
// A Writer writes a mail message. A mail message contains one or more text
// parts and zero or more attachments.
type Writer struct {
mw *message.Writer
}
// CreateWriter writes a mail header to w and creates a new Writer.
func CreateWriter(w io.Writer, header Header) (*Writer, error) {
header.Set("Content-Type", "multipart/mixed")
mw, err := message.CreateWriter(w, header.Header)
if err != nil {
return nil, err
}
return &Writer{mw}, nil
}
// CreateText creates a TextWriter. One or more parts representing the same text
// in different formats can be written to a TextWriter.
func (w *Writer) CreateText() (*TextWriter, error) {
h := make(message.Header)
h.Set("Content-Type", "multipart/alternative")
mw, err := w.mw.CreatePart(h)
if err != nil {
return nil, err
}
return &TextWriter{mw}, nil
}
// CreateSingleText creates a new single text part with the provided header. The
// body of the part should be written to the returned io.WriteCloser. Only one
// single text part should be written, use CreateText if you want multiple text
// parts.
func (w *Writer) CreateSingleText(header TextHeader) (io.WriteCloser, error) {
return w.mw.CreatePart(header.Header)
}
// CreateAttachment creates a new attachment with the provided header. The body
// of the part should be written to the returned io.WriteCloser.
func (w *Writer) CreateAttachment(header AttachmentHeader) (io.WriteCloser, error) {
return w.mw.CreatePart(header.Header)
}
// Close finishes the Writer.
func (w *Writer) Close() error {
return w.mw.Close()
}
// TextWriter writes a mail message's text.
type TextWriter struct {
mw *message.Writer
}
// CreatePart creates a new text part with the provided header. The body of the
// part should be written to the returned io.WriteCloser.
func (w *TextWriter) CreatePart(header TextHeader) (io.WriteCloser, error) {
return w.mw.CreatePart(header.Header)
}
// Close finishes the TextWriter.
func (w *TextWriter) Close() error {
return w.mw.Close()
}

5
vendor/github.com/emersion/go-message/message.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// Package message implements reading and writing multipurpose messages.
//
// RFC 2045, RFC 2046 and RFC 2047 defines MIME, and RFC 2183 defines the
// Content-Disposition header field.
package message

110
vendor/github.com/emersion/go-message/multipart.go generated vendored Normal file
View File

@@ -0,0 +1,110 @@
package message
import (
"io"
"mime/multipart"
)
// MultipartReader is an iterator over parts in a MIME multipart body.
type MultipartReader interface {
io.Closer
// NextPart returns the next part in the multipart or an error. When there are
// no more parts, the error io.EOF is returned.
//
// Entity.Body must be read completely before the next call to NextPart,
// otherwise it will be discarded.
NextPart() (*Entity, error)
}
type multipartReader struct {
r *multipart.Reader
}
// NextPart implements MultipartReader.
func (r *multipartReader) NextPart() (*Entity, error) {
p, err := r.r.NextPart()
if err != nil {
return nil, err
}
return New(Header(p.Header), p)
}
// Close implements io.Closer.
func (r *multipartReader) Close() error {
return nil
}
type multipartBody struct {
header Header
parts []*Entity
r *io.PipeReader
w *Writer
i int
}
// Read implements io.Reader.
func (m *multipartBody) Read(p []byte) (n int, err error) {
if m.r == nil {
r, w := io.Pipe()
m.r = r
m.w = newWriter(w, m.header)
// Prevent calls to NextPart to succeed
m.i = len(m.parts)
go func() {
if err := m.writeBodyTo(m.w); err != nil {
w.CloseWithError(err)
return
}
if err := m.w.Close(); err != nil {
w.CloseWithError(err)
return
}
w.Close()
}()
}
return m.r.Read(p)
}
// Close implements io.Closer.
func (m *multipartBody) Close() error {
if m.r != nil {
m.r.Close()
}
return nil
}
// NextPart implements MultipartReader.
func (m *multipartBody) NextPart() (*Entity, error) {
if m.i >= len(m.parts) {
return nil, io.EOF
}
part := m.parts[m.i]
m.i++
return part, nil
}
func (m *multipartBody) writeBodyTo(w *Writer) error {
for _, p := range m.parts {
pw, err := w.CreatePart(p.Header)
if err != nil {
return err
}
if err := p.writeBodyTo(pw); err != nil {
return err
}
if err := pw.Close(); err != nil {
return err
}
}
return nil
}

116
vendor/github.com/emersion/go-message/writer.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
package message
import (
"errors"
"io"
"mime/multipart"
"net/textproto"
"sort"
"strings"
)
// From https://golang.org/src/mime/multipart/writer.go?s=2140:2215#L76
func writeHeader(w io.Writer, header Header) error {
keys := make([]string, 0, len(header))
for k := range header {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
for _, v := range header[k] {
if _, err := io.WriteString(w, formatHeaderField(k, v)); err != nil {
return err
}
}
}
_, err := io.WriteString(w, "\r\n")
return err
}
// A Writer formats entities.
type Writer struct {
w io.Writer
c io.Closer
mw *multipart.Writer
}
// newWriter creates a new Writer writing to w with the provided header. Nothing
// is written to w when it is called. header is modified in-place.
func newWriter(w io.Writer, header Header) *Writer {
ww := &Writer{w: w}
mediaType, mediaParams, _ := header.ContentType()
if strings.HasPrefix(mediaType, "multipart/") {
ww.mw = multipart.NewWriter(ww.w)
// Do not set ww's io.Closer for now: if this is a multipart entity but
// CreatePart is not used (only Write is used), then the final boundary
// is expected to be written by the user too. In this case, ww.Close
// shouldn't write the final boundary.
if mediaParams["boundary"] != "" {
ww.mw.SetBoundary(mediaParams["boundary"])
} else {
mediaParams["boundary"] = ww.mw.Boundary()
header.SetContentType(mediaType, mediaParams)
}
header.Del("Content-Transfer-Encoding")
} else {
wc := encodingWriter(header.Get("Content-Transfer-Encoding"), ww.w)
ww.w = wc
ww.c = wc
}
return ww
}
// CreateWriter creates a new Writer writing to w. If header contains an
// encoding, data written to the Writer will automatically be encoded with it.
func CreateWriter(w io.Writer, header Header) (*Writer, error) {
ww := newWriter(w, header)
if err := writeHeader(w, header); err != nil {
return nil, err
}
return ww, nil
}
// Write implements io.Writer.
func (w *Writer) Write(b []byte) (int, error) {
return w.w.Write(b)
}
// Close implements io.Closer.
func (w *Writer) Close() error {
if w.c != nil {
return w.c.Close()
}
return nil
}
// CreatePart returns a Writer to a new part in this multipart entity. If this
// entity is not multipart, it fails. The body of the part should be written to
// the returned io.WriteCloser.
func (w *Writer) CreatePart(header Header) (*Writer, error) {
if w.mw == nil {
return nil, errors.New("cannot create a part in a non-multipart message")
}
if w.c == nil {
// We know that the user calls CreatePart so Close should write the final
// boundary
w.c = w.mw
}
// cw -> ww -> pw -> w.mw -> w.w
ww := &struct{ io.Writer }{nil}
cw := newWriter(ww, header)
pw, err := w.mw.CreatePart(textproto.MIMEHeader(header))
if err != nil {
return nil, err
}
ww.Writer = pw
return cw, nil
}

24
vendor/github.com/emersion/go-textwrapper/.gitignore generated vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
language: go

21
vendor/github.com/emersion/go-textwrapper/LICENSE generated vendored Normal file
View File

@@ -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.

27
vendor/github.com/emersion/go-textwrapper/README.md generated vendored Normal file
View File

@@ -0,0 +1,27 @@
# go-textwrapper
[![GoDoc](https://godoc.org/github.com/emersion/go-textwrapper?status.svg)](https://godoc.org/github.com/emersion/go-textwrapper)
[![Build Status](https://travis-ci.org/emersion/go-textwrapper.svg?branch=master)](https://travis-ci.org/emersion/go-textwrapper)
A writer that wraps long text lines to a specified length
## Usage
```go
import (
"os"
"github.com/emersion/go-textwrapper"
)
func main() {
w := textwrapper.New(os.Stdout, "/", 5)
w.Write([]byte("helloworldhelloworldhelloworld"))
// Output: hello/world/hello/world/hello/world
}
```
## License
MIT

61
vendor/github.com/emersion/go-textwrapper/wrapper.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// A writer that wraps long text lines to a specified length.
package textwrapper
import (
"io"
)
type writer struct {
Sep string
Len int
w io.Writer
i int
}
func (w *writer) Write(b []byte) (N int, err error) {
to := w.Len - w.i
for len(b) > to {
var n int
n, err = w.w.Write(b[:to])
if err != nil {
return
}
N += n
b = b[to:]
_, err = w.w.Write([]byte(w.Sep))
if err != nil {
return
}
w.i = 0
to = w.Len
}
w.i += len(b)
n, err := w.w.Write(b)
if err != nil {
return
}
N += n
return
}
// Returns a writer that splits its input into multiple parts that have the same
// length and adds a separator between these parts.
func New(w io.Writer, sep string, l int) io.Writer {
return &writer{
Sep: sep,
Len: l,
w: w,
}
}
// Creates a RFC822 text wrapper. It adds a CRLF (ie. \r\n) each 76 characters.
func NewRFC822(w io.Writer) io.Writer {
return New(w, "\r\n", 76)
}

View File

@@ -14,7 +14,11 @@ var fcntl64Syscall uintptr = SYS_FCNTL
// FcntlInt performs a fcntl syscall on fd with the provided command and argument. // FcntlInt performs a fcntl syscall on fd with the provided command and argument.
func FcntlInt(fd uintptr, cmd, arg int) (int, error) { func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
valptr, _, err := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(arg)) valptr, _, errno := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(arg))
var err error
if errno != 0 {
err = errno
}
return int(valptr), err return int(valptr), err
} }

View File

@@ -314,7 +314,11 @@ func UtimesNanoAt(dirfd int, path string, ts []Timespec, flags int) error {
// FcntlInt performs a fcntl syscall on fd with the provided command and argument. // FcntlInt performs a fcntl syscall on fd with the provided command and argument.
func FcntlInt(fd uintptr, cmd, arg int) (int, error) { func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
valptr, _, err := sysvicall6(uintptr(unsafe.Pointer(&procfcntl)), 3, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0) valptr, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procfcntl)), 3, uintptr(fd), uintptr(cmd), uintptr(arg), 0, 0, 0)
var err error
if errno != 0 {
err = errno
}
return int(valptr), err return int(valptr), err
} }

249
vendor/golang.org/x/text/encoding/charmap/charmap.go generated vendored Normal file
View File

@@ -0,0 +1,249 @@
// 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 maketables.go
// Package charmap provides simple character encodings such as IBM Code Page 437
// and Windows 1252.
package charmap // import "golang.org/x/text/encoding/charmap"
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/internal"
"golang.org/x/text/encoding/internal/identifier"
"golang.org/x/text/transform"
)
// These encodings vary only in the way clients should interpret them. Their
// coded character set is identical and a single implementation can be shared.
var (
// ISO8859_6E is the ISO 8859-6E encoding.
ISO8859_6E encoding.Encoding = &iso8859_6E
// ISO8859_6I is the ISO 8859-6I encoding.
ISO8859_6I encoding.Encoding = &iso8859_6I
// ISO8859_8E is the ISO 8859-8E encoding.
ISO8859_8E encoding.Encoding = &iso8859_8E
// ISO8859_8I is the ISO 8859-8I encoding.
ISO8859_8I encoding.Encoding = &iso8859_8I
iso8859_6E = internal.Encoding{
Encoding: ISO8859_6,
Name: "ISO-8859-6E",
MIB: identifier.ISO88596E,
}
iso8859_6I = internal.Encoding{
Encoding: ISO8859_6,
Name: "ISO-8859-6I",
MIB: identifier.ISO88596I,
}
iso8859_8E = internal.Encoding{
Encoding: ISO8859_8,
Name: "ISO-8859-8E",
MIB: identifier.ISO88598E,
}
iso8859_8I = internal.Encoding{
Encoding: ISO8859_8,
Name: "ISO-8859-8I",
MIB: identifier.ISO88598I,
}
)
// All is a list of all defined encodings in this package.
var All []encoding.Encoding = listAll
// TODO: implement these encodings, in order of importance.
// ASCII, ISO8859_1: Rather common. Close to Windows 1252.
// ISO8859_9: Close to Windows 1254.
// utf8Enc holds a rune's UTF-8 encoding in data[:len].
type utf8Enc struct {
len uint8
data [3]byte
}
// Charmap is an 8-bit character set encoding.
type Charmap struct {
// name is the encoding's name.
name string
// mib is the encoding type of this encoder.
mib identifier.MIB
// asciiSuperset states whether the encoding is a superset of ASCII.
asciiSuperset bool
// low is the lower bound of the encoded byte for a non-ASCII rune. If
// Charmap.asciiSuperset is true then this will be 0x80, otherwise 0x00.
low uint8
// replacement is the encoded replacement character.
replacement byte
// decode is the map from encoded byte to UTF-8.
decode [256]utf8Enc
// encoding is the map from runes to encoded bytes. Each entry is a
// uint32: the high 8 bits are the encoded byte and the low 24 bits are
// the rune. The table entries are sorted by ascending rune.
encode [256]uint32
}
// NewDecoder implements the encoding.Encoding interface.
func (m *Charmap) NewDecoder() *encoding.Decoder {
return &encoding.Decoder{Transformer: charmapDecoder{charmap: m}}
}
// NewEncoder implements the encoding.Encoding interface.
func (m *Charmap) NewEncoder() *encoding.Encoder {
return &encoding.Encoder{Transformer: charmapEncoder{charmap: m}}
}
// String returns the Charmap's name.
func (m *Charmap) String() string {
return m.name
}
// ID implements an internal interface.
func (m *Charmap) ID() (mib identifier.MIB, other string) {
return m.mib, ""
}
// charmapDecoder implements transform.Transformer by decoding to UTF-8.
type charmapDecoder struct {
transform.NopResetter
charmap *Charmap
}
func (m charmapDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
for i, c := range src {
if m.charmap.asciiSuperset && c < utf8.RuneSelf {
if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = c
nDst++
nSrc = i + 1
continue
}
decode := &m.charmap.decode[c]
n := int(decode.len)
if nDst+n > len(dst) {
err = transform.ErrShortDst
break
}
// It's 15% faster to avoid calling copy for these tiny slices.
for j := 0; j < n; j++ {
dst[nDst] = decode.data[j]
nDst++
}
nSrc = i + 1
}
return nDst, nSrc, err
}
// DecodeByte returns the Charmap's rune decoding of the byte b.
func (m *Charmap) DecodeByte(b byte) rune {
switch x := &m.decode[b]; x.len {
case 1:
return rune(x.data[0])
case 2:
return rune(x.data[0]&0x1f)<<6 | rune(x.data[1]&0x3f)
default:
return rune(x.data[0]&0x0f)<<12 | rune(x.data[1]&0x3f)<<6 | rune(x.data[2]&0x3f)
}
}
// charmapEncoder implements transform.Transformer by encoding from UTF-8.
type charmapEncoder struct {
transform.NopResetter
charmap *Charmap
}
func (m charmapEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
r, size := rune(0), 0
loop:
for nSrc < len(src) {
if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
r = rune(src[nSrc])
// Decode a 1-byte rune.
if r < utf8.RuneSelf {
if m.charmap.asciiSuperset {
nSrc++
dst[nDst] = uint8(r)
nDst++
continue
}
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
} else {
err = internal.RepertoireError(m.charmap.replacement)
}
break
}
}
// Binary search in [low, high) for that rune in the m.charmap.encode table.
for low, high := int(m.charmap.low), 0x100; ; {
if low >= high {
err = internal.RepertoireError(m.charmap.replacement)
break loop
}
mid := (low + high) / 2
got := m.charmap.encode[mid]
gotRune := rune(got & (1<<24 - 1))
if gotRune < r {
low = mid + 1
} else if gotRune > r {
high = mid
} else {
dst[nDst] = byte(got >> 24)
nDst++
break
}
}
nSrc += size
}
return nDst, nSrc, err
}
// EncodeRune returns the Charmap's byte encoding of the rune r. ok is whether
// r is in the Charmap's repertoire. If not, b is set to the Charmap's
// replacement byte. This is often the ASCII substitute character '\x1a'.
func (m *Charmap) EncodeRune(r rune) (b byte, ok bool) {
if r < utf8.RuneSelf && m.asciiSuperset {
return byte(r), true
}
for low, high := int(m.low), 0x100; ; {
if low >= high {
return m.replacement, false
}
mid := (low + high) / 2
got := m.encode[mid]
gotRune := rune(got & (1<<24 - 1))
if gotRune < r {
low = mid + 1
} else if gotRune > r {
high = mid
} else {
return byte(got >> 24), true
}
}
}

556
vendor/golang.org/x/text/encoding/charmap/maketables.go generated vendored Normal file
View File

@@ -0,0 +1,556 @@
// 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.
// +build ignore
package main
import (
"bufio"
"fmt"
"log"
"net/http"
"sort"
"strings"
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/internal/gen"
)
const ascii = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./0123456789:;<=>?` +
`@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` +
"`abcdefghijklmnopqrstuvwxyz{|}~\u007f"
var encodings = []struct {
name string
mib string
comment string
varName string
replacement byte
mapping string
}{
{
"IBM Code Page 037",
"IBM037",
"",
"CodePage037",
0x3f,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM037-2.1.2.ucm",
},
{
"IBM Code Page 437",
"PC8CodePage437",
"",
"CodePage437",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM437-2.1.2.ucm",
},
{
"IBM Code Page 850",
"PC850Multilingual",
"",
"CodePage850",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM850-2.1.2.ucm",
},
{
"IBM Code Page 852",
"PCp852",
"",
"CodePage852",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM852-2.1.2.ucm",
},
{
"IBM Code Page 855",
"IBM855",
"",
"CodePage855",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM855-2.1.2.ucm",
},
{
"Windows Code Page 858", // PC latin1 with Euro
"IBM00858",
"",
"CodePage858",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/windows-858-2000.ucm",
},
{
"IBM Code Page 860",
"IBM860",
"",
"CodePage860",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM860-2.1.2.ucm",
},
{
"IBM Code Page 862",
"PC862LatinHebrew",
"",
"CodePage862",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM862-2.1.2.ucm",
},
{
"IBM Code Page 863",
"IBM863",
"",
"CodePage863",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM863-2.1.2.ucm",
},
{
"IBM Code Page 865",
"IBM865",
"",
"CodePage865",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM865-2.1.2.ucm",
},
{
"IBM Code Page 866",
"IBM866",
"",
"CodePage866",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-ibm866.txt",
},
{
"IBM Code Page 1047",
"IBM1047",
"",
"CodePage1047",
0x3f,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM1047-2.1.2.ucm",
},
{
"IBM Code Page 1140",
"IBM01140",
"",
"CodePage1140",
0x3f,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/ibm-1140_P100-1997.ucm",
},
{
"ISO 8859-1",
"ISOLatin1",
"",
"ISO8859_1",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/iso-8859_1-1998.ucm",
},
{
"ISO 8859-2",
"ISOLatin2",
"",
"ISO8859_2",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-2.txt",
},
{
"ISO 8859-3",
"ISOLatin3",
"",
"ISO8859_3",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-3.txt",
},
{
"ISO 8859-4",
"ISOLatin4",
"",
"ISO8859_4",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-4.txt",
},
{
"ISO 8859-5",
"ISOLatinCyrillic",
"",
"ISO8859_5",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-5.txt",
},
{
"ISO 8859-6",
"ISOLatinArabic",
"",
"ISO8859_6,ISO8859_6E,ISO8859_6I",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-6.txt",
},
{
"ISO 8859-7",
"ISOLatinGreek",
"",
"ISO8859_7",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-7.txt",
},
{
"ISO 8859-8",
"ISOLatinHebrew",
"",
"ISO8859_8,ISO8859_8E,ISO8859_8I",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-8.txt",
},
{
"ISO 8859-9",
"ISOLatin5",
"",
"ISO8859_9",
encoding.ASCIISub,
"http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/iso-8859_9-1999.ucm",
},
{
"ISO 8859-10",
"ISOLatin6",
"",
"ISO8859_10",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-10.txt",
},
{
"ISO 8859-13",
"ISO885913",
"",
"ISO8859_13",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-13.txt",
},
{
"ISO 8859-14",
"ISO885914",
"",
"ISO8859_14",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-14.txt",
},
{
"ISO 8859-15",
"ISO885915",
"",
"ISO8859_15",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-15.txt",
},
{
"ISO 8859-16",
"ISO885916",
"",
"ISO8859_16",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-iso-8859-16.txt",
},
{
"KOI8-R",
"KOI8R",
"",
"KOI8R",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-koi8-r.txt",
},
{
"KOI8-U",
"KOI8U",
"",
"KOI8U",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-koi8-u.txt",
},
{
"Macintosh",
"Macintosh",
"",
"Macintosh",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-macintosh.txt",
},
{
"Macintosh Cyrillic",
"MacintoshCyrillic",
"",
"MacintoshCyrillic",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-x-mac-cyrillic.txt",
},
{
"Windows 874",
"Windows874",
"",
"Windows874",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-874.txt",
},
{
"Windows 1250",
"Windows1250",
"",
"Windows1250",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1250.txt",
},
{
"Windows 1251",
"Windows1251",
"",
"Windows1251",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1251.txt",
},
{
"Windows 1252",
"Windows1252",
"",
"Windows1252",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1252.txt",
},
{
"Windows 1253",
"Windows1253",
"",
"Windows1253",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1253.txt",
},
{
"Windows 1254",
"Windows1254",
"",
"Windows1254",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1254.txt",
},
{
"Windows 1255",
"Windows1255",
"",
"Windows1255",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1255.txt",
},
{
"Windows 1256",
"Windows1256",
"",
"Windows1256",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1256.txt",
},
{
"Windows 1257",
"Windows1257",
"",
"Windows1257",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1257.txt",
},
{
"Windows 1258",
"Windows1258",
"",
"Windows1258",
encoding.ASCIISub,
"http://encoding.spec.whatwg.org/index-windows-1258.txt",
},
{
"X-User-Defined",
"XUserDefined",
"It is defined at http://encoding.spec.whatwg.org/#x-user-defined",
"XUserDefined",
encoding.ASCIISub,
ascii +
"\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787" +
"\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f" +
"\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797" +
"\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f" +
"\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7" +
"\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af" +
"\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7" +
"\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf" +
"\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7" +
"\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf" +
"\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7" +
"\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df" +
"\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7" +
"\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef" +
"\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7" +
"\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff",
},
}
func getWHATWG(url string) string {
res, err := http.Get(url)
if err != nil {
log.Fatalf("%q: Get: %v", url, err)
}
defer res.Body.Close()
mapping := make([]rune, 128)
for i := range mapping {
mapping[i] = '\ufffd'
}
scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if s == "" || s[0] == '#' {
continue
}
x, y := 0, 0
if _, err := fmt.Sscanf(s, "%d\t0x%x", &x, &y); err != nil {
log.Fatalf("could not parse %q", s)
}
if x < 0 || 128 <= x {
log.Fatalf("code %d is out of range", x)
}
if 0x80 <= y && y < 0xa0 {
// We diverge from the WHATWG spec by mapping control characters
// in the range [0x80, 0xa0) to U+FFFD.
continue
}
mapping[x] = rune(y)
}
return ascii + string(mapping)
}
func getUCM(url string) string {
res, err := http.Get(url)
if err != nil {
log.Fatalf("%q: Get: %v", url, err)
}
defer res.Body.Close()
mapping := make([]rune, 256)
for i := range mapping {
mapping[i] = '\ufffd'
}
charsFound := 0
scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if s == "" || s[0] == '#' {
continue
}
var c byte
var r rune
if _, err := fmt.Sscanf(s, `<U%x> \x%x |0`, &r, &c); err != nil {
continue
}
mapping[c] = r
charsFound++
}
if charsFound < 200 {
log.Fatalf("%q: only %d characters found (wrong page format?)", url, charsFound)
}
return string(mapping)
}
func main() {
mibs := map[string]bool{}
all := []string{}
w := gen.NewCodeWriter()
defer w.WriteGoFile("tables.go", "charmap")
printf := func(s string, a ...interface{}) { fmt.Fprintf(w, s, a...) }
printf("import (\n")
printf("\t\"golang.org/x/text/encoding\"\n")
printf("\t\"golang.org/x/text/encoding/internal/identifier\"\n")
printf(")\n\n")
for _, e := range encodings {
varNames := strings.Split(e.varName, ",")
all = append(all, varNames...)
varName := varNames[0]
switch {
case strings.HasPrefix(e.mapping, "http://encoding.spec.whatwg.org/"):
e.mapping = getWHATWG(e.mapping)
case strings.HasPrefix(e.mapping, "http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/"):
e.mapping = getUCM(e.mapping)
}
asciiSuperset, low := strings.HasPrefix(e.mapping, ascii), 0x00
if asciiSuperset {
low = 0x80
}
lvn := 1
if strings.HasPrefix(varName, "ISO") || strings.HasPrefix(varName, "KOI") {
lvn = 3
}
lowerVarName := strings.ToLower(varName[:lvn]) + varName[lvn:]
printf("// %s is the %s encoding.\n", varName, e.name)
if e.comment != "" {
printf("//\n// %s\n", e.comment)
}
printf("var %s *Charmap = &%s\n\nvar %s = Charmap{\nname: %q,\n",
varName, lowerVarName, lowerVarName, e.name)
if mibs[e.mib] {
log.Fatalf("MIB type %q declared multiple times.", e.mib)
}
printf("mib: identifier.%s,\n", e.mib)
printf("asciiSuperset: %t,\n", asciiSuperset)
printf("low: 0x%02x,\n", low)
printf("replacement: 0x%02x,\n", e.replacement)
printf("decode: [256]utf8Enc{\n")
i, backMapping := 0, map[rune]byte{}
for _, c := range e.mapping {
if _, ok := backMapping[c]; !ok && c != utf8.RuneError {
backMapping[c] = byte(i)
}
var buf [8]byte
n := utf8.EncodeRune(buf[:], c)
if n > 3 {
panic(fmt.Sprintf("rune %q (%U) is too long", c, c))
}
printf("{%d,[3]byte{0x%02x,0x%02x,0x%02x}},", n, buf[0], buf[1], buf[2])
if i%2 == 1 {
printf("\n")
}
i++
}
printf("},\n")
printf("encode: [256]uint32{\n")
encode := make([]uint32, 0, 256)
for c, i := range backMapping {
encode = append(encode, uint32(i)<<24|uint32(c))
}
sort.Sort(byRune(encode))
for len(encode) < cap(encode) {
encode = append(encode, encode[len(encode)-1])
}
for i, enc := range encode {
printf("0x%08x,", enc)
if i%8 == 7 {
printf("\n")
}
}
printf("},\n}\n")
// Add an estimate of the size of a single Charmap{} struct value, which
// includes two 256 elem arrays of 4 bytes and some extra fields, which
// align to 3 uint64s on 64-bit architectures.
w.Size += 2*4*256 + 3*8
}
// TODO: add proper line breaking.
printf("var listAll = []encoding.Encoding{\n%s,\n}\n\n", strings.Join(all, ",\n"))
}
type byRune []uint32
func (b byRune) Len() int { return len(b) }
func (b byRune) Less(i, j int) bool { return b[i]&0xffffff < b[j]&0xffffff }
func (b byRune) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

7410
vendor/golang.org/x/text/encoding/charmap/tables.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

75
vendor/golang.org/x/text/encoding/internal/internal.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// 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 internal contains code that is shared among encoding implementations.
package internal
import (
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/internal/identifier"
"golang.org/x/text/transform"
)
// Encoding is an implementation of the Encoding interface that adds the String
// and ID methods to an existing encoding.
type Encoding struct {
encoding.Encoding
Name string
MIB identifier.MIB
}
// _ verifies that Encoding implements identifier.Interface.
var _ identifier.Interface = (*Encoding)(nil)
func (e *Encoding) String() string {
return e.Name
}
func (e *Encoding) ID() (mib identifier.MIB, other string) {
return e.MIB, ""
}
// SimpleEncoding is an Encoding that combines two Transformers.
type SimpleEncoding struct {
Decoder transform.Transformer
Encoder transform.Transformer
}
func (e *SimpleEncoding) NewDecoder() *encoding.Decoder {
return &encoding.Decoder{Transformer: e.Decoder}
}
func (e *SimpleEncoding) NewEncoder() *encoding.Encoder {
return &encoding.Encoder{Transformer: e.Encoder}
}
// FuncEncoding is an Encoding that combines two functions returning a new
// Transformer.
type FuncEncoding struct {
Decoder func() transform.Transformer
Encoder func() transform.Transformer
}
func (e FuncEncoding) NewDecoder() *encoding.Decoder {
return &encoding.Decoder{Transformer: e.Decoder()}
}
func (e FuncEncoding) NewEncoder() *encoding.Encoder {
return &encoding.Encoder{Transformer: e.Encoder()}
}
// A RepertoireError indicates a rune is not in the repertoire of a destination
// encoding. It is associated with an encoding-specific suggested replacement
// byte.
type RepertoireError byte
// Error implements the error interrface.
func (r RepertoireError) Error() string {
return "encoding: rune not supported by encoding."
}
// Replacement returns the replacement string associated with this error.
func (r RepertoireError) Replacement() byte { return byte(r) }
var ErrASCIIReplacement = RepertoireError(encoding.ASCIISub)

12
vendor/golang.org/x/text/encoding/japanese/all.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// 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 japanese
import (
"golang.org/x/text/encoding"
)
// All is a list of all defined encodings in this package.
var All = []encoding.Encoding{EUCJP, ISO2022JP, ShiftJIS}

225
vendor/golang.org/x/text/encoding/japanese/eucjp.go generated vendored Normal file
View File

@@ -0,0 +1,225 @@
// 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 japanese
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/internal"
"golang.org/x/text/encoding/internal/identifier"
"golang.org/x/text/transform"
)
// EUCJP is the EUC-JP encoding.
var EUCJP encoding.Encoding = &eucJP
var eucJP = internal.Encoding{
&internal.SimpleEncoding{eucJPDecoder{}, eucJPEncoder{}},
"EUC-JP",
identifier.EUCPkdFmtJapanese,
}
type eucJPDecoder struct{ transform.NopResetter }
// See https://encoding.spec.whatwg.org/#euc-jp-decoder.
func (eucJPDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
r, size := rune(0), 0
loop:
for ; nSrc < len(src); nSrc += size {
switch c0 := src[nSrc]; {
case c0 < utf8.RuneSelf:
r, size = rune(c0), 1
case c0 == 0x8e:
if nSrc+1 >= len(src) {
if !atEOF {
err = transform.ErrShortSrc
break loop
}
r, size = utf8.RuneError, 1
break
}
c1 := src[nSrc+1]
switch {
case c1 < 0xa1:
r, size = utf8.RuneError, 1
case c1 > 0xdf:
r, size = utf8.RuneError, 2
if c1 == 0xff {
size = 1
}
default:
r, size = rune(c1)+(0xff61-0xa1), 2
}
case c0 == 0x8f:
if nSrc+2 >= len(src) {
if !atEOF {
err = transform.ErrShortSrc
break loop
}
r, size = utf8.RuneError, 1
if p := nSrc + 1; p < len(src) && 0xa1 <= src[p] && src[p] < 0xfe {
size = 2
}
break
}
c1 := src[nSrc+1]
if c1 < 0xa1 || 0xfe < c1 {
r, size = utf8.RuneError, 1
break
}
c2 := src[nSrc+2]
if c2 < 0xa1 || 0xfe < c2 {
r, size = utf8.RuneError, 2
break
}
r, size = utf8.RuneError, 3
if i := int(c1-0xa1)*94 + int(c2-0xa1); i < len(jis0212Decode) {
r = rune(jis0212Decode[i])
if r == 0 {
r = utf8.RuneError
}
}
case 0xa1 <= c0 && c0 <= 0xfe:
if nSrc+1 >= len(src) {
if !atEOF {
err = transform.ErrShortSrc
break loop
}
r, size = utf8.RuneError, 1
break
}
c1 := src[nSrc+1]
if c1 < 0xa1 || 0xfe < c1 {
r, size = utf8.RuneError, 1
break
}
r, size = utf8.RuneError, 2
if i := int(c0-0xa1)*94 + int(c1-0xa1); i < len(jis0208Decode) {
r = rune(jis0208Decode[i])
if r == 0 {
r = utf8.RuneError
}
}
default:
r, size = utf8.RuneError, 1
}
if nDst+utf8.RuneLen(r) > len(dst) {
err = transform.ErrShortDst
break loop
}
nDst += utf8.EncodeRune(dst[nDst:], r)
}
return nDst, nSrc, err
}
type eucJPEncoder struct{ transform.NopResetter }
func (eucJPEncoder) 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
}
}
// func init checks that the switch covers all tables.
switch {
case encode0Low <= r && r < encode0High:
if r = rune(encode0[r-encode0Low]); r != 0 {
goto write2or3
}
case encode1Low <= r && r < encode1High:
if r = rune(encode1[r-encode1Low]); r != 0 {
goto write2or3
}
case encode2Low <= r && r < encode2High:
if r = rune(encode2[r-encode2Low]); r != 0 {
goto write2or3
}
case encode3Low <= r && r < encode3High:
if r = rune(encode3[r-encode3Low]); r != 0 {
goto write2or3
}
case encode4Low <= r && r < encode4High:
if r = rune(encode4[r-encode4Low]); r != 0 {
goto write2or3
}
case encode5Low <= r && r < encode5High:
if 0xff61 <= r && r < 0xffa0 {
goto write2
}
if r = rune(encode5[r-encode5Low]); r != 0 {
goto write2or3
}
}
err = internal.ErrASCIIReplacement
break
}
if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = uint8(r)
nDst++
continue
write2or3:
if r>>tableShift == jis0208 {
if nDst+2 > len(dst) {
err = transform.ErrShortDst
break
}
} else {
if nDst+3 > len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = 0x8f
nDst++
}
dst[nDst+0] = 0xa1 + uint8(r>>codeShift)&codeMask
dst[nDst+1] = 0xa1 + uint8(r)&codeMask
nDst += 2
continue
write2:
if nDst+2 > len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst+0] = 0x8e
dst[nDst+1] = uint8(r - (0xff61 - 0xa1))
nDst += 2
continue
}
return nDst, nSrc, err
}
func init() {
// Check that the hard-coded encode switch covers all tables.
if numEncodeTables != 6 {
panic("bad numEncodeTables")
}
}

299
vendor/golang.org/x/text/encoding/japanese/iso2022jp.go generated vendored Normal file
View File

@@ -0,0 +1,299 @@
// 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 japanese
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/internal"
"golang.org/x/text/encoding/internal/identifier"
"golang.org/x/text/transform"
)
// ISO2022JP is the ISO-2022-JP encoding.
var ISO2022JP encoding.Encoding = &iso2022JP
var iso2022JP = internal.Encoding{
internal.FuncEncoding{iso2022JPNewDecoder, iso2022JPNewEncoder},
"ISO-2022-JP",
identifier.ISO2022JP,
}
func iso2022JPNewDecoder() transform.Transformer {
return new(iso2022JPDecoder)
}
func iso2022JPNewEncoder() transform.Transformer {
return new(iso2022JPEncoder)
}
const (
asciiState = iota
katakanaState
jis0208State
jis0212State
)
const asciiEsc = 0x1b
type iso2022JPDecoder int
func (d *iso2022JPDecoder) Reset() {
*d = asciiState
}
func (d *iso2022JPDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
r, size := rune(0), 0
for ; nSrc < len(src); nSrc += size {
c0 := src[nSrc]
if c0 >= utf8.RuneSelf {
r, size = '\ufffd', 1
goto write
}
if c0 == asciiEsc {
if nSrc+2 >= len(src) {
if !atEOF {
return nDst, nSrc, transform.ErrShortSrc
}
// TODO: is it correct to only skip 1??
r, size = '\ufffd', 1
goto write
}
size = 3
c1 := src[nSrc+1]
c2 := src[nSrc+2]
switch {
case c1 == '$' && (c2 == '@' || c2 == 'B'): // 0x24 {0x40, 0x42}
*d = jis0208State
continue
case c1 == '$' && c2 == '(': // 0x24 0x28
if nSrc+3 >= len(src) {
if !atEOF {
return nDst, nSrc, transform.ErrShortSrc
}
r, size = '\ufffd', 1
goto write
}
size = 4
if src[nSrc+3] == 'D' {
*d = jis0212State
continue
}
case c1 == '(' && (c2 == 'B' || c2 == 'J'): // 0x28 {0x42, 0x4A}
*d = asciiState
continue
case c1 == '(' && c2 == 'I': // 0x28 0x49
*d = katakanaState
continue
}
r, size = '\ufffd', 1
goto write
}
switch *d {
case asciiState:
r, size = rune(c0), 1
case katakanaState:
if c0 < 0x21 || 0x60 <= c0 {
r, size = '\ufffd', 1
goto write
}
r, size = rune(c0)+(0xff61-0x21), 1
default:
if c0 == 0x0a {
*d = asciiState
r, size = rune(c0), 1
goto write
}
if nSrc+1 >= len(src) {
if !atEOF {
return nDst, nSrc, transform.ErrShortSrc
}
r, size = '\ufffd', 1
goto write
}
size = 2
c1 := src[nSrc+1]
i := int(c0-0x21)*94 + int(c1-0x21)
if *d == jis0208State && i < len(jis0208Decode) {
r = rune(jis0208Decode[i])
} else if *d == jis0212State && i < len(jis0212Decode) {
r = rune(jis0212Decode[i])
} else {
r = '\ufffd'
goto write
}
if r == 0 {
r = '\ufffd'
}
}
write:
if nDst+utf8.RuneLen(r) > len(dst) {
return nDst, nSrc, transform.ErrShortDst
}
nDst += utf8.EncodeRune(dst[nDst:], r)
}
return nDst, nSrc, err
}
type iso2022JPEncoder int
func (e *iso2022JPEncoder) Reset() {
*e = asciiState
}
func (e *iso2022JPEncoder) 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
}
}
// func init checks that the switch covers all tables.
//
// http://encoding.spec.whatwg.org/#iso-2022-jp says that "the index jis0212
// is not used by the iso-2022-jp encoder due to lack of widespread support".
//
// TODO: do we have to special-case U+00A5 and U+203E, as per
// http://encoding.spec.whatwg.org/#iso-2022-jp
// Doing so would mean that "\u00a5" would not be preserved
// after an encode-decode round trip.
switch {
case encode0Low <= r && r < encode0High:
if r = rune(encode0[r-encode0Low]); r>>tableShift == jis0208 {
goto writeJIS
}
case encode1Low <= r && r < encode1High:
if r = rune(encode1[r-encode1Low]); r>>tableShift == jis0208 {
goto writeJIS
}
case encode2Low <= r && r < encode2High:
if r = rune(encode2[r-encode2Low]); r>>tableShift == jis0208 {
goto writeJIS
}
case encode3Low <= r && r < encode3High:
if r = rune(encode3[r-encode3Low]); r>>tableShift == jis0208 {
goto writeJIS
}
case encode4Low <= r && r < encode4High:
if r = rune(encode4[r-encode4Low]); r>>tableShift == jis0208 {
goto writeJIS
}
case encode5Low <= r && r < encode5High:
if 0xff61 <= r && r < 0xffa0 {
goto writeKatakana
}
if r = rune(encode5[r-encode5Low]); r>>tableShift == jis0208 {
goto writeJIS
}
}
// Switch back to ASCII state in case of error so that an ASCII
// replacement character can be written in the correct state.
if *e != asciiState {
if nDst+3 > len(dst) {
err = transform.ErrShortDst
break
}
*e = asciiState
dst[nDst+0] = asciiEsc
dst[nDst+1] = '('
dst[nDst+2] = 'B'
nDst += 3
}
err = internal.ErrASCIIReplacement
break
}
if *e != asciiState {
if nDst+4 > len(dst) {
err = transform.ErrShortDst
break
}
*e = asciiState
dst[nDst+0] = asciiEsc
dst[nDst+1] = '('
dst[nDst+2] = 'B'
nDst += 3
} else if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = uint8(r)
nDst++
continue
writeJIS:
if *e != jis0208State {
if nDst+5 > len(dst) {
err = transform.ErrShortDst
break
}
*e = jis0208State
dst[nDst+0] = asciiEsc
dst[nDst+1] = '$'
dst[nDst+2] = 'B'
nDst += 3
} else if nDst+2 > len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst+0] = 0x21 + uint8(r>>codeShift)&codeMask
dst[nDst+1] = 0x21 + uint8(r)&codeMask
nDst += 2
continue
writeKatakana:
if *e != katakanaState {
if nDst+4 > len(dst) {
err = transform.ErrShortDst
break
}
*e = katakanaState
dst[nDst+0] = asciiEsc
dst[nDst+1] = '('
dst[nDst+2] = 'I'
nDst += 3
} else if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = uint8(r - (0xff61 - 0x21))
nDst++
continue
}
if atEOF && err == nil && *e != asciiState {
if nDst+3 > len(dst) {
err = transform.ErrShortDst
} else {
*e = asciiState
dst[nDst+0] = asciiEsc
dst[nDst+1] = '('
dst[nDst+2] = 'B'
nDst += 3
}
}
return nDst, nSrc, err
}

View File

@@ -0,0 +1,161 @@
// 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.
// +build ignore
package main
// This program generates tables.go:
// go run maketables.go | gofmt > tables.go
// TODO: Emoji extensions?
// http://www.unicode.org/faq/emoji_dingbats.html
// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
import (
"bufio"
"fmt"
"log"
"net/http"
"sort"
"strings"
)
type entry struct {
jisCode, table int
}
func main() {
fmt.Printf("// generated by go run maketables.go; DO NOT EDIT\n\n")
fmt.Printf("// Package japanese provides Japanese encodings such as EUC-JP and Shift JIS.\n")
fmt.Printf(`package japanese // import "golang.org/x/text/encoding/japanese"` + "\n\n")
reverse := [65536]entry{}
for i := range reverse {
reverse[i].table = -1
}
tables := []struct {
url string
name string
}{
{"http://encoding.spec.whatwg.org/index-jis0208.txt", "0208"},
{"http://encoding.spec.whatwg.org/index-jis0212.txt", "0212"},
}
for i, table := range tables {
res, err := http.Get(table.url)
if err != nil {
log.Fatalf("%q: Get: %v", table.url, err)
}
defer res.Body.Close()
mapping := [65536]uint16{}
scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if s == "" || s[0] == '#' {
continue
}
x, y := 0, uint16(0)
if _, err := fmt.Sscanf(s, "%d 0x%x", &x, &y); err != nil {
log.Fatalf("%q: could not parse %q", table.url, s)
}
if x < 0 || 120*94 <= x {
log.Fatalf("%q: JIS code %d is out of range", table.url, x)
}
mapping[x] = y
if reverse[y].table == -1 {
reverse[y] = entry{jisCode: x, table: i}
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("%q: scanner error: %v", table.url, err)
}
fmt.Printf("// jis%sDecode is the decoding table from JIS %s code to Unicode.\n// It is defined at %s\n",
table.name, table.name, table.url)
fmt.Printf("var jis%sDecode = [...]uint16{\n", table.name)
for i, m := range mapping {
if m != 0 {
fmt.Printf("\t%d: 0x%04X,\n", i, m)
}
}
fmt.Printf("}\n\n")
}
// Any run of at least separation continuous zero entries in the reverse map will
// be a separate encode table.
const separation = 1024
intervals := []interval(nil)
low, high := -1, -1
for i, v := range reverse {
if v.table == -1 {
continue
}
if low < 0 {
low = i
} else if i-high >= separation {
if high >= 0 {
intervals = append(intervals, interval{low, high})
}
low = i
}
high = i + 1
}
if high >= 0 {
intervals = append(intervals, interval{low, high})
}
sort.Sort(byDecreasingLength(intervals))
fmt.Printf("const (\n")
fmt.Printf("\tjis0208 = 1\n")
fmt.Printf("\tjis0212 = 2\n")
fmt.Printf("\tcodeMask = 0x7f\n")
fmt.Printf("\tcodeShift = 7\n")
fmt.Printf("\ttableShift = 14\n")
fmt.Printf(")\n\n")
fmt.Printf("const numEncodeTables = %d\n\n", len(intervals))
fmt.Printf("// encodeX are the encoding tables from Unicode to JIS code,\n")
fmt.Printf("// sorted by decreasing length.\n")
for i, v := range intervals {
fmt.Printf("// encode%d: %5d entries for runes in [%5d, %5d).\n", i, v.len(), v.low, v.high)
}
fmt.Printf("//\n")
fmt.Printf("// The high two bits of the value record whether the JIS code comes from the\n")
fmt.Printf("// JIS0208 table (high bits == 1) or the JIS0212 table (high bits == 2).\n")
fmt.Printf("// The low 14 bits are two 7-bit unsigned integers j1 and j2 that form the\n")
fmt.Printf("// JIS code (94*j1 + j2) within that table.\n")
fmt.Printf("\n")
for i, v := range intervals {
fmt.Printf("const encode%dLow, encode%dHigh = %d, %d\n\n", i, i, v.low, v.high)
fmt.Printf("var encode%d = [...]uint16{\n", i)
for j := v.low; j < v.high; j++ {
x := reverse[j]
if x.table == -1 {
continue
}
fmt.Printf("\t%d - %d: jis%s<<14 | 0x%02X<<7 | 0x%02X,\n",
j, v.low, tables[x.table].name, x.jisCode/94, x.jisCode%94)
}
fmt.Printf("}\n\n")
}
}
// interval is a half-open interval [low, high).
type interval struct {
low, high int
}
func (i interval) len() int { return i.high - i.low }
// byDecreasingLength sorts intervals by decreasing length.
type byDecreasingLength []interval
func (b byDecreasingLength) Len() int { return len(b) }
func (b byDecreasingLength) Less(i, j int) bool { return b[i].len() > b[j].len() }
func (b byDecreasingLength) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

189
vendor/golang.org/x/text/encoding/japanese/shiftjis.go generated vendored Normal file
View File

@@ -0,0 +1,189 @@
// 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 japanese
import (
"unicode/utf8"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/internal"
"golang.org/x/text/encoding/internal/identifier"
"golang.org/x/text/transform"
)
// ShiftJIS is the Shift JIS encoding, also known as Code Page 932 and
// Windows-31J.
var ShiftJIS encoding.Encoding = &shiftJIS
var shiftJIS = internal.Encoding{
&internal.SimpleEncoding{shiftJISDecoder{}, shiftJISEncoder{}},
"Shift JIS",
identifier.ShiftJIS,
}
type shiftJISDecoder struct{ transform.NopResetter }
func (shiftJISDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
r, size := rune(0), 0
loop:
for ; nSrc < len(src); nSrc += size {
switch c0 := src[nSrc]; {
case c0 < utf8.RuneSelf:
r, size = rune(c0), 1
case 0xa1 <= c0 && c0 < 0xe0:
r, size = rune(c0)+(0xff61-0xa1), 1
case (0x81 <= c0 && c0 < 0xa0) || (0xe0 <= c0 && c0 < 0xfd):
if c0 <= 0x9f {
c0 -= 0x70
} else {
c0 -= 0xb0
}
c0 = 2*c0 - 0x21
if nSrc+1 >= len(src) {
if !atEOF {
err = transform.ErrShortSrc
break loop
}
r, size = '\ufffd', 1
goto write
}
c1 := src[nSrc+1]
switch {
case c1 < 0x40:
r, size = '\ufffd', 1 // c1 is ASCII so output on next round
goto write
case c1 < 0x7f:
c0--
c1 -= 0x40
case c1 == 0x7f:
r, size = '\ufffd', 1 // c1 is ASCII so output on next round
goto write
case c1 < 0x9f:
c0--
c1 -= 0x41
case c1 < 0xfd:
c1 -= 0x9f
default:
r, size = '\ufffd', 2
goto write
}
r, size = '\ufffd', 2
if i := int(c0)*94 + int(c1); i < len(jis0208Decode) {
r = rune(jis0208Decode[i])
if r == 0 {
r = '\ufffd'
}
}
case c0 == 0x80:
r, size = 0x80, 1
default:
r, size = '\ufffd', 1
}
write:
if nDst+utf8.RuneLen(r) > len(dst) {
err = transform.ErrShortDst
break loop
}
nDst += utf8.EncodeRune(dst[nDst:], r)
}
return nDst, nSrc, err
}
type shiftJISEncoder struct{ transform.NopResetter }
func (shiftJISEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
r, size := rune(0), 0
loop:
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 loop
}
}
// func init checks that the switch covers all tables.
switch {
case encode0Low <= r && r < encode0High:
if r = rune(encode0[r-encode0Low]); r>>tableShift == jis0208 {
goto write2
}
case encode1Low <= r && r < encode1High:
if r = rune(encode1[r-encode1Low]); r>>tableShift == jis0208 {
goto write2
}
case encode2Low <= r && r < encode2High:
if r = rune(encode2[r-encode2Low]); r>>tableShift == jis0208 {
goto write2
}
case encode3Low <= r && r < encode3High:
if r = rune(encode3[r-encode3Low]); r>>tableShift == jis0208 {
goto write2
}
case encode4Low <= r && r < encode4High:
if r = rune(encode4[r-encode4Low]); r>>tableShift == jis0208 {
goto write2
}
case encode5Low <= r && r < encode5High:
if 0xff61 <= r && r < 0xffa0 {
r -= 0xff61 - 0xa1
goto write1
}
if r = rune(encode5[r-encode5Low]); r>>tableShift == jis0208 {
goto write2
}
}
err = internal.ErrASCIIReplacement
break
}
write1:
if nDst >= len(dst) {
err = transform.ErrShortDst
break
}
dst[nDst] = uint8(r)
nDst++
continue
write2:
j1 := uint8(r>>codeShift) & codeMask
j2 := uint8(r) & codeMask
if nDst+2 > len(dst) {
err = transform.ErrShortDst
break loop
}
if j1 <= 61 {
dst[nDst+0] = 129 + j1/2
} else {
dst[nDst+0] = 193 + j1/2
}
if j1&1 == 0 {
dst[nDst+1] = j2 + j2/63 + 64
} else {
dst[nDst+1] = j2 + 159
}
nDst += 2
continue
}
return nDst, nSrc, err
}

26971
vendor/golang.org/x/text/encoding/japanese/tables.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More