1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
package backendutil
import (
"bytes"
"errors"
"io"
"mime"
nettextproto "net/textproto"
"strings"
"github.com/emersion/go-imap"
"github.com/emersion/go-message/textproto"
)
var errNoSuchPart = errors.New("backendutil: no such message body part")
func multipartReader(header textproto.Header, body io.Reader) *textproto.MultipartReader {
contentType := header.Get("Content-Type")
if !strings.HasPrefix(strings.ToLower(contentType), "multipart/") {
return nil
}
_, params, err := mime.ParseMediaType(contentType)
if err != nil {
return nil
}
return textproto.NewMultipartReader(body, params["boundary"])
}
// FetchBodySection extracts a body section from a message.
func FetchBodySection(header textproto.Header, body io.Reader, section *imap.BodySectionName) (imap.Literal, error) {
// First, find the requested part using the provided path
for i := 0; i < len(section.Path); i++ {
n := section.Path[i]
mr := multipartReader(header, body)
if mr == nil {
// First part of non-multipart message refers to the message itself.
// See RFC 3501, Page 55.
if len(section.Path) == 1 && section.Path[0] == 1 {
break
}
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 {
body = p
header = p.Header
break
}
}
}
// Then, write the requested data to a buffer
b := new(bytes.Buffer)
resHeader := header
if section.Fields != nil {
// Copy header so we will not change value passed to us.
resHeader = header.Copy()
if section.NotFields {
for _, fieldName := range section.Fields {
resHeader.Del(fieldName)
}
} else {
fieldsMap := make(map[string]struct{}, len(section.Fields))
for _, field := range section.Fields {
fieldsMap[nettextproto.CanonicalMIMEHeaderKey(field)] = struct{}{}
}
for field := resHeader.Fields(); field.Next(); {
if _, ok := fieldsMap[field.Key()]; !ok {
field.Del()
}
}
}
}
// Write the header
err := textproto.WriteHeader(b, resHeader)
if err != nil {
return nil, err
}
switch section.Specifier {
case imap.TextSpecifier:
// The header hasn't been requested. Discard it.
b.Reset()
case imap.EntireSpecifier:
if len(section.Path) > 0 {
// When selecting a specific part by index, IMAP servers
// return only the text, not the associated MIME header.
b.Reset()
}
}
// Write the body, if requested
switch section.Specifier {
case imap.EntireSpecifier, imap.TextSpecifier:
if _, err := io.Copy(b, 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
}
|