mirror of
https://github.com/GRFreire/nthmail.git
synced 2026-01-09 04:49:39 +00:00
268 lines
5.1 KiB
Go
268 lines
5.1 KiB
Go
package mail_utils
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"mime"
|
|
"mime/multipart"
|
|
"mime/quotedprintable"
|
|
"net/mail"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type MIMEType uint8
|
|
type MediaType uint8
|
|
|
|
const (
|
|
PlainText MIMEType = iota
|
|
Html
|
|
Markdown
|
|
)
|
|
|
|
const (
|
|
NotMultipart MediaType = iota
|
|
Alternative
|
|
Mixed
|
|
)
|
|
|
|
type Mail_body struct {
|
|
MimeType MIMEType
|
|
Data string
|
|
}
|
|
|
|
type Mail_obj struct {
|
|
Id int
|
|
From string
|
|
Date time.Time
|
|
To []string
|
|
Cc []string
|
|
Bcc []string
|
|
Subject string
|
|
|
|
Body []Mail_body
|
|
MediaType
|
|
PreferedBodyIndex int
|
|
}
|
|
|
|
func Parse_mime_format(s string) (MIMEType, bool) {
|
|
var t MIMEType
|
|
|
|
if s == "" {
|
|
return t, false
|
|
}
|
|
|
|
switch {
|
|
case strings.EqualFold(s, "html"):
|
|
t = Html
|
|
case strings.EqualFold(s, "md"):
|
|
t = Markdown
|
|
case strings.EqualFold(s, "text"):
|
|
t = PlainText
|
|
default:
|
|
return t, false
|
|
}
|
|
|
|
return t, true
|
|
}
|
|
|
|
func Parse_mail(m_data []byte, header_only bool) (Mail_obj, error) {
|
|
var m Mail_obj
|
|
|
|
mail_msg, err := mail.ReadMessage(bytes.NewReader(m_data))
|
|
if err != nil {
|
|
return m, errors.New("Could not read message")
|
|
}
|
|
|
|
// HEADERS
|
|
dec := new(mime.WordDecoder)
|
|
m.From, _ = dec.DecodeHeader(mail_msg.Header.Get("From"))
|
|
m.Subject, _ = dec.DecodeHeader(mail_msg.Header.Get("Subject"))
|
|
|
|
to_addrs, _ := mail_msg.Header.AddressList("To")
|
|
m.To = make([]string, len(to_addrs))
|
|
for i, a := range to_addrs {
|
|
m.To[i] = a.Address
|
|
}
|
|
|
|
cc_addrs, _ := mail_msg.Header.AddressList("Cc")
|
|
m.Cc = make([]string, len(to_addrs))
|
|
for i, a := range cc_addrs {
|
|
m.Cc[i] = a.Address
|
|
}
|
|
|
|
bcc_addrs, _ := mail_msg.Header.AddressList("Bcc")
|
|
m.Bcc = make([]string, len(to_addrs))
|
|
for i, a := range bcc_addrs {
|
|
m.Bcc[i] = a.Address
|
|
}
|
|
|
|
if header_only {
|
|
return m, nil
|
|
}
|
|
|
|
content_type := mail_msg.Header.Get("Content-Type")
|
|
mediaType, params, err := mime.ParseMediaType(content_type)
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
|
|
if content_type == "" || !strings.HasPrefix(mediaType, "multipart/") {
|
|
txt_bytes, err := io.ReadAll(mail_msg.Body)
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
|
|
var body Mail_body
|
|
|
|
mail_body, err := Parse_mail_part(mail_msg.Header, txt_bytes)
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
|
|
body.Data = mail_body.Data
|
|
body.MimeType = mail_body.MimeType
|
|
|
|
m.MediaType = NotMultipart
|
|
m.Body = append(m.Body, body)
|
|
|
|
return m, nil
|
|
}
|
|
|
|
if mediaType == "multipart/mixed" {
|
|
m.MediaType = Mixed
|
|
} else if mediaType == "multipart/alternative" {
|
|
m.MediaType = Alternative
|
|
} else {
|
|
return m, errors.New("Not supported multipart type")
|
|
}
|
|
|
|
body, err := Parse_mail_multipart(mail_msg.Body, params["boundary"])
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
|
|
m.Body = body
|
|
|
|
return m, nil
|
|
}
|
|
|
|
type Header interface {
|
|
Get(string) string
|
|
}
|
|
|
|
func Parse_mail_part(header Header, body []byte) (Mail_body, error) {
|
|
content_transfer_encoding := header.Get("Content-Transfer-Encoding")
|
|
content_type := header.Get("Content-Type")
|
|
|
|
var mail_body Mail_body
|
|
|
|
switch {
|
|
case strings.HasPrefix(content_type, "text/plain"):
|
|
mail_body.MimeType = PlainText
|
|
case strings.HasPrefix(content_type, "text/markdown"):
|
|
mail_body.MimeType = Markdown
|
|
case strings.HasPrefix(content_type, "text/html"):
|
|
mail_body.MimeType = Html
|
|
default:
|
|
return mail_body, errors.New("Content type not supported: " + content_type)
|
|
}
|
|
|
|
switch {
|
|
case strings.EqualFold(content_transfer_encoding, "BASE64"):
|
|
decoded_content, err := base64.StdEncoding.DecodeString(string(body))
|
|
if err != nil {
|
|
return mail_body, err
|
|
}
|
|
|
|
mail_body.Data = string(decoded_content)
|
|
|
|
case strings.EqualFold(content_transfer_encoding, "QUOTED-PRINTABLE"):
|
|
decoded_content, err := io.ReadAll(quotedprintable.NewReader(bytes.NewReader(body)))
|
|
if err != nil {
|
|
return mail_body, err
|
|
}
|
|
|
|
mail_body.Data = string(decoded_content)
|
|
|
|
default:
|
|
mail_body.Data = string(body)
|
|
}
|
|
|
|
return mail_body, nil
|
|
}
|
|
|
|
func Parse_mail_multipart(mime_data io.Reader, boundary string) ([]Mail_body, error) {
|
|
var body []Mail_body
|
|
|
|
reader := multipart.NewReader(mime_data, boundary)
|
|
if reader == nil {
|
|
return body, nil
|
|
}
|
|
|
|
for {
|
|
new_part, err := reader.NextPart()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
return body, err
|
|
}
|
|
|
|
mediaType, params, err := mime.ParseMediaType(new_part.Header.Get("Content-Type"))
|
|
|
|
if err != nil {
|
|
return body, err
|
|
}
|
|
|
|
if strings.HasPrefix(mediaType, "multipart/") {
|
|
body_part, err := Parse_mail_multipart(new_part, params["boundary"])
|
|
if err != nil {
|
|
return body, err
|
|
}
|
|
|
|
body = append(body, body_part...)
|
|
|
|
} else {
|
|
|
|
part_data, err := io.ReadAll(new_part)
|
|
if err != nil {
|
|
return body, err
|
|
}
|
|
part_body, err := Parse_mail_part(new_part.Header, part_data)
|
|
if err != nil {
|
|
return body, err
|
|
}
|
|
body = append(body, part_body)
|
|
|
|
}
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
func Set_format_index(m Mail_obj, format MIMEType, pref bool) Mail_obj {
|
|
priority := []MIMEType{Html, Markdown, PlainText}
|
|
|
|
m.PreferedBodyIndex = -1
|
|
curr_p := len(priority)
|
|
for i, b := range m.Body {
|
|
if pref && format == b.MimeType {
|
|
m.PreferedBodyIndex = i
|
|
break
|
|
}
|
|
|
|
p := slices.Index(priority, b.MimeType)
|
|
if p < curr_p {
|
|
curr_p = p
|
|
m.PreferedBodyIndex = i
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|