mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-25 07:00:49 +01:00
Merge pull request #249 from wneessen/feature/145_add-eml-parser-to-generate-msg-from-eml-files
This PR introduces an EML parser to go-mail. It allows to read generic EML data from a file, a string or a reader into a go-mail Msg struct. It supports all types of message parts and encodings. It should be able to recognize Mulitpart messages as well as attachments and embeds (inline attachments). This PR closes #145
This commit is contained in:
commit
ffdea8337e
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] Custom error types for delivery errors
|
||||
* [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
|
||||
standard in a MUA.
|
||||
|
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
|||
package mail
|
||||
|
||||
// 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
|
||||
}
|
32
encoding.go
32
encoding.go
|
@ -132,17 +132,20 @@ const (
|
|||
|
||||
// List of MIME versions
|
||||
const (
|
||||
// Mime10 is the MIME Version 1.0
|
||||
Mime10 MIMEVersion = "1.0"
|
||||
// MIME10 is the MIME Version 1.0
|
||||
MIME10 MIMEVersion = "1.0"
|
||||
)
|
||||
|
||||
// List of common content types
|
||||
const (
|
||||
TypeTextPlain ContentType = "text/plain"
|
||||
TypeTextHTML ContentType = "text/html"
|
||||
TypeAppOctetStream ContentType = "application/octet-stream"
|
||||
TypePGPSignature ContentType = "application/pgp-signature"
|
||||
TypePGPEncrypted ContentType = "application/pgp-encrypted"
|
||||
TypeAppOctetStream ContentType = "application/octet-stream"
|
||||
TypeMultipartAlternative ContentType = "multipart/alternative"
|
||||
TypeMultipartMixed ContentType = "multipart/mixed"
|
||||
TypeMultipartRelated ContentType = "multipart/related"
|
||||
TypePGPSignature ContentType = "application/pgp-signature"
|
||||
TypePGPEncrypted ContentType = "application/pgp-encrypted"
|
||||
TypeTextHTML ContentType = "text/html"
|
||||
TypeTextPlain ContentType = "text/plain"
|
||||
)
|
||||
|
||||
// List of MIMETypes
|
||||
|
@ -152,12 +155,17 @@ const (
|
|||
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
|
||||
func (c Charset) String() string {
|
||||
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
|
||||
func TestCharset_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
|
7
file.go
7
file.go
|
@ -22,6 +22,13 @@ type File struct {
|
|||
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
|
||||
func WithFileName(name string) FileOption {
|
||||
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
|
||||
func TestFile_WithFileEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
|
|
@ -73,6 +73,9 @@ const (
|
|||
// HeaderPriority represents the "Priority" field
|
||||
HeaderPriority Header = "Priority"
|
||||
|
||||
// HeaderReferences is the "References" header field
|
||||
HeaderReferences Header = "References"
|
||||
|
||||
// HeaderReplyTo is the "Reply-To" header field
|
||||
HeaderReplyTo Header = "Reply-To"
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ func TestHeader_String(t *testing.T) {
|
|||
{"Header: Organization", HeaderOrganization, "Organization"},
|
||||
{"Header: Precedence", HeaderPrecedence, "Precedence"},
|
||||
{"Header: Priority", HeaderPriority, "Priority"},
|
||||
{"Header: HeaderReferences", HeaderReferences, "References"},
|
||||
{"Header: Reply-To", HeaderReplyTo, "Reply-To"},
|
||||
{"Header: Subject", HeaderSubject, "Subject"},
|
||||
{"Header: User-Agent", HeaderUserAgent, "User-Agent"},
|
||||
|
|
2
msg.go
2
msg.go
|
@ -136,7 +136,7 @@ func NewMsg(opts ...MsgOption) *Msg {
|
|||
encoding: EncodingQP,
|
||||
genHeader: make(map[Header][]string),
|
||||
preformHeader: make(map[Header]string),
|
||||
mimever: Mime10,
|
||||
mimever: MIME10,
|
||||
}
|
||||
|
||||
// Override defaults with optionally provided MsgOption functions
|
||||
|
|
|
@ -137,7 +137,7 @@ func TestNewMsgWithMIMEVersion(t *testing.T) {
|
|||
value MIMEVersion
|
||||
want MIMEVersion
|
||||
}{
|
||||
{"MIME version is 1.0", Mime10, "1.0"},
|
||||
{"MIME version is 1.0", MIME10, "1.0"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -89,11 +89,11 @@ func (mw *msgWriter) writeMsg(msg *Msg) {
|
|||
}
|
||||
|
||||
if msg.hasMixed() {
|
||||
mw.startMP("mixed", msg.boundary)
|
||||
mw.startMP(MIMEMixed, msg.boundary)
|
||||
mw.writeString(DoubleNewLine)
|
||||
}
|
||||
if msg.hasRelated() {
|
||||
mw.startMP("related", msg.boundary)
|
||||
mw.startMP(MIMERelated, msg.boundary)
|
||||
mw.writeString(DoubleNewLine)
|
||||
}
|
||||
if msg.hasAlt() {
|
||||
|
|
Loading…
Reference in a new issue