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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
package lib
import (
"bufio"
"bytes"
"io"
"mime"
"net/http"
"os"
"path/filepath"
"strings"
"git.sr.ht/~rjarry/aerc/lib/log"
"git.sr.ht/~rjarry/aerc/lib/xdg"
"github.com/emersion/go-message/mail"
"github.com/pkg/errors"
)
type Part struct {
MimeType string
Params map[string]string
Data []byte
Converted bool
ConversionError error
}
func NewPart(mimetype string, params map[string]string, body io.Reader,
) (*Part, error) {
var d []byte
var err error
var converted bool
if body == nil {
converted = true
} else {
d, err = io.ReadAll(body)
if err != nil {
return nil, err
}
}
return &Part{
MimeType: mimetype,
Params: params,
Data: d,
Converted: converted,
}, nil
}
func (p *Part) NewReader() io.Reader {
return bytes.NewReader(p.Data)
}
type Attachment interface {
Name() string
WriteTo(w *mail.Writer) error
}
type FileAttachment struct {
path string
}
func NewFileAttachment(path string) *FileAttachment {
return &FileAttachment{
path,
}
}
func (fa *FileAttachment) Name() string {
return fa.path
}
func (fa *FileAttachment) WriteTo(w *mail.Writer) error {
f, err := os.Open(xdg.ExpandHome(fa.path))
if err != nil {
return errors.Wrap(err, "os.Open")
}
defer f.Close()
reader := bufio.NewReader(f)
mimeType, params, err := FindMimeType(fa.path, reader)
if err != nil {
return errors.Wrap(err, "ParseMediaType")
}
filename := filepath.Base(fa.path)
params["name"] = filename
// set header fields
ah := mail.AttachmentHeader{}
ah.SetContentType(mimeType, params)
// setting the filename auto sets the content disposition
ah.SetFilename(filename)
fixContentTransferEncoding(mimeType, &ah)
aw, err := w.CreateAttachment(ah)
if err != nil {
return errors.Wrap(err, "CreateAttachment")
}
defer aw.Close()
if _, err := reader.WriteTo(aw); err != nil {
return errors.Wrap(err, "reader.WriteTo")
}
return nil
}
type PartAttachment struct {
part *Part
name string
}
func NewPartAttachment(part *Part, name string) *PartAttachment {
return &PartAttachment{
part,
name,
}
}
func (pa *PartAttachment) Name() string {
return pa.name
}
func (pa *PartAttachment) WriteTo(w *mail.Writer) error {
// set header fields
ah := mail.AttachmentHeader{}
ah.SetContentType(pa.part.MimeType, pa.part.Params)
// setting the filename auto sets the content disposition
ah.SetFilename(pa.Name())
fixContentTransferEncoding(pa.part.MimeType, &ah)
aw, err := w.CreateAttachment(ah)
if err != nil {
return errors.Wrap(err, "CreateAttachment")
}
defer aw.Close()
if _, err := io.Copy(aw, pa.part.NewReader()); err != nil {
return errors.Wrap(err, "io.Copy")
}
return nil
}
// SetUtf8Charset sets the charset in a params map to UTF-8.
func SetUtf8Charset(origParams map[string]string) map[string]string {
params := make(map[string]string)
for k, v := range origParams {
switch strings.ToLower(k) {
case "charset":
log.Debugf("substitute charset %s with utf-8", v)
params[k] = "utf-8"
default:
params[k] = v
}
}
return params
}
func FindMimeType(filename string, reader *bufio.Reader) (string, map[string]string, error) {
// if we have an extension, prefer that instead of trying to sniff the header.
// That's generally more accurate than sniffing as lots of things are zip files
// under the hood, e.g. most office file types
ext := filepath.Ext(filename)
var mimeString string
if mimeString = mime.TypeByExtension(ext); mimeString == "" {
// Sniff the mime type since it's not in the database
// http.DetectContentType only cares about the first 512 bytes
head, err := reader.Peek(512)
if err != nil && err != io.EOF {
return "", map[string]string{}, errors.Wrap(err, "Peek")
}
mimeString = http.DetectContentType(head)
}
// mimeString can contain type and params (like text encoding),
// so we need to break them apart before passing them to the headers
return mime.ParseMediaType(mimeString)
}
// fixContentTransferEncoding checks the mime type of the attachment and
// corrects the content-transfer-encoding if necessary.
//
// It's expressly forbidden by RFC2046 to set any other
// content-transfer-encoding than 7bit, 8bit, or binary for
// message/rfc822 mime types (see RFC2046, section 5.2.1)
func fixContentTransferEncoding(mimeType string, header *mail.AttachmentHeader) {
if strings.ToLower(mimeType) == "message/rfc822" {
header.Add("Content-Transfer-Encoding", "binary")
}
}
|