Provided a way to specify the encoding of a message part

This commit introduces a minor backward-compatibility break:
Message.SetBody, Message.AddAlternative and
Message.AddAlternativeWriter now accept a new variadic argument.

Since this argument is optional, in almost all cases there is
nothing to change. That is why Gomail version number wasn't bumped.

Fixes #47
This commit is contained in:
Alexandre Cesaro 2015-12-12 16:40:02 +01:00
parent 19acfc29a0
commit db70192787
4 changed files with 84 additions and 32 deletions

View File

@ -213,3 +213,7 @@ func ExampleSetCharset() {
func ExampleSetEncoding() {
m = gomail.NewMessage(gomail.SetEncoding(gomail.Base64))
}
func ExampleWithEncoding() {
m.SetBody("text/plain", "Hello!", gomail.SetPartEncoding(gomail.Unencoded))
}

View File

@ -11,7 +11,7 @@ import (
// Message represents an email.
type Message struct {
header header
parts []part
parts []*part
attachments []*file
embedded []*file
charset string
@ -23,8 +23,9 @@ type Message struct {
type header map[string][]string
type part struct {
header header
contentType string
copier func(io.Writer) error
encoding Encoding
}
// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
@ -179,16 +180,8 @@ func (m *Message) GetHeader(field string) []string {
// SetBody sets the body of the message. It replaces any content previously set
// by SetBody, AddAlternative or AddAlternativeWriter.
func (m *Message) SetBody(contentType, body string) {
m.parts = []part{
{
header: m.getPartHeader(contentType),
copier: func(w io.Writer) error {
_, err := io.WriteString(w, body)
return err
},
},
}
func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
m.parts = []*part{m.newPart(contentType, newCopier(body), settings)}
}
// AddAlternative adds an alternative part to the message.
@ -197,30 +190,48 @@ func (m *Message) SetBody(contentType, body string) {
// version for backward compatibility. AddAlternative appends the new part to
// the end of the message. So the plain text part should be added before the
// HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
func (m *Message) AddAlternative(contentType, body string) {
m.parts = append(m.parts, part{
header: m.getPartHeader(contentType),
copier: func(w io.Writer) error {
_, err := io.WriteString(w, body)
func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
m.AddAlternativeWriter(contentType, newCopier(body), settings...)
}
func newCopier(s string) func(io.Writer) error {
return func(w io.Writer) error {
_, err := io.WriteString(w, s)
return err
},
})
}
}
// AddAlternativeWriter adds an alternative part to the message. It can be
// useful with the text/template or html/template packages.
func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error) {
m.parts = append(m.parts, part{
header: m.getPartHeader(contentType),
copier: f,
})
func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
m.parts = append(m.parts, m.newPart(contentType, f, settings))
}
func (m *Message) getPartHeader(contentType string) header {
return map[string][]string{
"Content-Type": {contentType + "; charset=" + m.charset},
"Content-Transfer-Encoding": {string(m.encoding)},
func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
p := &part{
contentType: contentType,
copier: f,
encoding: m.encoding,
}
for _, s := range settings {
s(p)
}
return p
}
// A PartSetting can be used as an argument in Message.SetBody,
// Message.AddAlternative or Message.AddAlternativeWriter to configure the part
// added to a message.
type PartSetting func(*part)
// SetPartEncoding sets the encoding of the part added to the message. By
// default, parts use the same encoding than the message.
func SetPartEncoding(e Encoding) PartSetting {
return PartSetting(func(p *part) {
p.encoding = e
})
}
type file struct {

View File

@ -168,6 +168,36 @@ func TestAlternative(t *testing.T) {
testMessage(t, m, 1, want)
}
func TestPartSetting(t *testing.T) {
m := NewMessage()
m.SetHeader("From", "from@example.com")
m.SetHeader("To", "to@example.com")
m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded))
m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
want := &message{
from: "from@example.com",
to: []string{"to@example.com"},
content: "From: from@example.com\r\n" +
"To: to@example.com\r\n" +
"Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" +
"\r\n" +
"--_BOUNDARY_1_\r\n" +
"Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"¡Hola, señor!\r\n" +
"--_BOUNDARY_1_\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
"--_BOUNDARY_1_--\r\n",
}
testMessage(t, m, 1, want)
}
func TestBodyWriter(t *testing.T) {
m := NewMessage()
m.SetHeader("From", "from@example.com")

View File

@ -38,8 +38,7 @@ func (w *messageWriter) writeMessage(m *Message) {
w.openMultipart("alternative")
}
for _, part := range m.parts {
w.writeHeaders(part.header)
w.writeBody(part.copier, m.encoding)
w.writePart(part, m.charset)
}
if m.hasAlternativePart() {
w.closeMultipart()
@ -104,6 +103,14 @@ func (w *messageWriter) closeMultipart() {
}
}
func (w *messageWriter) writePart(p *part, charset string) {
w.writeHeaders(map[string][]string{
"Content-Type": {p.contentType + "; charset=" + charset},
"Content-Transfer-Encoding": {string(p.encoding)},
})
w.writeBody(p.copier, p.encoding)
}
func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
for _, f := range files {
if _, ok := f.Header["Content-Type"]; !ok {