2023-09-15 13:16:14 +02:00
|
|
|
package mail
|
|
|
|
|
|
|
|
import (
|
2023-10-13 15:06:28 +02:00
|
|
|
"bytes"
|
2023-09-15 13:16:14 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-09-21 13:50:36 +02:00
|
|
|
"mime"
|
2023-09-15 13:16:14 +02:00
|
|
|
nm "net/mail"
|
|
|
|
"os"
|
2023-10-13 15:06:28 +02:00
|
|
|
"strings"
|
2023-09-15 13:16:14 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// EMLToMsg will open an parse a .eml file at a provided file path and return a
|
|
|
|
// pre-filled Msg pointer
|
|
|
|
func EMLToMsg(fp string) (*Msg, error) {
|
|
|
|
m := &Msg{
|
|
|
|
addrHeader: make(map[AddrHeader][]*nm.Address),
|
|
|
|
genHeader: make(map[Header][]string),
|
|
|
|
preformHeader: make(map[Header]string),
|
|
|
|
mimever: Mime10,
|
|
|
|
}
|
|
|
|
|
2023-10-13 15:06:28 +02:00
|
|
|
pm, mbbuf, err := readEML(fp)
|
2023-09-15 13:16:14 +02:00
|
|
|
if err != nil || pm == nil {
|
|
|
|
return m, 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)
|
|
|
|
}
|
2023-10-13 15:06:28 +02:00
|
|
|
if err := parseEMLBodyParts(pm, mbbuf, m); err != nil {
|
|
|
|
return m, fmt.Errorf("failed to parse EML body parts: %w", err)
|
2023-09-27 11:29:58 +02:00
|
|
|
}
|
2023-09-21 13:50:36 +02:00
|
|
|
|
2023-09-15 13:16:14 +02:00
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// readEML opens an EML file and uses net/mail to parse the header and body
|
2023-10-13 15:06:28 +02:00
|
|
|
func readEML(fp string) (*nm.Message, *bytes.Buffer, error) {
|
2023-09-15 13:16:14 +02:00
|
|
|
fh, err := os.Open(fp)
|
|
|
|
if err != nil {
|
2023-10-13 15:06:28 +02:00
|
|
|
return nil, nil, fmt.Errorf("failed to open EML file: %w", err)
|
2023-09-15 13:16:14 +02:00
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = fh.Close()
|
|
|
|
}()
|
|
|
|
pm, err := nm.ReadMessage(fh)
|
|
|
|
if err != nil {
|
2023-10-13 15:06:28 +02:00
|
|
|
return pm, nil, fmt.Errorf("failed to parse EML: %w", err)
|
2023-09-15 13:16:14 +02:00
|
|
|
}
|
2023-10-13 15:06:28 +02:00
|
|
|
|
|
|
|
buf := bytes.Buffer{}
|
|
|
|
if _, err = buf.ReadFrom(pm.Body); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return pm, &buf, nil
|
2023-09-15 13:16:14 +02:00
|
|
|
}
|
|
|
|
|
2023-09-21 13:50:36 +02:00
|
|
|
// parseEMLHeaders will check the EML headers for the most common headers and set the
|
|
|
|
// according settings in the Msg
|
2023-09-15 13:16:14 +02:00
|
|
|
func parseEMLHeaders(mh *nm.Header, m *Msg) error {
|
|
|
|
commonHeaders := []Header{
|
|
|
|
HeaderContentType, HeaderImportance, HeaderInReplyTo, HeaderListUnsubscribe,
|
|
|
|
HeaderListUnsubscribePost, HeaderMessageID, HeaderMIMEVersion, HeaderOrganization,
|
2023-10-13 15:06:28 +02:00
|
|
|
HeaderPrecedence, HeaderPriority, HeaderReferences, HeaderSubject, HeaderUserAgent,
|
|
|
|
HeaderXMailer, HeaderXMSMailPriority, HeaderXPriority,
|
2023-09-15 13:16:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extract address headers
|
|
|
|
if v := mh.Get(HeaderFrom.String()); v != "" {
|
|
|
|
if err := m.From(v); err != nil {
|
|
|
|
return fmt.Errorf(`failed to parse "From:" header: %w`, err)
|
|
|
|
}
|
|
|
|
}
|
2023-09-21 13:50:36 +02:00
|
|
|
ahl := map[AddrHeader]func(...string) error{
|
|
|
|
HeaderTo: m.To,
|
|
|
|
HeaderCc: m.Cc,
|
|
|
|
HeaderBcc: m.Bcc,
|
|
|
|
}
|
|
|
|
for h, f := range ahl {
|
|
|
|
if v := mh.Get(h.String()); v != "" {
|
|
|
|
var als []string
|
|
|
|
pal, err := nm.ParseAddressList(v)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(`failed to parse address list: %w`, err)
|
|
|
|
}
|
|
|
|
for _, a := range pal {
|
|
|
|
als = append(als, a.String())
|
|
|
|
}
|
|
|
|
if err := f(als...); err != nil {
|
|
|
|
return fmt.Errorf(`failed to parse "To:" header: %w`, err)
|
|
|
|
}
|
2023-09-15 13:16:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract date from message
|
|
|
|
d, err := mh.Date()
|
|
|
|
if err != nil {
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, nm.ErrHeaderNotPresent):
|
|
|
|
m.SetDate()
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("failed to parse EML date: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
m.SetDateWithValue(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract common headers
|
|
|
|
for _, h := range commonHeaders {
|
|
|
|
if v := mh.Get(h.String()); v != "" {
|
|
|
|
m.SetGenHeader(h, v)
|
|
|
|
}
|
|
|
|
}
|
2023-09-21 13:50:36 +02:00
|
|
|
|
2023-09-15 13:16:14 +02:00
|
|
|
return nil
|
|
|
|
}
|
2023-10-13 15:06:28 +02:00
|
|
|
|
|
|
|
// parseEMLBodyParts ...
|
|
|
|
func parseEMLBodyParts(pm *nm.Message, mbbuf *bytes.Buffer, m *Msg) error {
|
|
|
|
// Extract the transfer encoding of the body
|
|
|
|
mt, par, err := mime.ParseMediaType(pm.Header.Get(HeaderContentType.String()))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to extract content type: %w", err)
|
|
|
|
}
|
|
|
|
if v, ok := par["charset"]; ok {
|
|
|
|
m.SetCharset(Charset(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
if cte := pm.Header.Get(HeaderContentTransferEnc.String()); cte != "" {
|
|
|
|
switch strings.ToLower(cte) {
|
|
|
|
case NoEncoding.String():
|
|
|
|
m.SetEncoding(NoEncoding)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(mt) {
|
|
|
|
case TypeTextPlain.String():
|
|
|
|
m.SetBodyString(TypeTextPlain, mbbuf.String())
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|