mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-25 07:00:49 +01:00
Refactor variable names in eml.go for clarity
Variable names in eml.go have been refactored for better readability and understanding. Shortened abbreviations have been expanded into meaningful names, and complex object names have been made simpler, making it easier to understand their role within the codebase. Cooperative variable names will improve maintainability and ease future development. This is a follow up to #179 which didn't consider this branch.
This commit is contained in:
parent
34f2141a26
commit
481fc1d48c
1 changed files with 120 additions and 120 deletions
240
eml.go
240
eml.go
|
@ -13,97 +13,97 @@ import (
|
|||
"mime"
|
||||
"mime/multipart"
|
||||
"mime/quotedprintable"
|
||||
nm "net/mail"
|
||||
netmail "net/mail"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EMLToMsgFromString will parse a given EML string and returns a pre-filled Msg pointer
|
||||
func EMLToMsgFromString(es string) (*Msg, error) {
|
||||
eb := bytes.NewBufferString(es)
|
||||
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(r io.Reader) (*Msg, error) {
|
||||
m := &Msg{
|
||||
addrHeader: make(map[AddrHeader][]*nm.Address),
|
||||
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,
|
||||
}
|
||||
|
||||
pm, bodybuf, err := readEMLFromReader(r)
|
||||
if err != nil || pm == nil {
|
||||
return m, fmt.Errorf("failed to parse EML from reader: %w", err)
|
||||
parsedMsg, bodybuf, err := readEMLFromReader(reader)
|
||||
if err != nil || parsedMsg == nil {
|
||||
return msg, fmt.Errorf("failed to parse EML from reader: %w", err)
|
||||
}
|
||||
|
||||
if err = parseEMLHeaders(&pm.Header, m); err != nil {
|
||||
return m, fmt.Errorf("failed to parse EML headers: %w", err)
|
||||
if err = parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
|
||||
return msg, fmt.Errorf("failed to parse EML headers: %w", err)
|
||||
}
|
||||
if err = parseEMLBodyParts(pm, bodybuf, m); err != nil {
|
||||
return m, fmt.Errorf("failed to parse EML body parts: %w", err)
|
||||
if err = parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
|
||||
return msg, fmt.Errorf("failed to parse EML body parts: %w", err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
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(fp string) (*Msg, error) {
|
||||
m := &Msg{
|
||||
addrHeader: make(map[AddrHeader][]*nm.Address),
|
||||
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,
|
||||
}
|
||||
|
||||
pm, bodybuf, err := readEML(fp)
|
||||
if err != nil || pm == nil {
|
||||
return m, fmt.Errorf("failed to parse EML file: %w", err)
|
||||
parsedMsg, bodybuf, err := readEML(filePath)
|
||||
if err != nil || parsedMsg == nil {
|
||||
return msg, fmt.Errorf("failed to parse EML file: %w", err)
|
||||
}
|
||||
|
||||
if err = parseEMLHeaders(&pm.Header, m); err != nil {
|
||||
return m, fmt.Errorf("failed to parse EML headers: %w", err)
|
||||
if err = parseEMLHeaders(&parsedMsg.Header, msg); err != nil {
|
||||
return msg, fmt.Errorf("failed to parse EML headers: %w", err)
|
||||
}
|
||||
if err = parseEMLBodyParts(pm, bodybuf, m); err != nil {
|
||||
return m, fmt.Errorf("failed to parse EML body parts: %w", err)
|
||||
if err = parseEMLBodyParts(parsedMsg, bodybuf, msg); err != nil {
|
||||
return msg, fmt.Errorf("failed to parse EML body parts: %w", err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// readEML opens an EML file and uses net/mail to parse the header and body
|
||||
func readEML(fp string) (*nm.Message, *bytes.Buffer, error) {
|
||||
fh, err := os.Open(fp)
|
||||
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() {
|
||||
_ = fh.Close()
|
||||
_ = fileHandle.Close()
|
||||
}()
|
||||
return readEMLFromReader(fh)
|
||||
return readEMLFromReader(fileHandle)
|
||||
}
|
||||
|
||||
// readEMLFromReader uses net/mail to parse the header and body from a given io.Reader
|
||||
func readEMLFromReader(r io.Reader) (*nm.Message, *bytes.Buffer, error) {
|
||||
pm, err := nm.ReadMessage(r)
|
||||
func readEMLFromReader(reader io.Reader) (*netmail.Message, *bytes.Buffer, error) {
|
||||
parsedMsg, err := netmail.ReadMessage(reader)
|
||||
if err != nil {
|
||||
return pm, nil, fmt.Errorf("failed to parse EML: %w", err)
|
||||
return parsedMsg, nil, fmt.Errorf("failed to parse EML: %w", err)
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
if _, err = buf.ReadFrom(pm.Body); err != nil {
|
||||
if _, err = buf.ReadFrom(parsedMsg.Body); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pm, &buf, nil
|
||||
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(mh *nm.Header, m *Msg) error {
|
||||
func parseEMLHeaders(mailHeader *netmail.Header, msg *Msg) error {
|
||||
commonHeaders := []Header{
|
||||
HeaderContentType, HeaderImportance, HeaderInReplyTo, HeaderListUnsubscribe,
|
||||
HeaderListUnsubscribePost, HeaderMessageID, HeaderMIMEVersion, HeaderOrganization,
|
||||
|
@ -112,72 +112,72 @@ func parseEMLHeaders(mh *nm.Header, m *Msg) error {
|
|||
}
|
||||
|
||||
// Extract content type, charset and encoding first
|
||||
if v := mh.Get(HeaderContentTransferEnc.String()); v != "" {
|
||||
if value := mailHeader.Get(HeaderContentTransferEnc.String()); value != "" {
|
||||
switch {
|
||||
case strings.EqualFold(v, EncodingQP.String()):
|
||||
m.SetEncoding(EncodingQP)
|
||||
case strings.EqualFold(v, EncodingB64.String()):
|
||||
m.SetEncoding(EncodingB64)
|
||||
case strings.EqualFold(value, EncodingQP.String()):
|
||||
msg.SetEncoding(EncodingQP)
|
||||
case strings.EqualFold(value, EncodingB64.String()):
|
||||
msg.SetEncoding(EncodingB64)
|
||||
default:
|
||||
m.SetEncoding(NoEncoding)
|
||||
msg.SetEncoding(NoEncoding)
|
||||
}
|
||||
}
|
||||
if v := mh.Get(HeaderContentType.String()); v != "" {
|
||||
ct, cs := parseContentType(v)
|
||||
if cs != "" {
|
||||
m.SetCharset(Charset(cs))
|
||||
if value := mailHeader.Get(HeaderContentType.String()); value != "" {
|
||||
contentType, charSet := parseContentType(value)
|
||||
if charSet != "" {
|
||||
msg.SetCharset(Charset(charSet))
|
||||
}
|
||||
m.setEncoder()
|
||||
if ct != "" {
|
||||
m.SetGenHeader(HeaderContentType, ct)
|
||||
msg.setEncoder()
|
||||
if contentType != "" {
|
||||
msg.SetGenHeader(HeaderContentType, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract address headers
|
||||
if v := mh.Get(HeaderFrom.String()); v != "" {
|
||||
if err := m.From(v); err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
ahl := map[AddrHeader]func(...string) error{
|
||||
HeaderTo: m.To,
|
||||
HeaderCc: m.Cc,
|
||||
HeaderBcc: m.Bcc,
|
||||
addrHeaders := map[AddrHeader]func(...string) error{
|
||||
HeaderTo: msg.To,
|
||||
HeaderCc: msg.Cc,
|
||||
HeaderBcc: msg.Bcc,
|
||||
}
|
||||
for h, f := range ahl {
|
||||
if v := mh.Get(h.String()); v != "" {
|
||||
var als []string
|
||||
pal, err := nm.ParseAddressList(v)
|
||||
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 _, a := range pal {
|
||||
als = append(als, a.String())
|
||||
for _, addr := range parsedAddrs {
|
||||
addrStrings = append(addrStrings, addr.String())
|
||||
}
|
||||
if err := f(als...); err != nil {
|
||||
if err = addrFunc(addrStrings...); err != nil {
|
||||
return fmt.Errorf(`failed to parse %q header: %w`, HeaderTo, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract date from message
|
||||
d, err := mh.Date()
|
||||
date, err := mailHeader.Date()
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, nm.ErrHeaderNotPresent):
|
||||
m.SetDate()
|
||||
case errors.Is(err, netmail.ErrHeaderNotPresent):
|
||||
msg.SetDate()
|
||||
default:
|
||||
return fmt.Errorf("failed to parse EML date: %w", err)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
m.SetDateWithValue(d)
|
||||
msg.SetDateWithValue(date)
|
||||
}
|
||||
|
||||
// Extract common headers
|
||||
for _, h := range commonHeaders {
|
||||
if v := mh.Get(h.String()); v != "" {
|
||||
m.SetGenHeader(h, v)
|
||||
for _, header := range commonHeaders {
|
||||
if value := mailHeader.Get(header.String()); value != "" {
|
||||
msg.SetGenHeader(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,25 +185,25 @@ func parseEMLHeaders(mh *nm.Header, m *Msg) error {
|
|||
}
|
||||
|
||||
// parseEMLBodyParts parses the body of a EML based on the different content types and encodings
|
||||
func parseEMLBodyParts(pm *nm.Message, bodybuf *bytes.Buffer, m *Msg) error {
|
||||
func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||
// Extract the transfer encoding of the body
|
||||
mediatype, params, err := mime.ParseMediaType(pm.Header.Get(HeaderContentType.String()))
|
||||
mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract content type: %w", err)
|
||||
}
|
||||
if v, ok := params["charset"]; ok {
|
||||
m.SetCharset(Charset(v))
|
||||
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, pm, bodybuf, m); err != nil {
|
||||
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, "multipart/mixed"):
|
||||
if err := parseEMLMultipartAlternative(params, bodybuf, m); err != nil {
|
||||
if err = parseEMLMultipartAlternative(params, bodybuf, msg); err != nil {
|
||||
return fmt.Errorf("failed to parse multipart/alternative body: %w", err)
|
||||
}
|
||||
default:
|
||||
|
@ -212,114 +212,114 @@ func parseEMLBodyParts(pm *nm.Message, bodybuf *bytes.Buffer, m *Msg) error {
|
|||
}
|
||||
|
||||
// parseEMLBodyPlain parses the mail body of plain type mails
|
||||
func parseEMLBodyPlain(mediatype string, pm *nm.Message, bodybuf *bytes.Buffer, m *Msg) error {
|
||||
cte := pm.Header.Get(HeaderContentTransferEnc.String())
|
||||
if strings.EqualFold(cte, NoEncoding.String()) {
|
||||
m.SetEncoding(NoEncoding)
|
||||
m.SetBodyString(ContentType(mediatype), bodybuf.String())
|
||||
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(cte, EncodingQP.String()) {
|
||||
m.SetEncoding(EncodingQP)
|
||||
qpr := quotedprintable.NewReader(bodybuf)
|
||||
qpbuf := bytes.Buffer{}
|
||||
if _, err := qpbuf.ReadFrom(qpr); err != 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)
|
||||
}
|
||||
m.SetBodyString(ContentType(mediatype), qpbuf.String())
|
||||
msg.SetBodyString(ContentType(mediatype), qpBuffer.String())
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(cte, EncodingB64.String()) {
|
||||
m.SetEncoding(EncodingB64)
|
||||
b64d := base64.NewDecoder(base64.StdEncoding, bodybuf)
|
||||
b64buf := bytes.Buffer{}
|
||||
if _, err := b64buf.ReadFrom(b64d); err != 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)
|
||||
}
|
||||
m.SetBodyString(ContentType(mediatype), b64buf.String())
|
||||
msg.SetBodyString(ContentType(mediatype), b64Buffer.String())
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
||||
}
|
||||
|
||||
// parseEMLMultipartAlternative parses a multipart/alternative body part of a EML
|
||||
func parseEMLMultipartAlternative(params map[string]string, bodybuf *bytes.Buffer, m *Msg) error {
|
||||
func parseEMLMultipartAlternative(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")
|
||||
}
|
||||
mpreader := multipart.NewReader(bodybuf, boundary)
|
||||
mpart, err := mpreader.NextPart()
|
||||
multipartReader := multipart.NewReader(bodybuf, boundary)
|
||||
multiPart, err := multipartReader.NextPart()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get next part of multipart message: %w", err)
|
||||
}
|
||||
for err == nil {
|
||||
mpdata, mperr := io.ReadAll(mpart)
|
||||
multiPartData, mperr := io.ReadAll(multiPart)
|
||||
if mperr != nil {
|
||||
_ = mpart.Close()
|
||||
_ = multiPart.Close()
|
||||
return fmt.Errorf("failed to read multipart: %w", err)
|
||||
}
|
||||
|
||||
mpContentType, ok := mpart.Header[HeaderContentType.String()]
|
||||
multiPartContentType, ok := multiPart.Header[HeaderContentType.String()]
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get content-type from part")
|
||||
}
|
||||
conType, charSet := parseContentType(mpContentType[0])
|
||||
p := m.newPart(ContentType(conType))
|
||||
contentType, charSet := parseContentType(multiPartContentType[0])
|
||||
p := msg.newPart(ContentType(contentType))
|
||||
p.SetCharset(Charset(charSet))
|
||||
|
||||
mpTransferEnc, ok := mpart.Header[HeaderContentTransferEnc.String()]
|
||||
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
|
||||
mpTransferEnc = []string{EncodingQP.String()}
|
||||
mutliPartTransferEnc = []string{EncodingQP.String()}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(mpTransferEnc[0], EncodingB64.String()):
|
||||
if err := handleEMLMultiPartBase64Encoding(mpdata, p); err != nil {
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingB64.String()):
|
||||
if err := handleEMLMultiPartBase64Encoding(multiPartData, p); err != nil {
|
||||
return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
|
||||
}
|
||||
case strings.EqualFold(mpTransferEnc[0], EncodingQP.String()):
|
||||
p.SetContent(string(mpdata))
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
|
||||
p.SetContent(string(multiPartData))
|
||||
default:
|
||||
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
||||
}
|
||||
|
||||
m.parts = append(m.parts, p)
|
||||
mpart, err = mpreader.NextPart()
|
||||
msg.parts = append(msg.parts, p)
|
||||
multiPart, err = multipartReader.NextPart()
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
_ = mpart.Close()
|
||||
_ = multiPart.Close()
|
||||
return fmt.Errorf("failed to read multipart: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part
|
||||
func handleEMLMultiPartBase64Encoding(mpdata []byte, p *Part) error {
|
||||
p.SetEncoding(EncodingB64)
|
||||
cont, err := base64.StdEncoding.DecodeString(string(mpdata))
|
||||
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)
|
||||
}
|
||||
p.SetContent(string(cont))
|
||||
part.SetContent(string(content))
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseContentType parses the Content-Type header and returns the type and charse as
|
||||
// separate string values
|
||||
func parseContentType(cth string) (ct string, cs string) {
|
||||
cts := strings.SplitN(cth, "; ", 2)
|
||||
if len(cts) != 2 {
|
||||
func parseContentType(contentTypeHeader string) (contentType string, charSet string) {
|
||||
contentTypeSplit := strings.SplitN(contentTypeHeader, "; ", 2)
|
||||
if len(contentTypeSplit) != 2 {
|
||||
return
|
||||
}
|
||||
ct = cts[0]
|
||||
if strings.HasPrefix(strings.ToLower(cts[1]), "charset=") {
|
||||
css := strings.SplitN(cts[1], "=", 2)
|
||||
if len(css) == 2 {
|
||||
cs = css[1]
|
||||
contentType = contentTypeSplit[0]
|
||||
if strings.HasPrefix(strings.ToLower(contentTypeSplit[1]), "charset=") {
|
||||
charSetSplit := strings.SplitN(contentTypeSplit[1], "=", 2)
|
||||
if len(charSetSplit) == 2 {
|
||||
charSet = charSetSplit[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
Loading…
Reference in a new issue