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() { func ExampleSetEncoding() {
m = gomail.NewMessage(gomail.SetEncoding(gomail.Base64)) 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. // Message represents an email.
type Message struct { type Message struct {
header header header header
parts []part parts []*part
attachments []*file attachments []*file
embedded []*file embedded []*file
charset string charset string
@ -23,8 +23,9 @@ type Message struct {
type header map[string][]string type header map[string][]string
type part struct { type part struct {
header header contentType string
copier func(io.Writer) error copier func(io.Writer) error
encoding Encoding
} }
// NewMessage creates a new message. It uses UTF-8 and quoted-printable 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 // SetBody sets the body of the message. It replaces any content previously set
// by SetBody, AddAlternative or AddAlternativeWriter. // by SetBody, AddAlternative or AddAlternativeWriter.
func (m *Message) SetBody(contentType, body string) { func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
m.parts = []part{ m.parts = []*part{m.newPart(contentType, newCopier(body), settings)}
{
header: m.getPartHeader(contentType),
copier: func(w io.Writer) error {
_, err := io.WriteString(w, body)
return err
},
},
}
} }
// AddAlternative adds an alternative part to the message. // 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 // 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 // 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 // HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
func (m *Message) AddAlternative(contentType, body string) { func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
m.parts = append(m.parts, part{ m.AddAlternativeWriter(contentType, newCopier(body), settings...)
header: m.getPartHeader(contentType), }
copier: func(w io.Writer) error {
_, err := io.WriteString(w, body) func newCopier(s string) func(io.Writer) error {
return func(w io.Writer) error {
_, err := io.WriteString(w, s)
return err return err
}, }
})
} }
// AddAlternativeWriter adds an alternative part to the message. It can be // AddAlternativeWriter adds an alternative part to the message. It can be
// useful with the text/template or html/template packages. // useful with the text/template or html/template packages.
func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error) { func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
m.parts = append(m.parts, part{ m.parts = append(m.parts, m.newPart(contentType, f, settings))
header: m.getPartHeader(contentType),
copier: f,
})
} }
func (m *Message) getPartHeader(contentType string) header { func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
return map[string][]string{ p := &part{
"Content-Type": {contentType + "; charset=" + m.charset}, contentType: contentType,
"Content-Transfer-Encoding": {string(m.encoding)}, 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 { type file struct {

View File

@ -168,6 +168,36 @@ func TestAlternative(t *testing.T) {
testMessage(t, m, 1, want) 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) { func TestBodyWriter(t *testing.T) {
m := NewMessage() m := NewMessage()
m.SetHeader("From", "from@example.com") m.SetHeader("From", "from@example.com")

View File

@ -38,8 +38,7 @@ func (w *messageWriter) writeMessage(m *Message) {
w.openMultipart("alternative") w.openMultipart("alternative")
} }
for _, part := range m.parts { for _, part := range m.parts {
w.writeHeaders(part.header) w.writePart(part, m.charset)
w.writeBody(part.copier, m.encoding)
} }
if m.hasAlternativePart() { if m.hasAlternativePart() {
w.closeMultipart() 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) { func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
for _, f := range files { for _, f := range files {
if _, ok := f.Header["Content-Type"]; !ok { if _, ok := f.Header["Content-Type"]; !ok {