mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-09 15:32:54 +01:00
Implemented MIME multipart handling for alternative content
This commit is contained in:
parent
a85b761f43
commit
8c804ec573
9 changed files with 303 additions and 38 deletions
|
@ -6,6 +6,9 @@ The main idea of this library was to provide a simple interface to sending mails
|
||||||
my [JS-Mailer](https://github.com/wneessen/js-mailer) project. It quickly evolved into a
|
my [JS-Mailer](https://github.com/wneessen/js-mailer) project. It quickly evolved into a
|
||||||
full-fledged mail library.
|
full-fledged mail library.
|
||||||
|
|
||||||
|
Parts (especially the msgWriter) of this library have been ported from the [GoMail](https://github.com/go-mail/mail)
|
||||||
|
which seems to not be maintained anymore.
|
||||||
|
|
||||||
**This library is "WIP" an should not be considered "production ready", yet.**
|
**This library is "WIP" an should not be considered "production ready", yet.**
|
||||||
|
|
||||||
go-mail follows idiomatic Go style and best practice. It's only dependency is the Go Standard Library.
|
go-mail follows idiomatic Go style and best practice. It's only dependency is the Go Standard Library.
|
||||||
|
|
14
client.go
14
client.go
|
@ -298,16 +298,10 @@ func (c *Client) Send(ml ...*Msg) error {
|
||||||
return fmt.Errorf("sending RCPT TO command failed: %w", err)
|
return fmt.Errorf("sending RCPT TO command failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w := os.Stderr
|
w, err := c.sc.Data()
|
||||||
|
if err != nil {
|
||||||
/*
|
return fmt.Errorf("sending DATA command failed: %w", err)
|
||||||
w, err := c.sc.Data()
|
}
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("sending DATA command failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
_, err = m.Write(w)
|
_, err = m.Write(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sending mail content failed: %w", err)
|
return fmt.Errorf("sending mail content failed: %w", err)
|
||||||
|
|
13
cmd/main.go
13
cmd/main.go
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/wneessen/go-mail"
|
"github.com/wneessen/go-mail"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,8 +13,6 @@ func main() {
|
||||||
fmt.Printf("$TEST_HOST env variable cannot be empty\n")
|
fmt.Printf("$TEST_HOST env variable cannot be empty\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
tu := os.Getenv("TEST_USER")
|
|
||||||
tp := os.Getenv("TEST_PASS")
|
|
||||||
|
|
||||||
fa := "Winni Neessen <wn@neessen.cloud>"
|
fa := "Winni Neessen <wn@neessen.cloud>"
|
||||||
toa := "Winfried Neessen <wn@neessen.net>"
|
toa := "Winfried Neessen <wn@neessen.net>"
|
||||||
|
@ -37,6 +36,15 @@ func main() {
|
||||||
m.SetDate()
|
m.SetDate()
|
||||||
m.SetBulk()
|
m.SetBulk()
|
||||||
|
|
||||||
|
m.SetBodyWriter(mail.TypeTextPlain, func(fw io.Writer) error {
|
||||||
|
_, err := io.WriteString(fw, "This is a writer test")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
m.AddAlternativeString(mail.TypeTextHTML, "This is HTML content")
|
||||||
|
//m.Write(os.Stdout)
|
||||||
|
|
||||||
|
tu := os.Getenv("TEST_USER")
|
||||||
|
tp := os.Getenv("TEST_PASS")
|
||||||
c, err := mail.NewClient(th, mail.WithTLSPolicy(mail.TLSMandatory),
|
c, err := mail.NewClient(th, mail.WithTLSPolicy(mail.TLSMandatory),
|
||||||
mail.WithSMTPAuth(mail.SMTPAuthLogin), mail.WithUsername(tu),
|
mail.WithSMTPAuth(mail.SMTPAuthLogin), mail.WithUsername(tu),
|
||||||
mail.WithPassword(tp))
|
mail.WithPassword(tp))
|
||||||
|
@ -48,4 +56,5 @@ func main() {
|
||||||
fmt.Printf("failed to dial: %s\n", err)
|
fmt.Printf("failed to dial: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
33
encoding.go
33
encoding.go
|
@ -1,10 +1,19 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
|
// Charset represents a character set for the encoding
|
||||||
|
type Charset string
|
||||||
|
|
||||||
|
// ContentType represents a content type for the Msg
|
||||||
|
type ContentType string
|
||||||
|
|
||||||
// Encoding represents a MIME encoding scheme like quoted-printable or Base64.
|
// Encoding represents a MIME encoding scheme like quoted-printable or Base64.
|
||||||
type Encoding string
|
type Encoding string
|
||||||
|
|
||||||
// Charset represents a character set for the encodingA
|
// MIMEVersion represents the MIME version for the mail
|
||||||
type Charset string
|
type MIMEVersion string
|
||||||
|
|
||||||
|
// MIMEType represents the MIME type for the mail
|
||||||
|
type MIMEType string
|
||||||
|
|
||||||
// List of supported encodings
|
// List of supported encodings
|
||||||
const (
|
const (
|
||||||
|
@ -117,6 +126,26 @@ const (
|
||||||
CharsetGBK Charset = "GBK"
|
CharsetGBK Charset = "GBK"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// List of MIME versions
|
||||||
|
const (
|
||||||
|
//Mime10 is the MIME Version 1.0
|
||||||
|
Mime10 MIMEVersion = "1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List of common content types
|
||||||
|
const (
|
||||||
|
TypeTextPlain ContentType = "text/plain"
|
||||||
|
TypeTextHTML ContentType = "text/html"
|
||||||
|
TypeAppOctetStream ContentType = "application/octet-stream"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List of MIMETypes
|
||||||
|
const (
|
||||||
|
MIMEAlternative MIMEType = "alternative"
|
||||||
|
MIMEMixed MIMEType = "mixed"
|
||||||
|
MIMERelated MIMEType = "related"
|
||||||
|
)
|
||||||
|
|
||||||
// String is a standard method to convert an Encoding into a printable format
|
// String is a standard method to convert an Encoding into a printable format
|
||||||
func (e Encoding) String() string {
|
func (e Encoding) String() string {
|
||||||
return string(e)
|
return string(e)
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -1,3 +1,10 @@
|
||||||
module github.com/wneessen/go-mail
|
module github.com/wneessen/go-mail
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/go-mail/mail/v2 v2.3.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/mail.v2 v2.3.1 // indirect
|
||||||
|
)
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -0,0 +1,6 @@
|
||||||
|
github.com/go-mail/mail/v2 v2.3.0 h1:wha99yf2v3cpUzD1V9ujP404Jbw2uEvs+rBJybkdYcw=
|
||||||
|
github.com/go-mail/mail/v2 v2.3.0/go.mod h1:oE2UK8qebZAjjV1ZYUpY7FPnbi/kIU53l1dmqPRb4go=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
|
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||||
|
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
|
@ -8,9 +8,15 @@ type AddrHeader string
|
||||||
|
|
||||||
// List of common generic header field names
|
// List of common generic header field names
|
||||||
const (
|
const (
|
||||||
|
// HeaderContentDisposition is the "Content-Disposition" header
|
||||||
|
HeaderContentDisposition Header = "Content-Disposition"
|
||||||
|
|
||||||
// HeaderContentLang is the "Content-Language" header
|
// HeaderContentLang is the "Content-Language" header
|
||||||
HeaderContentLang Header = "Content-Language"
|
HeaderContentLang Header = "Content-Language"
|
||||||
|
|
||||||
|
// HeaderContentTransferEnc is the "Content-Transfer-Encoding" header
|
||||||
|
HeaderContentTransferEnc Header = "Content-Transfer-Encoding"
|
||||||
|
|
||||||
// HeaderContentType is the "Content-Type" header
|
// HeaderContentType is the "Content-Type" header
|
||||||
HeaderContentType Header = "Content-Type"
|
HeaderContentType Header = "Content-Type"
|
||||||
|
|
||||||
|
|
131
msg.go
131
msg.go
|
@ -1,6 +1,7 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -21,6 +22,12 @@ var (
|
||||||
|
|
||||||
// Msg is the mail message struct
|
// Msg is the mail message struct
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
|
// addrHeader is a slice of strings that the different mail AddrHeader fields
|
||||||
|
addrHeader map[AddrHeader][]*mail.Address
|
||||||
|
|
||||||
|
// boundary is the MIME content boundary
|
||||||
|
boundary string
|
||||||
|
|
||||||
// charset represents the charset of the mail (defaults to UTF-8)
|
// charset represents the charset of the mail (defaults to UTF-8)
|
||||||
charset Charset
|
charset Charset
|
||||||
|
|
||||||
|
@ -33,20 +40,35 @@ type Msg struct {
|
||||||
// genHeader is a slice of strings that the different generic mail Header fields
|
// genHeader is a slice of strings that the different generic mail Header fields
|
||||||
genHeader map[Header][]string
|
genHeader map[Header][]string
|
||||||
|
|
||||||
// addrHeader is a slice of strings that the different mail AddrHeader fields
|
// mimever represents the MIME version
|
||||||
addrHeader map[AddrHeader][]*mail.Address
|
mimever MIMEVersion
|
||||||
|
|
||||||
|
// parts represent the different parts of the Msg
|
||||||
|
parts []*Part
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part is a part of the Msg
|
||||||
|
type Part struct {
|
||||||
|
w func(io.Writer) error
|
||||||
|
x io.Writer
|
||||||
|
ctype ContentType
|
||||||
|
enc Encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
// PartOption returns a function that can be used for grouping Part options
|
||||||
|
type PartOption func(*Part)
|
||||||
|
|
||||||
// MsgOption returns a function that can be used for grouping Msg options
|
// MsgOption returns a function that can be used for grouping Msg options
|
||||||
type MsgOption func(*Msg)
|
type MsgOption func(*Msg)
|
||||||
|
|
||||||
// NewMsg returns a new Msg pointer
|
// NewMsg returns a new Msg pointer
|
||||||
func NewMsg(o ...MsgOption) *Msg {
|
func NewMsg(o ...MsgOption) *Msg {
|
||||||
m := &Msg{
|
m := &Msg{
|
||||||
encoding: EncodingQP,
|
|
||||||
charset: CharsetUTF8,
|
|
||||||
genHeader: make(map[Header][]string),
|
|
||||||
addrHeader: make(map[AddrHeader][]*mail.Address),
|
addrHeader: make(map[AddrHeader][]*mail.Address),
|
||||||
|
charset: CharsetUTF8,
|
||||||
|
encoding: EncodingQP,
|
||||||
|
genHeader: make(map[Header][]string),
|
||||||
|
mimever: Mime10,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided MsgOption functions
|
// Override defaults with optionally provided MsgOption functions
|
||||||
|
@ -77,6 +99,13 @@ func WithEncoding(e Encoding) MsgOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithMIMEVersion overrides the default MIME version
|
||||||
|
func WithMIMEVersion(mv MIMEVersion) MsgOption {
|
||||||
|
return func(m *Msg) {
|
||||||
|
m.mimever = mv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetCharset sets the encoding charset of the Msg
|
// SetCharset sets the encoding charset of the Msg
|
||||||
func (m *Msg) SetCharset(c Charset) {
|
func (m *Msg) SetCharset(c Charset) {
|
||||||
m.charset = c
|
m.charset = c
|
||||||
|
@ -87,6 +116,16 @@ func (m *Msg) SetEncoding(e Encoding) {
|
||||||
m.encoding = e
|
m.encoding = e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBoundary sets the boundary of the Msg
|
||||||
|
func (m *Msg) SetBoundary(b string) {
|
||||||
|
m.boundary = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMIMEVersion sets the MIME version of the Msg
|
||||||
|
func (m *Msg) SetMIMEVersion(mv MIMEVersion) {
|
||||||
|
m.mimever = mv
|
||||||
|
}
|
||||||
|
|
||||||
// Encoding returns the currently set encoding of the Msg
|
// Encoding returns the currently set encoding of the Msg
|
||||||
func (m *Msg) Encoding() string {
|
func (m *Msg) Encoding() string {
|
||||||
return m.encoding.String()
|
return m.encoding.String()
|
||||||
|
@ -294,6 +333,40 @@ func (m *Msg) GetRecipients() ([]string, error) {
|
||||||
return rl, nil
|
return rl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBodyString sets the body of the message.
|
||||||
|
func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) {
|
||||||
|
buf := bytes.NewBufferString(b)
|
||||||
|
w := func(w io.Writer) error {
|
||||||
|
_, err := io.Copy(w, buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.SetBodyWriter(ct, w, o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBodyWriter sets the body of the message.
|
||||||
|
func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
||||||
|
p := m.NewPart(ct, o...)
|
||||||
|
p.w = w
|
||||||
|
m.parts = []*Part{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAlternativeString sets the alternative body of the message.
|
||||||
|
func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) {
|
||||||
|
buf := bytes.NewBufferString(b)
|
||||||
|
w := func(w io.Writer) error {
|
||||||
|
_, err := io.Copy(w, buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.AddAlternativeWriter(ct, w, o...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAlternativeWriter sets the body of the message.
|
||||||
|
func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) error, o ...PartOption) {
|
||||||
|
p := m.NewPart(ct, o...)
|
||||||
|
p.w = w
|
||||||
|
m.parts = append(m.parts, p)
|
||||||
|
}
|
||||||
|
|
||||||
// Write writes the formated Msg into a give io.Writer
|
// Write writes the formated Msg into a give io.Writer
|
||||||
func (m *Msg) Write(w io.Writer) (int64, error) {
|
func (m *Msg) Write(w io.Writer) (int64, error) {
|
||||||
mw := &msgWriter{w: w}
|
mw := &msgWriter{w: w}
|
||||||
|
@ -301,15 +374,50 @@ func (m *Msg) Write(w io.Writer) (int64, error) {
|
||||||
return mw.n, mw.err
|
return mw.n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPart returns a new Part for the Msg
|
||||||
|
func (m *Msg) NewPart(ct ContentType, o ...PartOption) *Part {
|
||||||
|
p := &Part{
|
||||||
|
ctype: ct,
|
||||||
|
enc: m.encoding,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override defaults with optionally provided MsgOption functions
|
||||||
|
for _, co := range o {
|
||||||
|
if co == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
co(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPartEncoding overrides the default Part encoding
|
||||||
|
func WithPartEncoding(e Encoding) PartOption {
|
||||||
|
return func(p *Part) {
|
||||||
|
p.enc = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEncoding creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
|
func (p *Part) SetEncoding(e Encoding) {
|
||||||
|
p.enc = e
|
||||||
|
}
|
||||||
|
|
||||||
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
// setEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
func (m *Msg) setEncoder() {
|
func (m *Msg) setEncoder() {
|
||||||
switch m.encoding {
|
m.encoder = getEncoder(m.encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEncoder creates a new mime.WordEncoder based on the encoding setting of the message
|
||||||
|
func getEncoder(e Encoding) mime.WordEncoder {
|
||||||
|
switch e {
|
||||||
case EncodingQP:
|
case EncodingQP:
|
||||||
m.encoder = mime.QEncoding
|
return mime.QEncoding
|
||||||
case EncodingB64:
|
case EncodingB64:
|
||||||
m.encoder = mime.BEncoding
|
return mime.BEncoding
|
||||||
default:
|
default:
|
||||||
m.encoder = mime.QEncoding
|
return mime.QEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,3 +426,8 @@ func (m *Msg) setEncoder() {
|
||||||
func (m *Msg) encodeString(s string) string {
|
func (m *Msg) encodeString(s string) string {
|
||||||
return m.encoder.Encode(string(m.charset), s)
|
return m.encoder.Encode(string(m.charset), s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasAlt returns true if the Msg has more than one part
|
||||||
|
func (m *Msg) hasAlt() bool {
|
||||||
|
return len(m.parts) > 1
|
||||||
|
}
|
||||||
|
|
128
msgwriter.go
128
msgwriter.go
|
@ -1,7 +1,12 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"mime/quotedprintable"
|
||||||
|
"net/textproto"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,17 +16,29 @@ const MaxHeaderLength = 76
|
||||||
|
|
||||||
// msgWriter handles the I/O to the io.WriteCloser of the SMTP client
|
// msgWriter handles the I/O to the io.WriteCloser of the SMTP client
|
||||||
type msgWriter struct {
|
type msgWriter struct {
|
||||||
w io.Writer
|
d int8
|
||||||
n int64
|
|
||||||
//writers [3]*multipart.Writer
|
|
||||||
//partWriter io.Writer
|
|
||||||
//depth uint8
|
|
||||||
err error
|
err error
|
||||||
|
mpw [3]*multipart.Writer
|
||||||
|
n int64
|
||||||
|
pw io.Writer
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the io.Writer interface for msgWriter
|
||||||
|
func (mw *msgWriter) Write(p []byte) (int, error) {
|
||||||
|
if mw.err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to write due to previous error: %w", mw.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, mw.err = mw.w.Write(p)
|
||||||
|
mw.n += int64(n)
|
||||||
|
return n, mw.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMsg formats the message and sends it to its io.Writer
|
// writeMsg formats the message and sends it to its io.Writer
|
||||||
func (mw *msgWriter) writeMsg(m *Msg) {
|
func (mw *msgWriter) writeMsg(m *Msg) {
|
||||||
if _, ok := m.genHeader["Date"]; !ok {
|
if _, ok := m.genHeader[HeaderDate]; !ok {
|
||||||
m.SetDate()
|
m.SetDate()
|
||||||
}
|
}
|
||||||
for k, v := range m.genHeader {
|
for k, v := range m.genHeader {
|
||||||
|
@ -36,8 +53,72 @@ func (mw *msgWriter) writeMsg(m *Msg) {
|
||||||
mw.writeHeader(Header(t), v...)
|
mw.writeHeader(Header(t), v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mw.writeString("\r\n")
|
mw.writeHeader(HeaderMIMEVersion, string(m.mimever))
|
||||||
mw.writeString("This is a test mail")
|
|
||||||
|
if m.hasAlt() {
|
||||||
|
mw.startMP(MIMEAlternative, m.boundary)
|
||||||
|
mw.writeString("\r\n\r\n")
|
||||||
|
}
|
||||||
|
for _, p := range m.parts {
|
||||||
|
mw.writePart(p, m.charset)
|
||||||
|
}
|
||||||
|
if m.hasAlt() {
|
||||||
|
mw.stopMP()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startMP writes a multipart beginning
|
||||||
|
func (mw *msgWriter) startMP(mt MIMEType, b string) {
|
||||||
|
mp := multipart.NewWriter(mw)
|
||||||
|
if b != "" {
|
||||||
|
mw.err = mp.SetBoundary(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := fmt.Sprintf("multipart/%s;\r\n boundary=%s", mt, mp.Boundary())
|
||||||
|
mw.mpw[mw.d] = mp
|
||||||
|
|
||||||
|
if mw.d == 0 {
|
||||||
|
mw.writeString(fmt.Sprintf("%s: %s", HeaderContentType, ct))
|
||||||
|
}
|
||||||
|
if mw.d > 0 {
|
||||||
|
mw.newPart(map[string][]string{"Content-Type": {ct}})
|
||||||
|
}
|
||||||
|
mw.d++
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopMP closes the multipart
|
||||||
|
func (mw *msgWriter) stopMP() {
|
||||||
|
if mw.d > 0 {
|
||||||
|
mw.err = mw.mpw[mw.d-1].Close()
|
||||||
|
mw.d--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPart creates a new MIME multipart io.Writer and sets the partwriter to it
|
||||||
|
func (mw *msgWriter) newPart(h map[string][]string) {
|
||||||
|
mw.pw, mw.err = mw.mpw[mw.d-1].CreatePart(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePart writes the corresponding part to the Msg body
|
||||||
|
func (mw *msgWriter) writePart(p *Part, cs Charset) {
|
||||||
|
mh := textproto.MIMEHeader{}
|
||||||
|
mh.Add(string(HeaderContentType), fmt.Sprintf("%s; charset=%s",
|
||||||
|
p.ctype, cs))
|
||||||
|
mh.Add(string(HeaderContentTransferEnc), string(p.enc))
|
||||||
|
if mw.d > 0 {
|
||||||
|
mw.newPart(mh)
|
||||||
|
}
|
||||||
|
mw.writeBody(p.w, p.enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeString writes a string into the msgWriter's io.Writer interface
|
||||||
|
func (mw *msgWriter) writeString(s string) {
|
||||||
|
if mw.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
n, mw.err = io.WriteString(mw.w, s)
|
||||||
|
mw.n += int64(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHeader writes a header into the msgWriter's io.Writer
|
// writeHeader writes a header into the msgWriter's io.Writer
|
||||||
|
@ -85,12 +166,29 @@ func (mw *msgWriter) writeHeader(k Header, v ...string) {
|
||||||
mw.writeString("\r\n")
|
mw.writeString("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeString writes a string into the msgWriter's io.Writer interface
|
// writeBody writes an io.Reader into an io.Writer using provided Encoding
|
||||||
func (mw *msgWriter) writeString(s string) {
|
func (mw *msgWriter) writeBody(f func(io.Writer) error, e Encoding) {
|
||||||
if mw.err != nil {
|
var w io.Writer
|
||||||
return
|
var ew io.WriteCloser
|
||||||
|
if mw.d == 0 {
|
||||||
|
w = mw.w
|
||||||
}
|
}
|
||||||
var n int
|
if mw.d > 0 {
|
||||||
n, mw.err = io.WriteString(mw.w, s)
|
w = mw.pw
|
||||||
mw.n += int64(n)
|
}
|
||||||
|
|
||||||
|
switch e {
|
||||||
|
case EncodingQP:
|
||||||
|
ew = quotedprintable.NewWriter(w)
|
||||||
|
case EncodingB64:
|
||||||
|
ew = base64.NewEncoder(base64.StdEncoding, w)
|
||||||
|
case NoEncoding:
|
||||||
|
mw.err = f(w)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
ew = quotedprintable.NewWriter(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
mw.err = f(ew)
|
||||||
|
mw.err = ew.Close()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue