1187 lines
28 KiB
Go
1187 lines
28 KiB
Go
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)
|
|
}
|
|
}
|