mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
Merge pull request #117 from wneessen/feature/107_provide-more-ways-for-middleware-to-interact-with-mail-parts
Provide more ways for middleware to interact with mail parts
This commit is contained in:
commit
d052289575
10 changed files with 334 additions and 16 deletions
|
@ -141,6 +141,8 @@ const (
|
||||||
TypeTextPlain ContentType = "text/plain"
|
TypeTextPlain ContentType = "text/plain"
|
||||||
TypeTextHTML ContentType = "text/html"
|
TypeTextHTML ContentType = "text/html"
|
||||||
TypeAppOctetStream ContentType = "application/octet-stream"
|
TypeAppOctetStream ContentType = "application/octet-stream"
|
||||||
|
TypePGPSignature ContentType = "application/pgp-signature"
|
||||||
|
TypePGPEncrypted ContentType = "application/pgp-encrypted"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of MIMETypes
|
// List of MIMETypes
|
||||||
|
|
43
file.go
43
file.go
|
@ -14,9 +14,12 @@ type FileOption func(*File)
|
||||||
|
|
||||||
// File is an attachment or embedded file of the Msg
|
// File is an attachment or embedded file of the Msg
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
ContentType ContentType
|
||||||
Header textproto.MIMEHeader
|
Desc string
|
||||||
Writer func(w io.Writer) (int64, error)
|
Enc Encoding
|
||||||
|
Header textproto.MIMEHeader
|
||||||
|
Name string
|
||||||
|
Writer func(w io.Writer) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFileName sets the filename of the File
|
// WithFileName sets the filename of the File
|
||||||
|
@ -26,11 +29,45 @@ func WithFileName(n string) FileOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithFileDescription sets an optional file description of the File that will be
|
||||||
|
// added as Content-Description part
|
||||||
|
func WithFileDescription(d string) FileOption {
|
||||||
|
return func(f *File) {
|
||||||
|
f.Desc = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFileEncoding sets the encoding of the File. By default we should always use
|
||||||
|
// Base64 encoding but there might be exceptions, where this might come handy.
|
||||||
|
// Please note that quoted-printable should never be used for attachments/embeds. If this
|
||||||
|
// is provided as argument, the function will automatically override back to Base64
|
||||||
|
func WithFileEncoding(e Encoding) FileOption {
|
||||||
|
return func(f *File) {
|
||||||
|
if e == EncodingQP {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.Enc = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFileContentType sets the content type of the File.
|
||||||
|
// By default go-mail will try to guess the file type and its corresponding
|
||||||
|
// content type and fall back to application/octet-stream if the file type
|
||||||
|
// could not be guessed. In some cases, however, it might be needed to force
|
||||||
|
// this to a specific type. For such situations this override method can
|
||||||
|
// be used
|
||||||
|
func WithFileContentType(t ContentType) FileOption {
|
||||||
|
return func(f *File) {
|
||||||
|
f.ContentType = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setHeader sets header fields to a File
|
// setHeader sets header fields to a File
|
||||||
func (f *File) setHeader(h Header, v string) {
|
func (f *File) setHeader(h Header, v string) {
|
||||||
f.Header.Set(string(h), v)
|
f.Header.Set(string(h), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHeader return header fields of a File
|
||||||
func (f *File) getHeader(h Header) (string, bool) {
|
func (f *File) getHeader(h Header) (string, bool) {
|
||||||
v := f.Header.Get(string(h))
|
v := f.Header.Get(string(h))
|
||||||
return v, v != ""
|
return v, v != ""
|
||||||
|
|
81
file_test.go
81
file_test.go
|
@ -30,3 +30,84 @@ func TestFile_SetGetHeader(t *testing.T) {
|
||||||
t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "", fi)
|
t.Errorf("getHeader returned wrong value. Expected: %s, got: %s", "", fi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFile_WithFileDescription tests the WithFileDescription option
|
||||||
|
func TestFile_WithFileDescription(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"File description: test", "test"},
|
||||||
|
{"File description: empty", ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
m := NewMsg()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m.AttachFile("file.go", WithFileDescription(tt.desc))
|
||||||
|
al := m.GetAttachments()
|
||||||
|
if len(al) <= 0 {
|
||||||
|
t.Errorf("AttachFile() failed. Attachment list is empty")
|
||||||
|
}
|
||||||
|
a := al[0]
|
||||||
|
if a.Desc != tt.desc {
|
||||||
|
t.Errorf("WithFileDescription() failed. Expected: %s, got: %s", tt.desc, a.Desc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFile_WithFileEncoding tests the WithFileEncoding option
|
||||||
|
func TestFile_WithFileEncoding(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
enc Encoding
|
||||||
|
want Encoding
|
||||||
|
}{
|
||||||
|
{"File encoding: 8bit raw", NoEncoding, NoEncoding},
|
||||||
|
{"File encoding: Base64", EncodingB64, EncodingB64},
|
||||||
|
{"File encoding: quoted-printable (not allowed)", EncodingQP, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
m := NewMsg()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m.AttachFile("file.go", WithFileEncoding(tt.enc))
|
||||||
|
al := m.GetAttachments()
|
||||||
|
if len(al) <= 0 {
|
||||||
|
t.Errorf("AttachFile() failed. Attachment list is empty")
|
||||||
|
}
|
||||||
|
a := al[0]
|
||||||
|
if a.Enc != tt.want {
|
||||||
|
t.Errorf("WithFileEncoding() failed. Expected: %s, got: %s", tt.enc, a.Enc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFile_WithFileContentType tests the WithFileContentType option
|
||||||
|
func TestFile_WithFileContentType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ct ContentType
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"File content-type: text/plain", TypeTextPlain, "text/plain"},
|
||||||
|
{"File content-type: html/html", TypeTextHTML, "text/html"},
|
||||||
|
{"File content-type: application/octet-stream", TypeAppOctetStream, "application/octet-stream"},
|
||||||
|
{"File content-type: application/pgp-encrypted", TypePGPEncrypted, "application/pgp-encrypted"},
|
||||||
|
{"File content-type: application/pgp-signature", TypePGPSignature, "application/pgp-signature"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
m := NewMsg()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m.AttachFile("file.go", WithFileContentType(tt.ct))
|
||||||
|
al := m.GetAttachments()
|
||||||
|
if len(al) <= 0 {
|
||||||
|
t.Errorf("AttachFile() failed. Attachment list is empty")
|
||||||
|
}
|
||||||
|
a := al[0]
|
||||||
|
if a.ContentType != ContentType(tt.want) {
|
||||||
|
t.Errorf("WithFileContentType() failed. Expected: %s, got: %s", tt.want, a.ContentType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@ type Importance int
|
||||||
|
|
||||||
// List of common generic header field names
|
// List of common generic header field names
|
||||||
const (
|
const (
|
||||||
|
// HeaderContentDescription is the "Content-Description" header
|
||||||
|
HeaderContentDescription Header = "Content-Description"
|
||||||
|
|
||||||
// HeaderContentDisposition is the "Content-Disposition" header
|
// HeaderContentDisposition is the "Content-Disposition" header
|
||||||
HeaderContentDisposition Header = "Content-Disposition"
|
HeaderContentDisposition Header = "Content-Disposition"
|
||||||
|
|
||||||
|
|
59
msg.go
59
msg.go
|
@ -41,6 +41,18 @@ const (
|
||||||
errParseMailAddr = "failed to parse mail address %q: %w"
|
errParseMailAddr = "failed to parse mail address %q: %w"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoPGP indicates that a message should not be treated as PGP encrypted
|
||||||
|
// or signed and is the default value for a message
|
||||||
|
NoPGP PGPType = iota
|
||||||
|
// PGPEncrypt indicates that a message should be treated as PGP encrypted
|
||||||
|
// This works closely together with the corresponding go-mail-middleware
|
||||||
|
PGPEncrypt
|
||||||
|
// PGPSignature indicates that a message should be treated as PGP signed
|
||||||
|
// This works closely together with the corresponding go-mail-middleware
|
||||||
|
PGPSignature
|
||||||
|
)
|
||||||
|
|
||||||
// MiddlewareType is the type description of the Middleware and needs to be returned
|
// MiddlewareType is the type description of the Middleware and needs to be returned
|
||||||
// in the Middleware interface by the Type method
|
// in the Middleware interface by the Type method
|
||||||
type MiddlewareType string
|
type MiddlewareType string
|
||||||
|
@ -51,6 +63,10 @@ type Middleware interface {
|
||||||
Type() MiddlewareType
|
Type() MiddlewareType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PGPType is a type alias for a int representing a type of PGP encryption
|
||||||
|
// or signature
|
||||||
|
type PGPType int
|
||||||
|
|
||||||
// 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 is a slice of strings that the different mail AddrHeader fields
|
||||||
|
@ -77,10 +93,8 @@ 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
|
||||||
|
|
||||||
// preformHeader is a slice of strings that the different generic mail Header fields
|
// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
|
||||||
// of which content is already preformated and will not be affected by the automatic line
|
middlewares []Middleware
|
||||||
// breaks
|
|
||||||
preformHeader map[Header]string
|
|
||||||
|
|
||||||
// mimever represents the MIME version
|
// mimever represents the MIME version
|
||||||
mimever MIMEVersion
|
mimever MIMEVersion
|
||||||
|
@ -88,8 +102,14 @@ type Msg struct {
|
||||||
// parts represent the different parts of the Msg
|
// parts represent the different parts of the Msg
|
||||||
parts []*Part
|
parts []*Part
|
||||||
|
|
||||||
// middlewares is the list of middlewares to apply to the Msg before sending in FIFO order
|
// preformHeader is a slice of strings that the different generic mail Header fields
|
||||||
middlewares []Middleware
|
// of which content is already preformated and will not be affected by the automatic line
|
||||||
|
// breaks
|
||||||
|
preformHeader map[Header]string
|
||||||
|
|
||||||
|
// pgptype indicates that a message has a PGPType assigned and therefore will generate
|
||||||
|
// different Content-Type settings in the msgWriter
|
||||||
|
pgptype PGPType
|
||||||
|
|
||||||
// sendError holds the SendError in case a Msg could not be delivered during the Client.Send operation
|
// sendError holds the SendError in case a Msg could not be delivered during the Client.Send operation
|
||||||
sendError error
|
sendError error
|
||||||
|
@ -161,6 +181,13 @@ func WithMiddleware(mw Middleware) MsgOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithPGPType overrides the default PGPType of the message
|
||||||
|
func WithPGPType(t PGPType) MsgOption {
|
||||||
|
return func(m *Msg) {
|
||||||
|
m.pgptype = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -182,6 +209,11 @@ func (m *Msg) SetMIMEVersion(mv MIMEVersion) {
|
||||||
m.mimever = mv
|
m.mimever = mv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPGPType sets the PGPType of the Msg
|
||||||
|
func (m *Msg) SetPGPType(t PGPType) {
|
||||||
|
m.pgptype = t
|
||||||
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
@ -774,10 +806,10 @@ func (m *Msg) EmbedFile(n string, o ...FileOption) {
|
||||||
|
|
||||||
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
// EmbedReader adds an embedded File from an io.Reader to the Msg
|
||||||
//
|
//
|
||||||
// CAVEAT: For AttachReader to work it has to read all data of the io.Reader
|
// CAVEAT: For EmbedReader to work it has to read all data of the io.Reader
|
||||||
// into memory first, so it can seek through it. Using larger amounts of
|
// into memory first, so it can seek through it. Using larger amounts of
|
||||||
// data on the io.Reader should be avoided. For such, it is recommeded to
|
// data on the io.Reader should be avoided. For such, it is recommeded to
|
||||||
// either use AttachFile or AttachReadSeeker instead
|
// either use EmbedFile or EmbedReadSeeker instead
|
||||||
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) {
|
func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) {
|
||||||
f := fileFromReader(n, r)
|
f := fileFromReader(n, r)
|
||||||
m.embeds = m.appendFile(m.embeds, f, o...)
|
m.embeds = m.appendFile(m.embeds, f, o...)
|
||||||
|
@ -1027,17 +1059,22 @@ func (m *Msg) hasAlt() bool {
|
||||||
c++
|
c++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c > 1
|
return c > 1 && m.pgptype == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasMixed returns true if the Msg has mixed parts
|
// hasMixed returns true if the Msg has mixed parts
|
||||||
func (m *Msg) hasMixed() bool {
|
func (m *Msg) hasMixed() bool {
|
||||||
return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
|
return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasRelated returns true if the Msg has related parts
|
// hasRelated returns true if the Msg has related parts
|
||||||
func (m *Msg) hasRelated() bool {
|
func (m *Msg) hasRelated() bool {
|
||||||
return (len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1
|
return m.pgptype == 0 && ((len(m.parts) > 0 && len(m.embeds) > 0) || len(m.embeds) > 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasPGPType returns true if the Msg should be treated as PGP encoded message
|
||||||
|
func (m *Msg) hasPGPType() bool {
|
||||||
|
return m.pgptype > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPart returns a new Part for the Msg
|
// newPart returns a new Part for the Msg
|
||||||
|
|
30
msg_test.go
30
msg_test.go
|
@ -179,6 +179,36 @@ func TestNewMsgWithBoundary(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewMsg_WithPGPType tests WithPGPType option
|
||||||
|
func TestNewMsg_WithPGPType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pt PGPType
|
||||||
|
hpt bool
|
||||||
|
}{
|
||||||
|
{"Not a PGP encoded message", NoPGP, false},
|
||||||
|
{"PGP encrypted message", PGPEncrypt, true},
|
||||||
|
{"PGP signed message", PGPSignature, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := NewMsg(WithPGPType(tt.pt))
|
||||||
|
if m.pgptype != tt.pt {
|
||||||
|
t.Errorf("WithPGPType() failed. Expected: %d, got: %d", tt.pt, m.pgptype)
|
||||||
|
}
|
||||||
|
m.pgptype = 99
|
||||||
|
m.SetPGPType(tt.pt)
|
||||||
|
if m.pgptype != tt.pt {
|
||||||
|
t.Errorf("SetPGPType() failed. Expected: %d, got: %d", tt.pt, m.pgptype)
|
||||||
|
}
|
||||||
|
if m.hasPGPType() != tt.hpt {
|
||||||
|
t.Errorf("hasPGPType() failed. Expected %t, got: %t", tt.hpt, m.hasPGPType())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type uppercaseMiddleware struct{}
|
type uppercaseMiddleware struct{}
|
||||||
|
|
||||||
func (mw uppercaseMiddleware) Handle(m *Msg) *Msg {
|
func (mw uppercaseMiddleware) Handle(m *Msg) *Msg {
|
||||||
|
|
30
msgwriter.go
30
msgwriter.go
|
@ -100,12 +100,22 @@ func (mw *msgWriter) writeMsg(m *Msg) {
|
||||||
mw.startMP(MIMEAlternative, m.boundary)
|
mw.startMP(MIMEAlternative, m.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
|
if m.hasPGPType() {
|
||||||
|
switch m.pgptype {
|
||||||
|
case PGPEncrypt:
|
||||||
|
mw.startMP(`encrypted; protocol="application/pgp-encrypted"`, m.boundary)
|
||||||
|
case PGPSignature:
|
||||||
|
mw.startMP(`signed; protocol="application/pgp-signature";`, m.boundary)
|
||||||
|
}
|
||||||
|
mw.writeString(DoubleNewLine)
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range m.parts {
|
for _, p := range m.parts {
|
||||||
if !p.del {
|
if !p.del {
|
||||||
mw.writePart(p, m.charset)
|
mw.writePart(p, m.charset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasAlt() {
|
if m.hasAlt() {
|
||||||
mw.stopMP()
|
mw.stopMP()
|
||||||
}
|
}
|
||||||
|
@ -172,17 +182,30 @@ func (mw *msgWriter) stopMP() {
|
||||||
// addFiles adds the attachments/embeds file content to the mail body
|
// addFiles adds the attachments/embeds file content to the mail body
|
||||||
func (mw *msgWriter) addFiles(fl []*File, a bool) {
|
func (mw *msgWriter) addFiles(fl []*File, a bool) {
|
||||||
for _, f := range fl {
|
for _, f := range fl {
|
||||||
|
e := EncodingB64
|
||||||
if _, ok := f.getHeader(HeaderContentType); !ok {
|
if _, ok := f.getHeader(HeaderContentType); !ok {
|
||||||
mt := mime.TypeByExtension(filepath.Ext(f.Name))
|
mt := mime.TypeByExtension(filepath.Ext(f.Name))
|
||||||
if mt == "" {
|
if mt == "" {
|
||||||
mt = "application/octet-stream"
|
mt = "application/octet-stream"
|
||||||
}
|
}
|
||||||
|
if f.ContentType != "" {
|
||||||
|
mt = string(f.ContentType)
|
||||||
|
}
|
||||||
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt,
|
f.setHeader(HeaderContentType, fmt.Sprintf(`%s; name="%s"`, mt,
|
||||||
mw.en.Encode(mw.c.String(), f.Name)))
|
mw.en.Encode(mw.c.String(), f.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok {
|
if _, ok := f.getHeader(HeaderContentTransferEnc); !ok {
|
||||||
f.setHeader(HeaderContentTransferEnc, string(EncodingB64))
|
if f.Enc != "" {
|
||||||
|
e = f.Enc
|
||||||
|
}
|
||||||
|
f.setHeader(HeaderContentTransferEnc, string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Desc != "" {
|
||||||
|
if _, ok := f.getHeader(HeaderContentDescription); !ok {
|
||||||
|
f.setHeader(HeaderContentDescription, f.Desc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := f.getHeader(HeaderContentDisposition); !ok {
|
if _, ok := f.getHeader(HeaderContentDisposition); !ok {
|
||||||
|
@ -208,7 +231,7 @@ func (mw *msgWriter) addFiles(fl []*File, a bool) {
|
||||||
if mw.d > 0 {
|
if mw.d > 0 {
|
||||||
mw.newPart(f.Header)
|
mw.newPart(f.Header)
|
||||||
}
|
}
|
||||||
mw.writeBody(f.Writer, EncodingB64)
|
mw.writeBody(f.Writer, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,6 +251,9 @@ func (mw *msgWriter) writePart(p *Part, cs Charset) {
|
||||||
}
|
}
|
||||||
if mw.d > 0 {
|
if mw.d > 0 {
|
||||||
mh := textproto.MIMEHeader{}
|
mh := textproto.MIMEHeader{}
|
||||||
|
if p.desc != "" {
|
||||||
|
mh.Add(string(HeaderContentDescription), p.desc)
|
||||||
|
}
|
||||||
mh.Add(string(HeaderContentType), ct)
|
mh.Add(string(HeaderContentType), ct)
|
||||||
mh.Add(string(HeaderContentTransferEnc), cte)
|
mh.Add(string(HeaderContentTransferEnc), cte)
|
||||||
mw.newPart(mh)
|
mw.newPart(mh)
|
||||||
|
|
|
@ -104,3 +104,32 @@ func TestMsgWriter_writeMsg(t *testing.T) {
|
||||||
t.Errorf(em)
|
t.Errorf(em)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMsgWriter_writeMsg_PGP tests the writeMsg method of the msgWriter with PGP types set
|
||||||
|
func TestMsgWriter_writeMsg_PGP(t *testing.T) {
|
||||||
|
m := NewMsg(WithPGPType(PGPEncrypt))
|
||||||
|
_ = m.From(`"Toni Tester" <test@example.com>`)
|
||||||
|
_ = m.To(`"Toni Receiver" <receiver@example.com>`)
|
||||||
|
m.Subject("This is a subject")
|
||||||
|
m.SetBodyString(TypeTextPlain, "This is the body")
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
mw := &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
|
mw.writeMsg(m)
|
||||||
|
ms := buf.String()
|
||||||
|
if !strings.Contains(ms, `encrypted; protocol="application/pgp-encrypted"`) {
|
||||||
|
t.Errorf("writeMsg failed. Expected PGP encoding header but didn't find it in message output")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = NewMsg(WithPGPType(PGPSignature))
|
||||||
|
_ = m.From(`"Toni Tester" <test@example.com>`)
|
||||||
|
_ = m.To(`"Toni Receiver" <receiver@example.com>`)
|
||||||
|
m.Subject("This is a subject")
|
||||||
|
m.SetBodyString(TypeTextPlain, "This is the body")
|
||||||
|
buf = bytes.Buffer{}
|
||||||
|
mw = &msgWriter{w: &buf, c: CharsetUTF8, en: mime.QEncoding}
|
||||||
|
mw.writeMsg(m)
|
||||||
|
ms = buf.String()
|
||||||
|
if !strings.Contains(ms, `signed; protocol="application/pgp-signature"`) {
|
||||||
|
t.Errorf("writeMsg failed. Expected PGP encoding header but didn't find it in message output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
18
part.go
18
part.go
|
@ -15,6 +15,7 @@ type PartOption func(*Part)
|
||||||
// Part is a part of the Msg
|
// Part is a part of the Msg
|
||||||
type Part struct {
|
type Part struct {
|
||||||
ctype ContentType
|
ctype ContentType
|
||||||
|
desc string
|
||||||
enc Encoding
|
enc Encoding
|
||||||
del bool
|
del bool
|
||||||
w func(io.Writer) (int64, error)
|
w func(io.Writer) (int64, error)
|
||||||
|
@ -44,6 +45,11 @@ func (p *Part) GetWriteFunc() func(io.Writer) (int64, error) {
|
||||||
return p.w
|
return p.w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDescription returns the currently set Content-Description of the Part
|
||||||
|
func (p *Part) GetDescription() string {
|
||||||
|
return p.desc
|
||||||
|
}
|
||||||
|
|
||||||
// SetContent overrides the content of the Part with the given string
|
// SetContent overrides the content of the Part with the given string
|
||||||
func (p *Part) SetContent(c string) {
|
func (p *Part) SetContent(c string) {
|
||||||
buf := bytes.NewBufferString(c)
|
buf := bytes.NewBufferString(c)
|
||||||
|
@ -60,6 +66,11 @@ func (p *Part) SetEncoding(e Encoding) {
|
||||||
p.enc = e
|
p.enc = e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDescription overrides the Content-Description of the Part
|
||||||
|
func (p *Part) SetDescription(d string) {
|
||||||
|
p.desc = d
|
||||||
|
}
|
||||||
|
|
||||||
// SetWriteFunc overrides the WriteFunc of the Part
|
// SetWriteFunc overrides the WriteFunc of the Part
|
||||||
func (p *Part) SetWriteFunc(w func(io.Writer) (int64, error)) {
|
func (p *Part) SetWriteFunc(w func(io.Writer) (int64, error)) {
|
||||||
p.w = w
|
p.w = w
|
||||||
|
@ -77,3 +88,10 @@ func WithPartEncoding(e Encoding) PartOption {
|
||||||
p.enc = e
|
p.enc = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithPartContentDescription overrides the default Part Content-Description
|
||||||
|
func WithPartContentDescription(d string) PartOption {
|
||||||
|
return func(p *Part) {
|
||||||
|
p.desc = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
part_test.go
55
part_test.go
|
@ -45,6 +45,36 @@ func TestPartEncoding(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPart_WithPartContentDescription tests the WithPartContentDescription method
|
||||||
|
func TestPart_WithPartContentDescription(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"Part description: test", "test"},
|
||||||
|
{"Part description: empty", ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
m := NewMsg()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
part := m.newPart(TypeTextPlain, WithPartContentDescription(tt.desc), nil)
|
||||||
|
if part == nil {
|
||||||
|
t.Errorf("newPart() WithPartContentDescription() failed: no part returned")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if part.desc != tt.desc {
|
||||||
|
t.Errorf("newPart() WithPartContentDescription() failed: expected: %s, got: %s", tt.desc,
|
||||||
|
part.desc)
|
||||||
|
}
|
||||||
|
part.desc = ""
|
||||||
|
part.SetDescription(tt.desc)
|
||||||
|
if part.desc != tt.desc {
|
||||||
|
t.Errorf("newPart() SetDescription() failed: expected: %s, got: %s", tt.desc, part.desc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestPartContentType tests Part.SetContentType
|
// TestPartContentType tests Part.SetContentType
|
||||||
func TestPart_SetContentType(t *testing.T) {
|
func TestPart_SetContentType(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -241,6 +271,31 @@ func TestPart_SetContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPart_SetDescription tests Part.SetDescription
|
||||||
|
func TestPart_SetDescription(t *testing.T) {
|
||||||
|
c := "This is a test"
|
||||||
|
d := "test-description"
|
||||||
|
m := NewMsg()
|
||||||
|
m.SetBodyString(TypeTextPlain, c)
|
||||||
|
pl, err := getPartList(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pd := pl[0].GetDescription()
|
||||||
|
if pd != "" {
|
||||||
|
t.Errorf("Part.GetDescription failed. Expected empty description but got: %s", pd)
|
||||||
|
}
|
||||||
|
pl[0].SetDescription(d)
|
||||||
|
if pl[0].desc != d {
|
||||||
|
t.Errorf("Part.SetDescription failed. Expected desc to be: %s, got: %s", d, pd)
|
||||||
|
}
|
||||||
|
pd = pl[0].GetDescription()
|
||||||
|
if pd != d {
|
||||||
|
t.Errorf("Part.GetDescription failed. Expected: %s, got: %s", d, pd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestPart_Delete tests Part.Delete
|
// TestPart_Delete tests Part.Delete
|
||||||
func TestPart_Delete(t *testing.T) {
|
func TestPart_Delete(t *testing.T) {
|
||||||
c := "This is a test with ümläutß"
|
c := "This is a test with ümläutß"
|
||||||
|
|
Loading…
Reference in a new issue