parent
afff51fd8c
commit
060a5f4e98
|
@ -7,6 +7,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -150,7 +151,8 @@ func TestAlternative(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/alternative;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -180,7 +182,8 @@ func TestPartSetting(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/alternative;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
|
||||||
|
@ -216,7 +219,8 @@ func TestBodyWriter(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/alternative;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -267,7 +271,8 @@ func TestAttachment(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -298,7 +303,8 @@ func TestAttachmentsOnly(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
|
"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
|
||||||
|
@ -331,7 +337,8 @@ func TestAttachments(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -369,7 +376,8 @@ func TestEmbedded(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/related; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/related;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -410,13 +418,16 @@ func TestFullMessage(t *testing.T) {
|
||||||
to: []string{"to@example.com"},
|
to: []string{"to@example.com"},
|
||||||
content: "From: from@example.com\r\n" +
|
content: "From: from@example.com\r\n" +
|
||||||
"To: to@example.com\r\n" +
|
"To: to@example.com\r\n" +
|
||||||
"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_1_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_1_\r\n" +
|
"--_BOUNDARY_1_\r\n" +
|
||||||
"Content-Type: multipart/related; boundary=_BOUNDARY_2_\r\n" +
|
"Content-Type: multipart/related;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_2_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_2_\r\n" +
|
"--_BOUNDARY_2_\r\n" +
|
||||||
"Content-Type: multipart/alternative; boundary=_BOUNDARY_3_\r\n" +
|
"Content-Type: multipart/alternative;\r\n" +
|
||||||
|
" boundary=_BOUNDARY_3_\r\n" +
|
||||||
"\r\n" +
|
"\r\n" +
|
||||||
"--_BOUNDARY_3_\r\n" +
|
"--_BOUNDARY_3_\r\n" +
|
||||||
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
||||||
|
@ -660,6 +671,32 @@ func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (str
|
||||||
return name, f, SetHeader(h)
|
return name, f, SetHeader(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLineLength(t *testing.T) {
|
||||||
|
switch runtime.Version()[:5] {
|
||||||
|
case "go1.2", "go1.3", "go1.4", "go1.5":
|
||||||
|
t.Skip("Only pass with Go 1.6+")
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewMessage()
|
||||||
|
m.SetAddressHeader("From", "from@example.com", "Señor From")
|
||||||
|
m.SetHeader("Subject", "{$firstname} Bienvendio a Apostólica, aquí inicia el camino de tu")
|
||||||
|
m.SetBody("text/plain", strings.Repeat("a", 100))
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
m.WriteTo(buf)
|
||||||
|
n := 0
|
||||||
|
for _, b := range buf.Bytes() {
|
||||||
|
if b == '\n' {
|
||||||
|
n = 0
|
||||||
|
} else {
|
||||||
|
n++
|
||||||
|
if n == 80 {
|
||||||
|
t.Errorf("A line is too long:\n%s", buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkFull(b *testing.B) {
|
func BenchmarkFull(b *testing.B) {
|
||||||
discardFunc := SendFunc(func(from string, to []string, m io.WriterTo) error {
|
discardFunc := SendFunc(func(from string, to []string, m io.WriterTo) error {
|
||||||
_, err := m.WriteTo(ioutil.Discard)
|
_, err := m.WriteTo(ioutil.Discard)
|
||||||
|
|
2
mime.go
2
mime.go
|
@ -5,6 +5,7 @@ package gomail
|
||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
"mime/quotedprintable"
|
"mime/quotedprintable"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var newQPWriter = quotedprintable.NewWriter
|
var newQPWriter = quotedprintable.NewWriter
|
||||||
|
@ -16,4 +17,5 @@ type mimeEncoder struct {
|
||||||
var (
|
var (
|
||||||
bEncoding = mimeEncoder{mime.BEncoding}
|
bEncoding = mimeEncoder{mime.BEncoding}
|
||||||
qEncoding = mimeEncoder{mime.QEncoding}
|
qEncoding = mimeEncoder{mime.QEncoding}
|
||||||
|
lastIndexByte = strings.LastIndexByte
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,4 +13,13 @@ type mimeEncoder struct {
|
||||||
var (
|
var (
|
||||||
bEncoding = mimeEncoder{quotedprintable.BEncoding}
|
bEncoding = mimeEncoder{quotedprintable.BEncoding}
|
||||||
qEncoding = mimeEncoder{quotedprintable.QEncoding}
|
qEncoding = mimeEncoder{quotedprintable.QEncoding}
|
||||||
|
lastIndexByte = func(s string, c byte) int {
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
|
||||||
|
if s[i] == c {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
79
writeto.go
79
writeto.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ type messageWriter struct {
|
||||||
|
|
||||||
func (w *messageWriter) openMultipart(mimeType string) {
|
func (w *messageWriter) openMultipart(mimeType string) {
|
||||||
mw := multipart.NewWriter(w)
|
mw := multipart.NewWriter(w)
|
||||||
contentType := "multipart/" + mimeType + "; boundary=" + mw.Boundary()
|
contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
|
||||||
w.writers[w.depth] = mw
|
w.writers[w.depth] = mw
|
||||||
|
|
||||||
if w.depth == 0 {
|
if w.depth == 0 {
|
||||||
|
@ -163,17 +164,87 @@ func (w *messageWriter) writeString(s string) {
|
||||||
|
|
||||||
func (w *messageWriter) writeHeader(k string, v ...string) {
|
func (w *messageWriter) writeHeader(k string, v ...string) {
|
||||||
w.writeString(k)
|
w.writeString(k)
|
||||||
|
if len(v) == 0 {
|
||||||
|
w.writeString(":\r\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
w.writeString(": ")
|
w.writeString(": ")
|
||||||
if len(v) > 0 {
|
|
||||||
w.writeString(v[0])
|
// Max header line length is 78 characters in RFC 5322 and 76 characters
|
||||||
for _, s := range v[1:] {
|
// in RFC 2047. So for the sake of simplicity we use the 76 characters
|
||||||
|
// limit.
|
||||||
|
charsLeft := 76 - len(k) - len(": ")
|
||||||
|
|
||||||
|
for i, s := range v {
|
||||||
|
// If the line is already too long, insert a newline right away.
|
||||||
|
if charsLeft < 1 {
|
||||||
|
if i == 0 {
|
||||||
|
w.writeString("\r\n ")
|
||||||
|
} else {
|
||||||
|
w.writeString(",\r\n ")
|
||||||
|
}
|
||||||
|
charsLeft = 75
|
||||||
|
} else if i != 0 {
|
||||||
w.writeString(", ")
|
w.writeString(", ")
|
||||||
|
charsLeft -= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// While the header content is too long, fold it by inserting a newline.
|
||||||
|
for len(s) > charsLeft {
|
||||||
|
s = w.writeLine(s, charsLeft)
|
||||||
|
charsLeft = 75
|
||||||
|
}
|
||||||
w.writeString(s)
|
w.writeString(s)
|
||||||
|
if i := lastIndexByte(s, '\n'); i != -1 {
|
||||||
|
charsLeft = 75 - (len(s) - i - 1)
|
||||||
|
} else {
|
||||||
|
charsLeft -= len(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.writeString("\r\n")
|
w.writeString("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *messageWriter) writeLine(s string, charsLeft int) string {
|
||||||
|
// If there is already a newline before the limit. Write the line.
|
||||||
|
if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
|
||||||
|
w.writeString(s[:i+1])
|
||||||
|
return s[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := charsLeft - 1; i >= 0; i-- {
|
||||||
|
if s[i] == ' ' {
|
||||||
|
w.writeString(s[:i])
|
||||||
|
w.writeString("\r\n ")
|
||||||
|
return s[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No space found so insert a newline if it was not the start of the
|
||||||
|
// line.
|
||||||
|
if charsLeft != 75 {
|
||||||
|
w.writeString("\r\n ")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could not insert a newline cleanly so look for a space or a newline
|
||||||
|
// even if it is after the limit.
|
||||||
|
for i := 75; i < len(s); i++ {
|
||||||
|
if s[i] == ' ' {
|
||||||
|
w.writeString(s[:i])
|
||||||
|
w.writeString("\r\n ")
|
||||||
|
return s[i+1:]
|
||||||
|
}
|
||||||
|
if s[i] == '\n' {
|
||||||
|
w.writeString(s[:i+1])
|
||||||
|
return s[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Too bad, no space or newline in the whole string. Just write everything.
|
||||||
|
w.writeString(s)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (w *messageWriter) writeHeaders(h map[string][]string) {
|
func (w *messageWriter) writeHeaders(h map[string][]string) {
|
||||||
if w.depth == 0 {
|
if w.depth == 0 {
|
||||||
for k, v := range h {
|
for k, v := range h {
|
||||||
|
|
Loading…
Reference in New Issue