mirror of
https://github.com/GRFreire/nthmail.git
synced 2026-01-09 21:09:39 +00:00
before, if the mail content-type was text, it would not consider the content encoding, only if it was multipart. now this step on the mail parsing is performed both on multipart mails and in text mails
251 lines
4.7 KiB
Go
251 lines
4.7 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
|
|
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.To, _ = dec.DecodeHeader(mail_msg.Header.Get("To"))
|
|
m.Bcc, _ = dec.DecodeHeader(mail_msg.Header.Get("Bcc"))
|
|
m.Subject, _ = dec.DecodeHeader(mail_msg.Header.Get("Subject"))
|
|
|
|
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
|
|
}
|