aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/emersion/go-imap/message.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emersion/go-imap/message.go')
-rw-r--r--vendor/github.com/emersion/go-imap/message.go1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/vendor/github.com/emersion/go-imap/message.go b/vendor/github.com/emersion/go-imap/message.go
new file mode 100644
index 0000000..bd28325
--- /dev/null
+++ b/vendor/github.com/emersion/go-imap/message.go
@@ -0,0 +1,1186 @@
+package imap
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "mime"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// System message flags, defined in RFC 3501 section 2.3.2.
+const (
+ SeenFlag = "\\Seen"
+ AnsweredFlag = "\\Answered"
+ FlaggedFlag = "\\Flagged"
+ DeletedFlag = "\\Deleted"
+ DraftFlag = "\\Draft"
+ RecentFlag = "\\Recent"
+)
+
+// ImportantFlag is a message flag to signal that a message is likely important
+// to the user. This flag is defined in RFC 8457 section 2.
+const ImportantFlag = "$Important"
+
+// TryCreateFlag is a special flag in MailboxStatus.PermanentFlags indicating
+// that it is possible to create new keywords by attempting to store those
+// flags in the mailbox.
+const TryCreateFlag = "\\*"
+
+var flags = []string{
+ SeenFlag,
+ AnsweredFlag,
+ FlaggedFlag,
+ DeletedFlag,
+ DraftFlag,
+ RecentFlag,
+}
+
+// A PartSpecifier specifies which parts of the MIME entity should be returned.
+type PartSpecifier string
+
+// Part specifiers described in RFC 3501 page 55.
+const (
+ // Refers to the entire part, including headers.
+ EntireSpecifier PartSpecifier = ""
+ // Refers to the header of the part. Must include the final CRLF delimiting
+ // the header and the body.
+ HeaderSpecifier = "HEADER"
+ // Refers to the text body of the part, omitting the header.
+ TextSpecifier = "TEXT"
+ // Refers to the MIME Internet Message Body header. Must include the final
+ // CRLF delimiting the header and the body.
+ MIMESpecifier = "MIME"
+)
+
+// CanonicalFlag returns the canonical form of a flag. Flags are case-insensitive.
+//
+// If the flag is defined in RFC 3501, it returns the flag with the case of the
+// RFC. Otherwise, it returns the lowercase version of the flag.
+func CanonicalFlag(flag string) string {
+ for _, f := range flags {
+ if strings.EqualFold(f, flag) {
+ return f
+ }
+ }
+ return strings.ToLower(flag)
+}
+
+func ParseParamList(fields []interface{}) (map[string]string, error) {
+ params := make(map[string]string)
+
+ var k string
+ for i, f := range fields {
+ p, err := ParseString(f)
+ if err != nil {
+ return nil, errors.New("Parameter list contains a non-string: " + err.Error())
+ }
+
+ if i%2 == 0 {
+ k = p
+ } else {
+ params[k] = p
+ k = ""
+ }
+ }
+
+ if k != "" {
+ return nil, errors.New("Parameter list contains a key without a value")
+ }
+ return params, nil
+}
+
+func FormatParamList(params map[string]string) []interface{} {
+ var fields []interface{}
+ for key, value := range params {
+ fields = append(fields, key, value)
+ }
+ return fields
+}
+
+var wordDecoder = &mime.WordDecoder{
+ CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
+ if CharsetReader != nil {
+ return CharsetReader(charset, input)
+ }
+ return nil, fmt.Errorf("imap: unhandled charset %q", charset)
+ },
+}
+
+func decodeHeader(s string) (string, error) {
+ dec, err := wordDecoder.DecodeHeader(s)
+ if err != nil {
+ return s, err
+ }
+ return dec, nil
+}
+
+func encodeHeader(s string) string {
+ return mime.QEncoding.Encode("utf-8", s)
+}
+
+func stringLowered(i interface{}) (string, bool) {
+ s, ok := i.(string)
+ return strings.ToLower(s), ok
+}
+
+func parseHeaderParamList(fields []interface{}) (map[string]string, error) {
+ params, err := ParseParamList(fields)
+ if err != nil {
+ return nil, err
+ }
+
+ for k, v := range params {
+ if lower := strings.ToLower(k); lower != k {
+ delete(params, k)
+ k = lower
+ }
+
+ params[k], _ = decodeHeader(v)
+ }
+
+ return params, nil
+}
+
+func formatHeaderParamList(params map[string]string) []interface{} {
+ encoded := make(map[string]string)
+ for k, v := range params {
+ encoded[k] = encodeHeader(v)
+ }
+ return FormatParamList(encoded)
+}
+
+// A message.
+type Message struct {
+ // The message sequence number. It must be greater than or equal to 1.
+ SeqNum uint32
+ // The mailbox items that are currently filled in. This map's values
+ // should not be used directly, they must only be used by libraries
+ // implementing extensions of the IMAP protocol.
+ Items map[FetchItem]interface{}
+
+ // The message envelope.
+ Envelope *Envelope
+ // The message body structure (either BODYSTRUCTURE or BODY).
+ BodyStructure *BodyStructure
+ // The message flags.
+ Flags []string
+ // The date the message was received by the server.
+ InternalDate time.Time
+ // The message size.
+ Size uint32
+ // The message unique identifier. It must be greater than or equal to 1.
+ Uid uint32
+ // The message body sections.
+ Body map[*BodySectionName]Literal
+
+ // The order in which items were requested. This order must be preserved
+ // because some bad IMAP clients (looking at you, Outlook!) refuse responses
+ // containing items in a different order.
+ itemsOrder []FetchItem
+}
+
+// Create a new empty message that will contain the specified items.
+func NewMessage(seqNum uint32, items []FetchItem) *Message {
+ msg := &Message{
+ SeqNum: seqNum,
+ Items: make(map[FetchItem]interface{}),
+ Body: make(map[*BodySectionName]Literal),
+ itemsOrder: items,
+ }
+
+ for _, k := range items {
+ msg.Items[k] = nil
+ }
+
+ return msg
+}
+
+// Parse a message from fields.
+func (m *Message) Parse(fields []interface{}) error {
+ m.Items = make(map[FetchItem]interface{})
+ m.Body = map[*BodySectionName]Literal{}
+ m.itemsOrder = nil
+
+ var k FetchItem
+ for i, f := range fields {
+ if i%2 == 0 { // It's a key
+ switch f := f.(type) {
+ case string:
+ k = FetchItem(strings.ToUpper(f))
+ case RawString:
+ k = FetchItem(strings.ToUpper(string(f)))
+ default:
+ return fmt.Errorf("cannot parse message: key is not a string, but a %T", f)
+ }
+ } else { // It's a value
+ m.Items[k] = nil
+ m.itemsOrder = append(m.itemsOrder, k)
+
+ switch k {
+ case FetchBody, FetchBodyStructure:
+ bs, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: BODYSTRUCTURE is not a list, but a %T", f)
+ }
+
+ m.BodyStructure = &BodyStructure{Extended: k == FetchBodyStructure}
+ if err := m.BodyStructure.Parse(bs); err != nil {
+ return err
+ }
+ case FetchEnvelope:
+ env, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: ENVELOPE is not a list, but a %T", f)
+ }
+
+ m.Envelope = &Envelope{}
+ if err := m.Envelope.Parse(env); err != nil {
+ return err
+ }
+ case FetchFlags:
+ flags, ok := f.([]interface{})
+ if !ok {
+ return fmt.Errorf("cannot parse message: FLAGS is not a list, but a %T", f)
+ }
+
+ m.Flags = make([]string, len(flags))
+ for i, flag := range flags {
+ s, _ := ParseString(flag)
+ m.Flags[i] = CanonicalFlag(s)
+ }
+ case FetchInternalDate:
+ date, _ := f.(string)
+ m.InternalDate, _ = time.Parse(DateTimeLayout, date)
+ case FetchRFC822Size:
+ m.Size, _ = ParseNumber(f)
+ case FetchUid:
+ m.Uid, _ = ParseNumber(f)
+ default:
+ // Likely to be a section of the body
+ // First check that the section name is correct
+ if section, err := ParseBodySectionName(k); err != nil {
+ // Not a section name, maybe an attribute defined in an IMAP extension
+ m.Items[k] = f
+ } else {
+ m.Body[section], _ = f.(Literal)
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (m *Message) formatItem(k FetchItem) []interface{} {
+ v := m.Items[k]
+ var kk interface{} = RawString(k)
+
+ switch k {
+ case FetchBody, FetchBodyStructure:
+ // Extension data is only returned with the BODYSTRUCTURE fetch
+ m.BodyStructure.Extended = k == FetchBodyStructure
+ v = m.BodyStructure.Format()
+ case FetchEnvelope:
+ v = m.Envelope.Format()
+ case FetchFlags:
+ flags := make([]interface{}, len(m.Flags))
+ for i, flag := range m.Flags {
+ flags[i] = RawString(flag)
+ }
+ v = flags
+ case FetchInternalDate:
+ v = m.InternalDate
+ case FetchRFC822Size:
+ v = m.Size
+ case FetchUid:
+ v = m.Uid
+ default:
+ for section, literal := range m.Body {
+ if section.value == k {
+ // This can contain spaces, so we can't pass it as a string directly
+ kk = section.resp()
+ v = literal
+ break
+ }
+ }
+ }
+
+ return []interface{}{kk, v}
+}
+
+func (m *Message) Format() []interface{} {
+ var fields []interface{}
+
+ // First send ordered items
+ processed := make(map[FetchItem]bool)
+ for _, k := range m.itemsOrder {
+ if _, ok := m.Items[k]; ok {
+ fields = append(fields, m.formatItem(k)...)
+ processed[k] = true
+ }
+ }
+
+ // Then send other remaining items
+ for k := range m.Items {
+ if !processed[k] {
+ fields = append(fields, m.formatItem(k)...)
+ }
+ }
+
+ return fields
+}
+
+// GetBody gets the body section with the specified name. Returns nil if it's not found.
+func (m *Message) GetBody(section *BodySectionName) Literal {
+ section = section.resp()
+
+ for s, body := range m.Body {
+ if section.Equal(s) {
+ if body == nil {
+ // Server can return nil, we need to treat as empty string per RFC 3501
+ body = bytes.NewReader(nil)
+ }
+ return body
+ }
+ }
+ return nil
+}
+
+// A body section name.
+// See RFC 3501 page 55.
+type BodySectionName struct {
+ BodyPartName
+
+ // If set to true, do not implicitly set the \Seen flag.
+ Peek bool
+ // The substring of the section requested. The first value is the position of
+ // the first desired octet and the second value is the maximum number of
+ // octets desired.
+ Partial []int
+
+ value FetchItem
+}
+
+func (section *BodySectionName) parse(s string) error {
+ section.value = FetchItem(s)
+
+ if s == "RFC822" {
+ s = "BODY[]"
+ }
+ if s == "RFC822.HEADER" {
+ s = "BODY.PEEK[HEADER]"
+ }
+ if s == "RFC822.TEXT" {
+ s = "BODY[TEXT]"
+ }
+
+ partStart := strings.Index(s, "[")
+ if partStart == -1 {
+ return errors.New("Invalid body section name: must contain an open bracket")
+ }
+
+ partEnd := strings.LastIndex(s, "]")
+ if partEnd == -1 {
+ return errors.New("Invalid body section name: must contain a close bracket")
+ }
+
+ name := s[:partStart]
+ part := s[partStart+1 : partEnd]
+ partial := s[partEnd+1:]
+
+ if name == "BODY.PEEK" {
+ section.Peek = true
+ } else if name != "BODY" {
+ return errors.New("Invalid body section name")
+ }
+
+ b := bytes.NewBufferString(part + string(cr) + string(lf))
+ r := NewReader(b)
+ fields, err := r.ReadFields()
+ if err != nil {
+ return err
+ }
+
+ if err := section.BodyPartName.parse(fields); err != nil {
+ return err
+ }
+
+ if len(partial) > 0 {
+ if !strings.HasPrefix(partial, "<") || !strings.HasSuffix(partial, ">") {
+ return errors.New("Invalid body section name: invalid partial")
+ }
+ partial = partial[1 : len(partial)-1]
+
+ partialParts := strings.SplitN(partial, ".", 2)
+
+ var from, length int
+ if from, err = strconv.Atoi(partialParts[0]); err != nil {
+ return errors.New("Invalid body section name: invalid partial: invalid from: " + err.Error())
+ }
+ section.Partial = []int{from}
+
+ if len(partialParts) == 2 {
+ if length, err = strconv.Atoi(partialParts[1]); err != nil {
+ return errors.New("Invalid body section name: invalid partial: invalid length: " + err.Error())
+ }
+ section.Partial = append(section.Partial, length)
+ }
+ }
+
+ return nil
+}
+
+func (section *BodySectionName) FetchItem() FetchItem {
+ if section.value != "" {
+ return section.value
+ }
+
+ s := "BODY"
+ if section.Peek {
+ s += ".PEEK"
+ }
+
+ s += "[" + section.BodyPartName.string() + "]"
+
+ if len(section.Partial) > 0 {
+ s += "<"
+ s += strconv.Itoa(section.Partial[0])
+
+ if len(section.Partial) > 1 {
+ s += "."
+ s += strconv.Itoa(section.Partial[1])
+ }
+
+ s += ">"
+ }
+
+ return FetchItem(s)
+}
+
+// Equal checks whether two sections are equal.
+func (section *BodySectionName) Equal(other *BodySectionName) bool {
+ if section.Peek != other.Peek {
+ return false
+ }
+ if len(section.Partial) != len(other.Partial) {
+ return false
+ }
+ if len(section.Partial) > 0 && section.Partial[0] != other.Partial[0] {
+ return false
+ }
+ if len(section.Partial) > 1 && section.Partial[1] != other.Partial[1] {
+ return false
+ }
+ return section.BodyPartName.Equal(&other.BodyPartName)
+}
+
+func (section *BodySectionName) resp() *BodySectionName {
+ resp := *section // Copy section
+ if resp.Peek {
+ resp.Peek = false
+ }
+ if len(resp.Partial) == 2 {
+ resp.Partial = []int{resp.Partial[0]}
+ }
+ if !strings.HasPrefix(string(resp.value), string(FetchRFC822)) {
+ resp.value = ""
+ }
+ return &resp
+}
+
+// ExtractPartial returns a subset of the specified bytes matching the partial requested in the
+// section name.
+func (section *BodySectionName) ExtractPartial(b []byte) []byte {
+ if len(section.Partial) != 2 {
+ return b
+ }
+
+ from := section.Partial[0]
+ length := section.Partial[1]
+ to := from + length
+ if from > len(b) {
+ return nil
+ }
+ if to > len(b) {
+ to = len(b)
+ }
+ return b[from:to]
+}
+
+// ParseBodySectionName parses a body section name.
+func ParseBodySectionName(s FetchItem) (*BodySectionName, error) {
+ section := new(BodySectionName)
+ err := section.parse(string(s))
+ return section, err
+}
+
+// A body part name.
+type BodyPartName struct {
+ // The specifier of the requested part.
+ Specifier PartSpecifier
+ // The part path. Parts indexes start at 1.
+ Path []int
+ // If Specifier is HEADER, contains header fields that will/won't be returned,
+ // depending of the value of NotFields.
+ Fields []string
+ // If set to true, Fields is a blacklist of fields instead of a whitelist.
+ NotFields bool
+}
+
+func (part *BodyPartName) parse(fields []interface{}) error {
+ if len(fields) == 0 {
+ return nil
+ }
+
+ name, ok := fields[0].(string)
+ if !ok {
+ return errors.New("Invalid body section name: part name must be a string")
+ }
+
+ args := fields[1:]
+
+ path := strings.Split(strings.ToUpper(name), ".")
+
+ end := 0
+loop:
+ for i, node := range path {
+ switch PartSpecifier(node) {
+ case EntireSpecifier, HeaderSpecifier, MIMESpecifier, TextSpecifier:
+ part.Specifier = PartSpecifier(node)
+ end = i + 1
+ break loop
+ }
+
+ index, err := strconv.Atoi(node)
+ if err != nil {
+ return errors.New("Invalid body part name: " + err.Error())
+ }
+ if index <= 0 {
+ return errors.New("Invalid body part name: index <= 0")
+ }
+
+ part.Path = append(part.Path, index)
+ }
+
+ if part.Specifier == HeaderSpecifier && len(path) > end && path[end] == "FIELDS" && len(args) > 0 {
+ end++
+ if len(path) > end && path[end] == "NOT" {
+ part.NotFields = true
+ }
+
+ names, ok := args[0].([]interface{})
+ if !ok {
+ return errors.New("Invalid body part name: HEADER.FIELDS must have a list argument")
+ }
+
+ for _, namei := range names {
+ if name, ok := namei.(string); ok {
+ part.Fields = append(part.Fields, name)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (part *BodyPartName) string() string {
+ path := make([]string, len(part.Path))
+ for i, index := range part.Path {
+ path[i] = strconv.Itoa(index)
+ }
+
+ if part.Specifier != EntireSpecifier {
+ path = append(path, string(part.Specifier))
+ }
+
+ if part.Specifier == HeaderSpecifier && len(part.Fields) > 0 {
+ path = append(path, "FIELDS")
+
+ if part.NotFields {
+ path = append(path, "NOT")
+ }
+ }
+
+ s := strings.Join(path, ".")
+
+ if len(part.Fields) > 0 {
+ s += " (" + strings.Join(part.Fields, " ") + ")"
+ }
+
+ return s
+}
+
+// Equal checks whether two body part names are equal.
+func (part *BodyPartName) Equal(other *BodyPartName) bool {
+ if part.Specifier != other.Specifier {
+ return false
+ }
+ if part.NotFields != other.NotFields {
+ return false
+ }
+ if len(part.Path) != len(other.Path) {
+ return false
+ }
+ for i, node := range part.Path {
+ if node != other.Path[i] {
+ return false
+ }
+ }
+ if len(part.Fields) != len(other.Fields) {
+ return false
+ }
+ for _, field := range part.Fields {
+ found := false
+ for _, f := range other.Fields {
+ if strings.EqualFold(field, f) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+// An address.
+type Address struct {
+ // The personal name.
+ PersonalName string
+ // The SMTP at-domain-list (source route).
+ AtDomainList string
+ // The mailbox name.
+ MailboxName string
+ // The host name.
+ HostName string
+}
+
+// Address returns the mailbox address (e.g. "foo@example.org").
+func (addr *Address) Address() string {
+ return addr.MailboxName + "@" + addr.HostName
+}
+
+// Parse an address from fields.
+func (addr *Address) Parse(fields []interface{}) error {
+ if len(fields) < 4 {
+ return errors.New("Address doesn't contain 4 fields")
+ }
+
+ if s, err := ParseString(fields[0]); err == nil {
+ addr.PersonalName, _ = decodeHeader(s)
+ }
+ if s, err := ParseString(fields[1]); err == nil {
+ addr.AtDomainList, _ = decodeHeader(s)
+ }
+
+ s, err := ParseString(fields[2])
+ if err != nil {
+ return errors.New("Mailbox name could not be parsed")
+ }
+ addr.MailboxName, _ = decodeHeader(s)
+
+ s, err = ParseString(fields[3])
+ if err != nil {
+ return errors.New("Host name could not be parsed")
+ }
+ addr.HostName, _ = decodeHeader(s)
+
+ return nil
+}
+
+// Format an address to fields.
+func (addr *Address) Format() []interface{} {
+ fields := make([]interface{}, 4)
+
+ if addr.PersonalName != "" {
+ fields[0] = encodeHeader(addr.PersonalName)
+ }
+ if addr.AtDomainList != "" {
+ fields[1] = addr.AtDomainList
+ }
+ if addr.MailboxName != "" {
+ fields[2] = addr.MailboxName
+ }
+ if addr.HostName != "" {
+ fields[3] = addr.HostName
+ }
+
+ return fields
+}
+
+// Parse an address list from fields.
+func ParseAddressList(fields []interface{}) (addrs []*Address) {
+ for _, f := range fields {
+ if addrFields, ok := f.([]interface{}); ok {
+ addr := &Address{}
+ if err := addr.Parse(addrFields); err == nil {
+ addrs = append(addrs, addr)
+ }
+ }
+ }
+
+ return
+}
+
+// Format an address list to fields.
+func FormatAddressList(addrs []*Address) interface{} {
+ if len(addrs) == 0 {
+ return nil
+ }
+
+ fields := make([]interface{}, len(addrs))
+
+ for i, addr := range addrs {
+ fields[i] = addr.Format()
+ }
+
+ return fields
+}
+
+// A message envelope, ie. message metadata from its headers.
+// See RFC 3501 page 77.
+type Envelope struct {
+ // The message date.
+ Date time.Time
+ // The message subject.
+ Subject string
+ // The From header addresses.
+ From []*Address
+ // The message senders.
+ Sender []*Address
+ // The Reply-To header addresses.
+ ReplyTo []*Address
+ // The To header addresses.
+ To []*Address
+ // The Cc header addresses.
+ Cc []*Address
+ // The Bcc header addresses.
+ Bcc []*Address
+ // The In-Reply-To header. Contains the parent Message-Id.
+ InReplyTo string
+ // The Message-Id header.
+ MessageId string
+}
+
+// Parse an envelope from fields.
+func (e *Envelope) Parse(fields []interface{}) error {
+ if len(fields) < 10 {
+ return errors.New("ENVELOPE doesn't contain 10 fields")
+ }
+
+ if date, ok := fields[0].(string); ok {
+ e.Date, _ = parseMessageDateTime(date)
+ }
+ if subject, err := ParseString(fields[1]); err == nil {
+ e.Subject, _ = decodeHeader(subject)
+ }
+ if from, ok := fields[2].([]interface{}); ok {
+ e.From = ParseAddressList(from)
+ }
+ if sender, ok := fields[3].([]interface{}); ok {
+ e.Sender = ParseAddressList(sender)
+ }
+ if replyTo, ok := fields[4].([]interface{}); ok {
+ e.ReplyTo = ParseAddressList(replyTo)
+ }
+ if to, ok := fields[5].([]interface{}); ok {
+ e.To = ParseAddressList(to)
+ }
+ if cc, ok := fields[6].([]interface{}); ok {
+ e.Cc = ParseAddressList(cc)
+ }
+ if bcc, ok := fields[7].([]interface{}); ok {
+ e.Bcc = ParseAddressList(bcc)
+ }
+ if inReplyTo, ok := fields[8].(string); ok {
+ e.InReplyTo = inReplyTo
+ }
+ if msgId, ok := fields[9].(string); ok {
+ e.MessageId = msgId
+ }
+
+ return nil
+}
+
+// Format an envelope to fields.
+func (e *Envelope) Format() (fields []interface{}) {
+ fields = make([]interface{}, 0, 10)
+ fields = append(fields, envelopeDateTime(e.Date))
+ if e.Subject != "" {
+ fields = append(fields, encodeHeader(e.Subject))
+ } else {
+ fields = append(fields, nil)
+ }
+ fields = append(fields,
+ FormatAddressList(e.From),
+ FormatAddressList(e.Sender),
+ FormatAddressList(e.ReplyTo),
+ FormatAddressList(e.To),
+ FormatAddressList(e.Cc),
+ FormatAddressList(e.Bcc),
+ )
+ if e.InReplyTo != "" {
+ fields = append(fields, e.InReplyTo)
+ } else {
+ fields = append(fields, nil)
+ }
+ if e.MessageId != "" {
+ fields = append(fields, e.MessageId)
+ } else {
+ fields = append(fields, nil)
+ }
+ return fields
+}
+
+// A body structure.
+// See RFC 3501 page 74.
+type BodyStructure struct {
+ // Basic fields
+
+ // The MIME type (e.g. "text", "image")
+ MIMEType string
+ // The MIME subtype (e.g. "plain", "png")
+ MIMESubType string
+ // The MIME parameters.
+ Params map[string]string
+
+ // The Content-Id header.
+ Id string
+ // The Content-Description header.
+ Description string
+ // The Content-Encoding header.
+ Encoding string
+ // The Content-Length header.
+ Size uint32
+
+ // Type-specific fields
+
+ // The children parts, if multipart.
+ Parts []*BodyStructure
+ // The envelope, if message/rfc822.
+ Envelope *Envelope
+ // The body structure, if message/rfc822.
+ BodyStructure *BodyStructure
+ // The number of lines, if text or message/rfc822.
+ Lines uint32
+
+ // Extension data
+
+ // True if the body structure contains extension data.
+ Extended bool
+
+ // The Content-Disposition header field value.
+ Disposition string
+ // The Content-Disposition header field parameters.
+ DispositionParams map[string]string
+ // The Content-Language header field, if multipart.
+ Language []string
+ // The content URI, if multipart.
+ Location []string
+
+ // The MD5 checksum.
+ MD5 string
+}
+
+func (bs *BodyStructure) Parse(fields []interface{}) error {
+ if len(fields) == 0 {
+ return nil
+ }
+
+ // Initialize params map
+ bs.Params = make(map[string]string)
+
+ switch fields[0].(type) {
+ case []interface{}: // A multipart body part
+ bs.MIMEType = "multipart"
+
+ end := 0
+ for i, fi := range fields {
+ switch f := fi.(type) {
+ case []interface{}: // A part
+ part := new(BodyStructure)
+ if err := part.Parse(f); err != nil {
+ return err
+ }
+ bs.Parts = append(bs.Parts, part)
+ case string:
+ end = i
+ }
+
+ if end > 0 {
+ break
+ }
+ }
+
+ bs.MIMESubType, _ = fields[end].(string)
+ end++
+
+ // GMail seems to return only 3 extension data fields. Parse as many fields
+ // as we can.
+ if len(fields) > end {
+ bs.Extended = true // Contains extension data
+
+ params, _ := fields[end].([]interface{})
+ bs.Params, _ = parseHeaderParamList(params)
+ end++
+ }
+ if len(fields) > end {
+ if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
+ if s, ok := disp[0].(string); ok {
+ bs.Disposition, _ = decodeHeader(s)
+ bs.Disposition = strings.ToLower(bs.Disposition)
+ }
+ if params, ok := disp[1].([]interface{}); ok {
+ bs.DispositionParams, _ = parseHeaderParamList(params)
+ }
+ }
+ end++
+ }
+ if len(fields) > end {
+ switch langs := fields[end].(type) {
+ case string:
+ bs.Language = []string{langs}
+ case []interface{}:
+ bs.Language, _ = ParseStringList(langs)
+ default:
+ bs.Language = nil
+ }
+ end++
+ }
+ if len(fields) > end {
+ location, _ := fields[end].([]interface{})
+ bs.Location, _ = ParseStringList(location)
+ end++
+ }
+ case string: // A non-multipart body part
+ if len(fields) < 7 {
+ return errors.New("Non-multipart body part doesn't have 7 fields")
+ }
+
+ bs.MIMEType, _ = stringLowered(fields[0])
+ bs.MIMESubType, _ = stringLowered(fields[1])
+
+ params, _ := fields[2].([]interface{})
+ bs.Params, _ = parseHeaderParamList(params)
+
+ bs.Id, _ = fields[3].(string)
+ if desc, err := ParseString(fields[4]); err == nil {
+ bs.Description, _ = decodeHeader(desc)
+ }
+ bs.Encoding, _ = stringLowered(fields[5])
+ bs.Size, _ = ParseNumber(fields[6])
+
+ end := 7
+
+ // Type-specific fields
+ if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
+ if len(fields)-end < 3 {
+ return errors.New("Missing type-specific fields for message/rfc822")
+ }
+
+ envelope, _ := fields[end].([]interface{})
+ bs.Envelope = new(Envelope)
+ bs.Envelope.Parse(envelope)
+
+ structure, _ := fields[end+1].([]interface{})
+ bs.BodyStructure = new(BodyStructure)
+ bs.BodyStructure.Parse(structure)
+
+ bs.Lines, _ = ParseNumber(fields[end+2])
+
+ end += 3
+ }
+ if strings.EqualFold(bs.MIMEType, "text") {
+ if len(fields)-end < 1 {
+ return errors.New("Missing type-specific fields for text/*")
+ }
+
+ bs.Lines, _ = ParseNumber(fields[end])
+ end++
+ }
+
+ // GMail seems to return only 3 extension data fields. Parse as many fields
+ // as we can.
+ if len(fields) > end {
+ bs.Extended = true // Contains extension data
+
+ bs.MD5, _ = fields[end].(string)
+ end++
+ }
+ if len(fields) > end {
+ if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
+ if s, ok := disp[0].(string); ok {
+ bs.Disposition, _ = decodeHeader(s)
+ bs.Disposition = strings.ToLower(bs.Disposition)
+ }
+ if params, ok := disp[1].([]interface{}); ok {
+ bs.DispositionParams, _ = parseHeaderParamList(params)
+ }
+ }
+ end++
+ }
+ if len(fields) > end {
+ switch langs := fields[end].(type) {
+ case string:
+ bs.Language = []string{langs}
+ case []interface{}:
+ bs.Language, _ = ParseStringList(langs)
+ default:
+ bs.Language = nil
+ }
+ end++
+ }
+ if len(fields) > end {
+ location, _ := fields[end].([]interface{})
+ bs.Location, _ = ParseStringList(location)
+ end++
+ }
+ }
+
+ return nil
+}
+
+func (bs *BodyStructure) Format() (fields []interface{}) {
+ if strings.EqualFold(bs.MIMEType, "multipart") {
+ for _, part := range bs.Parts {
+ fields = append(fields, part.Format())
+ }
+
+ fields = append(fields, bs.MIMESubType)
+
+ if bs.Extended {
+ extended := make([]interface{}, 4)
+
+ if bs.Params != nil {
+ extended[0] = formatHeaderParamList(bs.Params)
+ }
+ if bs.Disposition != "" {
+ extended[1] = []interface{}{
+ encodeHeader(bs.Disposition),
+ formatHeaderParamList(bs.DispositionParams),
+ }
+ }
+ if bs.Language != nil {
+ extended[2] = FormatStringList(bs.Language)
+ }
+ if bs.Location != nil {
+ extended[3] = FormatStringList(bs.Location)
+ }
+
+ fields = append(fields, extended...)
+ }
+ } else {
+ fields = make([]interface{}, 7)
+ fields[0] = bs.MIMEType
+ fields[1] = bs.MIMESubType
+ fields[2] = formatHeaderParamList(bs.Params)
+
+ if bs.Id != "" {
+ fields[3] = bs.Id
+ }
+ if bs.Description != "" {
+ fields[4] = encodeHeader(bs.Description)
+ }
+ if bs.Encoding != "" {
+ fields[5] = bs.Encoding
+ }
+
+ fields[6] = bs.Size
+
+ // Type-specific fields
+ if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
+ var env interface{}
+ if bs.Envelope != nil {
+ env = bs.Envelope.Format()
+ }
+
+ var bsbs interface{}
+ if bs.BodyStructure != nil {
+ bsbs = bs.BodyStructure.Format()
+ }
+
+ fields = append(fields, env, bsbs, bs.Lines)
+ }
+ if strings.EqualFold(bs.MIMEType, "text") {
+ fields = append(fields, bs.Lines)
+ }
+
+ // Extension data
+ if bs.Extended {
+ extended := make([]interface{}, 4)
+
+ if bs.MD5 != "" {
+ extended[0] = bs.MD5
+ }
+ if bs.Disposition != "" {
+ extended[1] = []interface{}{
+ encodeHeader(bs.Disposition),
+ formatHeaderParamList(bs.DispositionParams),
+ }
+ }
+ if bs.Language != nil {
+ extended[2] = FormatStringList(bs.Language)
+ }
+ if bs.Location != nil {
+ extended[3] = FormatStringList(bs.Location)
+ }
+
+ fields = append(fields, extended...)
+ }
+ }
+
+ return
+}
+
+// Filename parses the body structure's filename, if it's an attachment. An
+// empty string is returned if the filename isn't specified. An error is
+// returned if and only if a charset error occurs, in which case the undecoded
+// filename is returned too.
+func (bs *BodyStructure) Filename() (string, error) {
+ raw, ok := bs.DispositionParams["filename"]
+ if !ok {
+ // Using "name" in Content-Type is discouraged
+ raw = bs.Params["name"]
+ }
+ return decodeHeader(raw)
+}
+
+// BodyStructureWalkFunc is the type of the function called for each body
+// structure visited by BodyStructure.Walk. The path argument contains the IMAP
+// part path (see BodyPartName).
+//
+// The function should return true to visit all of the part's children or false
+// to skip them.
+type BodyStructureWalkFunc func(path []int, part *BodyStructure) (walkChildren bool)
+
+// Walk walks the body structure tree, calling f for each part in the tree,
+// including bs itself. The parts are visited in DFS pre-order.
+func (bs *BodyStructure) Walk(f BodyStructureWalkFunc) {
+ // Non-multipart messages only have part 1
+ if len(bs.Parts) == 0 {
+ f([]int{1}, bs)
+ return
+ }
+
+ bs.walk(f, nil)
+}
+
+func (bs *BodyStructure) walk(f BodyStructureWalkFunc, path []int) {
+ if !f(path, bs) {
+ return
+ }
+
+ for i, part := range bs.Parts {
+ num := i + 1
+
+ partPath := append([]int(nil), path...)
+ partPath = append(partPath, num)
+
+ part.walk(f, partPath)
+ }
+}