summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/rsc/imap/imap.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/rsc/imap/imap.go')
-rw-r--r--vendor/github.com/mattermost/rsc/imap/imap.go1110
1 files changed, 0 insertions, 1110 deletions
diff --git a/vendor/github.com/mattermost/rsc/imap/imap.go b/vendor/github.com/mattermost/rsc/imap/imap.go
deleted file mode 100644
index 6555984d2..000000000
--- a/vendor/github.com/mattermost/rsc/imap/imap.go
+++ /dev/null
@@ -1,1110 +0,0 @@
-package imap
-
-import (
- "bufio"
- "bytes"
- "crypto/md5"
- "crypto/tls"
- "fmt"
- "io"
- "log"
- "net"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "sync"
-)
-
-var Debug = false
-
-const tag = "#"
-
-// A Mode specifies the IMAP connection mode.
-type Mode int
-
-const (
- Unencrypted Mode = iota // unencrypted TCP connection
- StartTLS // use IMAP STARTTLS command - unimplemented!
- TLS // direct TLS connection
- Command // exec shell command (server name)
-)
-
-type lock struct {
- locked bool
- mu sync.Mutex
-}
-
-func (l *lock) lock() {
- l.mu.Lock()
- l.locked = true
-}
-
-func (l *lock) unlock() {
- l.mustBeLocked()
- l.locked = false
- l.mu.Unlock()
-}
-
-func (l *lock) mustBeLocked() {
- if !l.locked {
- panic("not locked")
- }
-}
-
-type Client struct {
- server string
- user string
- passwd string
- mode Mode
- root string
-
- io lock
- rw io.ReadWriteCloser // i/o to server
- b *bufio.Reader // buffered rw
- autoReconnect bool // reconnect on failure
- connected bool // rw is active
-
- data lock
- capability map[string]bool
- flags Flags
- boxByName map[string]*Box // all known boxes
- allBox []*Box // all known boxes (do we need this?)
- rootBox *Box // root of box tree
- inbox *Box // inbox (special, not in tree)
- box *Box // selected (current) box
- nextBox *Box // next box to select (do we need this?)
-}
-
-func NewClient(mode Mode, server, user, passwd string, root string) (*Client, error) {
- c := &Client{
- server: server,
- user: user,
- passwd: passwd,
- mode: mode,
- root: root,
- boxByName: map[string]*Box{},
- }
- c.io.lock()
- if err := c.reconnect(); err != nil {
- return nil, err
- }
- c.autoReconnect = true
- c.io.unlock()
-
- return c, nil
-}
-
-func (c *Client) Close() error {
- c.io.lock()
- c.autoReconnect = false
- c.connected = false
- if c.rw != nil {
- c.rw.Close()
- c.rw = nil
- }
- c.io.unlock()
- return nil
-}
-
-func (c *Client) reconnect() error {
- c.io.mustBeLocked()
- c.autoReconnect = false
- if c.rw != nil {
- c.rw.Close()
- c.rw = nil
- }
-
- if Debug {
- log.Printf("dial %s...", c.server)
- }
- rw, err := dial(c.server, c.mode)
- if err != nil {
- return err
- }
-
- c.rw = rw
- c.connected = true
- c.capability = nil
- c.box = nil
- if Debug {
- c.b = bufio.NewReader(&tee{rw, os.Stderr})
- } else {
- c.b = bufio.NewReader(rw)
- }
- x, err := c.rdsx()
- if x == nil {
- err = fmt.Errorf("no greeting from %s: %v", c.server, err)
- goto Error
- }
- if len(x.sx) < 2 || !x.sx[0].isAtom("*") || !x.sx[1].isAtom("PREAUTH") {
- if !x.ok() {
- err = fmt.Errorf("bad greeting - %s", x)
- goto Error
- }
- if err = c.login(); err != nil {
- goto Error
- }
- }
- if c.capability == nil {
- if err = c.cmd(nil, "CAPABILITY"); err != nil {
- goto Error
- }
- if c.capability == nil {
- err = fmt.Errorf("CAPABILITY command did not return capability list")
- goto Error
- }
- }
- if err := c.getBoxes(); err != nil {
- goto Error
- }
- c.autoReconnect = true
- return nil
-
-Error:
- if c.rw != nil {
- c.rw.Close()
- c.rw = nil
- }
- c.autoReconnect = true
- c.connected = false
- return err
-}
-
-var testDial func(string, Mode) (io.ReadWriteCloser, error)
-
-func dial(server string, mode Mode) (io.ReadWriteCloser, error) {
- if testDial != nil {
- return testDial(server, mode)
- }
- switch mode {
- default:
- // also case Unencrypted
- return net.Dial("tcp", server+":143")
- case StartTLS:
- return nil, fmt.Errorf("StartTLS not supported")
- case TLS:
- return tls.Dial("tcp", server+":993", nil)
- case Command:
- cmd := exec.Command("sh", "-c", server)
- cmd.Stderr = os.Stderr
- r, err := cmd.StdoutPipe()
- if err != nil {
- return nil, err
- }
- w, err := cmd.StdinPipe()
- if err != nil {
- r.Close()
- return nil, err
- }
- if err := cmd.Start(); err != nil {
- r.Close()
- w.Close()
- return nil, err
- }
- return &pipe2{r, w}, nil
- }
- panic("not reached")
-}
-
-type pipe2 struct {
- io.ReadCloser
- io.WriteCloser
-}
-
-func (p *pipe2) Close() error {
- p.ReadCloser.Close()
- p.WriteCloser.Close()
- return nil
-}
-
-type tee struct {
- r io.Reader
- w io.Writer
-}
-
-func (t tee) Read(p []byte) (n int, err error) {
- n, err = t.r.Read(p)
- if n > 0 {
- t.w.Write(p[0:n])
- }
- return
-}
-
-func (c *Client) rdsx() (*sx, error) {
- c.io.mustBeLocked()
- return rdsx(c.b)
-}
-
-type sxError struct {
- x *sx
-}
-
-func (e *sxError) Error() string { return e.x.String() }
-
-func (c *Client) cmd(b *Box, format string, args ...interface{}) error {
- x, err := c.cmdsx(b, format, args...)
- if err != nil {
- return err
- }
- if !x.ok() {
- return &sxError{x}
- }
- return nil
-}
-
-// cmdsx0 runs a single command and return the sx. Does not redial.
-func (c *Client) cmdsx0(format string, args ...interface{}) (*sx, error) {
- c.io.mustBeLocked()
- if c.rw == nil || !c.connected {
- return nil, fmt.Errorf("not connected")
- }
-
- cmd := fmt.Sprintf(format, args...)
- if Debug {
- fmt.Fprintf(os.Stderr, ">>> %s %s\n", tag, cmd)
- }
- if _, err := fmt.Fprintf(c.rw, "%s %s\r\n", tag, cmd); err != nil {
- c.connected = false
- return nil, err
- }
- return c.waitsx()
-}
-
-// cmdsx runs a command on box b. It does redial.
-func (c *Client) cmdsx(b *Box, format string, args ...interface{}) (*sx, error) {
- c.io.mustBeLocked()
- c.nextBox = b
-
-Trying:
- for tries := 0; ; tries++ {
- if c.rw == nil || !c.connected {
- if !c.autoReconnect {
- return nil, fmt.Errorf("not connected")
- }
- if err := c.reconnect(); err != nil {
- return nil, err
- }
- if b != nil && c.nextBox == nil {
- // box disappeared on reconnect
- return nil, fmt.Errorf("box is gone")
- }
- }
-
- if b != nil && b != c.box {
- if c.box != nil {
- // TODO c.box.init = false
- }
- c.box = b
- if _, err := c.cmdsx0("SELECT %s", iquote(b.Name)); err != nil {
- c.box = nil
- if tries++; tries == 1 && (c.rw == nil || !c.connected) {
- continue Trying
- }
- return nil, err
- }
- }
-
- x, err := c.cmdsx0(format, args...)
- if err != nil {
- if tries++; tries == 1 && (c.rw == nil || !c.connected) {
- continue Trying
- }
- return nil, err
- }
- return x, nil
- }
- panic("not reached")
-}
-
-func (c *Client) waitsx() (*sx, error) {
- c.io.mustBeLocked()
- for {
- x, err := c.rdsx()
- if err != nil {
- c.connected = false
- return nil, err
- }
- if len(x.sx) >= 1 && x.sx[0].kind == sxAtom {
- if x.sx[0].isAtom(tag) {
- return x, nil
- }
- if x.sx[0].isAtom("*") {
- c.unexpected(x)
- }
- }
- if x.kind == sxList && len(x.sx) == 0 {
- c.connected = false
- return nil, fmt.Errorf("empty response")
- }
- }
- panic("not reached")
-}
-
-func iquote(s string) string {
- if s == "" {
- return `""`
- }
-
- for i := 0; i < len(s); i++ {
- if s[i] >= 0x80 || s[i] <= ' ' || s[i] == '\\' || s[i] == '"' {
- goto Quote
- }
- }
- return s
-
-Quote:
- var b bytes.Buffer
- b.WriteByte('"')
- for i := 0; i < len(s); i++ {
- if s[i] == '\\' || s[i] == '"' {
- b.WriteByte('\\')
- }
- b.WriteByte(s[i])
- }
- b.WriteByte('"')
- return b.String()
-}
-
-func (c *Client) login() error {
- c.io.mustBeLocked()
- x, err := c.cmdsx(nil, "LOGIN %s %s", iquote(c.user), iquote(c.passwd))
- if err != nil {
- return err
- }
- if !x.ok() {
- return fmt.Errorf("login rejected: %s", x)
- }
- return nil
-}
-
-func (c *Client) getBoxes() error {
- c.io.mustBeLocked()
- for _, b := range c.allBox {
- b.dead = true
- // b.exists = 0
- // b.maxSeen = 0
- }
- list := "LIST"
- if c.capability["XLIST"] { // Gmail extension
- list = "XLIST"
- }
- if err := c.cmd(nil, "%s %s *", list, iquote(c.root)); err != nil {
- return err
- }
- if err := c.cmd(nil, "%s %s INBOX", list, iquote(c.root)); err != nil {
- return err
- }
- if c.nextBox != nil && c.nextBox.dead {
- c.nextBox = nil
- }
- for _, b := range c.allBox {
- if b.dead {
- delete(c.boxByName, b.Name)
- }
- b.firstNum = 0
- }
- c.allBox = boxTrim(c.allBox)
- for _, b := range c.allBox {
- b.child = boxTrim(b.child)
- }
- return nil
-}
-
-func boxTrim(list []*Box) []*Box {
- w := 0
- for _, b := range list {
- if !b.dead {
- list[w] = b
- w++
- }
- }
- return list[:w]
-}
-
-const maxFetch = 1000
-
-func (c *Client) setAutoReconnect(b bool) {
- c.autoReconnect = b
-}
-
-func (c *Client) check(b *Box) error {
- c.io.mustBeLocked()
- if b.dead {
- return fmt.Errorf("box is gone")
- }
-
- b.load = true
-
- // Update exists count.
- if err := c.cmd(b, "NOOP"); err != nil {
- return err
- }
-
- // Have to get through this in one session.
- // Caller can call again if we get disconnected
- // and return an error.
- c.autoReconnect = false
- defer c.setAutoReconnect(true)
-
- // First load after reconnect: figure out what changed.
- if b.firstNum == 0 && len(b.msgByUID) > 0 {
- var lo, hi uint32 = 1<<32 - 1, 0
- for _, m := range b.msgByUID {
- m.dead = true
- uid := uint32(m.UID)
- if lo > uid {
- lo = uid
- }
- if hi < uid {
- hi = uid
- }
- m.num = 0
- }
- if err := c.cmd(b, "UID FETCH %d:%d FLAGS", lo, hi); err != nil {
- return err
- }
- for _, m := range b.msgByUID {
- if m.dead {
- delete(b.msgByUID, m.UID)
- }
- }
- }
-
- // First-ever load.
- if b.firstNum == 0 {
- if b.exists <= maxFetch {
- b.firstNum = 1
- } else {
- b.firstNum = b.exists - maxFetch + 1
- }
- n := b.exists - b.firstNum + 1
- b.msgByNum = make([]*Msg, n)
- return c.fetchBox(b, b.firstNum, 0)
- }
-
- if b.exists <= b.maxSeen {
- return nil
- }
- return c.fetchBox(b, b.maxSeen, 0)
-}
-
-func (c *Client) fetchBox(b *Box, lo int, hi int) error {
- c.io.mustBeLocked()
- if b != c.box {
- if err := c.cmd(b, "NOOP"); err != nil {
- return err
- }
- }
- extra := ""
- if c.IsGmail() {
- extra = " X-GM-MSGID X-GM-THRID X-GM-LABELS"
- }
- slo := fmt.Sprint(lo)
- shi := "*"
- if hi > 0 {
- shi = fmt.Sprint(hi)
- }
- return c.cmd(b, "FETCH %s:%s (FLAGS UID INTERNALDATE RFC822.SIZE ENVELOPE BODY%s)", slo, shi, extra)
-}
-
-func (c *Client) IsGmail() bool {
- return c.capability["X-GM-EXT-1"]
-}
-
-// Table-driven IMAP "unexpected response" parser.
-// All the interesting data is in the unexpected responses.
-
-var unextab = []struct {
- num int
- name string
- fmt string
- fn func(*Client, *sx)
-}{
- {0, "BYE", "", xbye},
- {0, "CAPABILITY", "", xcapability},
- {0, "FLAGS", "AAL", xflags},
- {0, "LIST", "AALSS", xlist},
- {0, "XLIST", "AALSS", xlist},
- {0, "OK", "", xok},
- // {0, "SEARCH", "AAN*", xsearch},
- {1, "EXISTS", "ANA", xexists},
- {1, "EXPUNGE", "ANA", xexpunge},
- {1, "FETCH", "ANAL", xfetch},
- // {1, "RECENT", "ANA", xrecent}, // why do we care?
-}
-
-func (c *Client) unexpected(x *sx) {
- c.io.mustBeLocked()
- var num int
- var name string
-
- if len(x.sx) >= 3 && x.sx[1].kind == sxNumber && x.sx[2].kind == sxAtom {
- num = 1
- name = string(x.sx[2].data)
- } else if len(x.sx) >= 2 && x.sx[1].kind == sxAtom {
- num = 0
- name = string(x.sx[1].data)
- } else {
- return
- }
-
- c.data.lock()
- for _, t := range unextab {
- if t.num == num && strings.EqualFold(t.name, name) {
- if t.fmt != "" && !x.match(t.fmt) {
- log.Printf("malformd %s: %s", name, x)
- continue
- }
- t.fn(c, x)
- }
- }
- c.data.unlock()
-}
-
-func xbye(c *Client, x *sx) {
- c.io.mustBeLocked()
- c.rw.Close()
- c.rw = nil
- c.connected = false
-}
-
-func xflags(c *Client, x *sx) {
- c.data.mustBeLocked()
- // This response contains in x.sx[2] the list of flags
- // that can be validly attached to messages in c.box.
- if b := c.box; b != nil {
- c.flags = x.sx[2].parseFlags()
- }
-}
-
-func xcapability(c *Client, x *sx) {
- c.data.mustBeLocked()
- c.capability = make(map[string]bool)
- for _, xx := range x.sx[2:] {
- if xx.kind == sxAtom {
- c.capability[string(xx.data)] = true
- }
- }
-}
-
-func xlist(c *Client, x *sx) {
- c.data.mustBeLocked()
- s := string(x.sx[4].data)
- t := string(x.sx[3].data)
-
- // INBOX is the special name for the main mailbox.
- // All the other mailbox names have the root prefix removed, if applicable.
- inbox := strings.EqualFold(s, "inbox")
- if inbox {
- s = "inbox"
- }
-
- b := c.newBox(s, t, inbox)
- if b == nil {
- return
- }
- if inbox {
- c.inbox = b
- }
- if s == c.root {
- c.rootBox = b
- }
- b.dead = false
- b.flags = x.sx[2].parseFlags()
-}
-
-func xexists(c *Client, x *sx) {
- c.data.mustBeLocked()
- if b := c.box; b != nil {
- b.exists = int(x.sx[1].number)
- if b.exists < b.maxSeen {
- b.maxSeen = b.exists
- }
- }
-}
-
-func xexpunge(c *Client, x *sx) {
- c.data.mustBeLocked()
- if b := c.box; b != nil {
- n := int(x.sx[1].number)
- bynum := b.msgByNum
- if bynum != nil {
- if n < b.firstNum {
- b.firstNum--
- } else if n < b.firstNum+len(bynum) {
- copy(bynum[n-b.firstNum:], bynum[n-b.firstNum+1:])
- b.msgByNum = bynum[:len(bynum)-1]
- } else {
- log.Printf("expunge unexpected message %d %d %d", b.firstNum, b.exists, b.firstNum+len(bynum))
- }
- }
- if n <= b.exists {
- b.exists--
- }
- }
-}
-
-// Table-driven OK info parser.
-
-var oktab = []struct {
- name string
- kind sxKind
- fn func(*Client, *Box, *sx)
-}{
- {"UIDVALIDITY", sxNumber, xokuidvalidity},
- {"PERMANENTFLAGS", sxList, xokpermflags},
- {"UNSEEN", sxNumber, xokunseen},
- {"READ-WRITE", 0, xokreadwrite},
- {"READ-ONLY", 0, xokreadonly},
-}
-
-func xok(c *Client, x *sx) {
- c.data.mustBeLocked()
- b := c.box
- if b == nil {
- return
- }
- if len(x.sx) >= 4 && x.sx[2].kind == sxAtom && x.sx[2].data[0] == '[' {
- var arg *sx
- if x.sx[3].kind == sxAtom && x.sx[3].data[0] == ']' {
- arg = nil
- } else if x.sx[4].kind == sxAtom && x.sx[4].data[0] == ']' {
- arg = x.sx[3]
- } else {
- log.Printf("cannot parse OK: %s", x)
- return
- }
- x.sx[2].data = x.sx[2].data[1:]
- for _, t := range oktab {
- if x.sx[2].isAtom(t.name) {
- if t.kind != 0 && (arg == nil || arg.kind != t.kind) {
- log.Printf("malformed %s: %s", t.name, arg)
- continue
- }
- t.fn(c, b, arg)
- }
- }
- }
-}
-
-func xokuidvalidity(c *Client, b *Box, x *sx) {
- c.data.mustBeLocked()
- n := uint32(x.number)
- if b.validity != n {
- if b.msgByUID != nil {
- log.Printf("imap: UID validity reset for %s", b.Name)
- }
- b.validity = n
- b.maxSeen = 0
- b.firstNum = 0
- b.msgByNum = nil
- b.msgByUID = nil
- }
-}
-
-func xokpermflags(c *Client, b *Box, x *sx) {
- c.data.mustBeLocked()
- b.permFlags = x.parseFlags()
-}
-
-func xokunseen(c *Client, b *Box, x *sx) {
- c.data.mustBeLocked()
- b.unseen = int(x.number)
-}
-
-func xokreadwrite(c *Client, b *Box, x *sx) {
- c.data.mustBeLocked()
- b.readOnly = false
-}
-
-func xokreadonly(c *Client, b *Box, x *sx) {
- c.data.mustBeLocked()
- b.readOnly = true
-}
-
-// Table-driven FETCH message info parser.
-
-var msgtab = []struct {
- name string
- fn func(*Msg, *sx, *sx)
-}{
- {"FLAGS", xmsgflags},
- {"INTERNALDATE", xmsgdate},
- {"RFC822.SIZE", xmsgrfc822size},
- {"ENVELOPE", xmsgenvelope},
- {"X-GM-MSGID", xmsggmmsgid},
- {"X-GM-THRID", xmsggmthrid},
- {"BODY", xmsgbody},
- {"BODY[", xmsgbodydata},
-}
-
-func xfetch(c *Client, x *sx) {
- c.data.mustBeLocked()
- if c.box == nil {
- log.Printf("FETCH but no open box: %s", x)
- return
- }
-
- // * 152 FETCH (UID 185 FLAGS() ...)
- n := x.sx[1].number
- xx := x.sx[3]
- if len(xx.sx)%2 != 0 {
- log.Printf("malformed FETCH: %s", x)
- return
- }
- var uid uint64
- for i := 0; i < len(xx.sx); i += 2 {
- if xx.sx[i].isAtom("UID") {
- if xx.sx[i+1].kind == sxNumber {
- uid = uint64(xx.sx[i+1].number) | uint64(c.box.validity)<<32
- goto HaveUID
- }
- }
- }
- // This happens; too bad.
- // log.Printf("FETCH without UID: %s", x)
- return
-
-HaveUID:
- if m := c.box.msgByUID[uid]; m != nil && m.dead {
- // FETCH during box garbage collection.
- m.dead = false
- m.num = int(n)
- return
- }
- m := c.box.newMsg(uid, int(n))
- for i := 0; i < len(xx.sx); i += 2 {
- k, v := xx.sx[i], xx.sx[i+1]
- for _, t := range msgtab {
- if k.isAtom(t.name) {
- t.fn(m, k, v)
- }
- }
- }
-}
-
-func xmsggmmsgid(m *Msg, k, v *sx) {
- m.GmailID = uint64(v.number)
-}
-
-func xmsggmthrid(m *Msg, k, v *sx) {
- m.GmailThread = uint64(v.number)
-}
-
-func xmsgflags(m *Msg, k, v *sx) {
- m.Flags = v.parseFlags()
-}
-
-func xmsgrfc822size(m *Msg, k, v *sx) {
- m.Bytes = v.number
-}
-
-func xmsgdate(m *Msg, k, v *sx) {
- m.Date = v.parseDate()
-}
-
-func xmsgenvelope(m *Msg, k, v *sx) {
- m.Hdr = parseEnvelope(v)
-}
-
-func parseEnvelope(v *sx) *MsgHdr {
- if v.kind != sxList || !v.match("SSLLLLLLSS") {
- log.Printf("bad envelope: %s", v)
- return nil
- }
-
- hdr := &MsgHdr{
- Date: v.sx[0].nstring(),
- Subject: unrfc2047(v.sx[1].nstring()),
- From: parseAddrs(v.sx[2]),
- Sender: parseAddrs(v.sx[3]),
- ReplyTo: parseAddrs(v.sx[4]),
- To: parseAddrs(v.sx[5]),
- CC: parseAddrs(v.sx[6]),
- BCC: parseAddrs(v.sx[7]),
- InReplyTo: unrfc2047(v.sx[8].nstring()),
- MessageID: unrfc2047(v.sx[9].nstring()),
- }
-
- h := md5.New()
- fmt.Fprintf(h, "date: %s\n", hdr.Date)
- fmt.Fprintf(h, "subject: %s\n", hdr.Subject)
- fmt.Fprintf(h, "from: %s\n", hdr.From)
- fmt.Fprintf(h, "sender: %s\n", hdr.Sender)
- fmt.Fprintf(h, "replyto: %s\n", hdr.ReplyTo)
- fmt.Fprintf(h, "to: %s\n", hdr.To)
- fmt.Fprintf(h, "cc: %s\n", hdr.CC)
- fmt.Fprintf(h, "bcc: %s\n", hdr.BCC)
- fmt.Fprintf(h, "inreplyto: %s\n", hdr.InReplyTo)
- fmt.Fprintf(h, "messageid: %s\n", hdr.MessageID)
- hdr.Digest = fmt.Sprintf("%x", h.Sum(nil))
-
- return hdr
-}
-
-func parseAddrs(x *sx) []Addr {
- var addr []Addr
- for _, xx := range x.sx {
- if !xx.match("SSSS") {
- log.Printf("bad address: %s", x)
- continue
- }
- name := unrfc2047(xx.sx[0].nstring())
- // sx[1] is route
- local := unrfc2047(xx.sx[2].nstring())
- host := unrfc2047(xx.sx[3].nstring())
- if local == "" || host == "" {
- // rfc822 group syntax
- addr = append(addr, Addr{name, ""})
- continue
- }
- addr = append(addr, Addr{name, local + "@" + host})
- }
- return addr
-}
-
-func xmsgbody(m *Msg, k, v *sx) {
- if v.isNil() {
- return
- }
- if v.kind != sxList {
- log.Printf("bad body: %s", v)
- }
-
- // To follow the structure exactly we should be doing this
- // to m.NewPart(m.Part[0]) with type message/rfc822,
- // but the extra layer is redundant - what else would be in
- // a mailbox?
- parseStructure(&m.Root, v)
- n := m.num
- if m.Box.maxSeen < n {
- m.Box.maxSeen = n
- }
-}
-
-func parseStructure(p *MsgPart, x *sx) {
- if x.isNil() {
- return
- }
- if x.kind != sxList {
- log.Printf("bad structure: %s", x)
- return
- }
- if x.sx[0].isList() {
- // multipart
- var i int
- for i = 0; i < len(x.sx) && x.sx[i].isList(); i++ {
- parseStructure(p.newPart(), x.sx[i])
- }
- if i != len(x.sx)-1 || !x.sx[i].isString() {
- log.Printf("bad multipart structure: %s", x)
- p.Type = "multipart/mixed"
- return
- }
- s := strlwr(x.sx[i].nstring())
- p.Type = "multipart/" + s
- return
- }
-
- // single type
- if len(x.sx) < 2 || !x.sx[0].isString() {
- log.Printf("bad type structure: %s", x)
- return
- }
- s := strlwr(x.sx[0].nstring())
- t := strlwr(x.sx[1].nstring())
- p.Type = s + "/" + t
- if len(x.sx) < 7 || !x.sx[2].isList() || !x.sx[3].isString() || !x.sx[4].isString() || !x.sx[5].isString() || !x.sx[6].isNumber() {
- log.Printf("bad part structure: %s", x)
- return
- }
- parseParams(p, x.sx[2])
- p.ContentID = x.sx[3].nstring()
- p.Desc = x.sx[4].nstring()
- p.Encoding = x.sx[5].nstring()
- p.Bytes = x.sx[6].number
- if p.Type == "message/rfc822" {
- if len(x.sx) < 10 || !x.sx[7].isList() || !x.sx[8].isList() || !x.sx[9].isNumber() {
- log.Printf("bad rfc822 structure: %s", x)
- return
- }
- p.Hdr = parseEnvelope(x.sx[7])
- parseStructure(p.newPart(), x.sx[8])
- p.Lines = x.sx[9].number
- }
- if s == "text" {
- if len(x.sx) < 8 || !x.sx[7].isNumber() {
- log.Printf("bad text structure: %s", x)
- return
- }
- p.Lines = x.sx[7].number
- }
-}
-
-func parseParams(p *MsgPart, x *sx) {
- if x.isNil() {
- return
- }
- if len(x.sx)%2 != 0 {
- log.Printf("bad message params: %s", x)
- return
- }
-
- for i := 0; i < len(x.sx); i += 2 {
- k, v := x.sx[i].nstring(), x.sx[i+1].nstring()
- k = strlwr(k)
- switch strlwr(k) {
- case "charset":
- p.Charset = strlwr(v)
- case "name":
- p.Name = v
- }
- }
-}
-
-func (c *Client) fetch(p *MsgPart, what string) {
- c.io.mustBeLocked()
- id := p.ID
- if what != "" {
- if id != "" {
- id += "."
- }
- id += what
- }
- c.cmd(p.Msg.Box, "UID FETCH %d BODY[%s]", p.Msg.UID&(1<<32-1), id)
-}
-
-func xmsgbodydata(m *Msg, k, v *sx) {
- // k.data is []byte("BODY[...")
- name := string(k.data[5:])
- if i := strings.Index(name, "]"); i >= 0 {
- name = name[:i]
- }
-
- p := &m.Root
- for name != "" && '1' <= name[0] && name[0] <= '9' {
- var num int
- num, name = parseNum(name)
- if num == 0 {
- log.Printf("unexpected body name: %s", k.data)
- return
- }
- num--
- if num >= len(p.Child) {
- log.Printf("invalid body name: %s", k.data)
- return
- }
- p = p.Child[num]
- }
-
- switch strlwr(name) {
- case "":
- p.raw = v.nbytes()
- case "mime":
- p.mimeHeader = nocr(v.nbytes())
- case "header":
- p.rawHeader = nocr(v.nbytes())
- case "text":
- p.rawBody = nocr(v.nbytes())
- }
-}
-
-func parseNum(name string) (int, string) {
- rest := ""
- i := strings.Index(name, ".")
- if i >= 0 {
- name, rest = name[:i], name[i+1:]
- }
- n, _ := strconv.Atoi(name)
- return n, rest
-}
-
-func nocr(b []byte) []byte {
- w := 0
- for _, c := range b {
- if c != '\r' {
- b[w] = c
- w++
- }
- }
- return b[:w]
-}
-
-type uidList []*Msg
-
-func (l uidList) String() string {
- var b bytes.Buffer
- for i, m := range l {
- if i > 0 {
- b.WriteByte(',')
- }
- fmt.Fprintf(&b, "%d", m.UID&(1<<32-1))
- }
- return b.String()
-}
-
-func (c *Client) deleteList(msgs []*Msg) error {
- if len(msgs) == 0 {
- return nil
- }
- c.io.mustBeLocked()
-
- b := msgs[0].Box
- for _, m := range msgs {
- if m.Box != b {
- return fmt.Errorf("messages span boxes: %q and %q", b.Name, m.Box.Name)
- }
- if uint32(m.UID>>32) != b.validity {
- return fmt.Errorf("stale message")
- }
- }
-
- err := c.cmd(b, "UID STORE %s +FLAGS (\\Deleted)", uidList(msgs))
- if err == nil && c.box == b {
- err = c.cmd(b, "EXPUNGE")
- }
- return err
-}
-
-func (c *Client) copyList(dst, src *Box, msgs []*Msg) error {
- if len(msgs) == 0 {
- return nil
- }
- c.io.mustBeLocked()
-
- for _, m := range msgs {
- if m.Box != src {
- return fmt.Errorf("messages span boxes: %q and %q", src.Name, m.Box.Name)
- }
- if uint32(m.UID>>32) != src.validity {
- return fmt.Errorf("stale message")
- }
- }
-
- var name string
- if dst == c.inbox {
- name = "INBOX"
- } else {
- name = iquote(dst.Name)
- }
- return c.cmd(src, "UID COPY %s %s", uidList(msgs), name)
-}
-
-func (c *Client) muteList(src *Box, msgs []*Msg) error {
- if len(msgs) == 0 {
- return nil
- }
- c.io.mustBeLocked()
-
- for _, m := range msgs {
- if m.Box != src {
- return fmt.Errorf("messages span boxes: %q and %q", src.Name, m.Box.Name)
- }
- if uint32(m.UID>>32) != src.validity {
- return fmt.Errorf("stale message")
- }
- }
-
- return c.cmd(src, "UID STORE %s +X-GM-LABELS (\\Muted)", uidList(msgs))
-}