mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-23 22:20:51 +01:00
Compare commits
62 commits
d8a3d0b682
...
ffdea8337e
Author | SHA1 | Date | |
---|---|---|---|
ffdea8337e | |||
7e4bb00538 | |||
d9290973be | |||
2fa52e5341 | |||
8924e426d6 | |||
4e7082a540 | |||
ca201b1548 | |||
68d0fe9cd3 | |||
0bf3f73e9b | |||
fcc5209852 | |||
84e6275cb2 | |||
b709df4b2d | |||
8b1208949f | |||
faab5323fd | |||
db9893f15a | |||
0cec8a28f7 | |||
9fa8644a9a | |||
70c9387003 | |||
4c88dce2a8 | |||
bb33c4a921 | |||
52ef13a5d5 | |||
e95799ad60 | |||
85c07c22cc | |||
29305675d6 | |||
04c41a1f10 | |||
047d923b45 | |||
481fc1d48c | |||
34f2141a26 | |||
4ef24a5234 | |||
2e548512c6 | |||
ee3283b00b | |||
4ec95adb30 | |||
b957445489 | |||
f5bbc558b2 | |||
3facbde703 | |||
59e85809f7 | |||
53566a93cd | |||
4202f705a0 | |||
f759b1b5fb | |||
bda6b8c899 | |||
8da2abcf01 | |||
f7ba3a1d02 | |||
60222c95e7 | |||
0d9ba278fe | |||
8b19e497a0 | |||
a12d9c327f | |||
1e447b5f2c | |||
e9331e0b7c | |||
8a1391b9df | |||
9607d08469 | |||
f9140ce90e | |||
6bda5d1aaa | |||
2bba7b902b | |||
54ccb80925 | |||
a3b3deb467 | |||
e7c717d0fc | |||
07a3450103 | |||
1a9141074a | |||
f60b689b03 | |||
3d50370a4c | |||
d733b6e17d | |||
d8d2a6e714 |
13 changed files with 1406 additions and 17 deletions
|
@ -57,6 +57,7 @@ Some of the features of this library:
|
||||||
* [X] Debug logging of SMTP traffic
|
* [X] Debug logging of SMTP traffic
|
||||||
* [X] Custom error types for delivery errors
|
* [X] Custom error types for delivery errors
|
||||||
* [X] Custom dial-context functions for more control over the connection (proxing, DNS hooking, etc.)
|
* [X] Custom dial-context functions for more control over the connection (proxing, DNS hooking, etc.)
|
||||||
|
* [X] Output a go-mail message as EML file and parse EML file into a go-mail message
|
||||||
|
|
||||||
go-mail works like a programatic email client and provides lots of methods and functionalities you would consider
|
go-mail works like a programatic email client and provides lots of methods and functionalities you would consider
|
||||||
standard in a MUA.
|
standard in a MUA.
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
// VERSION is used in the default user agent string
|
// VERSION is used in the default user agent string
|
||||||
const VERSION = "0.4.1"
|
const VERSION = "0.4.2"
|
||||||
|
|
418
eml.go
Normal file
418
eml.go
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"mime/multipart"
|
||||||
|
"mime/quotedprintable"
|
||||||
|
netmail "net/mail"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EMLToMsgFromString will parse a given EML string and returns a pre-filled Msg pointer
|
||||||
|
func EMLToMsgFromString(emlString string) (*Msg, error) {
|
||||||
|
eb := bytes.NewBufferString(emlString)
|
||||||
|
return EMLToMsgFromReader(eb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EMLToMsgFromReader will parse a reader that holds EML content and returns a pre-filled
|
||||||
|
// Msg pointer
|
||||||
|
func EMLToMsgFromReader(reader io.Reader) (*Msg, error) {
|
||||||
|
msg := &Msg{
|
||||||
|
addrHeader: make(map[AddrHeader][]*netmail.Address),
|
||||||
|
genHeader: make(map[Header][]string),
|
||||||
|
preformHeader: make(map[Header]string),
|
||||||
|
mimever: MIME10,
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedMsg, bodybuf, err := readEMLFromReader(reader)
|
||||||
|
if err != nil || parsedMsg == nil {
|
||||||
|
return msg, fmt.Errorf("failed to parse EML from reader: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseEML(parsedMsg, bodybuf, msg); err != nil {
|
||||||
|
return msg, fmt.Errorf("failed to parse EML contents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EMLToMsgFromFile will open and parse a .eml file at a provided file path and returns a
|
||||||
|
// pre-filled Msg pointer
|
||||||
|
func EMLToMsgFromFile(filePath string) (*Msg, error) {
|
||||||
|
msg := &Msg{
|
||||||
|
addrHeader: make(map[AddrHeader][]*netmail.Address),
|
||||||
|
genHeader: make(map[Header][]string),
|
||||||
|
preformHeader: make(map[Header]string),
|
||||||
|
mimever: MIME10,
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedMsg, bodybuf, err := readEML(filePath)
|
||||||
|
if err != nil || parsedMsg == nil {
|
||||||
|
return msg, fmt.Errorf("failed to parse EML file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseEML(parsedMsg, bodybuf, msg); err != nil {
|
||||||
|
return msg, fmt.Errorf("failed to parse EML contents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEML parses the EML's headers and body and inserts the parsed values into the Msg
|
||||||
|
func parseEML(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||||
|
if err := parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse EML headers: %w", err)
|
||||||
|
}
|
||||||
|
if err := parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse EML body parts: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEML opens an EML file and uses net/mail to parse the header and body
|
||||||
|
func readEML(filePath string) (*netmail.Message, *bytes.Buffer, error) {
|
||||||
|
fileHandle, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to open EML file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fileHandle.Close()
|
||||||
|
}()
|
||||||
|
return readEMLFromReader(fileHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEMLFromReader uses net/mail to parse the header and body from a given io.Reader
|
||||||
|
func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error) {
|
||||||
|
parsedMsg, err := netmail.ReadMessage(reader)
|
||||||
|
if err != nil {
|
||||||
|
return parsedMsg, nil, fmt.Errorf("failed to parse EML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
if _, err = buf.ReadFrom(parsedMsg.Body); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedMsg, &buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLHeaders will check the EML headers for the most common headers and set the
|
||||||
|
// according settings in the Msg
|
||||||
|
func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error {
|
||||||
|
commonHeaders := []Header{
|
||||||
|
HeaderContentType, HeaderImportance, HeaderInReplyTo, HeaderListUnsubscribe,
|
||||||
|
HeaderListUnsubscribePost, HeaderMessageID, HeaderMIMEVersion, HeaderOrganization,
|
||||||
|
HeaderPrecedence, HeaderPriority, HeaderReferences, HeaderSubject, HeaderUserAgent,
|
||||||
|
HeaderXMailer, HeaderXMSMailPriority, HeaderXPriority,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract content type, charset and encoding first
|
||||||
|
parseEMLEncoding(mailHeader, msg)
|
||||||
|
parseEMLContentTypeCharset(mailHeader, msg)
|
||||||
|
|
||||||
|
// Extract address headers
|
||||||
|
if value := mailHeader.Get(HeaderFrom.String()); value != "" {
|
||||||
|
if err := msg.From(value); err != nil {
|
||||||
|
return fmt.Errorf(`failed to parse %q header: %w`, HeaderFrom, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrHeaders := map[AddrHeader]func(...string) error{
|
||||||
|
HeaderTo: msg.To,
|
||||||
|
HeaderCc: msg.Cc,
|
||||||
|
HeaderBcc: msg.Bcc,
|
||||||
|
}
|
||||||
|
for addrHeader, addrFunc := range addrHeaders {
|
||||||
|
if v := mailHeader.Get(addrHeader.String()); v != "" {
|
||||||
|
var addrStrings []string
|
||||||
|
parsedAddrs, err := netmail.ParseAddressList(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`failed to parse address list: %w`, err)
|
||||||
|
}
|
||||||
|
for _, addr := range parsedAddrs {
|
||||||
|
addrStrings = append(addrStrings, addr.String())
|
||||||
|
}
|
||||||
|
if err = addrFunc(addrStrings...); err != nil {
|
||||||
|
return fmt.Errorf(`failed to parse %q header: %w`, addrHeader, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract date from message
|
||||||
|
date, err := mailHeader.Date()
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, netmail.ErrHeaderNotPresent):
|
||||||
|
msg.SetDate()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("failed to parse EML date: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
msg.SetDateWithValue(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract common headers
|
||||||
|
for _, header := range commonHeaders {
|
||||||
|
if value := mailHeader.Get(header.String()); value != "" {
|
||||||
|
if strings.EqualFold(header.String(), HeaderContentType.String()) &&
|
||||||
|
strings.HasPrefix(value, TypeMultipartMixed.String()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg.SetGenHeader(header, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLBodyParts parses the body of a EML based on the different content types and encodings
|
||||||
|
func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||||
|
// Extract the transfer encoding of the body
|
||||||
|
mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract content type: %w", err)
|
||||||
|
}
|
||||||
|
if value, ok := params["charset"]; ok {
|
||||||
|
msg.SetCharset(Charset(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(mediatype, TypeTextPlain.String()),
|
||||||
|
strings.EqualFold(mediatype, TypeTextHTML.String()):
|
||||||
|
if err = parseEMLBodyPlain(mediatype, parsedMsg, bodybuf, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse plain body: %w", err)
|
||||||
|
}
|
||||||
|
case strings.EqualFold(mediatype, TypeMultipartAlternative.String()),
|
||||||
|
strings.EqualFold(mediatype, TypeMultipartMixed.String()),
|
||||||
|
strings.EqualFold(mediatype, TypeMultipartRelated.String()):
|
||||||
|
if err = parseEMLMultipart(params, bodybuf, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse multipart body: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("failed to parse body, unknown content type: %s", mediatype)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLBodyPlain parses the mail body of plain type mails
|
||||||
|
func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||||
|
contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String())
|
||||||
|
if strings.EqualFold(contentTransferEnc, NoEncoding.String()) {
|
||||||
|
msg.SetEncoding(NoEncoding)
|
||||||
|
msg.SetBodyString(ContentType(mediatype), bodybuf.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.EqualFold(contentTransferEnc, EncodingQP.String()) {
|
||||||
|
msg.SetEncoding(EncodingQP)
|
||||||
|
qpReader := quotedprintable.NewReader(bodybuf)
|
||||||
|
qpBuffer := bytes.Buffer{}
|
||||||
|
if _, err := qpBuffer.ReadFrom(qpReader); err != nil {
|
||||||
|
return fmt.Errorf("failed to read quoted-printable body: %w", err)
|
||||||
|
}
|
||||||
|
msg.SetBodyString(ContentType(mediatype), qpBuffer.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.EqualFold(contentTransferEnc, EncodingB64.String()) {
|
||||||
|
msg.SetEncoding(EncodingB64)
|
||||||
|
b64Decoder := base64.NewDecoder(base64.StdEncoding, bodybuf)
|
||||||
|
b64Buffer := bytes.Buffer{}
|
||||||
|
if _, err := b64Buffer.ReadFrom(b64Decoder); err != nil {
|
||||||
|
return fmt.Errorf("failed to read base64 body: %w", err)
|
||||||
|
}
|
||||||
|
msg.SetBodyString(ContentType(mediatype), b64Buffer.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLMultipart parses a multipart body part of a EML
|
||||||
|
func parseEMLMultipart(params map[string]string, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||||
|
boundary, ok := params["boundary"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("no boundary tag found in multipart body")
|
||||||
|
}
|
||||||
|
multipartReader := multipart.NewReader(bodybuf, boundary)
|
||||||
|
ReadNextPart:
|
||||||
|
multiPart, err := multipartReader.NextPart()
|
||||||
|
defer func() {
|
||||||
|
if multiPart != nil {
|
||||||
|
_ = multiPart.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return fmt.Errorf("failed to get next part of multipart message: %w", err)
|
||||||
|
}
|
||||||
|
for err == nil {
|
||||||
|
// Multipart/related and Multipart/alternative parts need to be parsed seperately
|
||||||
|
if contentTypeSlice, ok := multiPart.Header[HeaderContentType.String()]; ok && len(contentTypeSlice) == 1 {
|
||||||
|
contentType, _ := parseMultiPartHeader(contentTypeSlice[0])
|
||||||
|
if strings.EqualFold(contentType, TypeMultipartRelated.String()) ||
|
||||||
|
strings.EqualFold(contentType, TypeMultipartAlternative.String()) {
|
||||||
|
relatedPart := &netmail.Message{
|
||||||
|
Header: netmail.Header(multiPart.Header),
|
||||||
|
Body: multiPart,
|
||||||
|
}
|
||||||
|
relatedBuf := &bytes.Buffer{}
|
||||||
|
if _, err = relatedBuf.ReadFrom(multiPart); err != nil {
|
||||||
|
return fmt.Errorf("failed to read related multipart message to buffer: %w", err)
|
||||||
|
}
|
||||||
|
if err := parseEMLBodyParts(relatedPart, relatedBuf, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse related multipart body: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content-Disposition header means we have an attachment or embed
|
||||||
|
if contentDisposition, ok := multiPart.Header[HeaderContentDisposition.String()]; ok {
|
||||||
|
if err = parseEMLAttachmentEmbed(contentDisposition, multiPart, msg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse attachment/embed: %w", err)
|
||||||
|
}
|
||||||
|
goto ReadNextPart
|
||||||
|
}
|
||||||
|
|
||||||
|
multiPartData, mperr := io.ReadAll(multiPart)
|
||||||
|
if mperr != nil {
|
||||||
|
_ = multiPart.Close()
|
||||||
|
return fmt.Errorf("failed to read multipart: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
multiPartContentType, ok := multiPart.Header[HeaderContentType.String()]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to get content-type from part")
|
||||||
|
}
|
||||||
|
contentType, optional := parseMultiPartHeader(multiPartContentType[0])
|
||||||
|
if strings.EqualFold(contentType, TypeMultipartRelated.String()) {
|
||||||
|
goto ReadNextPart
|
||||||
|
}
|
||||||
|
part := msg.newPart(ContentType(contentType))
|
||||||
|
if charset, ok := optional["charset"]; ok {
|
||||||
|
part.SetCharset(Charset(charset))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutliPartTransferEnc, ok := multiPart.Header[HeaderContentTransferEnc.String()]
|
||||||
|
if !ok {
|
||||||
|
// If CTE is empty we can assume that it's a quoted-printable CTE since the
|
||||||
|
// GO stdlib multipart packages deletes that header
|
||||||
|
// See: https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/mime/multipart/multipart.go;l=161
|
||||||
|
mutliPartTransferEnc = []string{EncodingQP.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(mutliPartTransferEnc[0], EncodingB64.String()):
|
||||||
|
if err := handleEMLMultiPartBase64Encoding(multiPartData, part); err != nil {
|
||||||
|
return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
|
||||||
|
}
|
||||||
|
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
|
||||||
|
part.SetContent(string(multiPartData))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.parts = append(msg.parts, part)
|
||||||
|
multiPart, err = multipartReader.NextPart()
|
||||||
|
}
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
return fmt.Errorf("failed to read multipart: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLEncoding parses and determines the encoding of the message
|
||||||
|
func parseEMLEncoding(mailHeader *netmail.Header, msg *Msg) {
|
||||||
|
if value := mailHeader.Get(HeaderContentTransferEnc.String()); value != "" {
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(value, EncodingQP.String()):
|
||||||
|
msg.SetEncoding(EncodingQP)
|
||||||
|
case strings.EqualFold(value, EncodingB64.String()):
|
||||||
|
msg.SetEncoding(EncodingB64)
|
||||||
|
default:
|
||||||
|
msg.SetEncoding(NoEncoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLContentTypeCharset parses and determines the charset and content type of the message
|
||||||
|
func parseEMLContentTypeCharset(mailHeader *netmail.Header, msg *Msg) {
|
||||||
|
if value := mailHeader.Get(HeaderContentType.String()); value != "" {
|
||||||
|
contentType, optional := parseMultiPartHeader(value)
|
||||||
|
if charset, ok := optional["charset"]; ok {
|
||||||
|
msg.SetCharset(Charset(charset))
|
||||||
|
}
|
||||||
|
msg.setEncoder()
|
||||||
|
if contentType != "" && !strings.EqualFold(contentType, TypeMultipartMixed.String()) {
|
||||||
|
msg.SetGenHeader(HeaderContentType, contentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part
|
||||||
|
func handleEMLMultiPartBase64Encoding(multiPartData []byte, part *Part) error {
|
||||||
|
part.SetEncoding(EncodingB64)
|
||||||
|
content, err := base64.StdEncoding.DecodeString(string(multiPartData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode base64 part: %w", err)
|
||||||
|
}
|
||||||
|
part.SetContent(string(content))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMultiPartHeader parses a multipart header and returns the value and optional parts as
|
||||||
|
// separate map
|
||||||
|
func parseMultiPartHeader(multiPartHeader string) (header string, optional map[string]string) {
|
||||||
|
optional = make(map[string]string)
|
||||||
|
headerSplit := strings.SplitN(multiPartHeader, ";", 2)
|
||||||
|
header = headerSplit[0]
|
||||||
|
if len(headerSplit) == 2 {
|
||||||
|
optString := strings.TrimLeft(headerSplit[1], " ")
|
||||||
|
optSplit := strings.SplitN(optString, "=", 2)
|
||||||
|
if len(optSplit) == 2 {
|
||||||
|
optional[optSplit[0]] = optSplit[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEMLAttachmentEmbed parses a multipart that is an attachment or embed
|
||||||
|
func parseEMLAttachmentEmbed(contentDisposition []string, multiPart *multipart.Part, msg *Msg) error {
|
||||||
|
cdType, optional := parseMultiPartHeader(contentDisposition[0])
|
||||||
|
filename := "generic.attachment"
|
||||||
|
if name, ok := optional["filename"]; ok {
|
||||||
|
filename = name[1 : len(name)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataReader io.Reader
|
||||||
|
dataReader = multiPart
|
||||||
|
contentTransferEnc, _ := parseMultiPartHeader(multiPart.Header.Get(HeaderContentTransferEnc.String()))
|
||||||
|
b64Decoder := base64.NewDecoder(base64.StdEncoding, multiPart)
|
||||||
|
if strings.EqualFold(contentTransferEnc, EncodingB64.String()) {
|
||||||
|
dataReader = b64Decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(cdType) {
|
||||||
|
case "attachment":
|
||||||
|
if err := msg.AttachReader(filename, dataReader); err != nil {
|
||||||
|
return fmt.Errorf("failed to attach multipart body: %w", err)
|
||||||
|
}
|
||||||
|
case "inline":
|
||||||
|
if contentID, _ := parseMultiPartHeader(multiPart.Header.Get(HeaderContentID.String())); contentID != "" {
|
||||||
|
if err := msg.EmbedReader(filename, dataReader, WithFileContentID(contentID)); err != nil {
|
||||||
|
return fmt.Errorf("failed to embed multipart body: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := msg.EmbedReader(filename, dataReader); err != nil {
|
||||||
|
return fmt.Errorf("failed to embed multipart body: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
881
eml_test.go
Normal file
881
eml_test.go
Normal file
|
@ -0,0 +1,881 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exampleMailPlainNoEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainBrokenBody = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
This plain text body should not be parsed as Base64.
|
||||||
|
`
|
||||||
|
exampleMailPlainNoContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
|
||||||
|
This plain text body should not be parsed as Base64.
|
||||||
|
`
|
||||||
|
exampleMailPlainUnknownContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: application/unknown; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
This plain text body should not be parsed as Base64.
|
||||||
|
`
|
||||||
|
exampleMailPlainBrokenHeader = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version = 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent = go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From = "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding = 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainBrokenFrom = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: Toni Tester" go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainBrokenTo = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: go-mail+test.go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainNoEncInvalidDate = `Date: Inv, 99 Nov 9999 99:99:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainNoEncNoDate = `MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text without encoding
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainQP = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text quoted-printable
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very lo=
|
||||||
|
ng so it
|
||||||
|
should be wrapped.
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainUnsupportedTransferEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text quoted-printable
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=ISO-8859-1
|
||||||
|
Content-Transfer-Encoding: unknown
|
||||||
|
|
||||||
|
Dear Customer,
|
||||||
|
|
||||||
|
This is a test mail. Please do not reply to this. Also this line is very long so it should be wrapped.
|
||||||
|
㋛
|
||||||
|
This is not ==D3
|
||||||
|
|
||||||
|
Thank your for your business!
|
||||||
|
The go-mail team
|
||||||
|
|
||||||
|
--
|
||||||
|
This is a signature`
|
||||||
|
exampleMailPlainB64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64
|
||||||
|
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
|
||||||
|
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
|
||||||
|
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
|
||||||
|
ClRoaXMgaXMgYSBzaWduYXR1cmU=`
|
||||||
|
exampleMailPlainB64WithAttachment = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with attachment
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary=45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
|
||||||
|
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
|
||||||
|
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
|
||||||
|
ClRoaXMgaXMgYSBzaWduYXR1cmU=
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Disposition: attachment; filename="test.attachment"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: application/octet-stream; name="test.attachment"
|
||||||
|
|
||||||
|
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
|
||||||
|
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
|
||||||
|
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
|
||||||
|
exampleMailPlainB64WithAttachmentNoBoundary = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with attachment
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
|
||||||
|
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
|
||||||
|
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
|
||||||
|
ClRoaXMgaXMgYSBzaWduYXR1cmU=
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Disposition: attachment; filename="test.attachment"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: application/octet-stream; name="test.attachment"
|
||||||
|
|
||||||
|
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
|
||||||
|
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
|
||||||
|
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
|
||||||
|
exampleMailPlainB64BrokenBody = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with attachment
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary=45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Transfer-Encoding = base64
|
||||||
|
Content-Type = text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
RGVhciBDdXN0b21lciwKClRoaXMgaXMgYSB0ZXN0IG1haWwuIFBsZWFzZSBkbyBub3QgcmVwbHkg
|
||||||
|
dG8gdGhpcy4gQWxzbyB0aGlzIGxpbmUgaXMgdmVyeSBsb25nIHNvIGl0CnNob3VsZCBiZSB3cmFw
|
||||||
|
cGVkLgoKClRoYW5rIHlvdXIgZm9yIHlvdXIgYnVzaW5lc3MhClRoZSBnby1tYWlsIHRlYW0KCi0t
|
||||||
|
ClRoaXMgaXMgYSBzaWduYXR1cmU=
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7
|
||||||
|
Content-Disposition: attachment; filename="test.attachment"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: application/octet-stream; name="test.attachment"
|
||||||
|
|
||||||
|
VGhpcyBpcyBhIHNpbXBsZSB0ZXN0IHRleHQgZmlsZSBhdHRhY2htZW50LgoKSXQgCiAgaGFzCiAg
|
||||||
|
ICAgc2V2ZXJhbAogICAgICAgICAgICBuZXdsaW5lcwoJICAgICAgICAgICAgYW5kCgkgICAgc3Bh
|
||||||
|
Y2VzCiAgICAgaW4KICBpdAouCgpBcyB3ZWxsIGFzIGFuIGVtb2ppOiDwn5mCCg==
|
||||||
|
|
||||||
|
--45c75ff528359022eb03679fbe91877d75343f2e1f8193e349deffa33ff7--`
|
||||||
|
exampleMailPlainB64WithEmbed = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with embed
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
boundary=ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
This is a test body string
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
Content-Disposition: inline; filename="pixel.png"
|
||||||
|
Content-Id: <pixel.png>
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: image/png; name="pixel.png"
|
||||||
|
|
||||||
|
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2O
|
||||||
|
UAAAAABJRU5ErkJggg==
|
||||||
|
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b--`
|
||||||
|
exampleMailPlainB64WithEmbedNoContentID = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with embed
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
boundary=ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
This is a test body string
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b
|
||||||
|
Content-Disposition: inline; filename="pixel.png"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: image/png; name="pixel.png"
|
||||||
|
|
||||||
|
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2O
|
||||||
|
UAAAAABJRU5ErkJggg==
|
||||||
|
|
||||||
|
--ffbcfb94b44e5297325102f6ced05b3b37f1d70fc38a5e78dc73c1a8434b--`
|
||||||
|
exampleMailMultipartMixedAlternativeRelated = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||||
|
Subject: Example mail // plain text base64 with attachment, embed and alternative part
|
||||||
|
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||||
|
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||||
|
To: <go-mail+test@go-mail.dev>
|
||||||
|
Cc: <go-mail+cc@go-mail.dev>
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary=fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5
|
||||||
|
|
||||||
|
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
boundary=5897e40a22c608e252cfab849e966112fcbd5a1c291208026b3ca2bfab8a
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--5897e40a22c608e252cfab849e966112fcbd5a1c291208026b3ca2bfab8a
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary=cbace12de35ef4eae53fd974ccd41cb2aee4f9c9c76057ec8bfdd0c97813
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--cbace12de35ef4eae53fd974ccd41cb2aee4f9c9c76057ec8bfdd0c97813
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
|
RGVhciBDdXN0b21lciwKCkdvb2QgbmV3cyEgWW91ciBvcmRlciBpcyBvbiB0aGUgd2F5IGFuZCBp
|
||||||
|
bnZvaWNlIGlzIGF0dGFjaGVkIQoKWW91IHdpbGwgZmluZCB5b3VyIHRyYWNraW5nIG51bWJlciBv
|
||||||
|
biB0aGUgaW52b2ljZS4gVHJhY2tpbmcgZGF0YSBtYXkgdGFrZQp1cCB0byAyNCBob3VycyB0byBi
|
||||||
|
ZSBhY2Nlc3NpYmxlIG9ubGluZS4KCuKAoiBQbGVhc2UgcmVtaXQgcGF5bWVudCBhdCB5b3VyIGVh
|
||||||
|
cmxpZXN0IGNvbnZlbmllbmNlIHVubGVzcyBpbnZvaWNlIGlzCm1hcmtlZCDigJxQQUlE4oCdLgri
|
||||||
|
gKIgU29tZSBpdGVtcyBtYXkgc2hpcCBzZXBhcmF0ZWx5IGZyb20gbXVsdGlwbGUgbG9jYXRpb25z
|
||||||
|
LiBTZXBhcmF0ZQppbnZvaWNlcyB3aWxsIGJlIGlzc3VlZCB3aGVuIGFwcGxpY2FibGUuCuKAoiBQ
|
||||||
|
TEVBU0UgSU5TUEVDVCBVUE9OIFJFQ0VJUFQgRk9SIFBBVFRFUk4sIENPTE9SLCBERUZFQ1RTLCBE
|
||||||
|
QU1BR0UgRlJPTQpTSElQUElORywgQ09SUkVDVCBZQVJEQUdFLCBFVEMhIE9uY2UgYW4gb3JkZXIg
|
||||||
|
aXMgY3V0IG9yIHNld24sIG5vIHJldHVybnMKd2lsbCBiZSBhY2NlcHRlZCBmb3IgYW55IHJlYXNv
|
||||||
|
biwgbm8gbWF0dGVyIHRoZSBwYXJ0eSBpbiBlcnJvci4gTm8gcmV0dXJucwp3aWxsIGJlIGF1dGhv
|
||||||
|
cml6ZWQgYWZ0ZXIgMzAgZGF5cyBvZiBpbnZvaWNlIGRhdGUuIE5vIGV4Y2VwdGlvbnMgd2lsbCBi
|
||||||
|
ZQptYWRlLgoKVGhhbmsgeW91ciBmb3IgeW91ciBidXNpbmVzcyEKCk5haWxkb2N0b3IgRmFicmlj
|
||||||
|
cw==
|
||||||
|
|
||||||
|
--cbace12de35ef4eae53fd974ccd41cb2aee4f9c9c76057ec8bfdd0c97813
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/html; charset=UTF-8
|
||||||
|
|
||||||
|
PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGJvZHk+CjxwPlRoaXMgaXMgSFRNTCA8
|
||||||
|
c3Ryb25nPmluIEJPTEQ8L3N0cm9uZz4KPHA+ClRoaXMgaXMgYW4gZW1iZWRkZWQgcGljdHVyZTog
|
||||||
|
CjxpbWcgYWx0PSJwaXhlbC5wbmciIHNyYz0iY2lkOmltYWdlMS5wbmciPgo8YnI+Rm9vPC9wPg==
|
||||||
|
|
||||||
|
--cbace12de35ef4eae53fd974ccd41cb2aee4f9c9c76057ec8bfdd0c97813--
|
||||||
|
|
||||||
|
--5897e40a22c608e252cfab849e966112fcbd5a1c291208026b3ca2bfab8a
|
||||||
|
Content-Disposition: inline; filename="pixel.png"
|
||||||
|
Content-Id: image1.png
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: image/png; name="pixel.png"
|
||||||
|
|
||||||
|
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAFEmlUWHRYTUw6Y29tLmFkb2JlLnht
|
||||||
|
cAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQi
|
||||||
|
Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUg
|
||||||
|
NS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIy
|
||||||
|
LXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1s
|
||||||
|
bnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6cGhvdG9zaG9w
|
||||||
|
PSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZXhpZj0iaHR0
|
||||||
|
cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRv
|
||||||
|
YmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hh
|
||||||
|
cC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9z
|
||||||
|
VHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDYtMjhUMTM6MjY6
|
||||||
|
MDYrMDIwMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiCiAg
|
||||||
|
IHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiCiAgIHBob3Rvc2hv
|
||||||
|
cDpEYXRlQ3JlYXRlZD0iMjAyNC0wNi0yOFQxMzoyNjowNiswMjAwIgogICBwaG90b3Nob3A6Q29s
|
||||||
|
b3JNb2RlPSIxIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0iR3JleXNjYWxlIEQ1MCIKICAgZXhp
|
||||||
|
ZjpQaXhlbFhEaW1lbnNpb249IjEwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMTAiCiAgIGV4
|
||||||
|
aWY6Q29sb3JTcGFjZT0iNjU1MzUiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iMTAiCiAgIHRpZmY6SW1h
|
||||||
|
Z2VMZW5ndGg9IjEwIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0
|
||||||
|
aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIj4KICAgPHhtcE1NOkhpc3Rvcnk+
|
||||||
|
CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQi
|
||||||
|
CiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IFBob3RvIDIgMi4zLjAiCiAgICAg
|
||||||
|
IHN0RXZ0OndoZW49IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiLz4KICAgIDwvcmRmOlNlcT4K
|
||||||
|
ICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6
|
||||||
|
eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/PpwIGG4AAADdaUNDUEdyZXlzY2FsZSBENTAAABiV
|
||||||
|
dVC9CsJAGEul6KCDg4vSoQ+gIIjiKoou6qAVrLiUs/5gq8e1In0v30TwGRycnc0VcRD9IJdwfMmR
|
||||||
|
A4xlIMLIrAPhIVaDSceduws7d0cWFvIow/JEJEfTvoO/87zB0Hyt6az/ez/HXPmRIF+IlpAqJj+I
|
||||||
|
4TmW1EaburR3Jl3qIXUxDE7i7dWvFvzDbEquEBYGUPCRIIKAh4DaRg9N6H6/ffXUN8aRm4KnpFth
|
||||||
|
hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
|
||||||
|
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
|
||||||
|
|
||||||
|
--5897e40a22c608e252cfab849e966112fcbd5a1c291208026b3ca2bfab8a--
|
||||||
|
|
||||||
|
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5
|
||||||
|
Content-Disposition: attachment; filename="attachment.png"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: image/png; name="attachment.png"
|
||||||
|
|
||||||
|
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAFEmlUWHRYTUw6Y29tLmFkb2JlLnht
|
||||||
|
cAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQi
|
||||||
|
Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUg
|
||||||
|
NS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIy
|
||||||
|
LXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1s
|
||||||
|
bnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6cGhvdG9zaG9w
|
||||||
|
PSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZXhpZj0iaHR0
|
||||||
|
cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRv
|
||||||
|
YmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hh
|
||||||
|
cC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9z
|
||||||
|
VHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDYtMjhUMTM6MjY6
|
||||||
|
MDYrMDIwMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiCiAg
|
||||||
|
IHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiCiAgIHBob3Rvc2hv
|
||||||
|
cDpEYXRlQ3JlYXRlZD0iMjAyNC0wNi0yOFQxMzoyNjowNiswMjAwIgogICBwaG90b3Nob3A6Q29s
|
||||||
|
b3JNb2RlPSIxIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0iR3JleXNjYWxlIEQ1MCIKICAgZXhp
|
||||||
|
ZjpQaXhlbFhEaW1lbnNpb249IjEwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMTAiCiAgIGV4
|
||||||
|
aWY6Q29sb3JTcGFjZT0iNjU1MzUiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iMTAiCiAgIHRpZmY6SW1h
|
||||||
|
Z2VMZW5ndGg9IjEwIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0
|
||||||
|
aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIj4KICAgPHhtcE1NOkhpc3Rvcnk+
|
||||||
|
CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQi
|
||||||
|
CiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IFBob3RvIDIgMi4zLjAiCiAgICAg
|
||||||
|
IHN0RXZ0OndoZW49IjIwMjQtMDYtMjhUMTM6Mjc6MDgrMDI6MDAiLz4KICAgIDwvcmRmOlNlcT4K
|
||||||
|
ICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6
|
||||||
|
eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/PpwIGG4AAADdaUNDUEdyZXlzY2FsZSBENTAAABiV
|
||||||
|
dVC9CsJAGEul6KCDg4vSoQ+gIIjiKoou6qAVrLiUs/5gq8e1In0v30TwGRycnc0VcRD9IJdwfMmR
|
||||||
|
A4xlIMLIrAPhIVaDSceduws7d0cWFvIow/JEJEfTvoO/87zB0Hyt6az/ez/HXPmRIF+IlpAqJj+I
|
||||||
|
4TmW1EaburR3Jl3qIXUxDE7i7dWvFvzDbEquEBYGUPCRIIKAh4DaRg9N6H6/ffXUN8aRm4KnpFth
|
||||||
|
hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
|
||||||
|
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
|
||||||
|
|
||||||
|
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEMLToMsgFromString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
eml string
|
||||||
|
enc string
|
||||||
|
sub string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Plain text no encoding", exampleMailPlainNoEnc, "8bit",
|
||||||
|
"Example mail // plain text without encoding",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Plain text quoted-printable", exampleMailPlainQP, "quoted-printable",
|
||||||
|
"Example mail // plain text quoted-printable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Plain text base64", exampleMailPlainB64, "base64",
|
||||||
|
"Example mail // plain text base64",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
msg, err := EMLToMsgFromString(tt.eml)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse EML: %s", err)
|
||||||
|
}
|
||||||
|
if msg.Encoding() != tt.enc {
|
||||||
|
t.Errorf("EMLToMsgFromString failed: expected encoding: %s, but got: %s", tt.enc, msg.Encoding())
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], tt.sub) {
|
||||||
|
t.Errorf("EMLToMsgFromString failed: expected subject: %s, but got: %s",
|
||||||
|
tt.sub, subject[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
eml string
|
||||||
|
enc string
|
||||||
|
sub string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Plain text no encoding", exampleMailPlainNoEnc, "8bit",
|
||||||
|
"Example mail // plain text without encoding",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Plain text quoted-printable", exampleMailPlainQP, "quoted-printable",
|
||||||
|
"Example mail // plain text quoted-printable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Plain text base64", exampleMailPlainB64, "base64",
|
||||||
|
"Example mail // plain text base64",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tempDir, tempFile, err := stringToTempFile(tt.eml, tt.name)
|
||||||
|
defer func() {
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
msg, err := EMLToMsgFromFile(tempFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse EML: %s", err)
|
||||||
|
}
|
||||||
|
if msg.Encoding() != tt.enc {
|
||||||
|
t.Errorf("EMLToMsgFromString failed: expected encoding: %s, but got: %s", tt.enc, msg.Encoding())
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], tt.sub) {
|
||||||
|
t.Errorf("EMLToMsgFromString failed: expected subject: %s, but got: %s",
|
||||||
|
tt.sub, subject[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromReaderFailing(t *testing.T) {
|
||||||
|
mailbuf := bytes.NewBufferString(exampleMailPlainBrokenFrom)
|
||||||
|
_, err := EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken FROM was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainBrokenHeader)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken header was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainB64BrokenBody)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainBrokenBody)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainUnknownContentType)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainNoContentType)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
mailbuf.Reset()
|
||||||
|
mailbuf.WriteString(exampleMailPlainUnsupportedTransferEnc)
|
||||||
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with unsupported Transer-Encoding was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromFileFailing(t *testing.T) {
|
||||||
|
tempDir, tempFile, err := stringToTempFile(exampleMailPlainBrokenFrom, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken FROM was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainBrokenHeader, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken header was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainB64BrokenBody, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainBrokenBody, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with broken body was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnknownContentType, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainNoContentType, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnsupportedTransferEnc, "testmail")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||||
|
}
|
||||||
|
_, err = EMLToMsgFromFile(tempFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML from Reader with unsupported Transer-Encoding was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
|
t.Error("failed to remove temp dir:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringBrokenDate(t *testing.T) {
|
||||||
|
_, err := EMLToMsgFromString(exampleMailPlainNoEncInvalidDate)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML with invalid date was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
m, err := EMLToMsgFromString(exampleMailPlainNoEncNoDate)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EML with no date parsing failed: %s", err)
|
||||||
|
}
|
||||||
|
da := m.GetGenHeader(HeaderDate)
|
||||||
|
if len(da) < 1 {
|
||||||
|
t.Error("EML with no date expected current date, but got nothing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d := da[0]
|
||||||
|
if d != now.Format(time.RFC1123Z) {
|
||||||
|
t.Errorf("EML with no date expected: %s, got: %s", now.Format(time.RFC1123Z), d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringBrokenFrom(t *testing.T) {
|
||||||
|
_, err := EMLToMsgFromString(exampleMailPlainBrokenFrom)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML with broken FROM was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringBrokenTo(t *testing.T) {
|
||||||
|
_, err := EMLToMsgFromString(exampleMailPlainBrokenTo)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML with broken TO was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringNoBoundary(t *testing.T) {
|
||||||
|
_, err := EMLToMsgFromString(exampleMailPlainB64WithAttachmentNoBoundary)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("EML with no boundary was supposed to fail, but didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringWithAttachment(t *testing.T) {
|
||||||
|
wantSubject := "Example mail // plain text base64 with attachment"
|
||||||
|
msg, err := EMLToMsgFromString(exampleMailPlainB64WithAttachment)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EML with attachment failed: %s", err)
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with attachment failed: expected subject: %s, but got: %s",
|
||||||
|
wantSubject, subject[0])
|
||||||
|
}
|
||||||
|
if len(msg.attachments) != 1 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with attachment failed: expected no. of attachments: %d, but got: %d",
|
||||||
|
1, len(msg.attachments))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringWithEmbed(t *testing.T) {
|
||||||
|
wantSubject := "Example mail // plain text base64 with embed"
|
||||||
|
msg, err := EMLToMsgFromString(exampleMailPlainB64WithEmbed)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EML with embed failed: %s", err)
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with embed failed: expected subject: %s, but got: %s",
|
||||||
|
wantSubject, subject[0])
|
||||||
|
}
|
||||||
|
if len(msg.embeds) != 1 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with embed failed: expected no. of embeds: %d, but got: %d",
|
||||||
|
1, len(msg.embeds))
|
||||||
|
}
|
||||||
|
msg, err = EMLToMsgFromString(exampleMailPlainB64WithEmbedNoContentID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EML with embed failed: %s", err)
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with embed failed: expected subject: %s, but got: %s",
|
||||||
|
wantSubject, subject[0])
|
||||||
|
}
|
||||||
|
if len(msg.embeds) != 1 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML with embed failed: expected no. of embeds: %d, but got: %d",
|
||||||
|
1, len(msg.embeds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEMLToMsgFromStringMultipartMixedAlternativeRelated(t *testing.T) {
|
||||||
|
wantSubject := "Example mail // plain text base64 with attachment, embed and alternative part"
|
||||||
|
msg, err := EMLToMsgFromString(exampleMailMultipartMixedAlternativeRelated)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("EML multipart mixed, related, alternative failed: %s", err)
|
||||||
|
}
|
||||||
|
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected subject: %s,"+
|
||||||
|
" but got: %s", wantSubject, subject[0])
|
||||||
|
}
|
||||||
|
if len(msg.embeds) != 1 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected no. of "+
|
||||||
|
"embeds: %d, but got: %d", 1, len(msg.embeds))
|
||||||
|
}
|
||||||
|
if len(msg.attachments) != 1 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected no. of "+
|
||||||
|
"attachments: %d, but got: %d", 1, len(msg.attachments))
|
||||||
|
}
|
||||||
|
if len(msg.parts) != 3 {
|
||||||
|
t.Errorf("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected no. of "+
|
||||||
|
"parts: %d, but got: %d", 3, len(msg.parts))
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPlain, hasHTML, hasAlternative bool
|
||||||
|
for _, part := range msg.parts {
|
||||||
|
if strings.EqualFold(part.contentType.String(), TypeMultipartAlternative.String()) {
|
||||||
|
hasAlternative = true
|
||||||
|
}
|
||||||
|
if strings.EqualFold(part.contentType.String(), TypeTextPlain.String()) {
|
||||||
|
hasPlain = true
|
||||||
|
}
|
||||||
|
if strings.EqualFold(part.contentType.String(), TypeTextHTML.String()) {
|
||||||
|
hasHTML = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasPlain {
|
||||||
|
t.Error("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected PLAIN " +
|
||||||
|
"but got none")
|
||||||
|
}
|
||||||
|
if !hasHTML {
|
||||||
|
t.Error("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected HTML " +
|
||||||
|
"but got none")
|
||||||
|
}
|
||||||
|
if !hasAlternative {
|
||||||
|
t.Error("EMLToMsgFromString of EML multipart mixed, related, alternative failed: expected Alternative " +
|
||||||
|
"but got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringToTempFile is a helper method that will create a temporary file form a give data string
|
||||||
|
func stringToTempFile(data, name string) (string, string, error) {
|
||||||
|
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name))
|
||||||
|
if err != nil {
|
||||||
|
return tempDir, "", fmt.Errorf("failed to create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
filePath := fmt.Sprintf("%s/%s", tempDir, name)
|
||||||
|
err = os.WriteFile(filePath, []byte(data), 0o666)
|
||||||
|
if err != nil {
|
||||||
|
return tempDir, "", fmt.Errorf("failed to write data to temp file: %w", err)
|
||||||
|
}
|
||||||
|
return tempDir, filePath, nil
|
||||||
|
}
|
26
encoding.go
26
encoding.go
|
@ -132,17 +132,20 @@ const (
|
||||||
|
|
||||||
// List of MIME versions
|
// List of MIME versions
|
||||||
const (
|
const (
|
||||||
// Mime10 is the MIME Version 1.0
|
// MIME10 is the MIME Version 1.0
|
||||||
Mime10 MIMEVersion = "1.0"
|
MIME10 MIMEVersion = "1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of common content types
|
// List of common content types
|
||||||
const (
|
const (
|
||||||
TypeTextPlain ContentType = "text/plain"
|
|
||||||
TypeTextHTML ContentType = "text/html"
|
|
||||||
TypeAppOctetStream ContentType = "application/octet-stream"
|
TypeAppOctetStream ContentType = "application/octet-stream"
|
||||||
|
TypeMultipartAlternative ContentType = "multipart/alternative"
|
||||||
|
TypeMultipartMixed ContentType = "multipart/mixed"
|
||||||
|
TypeMultipartRelated ContentType = "multipart/related"
|
||||||
TypePGPSignature ContentType = "application/pgp-signature"
|
TypePGPSignature ContentType = "application/pgp-signature"
|
||||||
TypePGPEncrypted ContentType = "application/pgp-encrypted"
|
TypePGPEncrypted ContentType = "application/pgp-encrypted"
|
||||||
|
TypeTextHTML ContentType = "text/html"
|
||||||
|
TypeTextPlain ContentType = "text/plain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of MIMETypes
|
// List of MIMETypes
|
||||||
|
@ -152,12 +155,17 @@ const (
|
||||||
MIMERelated MIMEType = "related"
|
MIMERelated MIMEType = "related"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String is a standard method to convert an Encoding into a printable format
|
|
||||||
func (e Encoding) String() string {
|
|
||||||
return string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is a standard method to convert an Charset into a printable format
|
// String is a standard method to convert an Charset into a printable format
|
||||||
func (c Charset) String() string {
|
func (c Charset) String() string {
|
||||||
return string(c)
|
return string(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String is a standard method to convert an ContentType into a printable format
|
||||||
|
func (c ContentType) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a standard method to convert an Encoding into a printable format
|
||||||
|
func (e Encoding) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,50 @@ func TestEncoding_String(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContentType_String tests the string method of the ContentType object
|
||||||
|
func TestContentType_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ct ContentType
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"ContentType: text/plain", TypeTextPlain, "text/plain"},
|
||||||
|
{"ContentType: text/html", TypeTextHTML, "text/html"},
|
||||||
|
{
|
||||||
|
"ContentType: application/octet-stream", TypeAppOctetStream,
|
||||||
|
"application/octet-stream",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContentType: multipart/alternative", TypeMultipartAlternative,
|
||||||
|
"multipart/alternative",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContentType: multipart/mixed", TypeMultipartMixed,
|
||||||
|
"multipart/mixed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContentType: multipart/related", TypeMultipartRelated,
|
||||||
|
"multipart/related",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContentType: application/pgp-signature", TypePGPSignature,
|
||||||
|
"application/pgp-signature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ContentType: application/pgp-encrypted", TypePGPEncrypted,
|
||||||
|
"application/pgp-encrypted",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.ct.String() != tt.want {
|
||||||
|
t.Errorf("wrong string for Content-Type returned. Expected: %s, got: %s",
|
||||||
|
tt.want, tt.ct.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestCharset_String tests the string method of the Charset object
|
// TestCharset_String tests the string method of the Charset object
|
||||||
func TestCharset_String(t *testing.T) {
|
func TestCharset_String(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
7
file.go
7
file.go
|
@ -22,6 +22,13 @@ type File struct {
|
||||||
Writer func(w io.Writer) (int64, error)
|
Writer func(w io.Writer) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithFileContentID sets the Content-ID header for the File
|
||||||
|
func WithFileContentID(id string) FileOption {
|
||||||
|
return func(f *File) {
|
||||||
|
f.Header.Set(HeaderContentID.String(), id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithFileName sets the filename of the File
|
// WithFileName sets the filename of the File
|
||||||
func WithFileName(name string) FileOption {
|
func WithFileName(name string) FileOption {
|
||||||
return func(f *File) {
|
return func(f *File) {
|
||||||
|
|
26
file_test.go
26
file_test.go
|
@ -56,6 +56,32 @@ func TestFile_WithFileDescription(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFile_WithContentID tests the WithFileContentID option
|
||||||
|
func TestFile_WithContentID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
contentid string
|
||||||
|
}{
|
||||||
|
{"File Content-ID: test", "test"},
|
||||||
|
{"File Content-ID: empty", ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
m := NewMsg()
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m.AttachFile("file.go", WithFileContentID(tt.contentid))
|
||||||
|
al := m.GetAttachments()
|
||||||
|
if len(al) <= 0 {
|
||||||
|
t.Errorf("AttachFile() failed. Attachment list is empty")
|
||||||
|
}
|
||||||
|
a := al[0]
|
||||||
|
if a.Header.Get(HeaderContentID.String()) != tt.contentid {
|
||||||
|
t.Errorf("WithFileContentID() failed. Expected: %s, got: %s", tt.contentid,
|
||||||
|
a.Header.Get(HeaderContentID.String()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestFile_WithFileEncoding tests the WithFileEncoding option
|
// TestFile_WithFileEncoding tests the WithFileEncoding option
|
||||||
func TestFile_WithFileEncoding(t *testing.T) {
|
func TestFile_WithFileEncoding(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
@ -73,6 +73,9 @@ const (
|
||||||
// HeaderPriority represents the "Priority" field
|
// HeaderPriority represents the "Priority" field
|
||||||
HeaderPriority Header = "Priority"
|
HeaderPriority Header = "Priority"
|
||||||
|
|
||||||
|
// HeaderReferences is the "References" header field
|
||||||
|
HeaderReferences Header = "References"
|
||||||
|
|
||||||
// HeaderReplyTo is the "Reply-To" header field
|
// HeaderReplyTo is the "Reply-To" header field
|
||||||
HeaderReplyTo Header = "Reply-To"
|
HeaderReplyTo Header = "Reply-To"
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ func TestHeader_String(t *testing.T) {
|
||||||
{"Header: Organization", HeaderOrganization, "Organization"},
|
{"Header: Organization", HeaderOrganization, "Organization"},
|
||||||
{"Header: Precedence", HeaderPrecedence, "Precedence"},
|
{"Header: Precedence", HeaderPrecedence, "Precedence"},
|
||||||
{"Header: Priority", HeaderPriority, "Priority"},
|
{"Header: Priority", HeaderPriority, "Priority"},
|
||||||
|
{"Header: HeaderReferences", HeaderReferences, "References"},
|
||||||
{"Header: Reply-To", HeaderReplyTo, "Reply-To"},
|
{"Header: Reply-To", HeaderReplyTo, "Reply-To"},
|
||||||
{"Header: Subject", HeaderSubject, "Subject"},
|
{"Header: Subject", HeaderSubject, "Subject"},
|
||||||
{"Header: User-Agent", HeaderUserAgent, "User-Agent"},
|
{"Header: User-Agent", HeaderUserAgent, "User-Agent"},
|
||||||
|
|
2
msg.go
2
msg.go
|
@ -136,7 +136,7 @@ func NewMsg(opts ...MsgOption) *Msg {
|
||||||
encoding: EncodingQP,
|
encoding: EncodingQP,
|
||||||
genHeader: make(map[Header][]string),
|
genHeader: make(map[Header][]string),
|
||||||
preformHeader: make(map[Header]string),
|
preformHeader: make(map[Header]string),
|
||||||
mimever: Mime10,
|
mimever: MIME10,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override defaults with optionally provided MsgOption functions
|
// Override defaults with optionally provided MsgOption functions
|
||||||
|
|
|
@ -137,7 +137,7 @@ func TestNewMsgWithMIMEVersion(t *testing.T) {
|
||||||
value MIMEVersion
|
value MIMEVersion
|
||||||
want MIMEVersion
|
want MIMEVersion
|
||||||
}{
|
}{
|
||||||
{"MIME version is 1.0", Mime10, "1.0"},
|
{"MIME version is 1.0", MIME10, "1.0"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -89,11 +89,11 @@ func (mw *msgWriter) writeMsg(msg *Msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.hasMixed() {
|
if msg.hasMixed() {
|
||||||
mw.startMP("mixed", msg.boundary)
|
mw.startMP(MIMEMixed, msg.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
if msg.hasRelated() {
|
if msg.hasRelated() {
|
||||||
mw.startMP("related", msg.boundary)
|
mw.startMP(MIMERelated, msg.boundary)
|
||||||
mw.writeString(DoubleNewLine)
|
mw.writeString(DoubleNewLine)
|
||||||
}
|
}
|
||||||
if msg.hasAlt() {
|
if msg.hasAlt() {
|
||||||
|
|
Loading…
Reference in a new issue