فهرست منبع

Now using the standard library mime/quotedprintable package to encode/decode content
Fixed some incorrect tests

Jordan Wright 10 سال پیش
والد
کامیت
e95df3c231
2فایلهای تغییر یافته به همراه49 افزوده شده و 144 حذف شده
  1. 18 131
      email.go
  2. 31 13
      email_test.go

+ 18 - 131
email.go

@@ -10,6 +10,7 @@ import (
 	"io"
 	"mime"
 	"mime/multipart"
+	"mime/quotedprintable"
 	"net/mail"
 	"net/smtp"
 	"net/textproto"
@@ -156,8 +157,12 @@ func (e *Email) Bytes() ([]byte, error) {
 			if _, err := subWriter.CreatePart(header); err != nil {
 				return nil, err
 			}
+			qp := quotedprintable.NewWriter(buff)
 			// Write the text
-			if err := quotePrintEncode(buff, e.Text); err != nil {
+			if _, err := qp.Write(e.Text); err != nil {
+				return nil, err
+			}
+			if err := qp.Close(); err != nil {
 				return nil, err
 			}
 		}
@@ -167,8 +172,12 @@ func (e *Email) Bytes() ([]byte, error) {
 			if _, err := subWriter.CreatePart(header); err != nil {
 				return nil, err
 			}
-			// Write the text
-			if err := quotePrintEncode(buff, e.HTML); err != nil {
+			qp := quotedprintable.NewWriter(buff)
+			// Write the HTML
+			if _, err := qp.Write(e.HTML); err != nil {
+				return nil, err
+			}
+			if err := qp.Close(); err != nil {
 				return nil, err
 			}
 		}
@@ -227,74 +236,6 @@ type Attachment struct {
 	Content  []byte
 }
 
-// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
-func quotePrintEncode(w io.Writer, body []byte) error {
-	var buf [3]byte
-	mc := 0
-	for _, c := range body {
-		// We're assuming Unix style text formats as input (LF line break), and
-		// quoted-printable uses CRLF line breaks. (Literal CRs will become
-		// "=0D", but probably shouldn't be there to begin with!)
-		if c == '\n' {
-			io.WriteString(w, "\r\n")
-			mc = 0
-			continue
-		}
-
-		var nextOut []byte
-		if isPrintable[c] {
-			buf[0] = c
-			nextOut = buf[:1]
-		} else {
-			nextOut = buf[:]
-			qpEscape(nextOut, c)
-		}
-
-		// Add a soft line break if the next (encoded) byte would push this line
-		// to or past the limit.
-		if mc+len(nextOut) >= MaxLineLength {
-			if _, err := io.WriteString(w, "=\r\n"); err != nil {
-				return err
-			}
-			mc = 0
-		}
-
-		if _, err := w.Write(nextOut); err != nil {
-			return err
-		}
-		mc += len(nextOut)
-	}
-	// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
-	if mc > 0 {
-		io.WriteString(w, "=\r\n")
-	}
-	return nil
-}
-
-// isPrintable holds true if the byte given is "printable" according to RFC 2045, false otherwise
-var isPrintable [256]bool
-
-func init() {
-	for c := '!'; c <= '<'; c++ {
-		isPrintable[c] = true
-	}
-	for c := '>'; c <= '~'; c++ {
-		isPrintable[c] = true
-	}
-	isPrintable[' '] = true
-	isPrintable['\n'] = true
-	isPrintable['\t'] = true
-}
-
-// qpEscape is a helper function for quotePrintEncode which escapes a
-// non-printable byte. Expects len(dest) == 3.
-func qpEscape(dest []byte, c byte) {
-	const nums = "0123456789ABCDEF"
-	dest[0] = '='
-	dest[1] = nums[(c&0xf0)>>4]
-	dest[2] = nums[(c & 0xf)]
-}
-
 // base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
 // The output is then written to the specified io.Writer
 func base64Wrap(w io.Writer, b []byte) {
@@ -326,68 +267,14 @@ func headerToBytes(buff *bytes.Buffer, header textproto.MIMEHeader) {
 			// bytes.Buffer.Write() never returns an error.
 			io.WriteString(buff, field)
 			io.WriteString(buff, ": ")
-			buff.Write(encodeHeader(field, subval))
-			io.WriteString(buff, "\r\n")
-		}
-	}
-}
-
-// encodeHeader checks whether the header value needs to be encoded, and returns the header-safe byte stream.
-// If the field type is not encodable, or if the string contains only US-ASCII chars, the value is returned as is.
-func encodeHeader(field string, value string) []byte {
-	if field == "Content-Type" || field == "Content-Disposition" {
-		return []byte(value)
-	}
-	ascii := true
-	for i := 0; i < len(value); i++ {
-		if value[i] < ' ' || value[i] > '~' {
-			ascii = false
-			break
-		}
-	}
-	if ascii {
-		return []byte(value)
-	}
-	var b bytes.Buffer
-	encodeText(&b, value, true)
-	return b.Bytes()
-}
-
-// encodeText performs a UTF-8 "Q" encoding on the given string, according to RFC 2047.
-// Output bytes are written to "buff".
-func encodeText(buff *bytes.Buffer, s string, first bool) {
-	// First off, calculate the resulting encoded value's length.
-	encodedLen := 0
-	for i := 0; i < len(s); i++ {
-		if isPrintable[s[i]] {
-			encodedLen++
-		} else {
-			encodedLen = encodedLen + 3 // 1:3 conversion rate for Q encoding.
-		}
-	}
-	encodedLen = encodedLen + 12 // 12 = size of "=?UTF-8?Q?" + "?=
-
-	if encodedLen > MaxLineLength {
-		// Split the text (keeping multi-byte characters together), and recurse.
-		r := []rune(s)
-		encodeText(buff, string(r[:len(r)/2]), first)
-		encodeText(buff, string(r[len(r)/2:]), false)
-	} else {
-		if !first {
-			buff.WriteString("\r\n ")
-		}
-		buff.WriteString("=?UTF-8?Q?")
-
-		for i := 0; i < len(s); i++ {
-			switch c := s[i]; {
-			case c == ' ':
-				buff.WriteByte('_')
-			case isPrintable[c]:
-				buff.WriteByte(c)
+			// Write the encoded header if needed
+			switch {
+			case field == "Content-Type" || field == "Content-Disposition":
+				buff.Write([]byte(subval))
 			default:
-				fmt.Fprintf(buff, "=%02X", c)
+				buff.Write([]byte(mime.QEncoding.Encode("UTF-8", subval)))
 			}
+			io.WriteString(buff, "\r\n")
 		}
-		buff.WriteString("?=")
 	}
 }

+ 31 - 13
email_test.go

@@ -9,6 +9,7 @@ import (
 	"io/ioutil"
 	"mime"
 	"mime/multipart"
+	"mime/quotedprintable"
 	"net/mail"
 	"net/smtp"
 )
@@ -135,6 +136,7 @@ func Test_base64Wrap(t *testing.T) {
 	}
 }
 
+// *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
 func Test_quotedPrintEncode(t *testing.T) {
 	var buf bytes.Buffer
 	text := []byte("Dear reader!\n\n" +
@@ -147,30 +149,44 @@ func Test_quotedPrintEncode(t *testing.T) {
 		" within\r\n" +
 		"the quoted-printable encoding.\r\n" +
 		"There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
-		"s so=0D\r\n" +
+		"s so\r\n" +
 		"it can come out a little weird.  Also, we need to support unicode so here's=\r\n" +
 		" a fish: =F0=9F=90=9F\r\n")
-
-	if err := quotePrintEncode(&buf, text); err != nil {
+	qp := quotedprintable.NewWriter(&buf)
+	if _, err := qp.Write(text); err != nil {
 		t.Fatal("quotePrintEncode: ", err)
 	}
-
+	if err := qp.Close(); err != nil {
+		t.Fatal("Error closing writer", err)
+	}
 	if b := buf.Bytes(); !bytes.Equal(b, expected) {
 		t.Errorf("quotedPrintEncode generated incorrect results: %#q != %#q", b, expected)
 	}
 }
 
-func Benchmark_quotedPrintEncode(b *testing.B) {
-	text := []byte("Dear reader!\n\n" +
-		"This is a test email to try and capture some of the corner cases that exist within\n" +
-		"the quoted-printable encoding.\n" +
+// *Since the mime library in use by ```email``` is now in the stdlib, this test is deprecated
+func Test_quotedPrintDecode(t *testing.T) {
+	text := []byte("Dear reader!\r\n\r\n" +
+		"This is a test email to try and capture some of the corner cases that exist=\r\n" +
+		" within\r\n" +
+		"the quoted-printable encoding.\r\n" +
+		"There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
+		"s so\r\n" +
+		"it can come out a little weird.  Also, we need to support unicode so here's=\r\n" +
+		" a fish: =F0=9F=90=9F\r\n")
+	expected := []byte("Dear reader!\r\n\r\n" +
+		"This is a test email to try and capture some of the corner cases that exist within\r\n" +
+		"the quoted-printable encoding.\r\n" +
 		"There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
-		"it can come out a little weird.  Also, we need to support unicode so here's a fish: 🐟\n")
+		"it can come out a little weird.  Also, we need to support unicode so here's a fish: 🐟\r\n")
+	qp := quotedprintable.NewReader(bytes.NewReader(text))
+	got, err := ioutil.ReadAll(qp)
+	if err != nil {
+		t.Fatal("quotePrintDecode: ", err)
+	}
 
-	for i := 0; i <= b.N; i++ {
-		if err := quotePrintEncode(ioutil.Discard, text); err != nil {
-			panic(err)
-		}
+	if !bytes.Equal(got, expected) {
+		t.Errorf("quotedPrintDecode generated incorrect results: %#q != %#q", got, expected)
 	}
 }
 
@@ -185,6 +201,7 @@ func Benchmark_base64Wrap(b *testing.B) {
 	}
 }
 
+/*
 func Test_encodeHeader(t *testing.T) {
 	// Plain ASCII (unchanged).
 	subject := "Plain ASCII email subject, !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
@@ -206,3 +223,4 @@ func Test_encodeHeader(t *testing.T) {
 		t.Errorf("encodeHeader generated incorrect results: %#q != %#q", b, expected)
 	}
 }
+*/