func NewMessageEncoder() *MessageEncoder { b := &bytes.Buffer{} return &MessageEncoder{ b, quotedprintable.NewWriter(b), } }
// Write the attachment to the specified multipart writer. func (a Attachment) Write(w *multipart.Writer) error { headers := make(textproto.MIMEHeader) if len(a.Filename) != 0 { headers.Add("Content-Type", fmt.Sprintf("%s; name=%s", a.ContentType, a.Filename)) } else { headers.Add("Content-Type", a.ContentType) } if a.Encoded { headers.Add("Content-Transfer-Encoding", "base64") } else { headers.Add("Content-Transfer-Encoding", "quoted-printable") } p, err := w.CreatePart(headers) if err != nil { return err } if a.Encoded { if _, err := p.Write([]byte(a.Content)); err != nil { return err } } else { q := quotedprintable.NewWriter(p) if _, err := q.Write([]byte(a.Content)); err != nil { return err } return q.Close() } return nil }
// *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" + "This is a test email to try and capture some of the corner cases that exist within\n" + "the quoted-printable encoding.\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") expected := []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") 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 ExampleNewWriter() { w := quotedprintable.NewWriter(os.Stdout) w.Write([]byte("These symbols will be escaped: = \t")) w.Close() // Output: // These symbols will be escaped: =3D =09 }
func EncodeQuotedPrintable(value []byte) []byte { b := bytes.Buffer{} enc := MessageEncoder{ &b, quotedprintable.NewWriter(&b), } enc.Write(value) return enc.Bytes() }
func EncodeQuotedPrintableString(value string) string { b := bytes.Buffer{} enc := MessageEncoder{ &b, quotedprintable.NewWriter(&b), } enc.Write([]byte(value)) return enc.String() }
// qpEncode uses the quoted-printable encoding to encode the provided text func qpEncode(text []byte) []byte { // create buffer buf := new(bytes.Buffer) encoder := quotedprintable.NewWriter(buf) encoder.Write(text) encoder.Close() return buf.Bytes() }
// writeText ... func (m *Message) writeText(w io.Writer, total int64) (int64, error) { written, err := io.WriteString(w, "Content-Transfer-Encoding: quoted-printable\r\n\r\n") total += int64(written) if err != nil { return total, err } // quotedprintable takes care of wrapping content at a good line length already qpWriter := quotedprintable.NewWriter(w) written, err = qpWriter.Write(m.Body) qpWriter.Close() // Must remember to close the wrapper, as it needs to flush to underlying writer return total + int64(written), err }
// Marshal builds a textual representation of a message with headers and quoted-printable body. // It ignores ReturnPath, HTML and Parts. func (m *Message) Marshal() ([]byte, error) { q := mime.QEncoding buf := new(bytes.Buffer) buf.WriteString(fmt.Sprintf("From: <%v>\n", m.From)) if len(m.To) > 0 { buf.WriteString("To: ") for i, v := range m.To { if i != 0 { buf.WriteString(", ") } buf.WriteString(fmt.Sprintf("<%v>", v)) } buf.WriteString("\n") } if len(m.CC) > 0 { buf.WriteString("CC: ") for i, v := range m.CC { if i != 0 { buf.WriteString(", ") } buf.WriteString(fmt.Sprintf("<%v>", v)) } buf.WriteString("\n") } buf.WriteString(fmt.Sprintf("Message-ID: %v\n", m.ID)) buf.WriteString(fmt.Sprintf("Subject: %v\n", q.Encode("utf-8", m.Subject))) buf.WriteString(fmt.Sprintf("Date: %v\n", m.Date.Format("Mon, 2 Jan 2006 15:04:05 -0700 (MST)"))) for k, v := range m.Headers { switch k { case "Content-Type", "Content-Transfer-Encoding", "Content-Disposition": continue } buf.WriteString(fmt.Sprintf("%v: %v\n", k, q.Encode("utf-8", v))) } if m.IsHTML { buf.WriteString("Content-Type: text/html; charset=utf-8;\n") } else { buf.WriteString("Content-Type: text/plain; charset=utf-8;\n") } buf.WriteString("Content-Transfer-Encoding: quoted-printable\n") buf.WriteString("\n") w := quotedprintable.NewWriter(buf) if _, err := w.Write([]byte(m.Body)); err != nil { return nil, err } if err := w.Close(); err != nil { return nil, err } return buf.Bytes(), nil }
func quotedBody(body []byte) ([]byte, error) { var buf bytes.Buffer w := quotedprintable.NewWriter(&buf) _, err := w.Write(body) if err != nil { return nil, err } err = w.Close() if err != nil { return nil, err } return buf.Bytes(), nil }
func main() { flag.Parse() var in io.Reader var out io.Writer if input == nil || strings.TrimSpace(*input) == "" { in = bufio.NewReader(os.Stdin) } else { fileBytes, err := ioutil.ReadFile(*input) if err != nil { log.Fatal(err) } in = bytes.NewReader(fileBytes) } if decode != nil && *decode == true { in = qp.NewReader(in) } inBytes, err := ioutil.ReadAll(in) if err != nil { log.Fatal(err) } if output == nil || strings.TrimSpace(*output) == "" { out = os.Stdout } else { out, err = os.OpenFile(*output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { log.Fatal(err) } } if decode == nil || *decode == false { out = qp.NewWriter(out) } n, err := out.Write(inBytes) if err != nil { log.Fatal(err) } if n != len(inBytes) { log.Fatalf("Partial write (%d != %d)\n", n, len(inBytes)) } }
func (m *MailerImpl) generateMessage(to []string, subject, body string) ([]byte, error) { mid := m.csprgSource.generate() now := m.clk.Now().UTC() addrs := []string{} for _, a := range to { if !isASCII(a) { return nil, fmt.Errorf("Non-ASCII email address") } addrs = append(addrs, strconv.Quote(a)) } headers := []string{ fmt.Sprintf("To: %s", strings.Join(addrs, ", ")), fmt.Sprintf("From: %s", m.from), fmt.Sprintf("Subject: %s", subject), fmt.Sprintf("Date: %s", now.Format(time.RFC822)), fmt.Sprintf("Message-Id: <%s.%s.%s>", now.Format("20060102T150405"), mid.String(), m.from), "MIME-Version: 1.0", "Content-Type: text/plain; charset=UTF-8", "Content-Transfer-Encoding: quoted-printable", } for i := range headers[1:] { // strip LFs headers[i] = strings.Replace(headers[i], "\n", "", -1) } bodyBuf := new(bytes.Buffer) mimeWriter := quotedprintable.NewWriter(bodyBuf) _, err := mimeWriter.Write([]byte(body)) if err != nil { return nil, err } err = mimeWriter.Close() if err != nil { return nil, err } return []byte(fmt.Sprintf( "%s\r\n\r\n%s\r\n", strings.Join(headers, "\r\n"), bodyBuf.String(), )), nil }
// Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc. func (e *Email) Bytes() ([]byte, error) { // TODO: better guess buffer size buff := bytes.NewBuffer(make([]byte, 0, 4096)) headers := e.msgHeaders() w := multipart.NewWriter(buff) // TODO: determine the content type based on message/attachment mix. headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary()) headerToBytes(buff, headers) io.WriteString(buff, "\r\n") // Start the multipart/mixed part fmt.Fprintf(buff, "--%s\r\n", w.Boundary()) header := textproto.MIMEHeader{} // Check to see if there is a Text or HTML field if len(e.Text) > 0 || len(e.HTML) > 0 { subWriter := multipart.NewWriter(buff) // Create the multipart alternative part header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary())) // Write the header headerToBytes(buff, header) // Create the body sections if len(e.Text) > 0 { header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8")) header.Set("Content-Transfer-Encoding", "quoted-printable") if _, err := subWriter.CreatePart(header); err != nil { return nil, err } qp := quotedprintable.NewWriter(buff) // Write the text if _, err := qp.Write(e.Text); err != nil { return nil, err } if err := qp.Close(); err != nil { return nil, err } } if len(e.HTML) > 0 { header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8")) header.Set("Content-Transfer-Encoding", "quoted-printable") if _, err := subWriter.CreatePart(header); err != nil { return nil, err } 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 } } if err := subWriter.Close(); err != nil { return nil, err } } // Create attachment part, if necessary for _, a := range e.Attachments { ap, err := w.CreatePart(a.Header) if err != nil { return nil, err } // Write the base64Wrapped content to the part base64Wrap(ap, a.Content) } if err := w.Close(); err != nil { return nil, err } return buff.Bytes(), nil }
func (e *Email) WriteTo(w io.Writer) (int64, error) { wc := &writeCounter{w: w} w = wc mpw := multipart.NewWriter(wc) // Write top-level headers. headers := e.Header headers.Set("Content-Type", fmt.Sprintf(`multipart/alternative; boundary="%s"`, mpw.Boundary())) headers.Set("MIME-Version", "1.0") for k, vv := range headers { for _, v := range vv { if _, err := fmt.Fprintf(w, "%s: %s\n", k, v); err != nil { return wc.n, err } } } if _, err := fmt.Fprintln(w); err != nil { return wc.n, err } // Write text part. textHeader := textproto.MIMEHeader{} textHeader.Set("Content-Type", `text/plain; charset="utf-8"; format="fixed"`) textHeader.Set("Content-Transfer-Encoding", "quoted-printable") pw, err := mpw.CreatePart(textHeader) if err != nil { return wc.n, fmt.Errorf("create text part: %s", err) } qpw := quotedprintable.NewWriter(pw) if _, err := qpw.Write(e.Text); err != nil { return wc.n, fmt.Errorf("write text part: %s", err) } if err := qpw.Close(); err != nil { return wc.n, fmt.Errorf("close text part: %s", err) } // Write top-level HTML multipart/related headers. // Chicken-and-egg: we need the inner multipart boundary to write the outer // part headers, but we can't get the boundary until we've constructed // multipart.Writer with a part writer. We'll generate the boundary ahead // of time and then manually supply it to the inner multipart.Writer. htmlRelatedBoundary := multipart.NewWriter(nil).Boundary() htmlRelatedHeader := textproto.MIMEHeader{} htmlRelatedHeader.Set("Content-Type", fmt.Sprintf(`multipart/related; boundary="%s"`, htmlRelatedBoundary)) htmlRelatedHeader.Set("MIME-Version", "1.0") pw, err = mpw.CreatePart(htmlRelatedHeader) if err != nil { return wc.n, fmt.Errorf("create html multipart part: %s", err) } // Create inner multipart data for HTML with attachments. htmlmpw := multipart.NewWriter(pw) htmlmpw.SetBoundary(htmlRelatedBoundary) if err != nil { return wc.n, fmt.Errorf("set html multipart boundary: %s", err) } // Write html part. htmlHeader := textproto.MIMEHeader{} htmlHeader.Set("Content-Type", `text/html; charset="utf-8"`) htmlHeader.Set("Content-Transfer-Encoding", "quoted-printable") htmlpw, err := htmlmpw.CreatePart(htmlHeader) if err != nil { return wc.n, fmt.Errorf("create html part: %s", err) } qpw = quotedprintable.NewWriter(htmlpw) if _, err := qpw.Write(e.HTML); err != nil { return wc.n, fmt.Errorf("write html part: %s", err) } if err := qpw.Close(); err != nil { return wc.n, fmt.Errorf("close html part: %s", err) } // Write attachments. for _, att := range e.Attachments { attHeader := textproto.MIMEHeader{} attHeader.Set("Content-ID", fmt.Sprintf("<%s>", att.ContentID)) attHeader.Set("Content-Type", mime.TypeByExtension(filepath.Ext(att.Name))) attHeader.Set("Content-Transfer-Encoding", "base64") attHeader.Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, att.Name)) htmlpw, err := htmlmpw.CreatePart(attHeader) if err != nil { return wc.n, fmt.Errorf("create attachment %s: %s", att.Name, err) } b64w := base64.NewEncoder(base64.StdEncoding, htmlpw) if _, err := b64w.Write(att.Content); err != nil { return wc.n, fmt.Errorf("write attachment %s: %s", att.Name, err) } if err := b64w.Close(); err != nil { return wc.n, fmt.Errorf("close attachment %s: %s", att.Name, err) } } // Finalize HTML multipart/related. if err := htmlmpw.Close(); err != nil { return wc.n, fmt.Errorf("multipart close: %s", err) } // Finalize. if err := mpw.Close(); err != nil { return wc.n, fmt.Errorf("multipart close: %s", err) } return wc.n, nil }