-
Notifications
You must be signed in to change notification settings - Fork 402
/
message.go
129 lines (107 loc) · 3.47 KB
/
message.go
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
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information
package post
import (
"bytes"
"fmt"
"io"
"mime"
"mime/multipart"
"mime/quotedprintable"
"net/textproto"
"time"
"github.com/zeebo/errs"
)
// Message is RFC compliant email message
type Message struct {
From Address
To []Address
Subject string
ID string
Date time.Time
ReceiptTo []string
PlainText string
Parts []Part
}
// Part represent one part of multipart message
type Part struct {
Type string
Encoding string
Disposition string
Content string
}
// Error is the default message errs class
var Error = errs.Class("Email message error")
// Bytes builds message and returns result as bytes
func (msg *Message) Bytes() (data []byte, err error) {
// always returns nil error on read and write, so most of the errors can be ignored
var body bytes.Buffer
// write headers
fmt.Fprintf(&body, "MIME-Version: 1.0\r\n")
fmt.Fprintf(&body, "Subject: %v\r\n", mime.QEncoding.Encode("utf-8", msg.Subject))
fmt.Fprintf(&body, "From: %s\r\n", &msg.From)
for _, to := range msg.To {
fmt.Fprintf(&body, "To: %s\r\n", &to) // nolint:scopelint
}
for _, recipient := range msg.ReceiptTo {
fmt.Fprintf(&body, "Disposition-Notification-To: <%v>\r\n", mime.QEncoding.Encode("utf-8", recipient))
}
// date and id are optional as they can be set by server itself
if !msg.Date.IsZero() {
fmt.Fprintf(&body, "Date: %v\r\n", msg.Date)
}
if msg.ID != "" {
fmt.Fprintf(&body, "Message-ID: <%v>\r\n", mime.QEncoding.Encode("utf-8", msg.ID))
}
switch {
// multipart upload
case len(msg.Parts) > 0:
wr := multipart.NewWriter(&body)
defer func() { err = errs.Combine(err, wr.Close()) }()
fmt.Fprintf(&body, "Content-Type: multipart/alternative;")
fmt.Fprintf(&body, "\tboundary=\"%v\"\r\n", wr.Boundary())
fmt.Fprintf(&body, "\r\n")
var sub io.Writer
if len(msg.PlainText) > 0 {
sub, err := wr.CreatePart(textproto.MIMEHeader{
"Content-Type": []string{"text/plain; charset=UTF-8; format=flowed"},
"Content-Transfer-Encoding": []string{"quoted-printable"},
})
if err != nil {
return nil, Error.Wrap(err)
}
enc := quotedprintable.NewWriter(sub)
defer func() { err = errs.Combine(err, enc.Close()) }()
_, err = enc.Write([]byte(msg.PlainText))
if err != nil {
return nil, Error.Wrap(err)
}
}
for _, part := range msg.Parts {
header := textproto.MIMEHeader{"Content-Type": []string{mime.QEncoding.Encode("utf-8", part.Type)}}
if part.Encoding != "" {
header["Content-Transfer-Encoding"] = []string{mime.QEncoding.Encode("utf-8", part.Encoding)}
}
if part.Disposition != "" {
header["Content-Disposition"] = []string{mime.QEncoding.Encode("utf-8", part.Disposition)}
}
sub, _ = wr.CreatePart(header)
fmt.Fprint(sub, part.Content)
}
// fallback if there are no parts, write PlainText with appropriate Content-Type
default:
fmt.Fprintf(&body, "Content-Type: text/plain; charset=UTF-8; format=flowed\r\n")
fmt.Fprintf(&body, "Content-Transfer-Encoding: quoted-printable\r\n\r\n")
enc := quotedprintable.NewWriter(&body)
defer func() { err = errs.Combine(err, enc.Close()) }()
if _, err := enc.Write([]byte(msg.PlainText)); err != nil {
return nil, Error.Wrap(err)
}
}
return tocrlf(body.Bytes()), nil
}
func tocrlf(data []byte) []byte {
lf := bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
crlf := bytes.Replace(lf, []byte("\n"), []byte("\r\n"), -1)
return crlf
}