refactoring, created email package
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"maunium.net/go/mautrix/format"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
)
|
||||
|
||||
@@ -415,7 +416,7 @@ func (b *Bot) runSend(ctx context.Context) {
|
||||
tos := strings.Split(to, ",")
|
||||
// validate first
|
||||
for _, to := range tos {
|
||||
if !utils.AddressValid(to) {
|
||||
if !email.AddressValid(to) {
|
||||
b.Error(ctx, evt.RoomID, "email address is not valid")
|
||||
return
|
||||
}
|
||||
@@ -426,10 +427,10 @@ func (b *Bot) runSend(ctx context.Context) {
|
||||
|
||||
domain := utils.SanitizeDomain(cfg.Domain())
|
||||
from := mailbox + "@" + domain
|
||||
ID := utils.MessageID(evt.ID, domain)
|
||||
ID := email.MessageID(evt.ID, domain)
|
||||
for _, to := range tos {
|
||||
email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, htmlBody, nil)
|
||||
data := email.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||
eml := email.New(ID, "", " "+ID, subject, from, to, body, htmlBody, nil)
|
||||
data := eml.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||
if data == "" {
|
||||
b.SendError(ctx, evt.RoomID, "email body is empty")
|
||||
return
|
||||
@@ -437,14 +438,14 @@ func (b *Bot) runSend(ctx context.Context) {
|
||||
queued, err := b.Sendmail(evt.ID, from, to, data)
|
||||
if queued {
|
||||
b.log.Error("cannot send email: %v", err)
|
||||
b.saveSentMetadata(ctx, queued, evt.ID, email, &cfg)
|
||||
b.saveSentMetadata(ctx, queued, evt.ID, eml, &cfg)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
b.Error(ctx, evt.RoomID, "cannot send email to %s: %v", to, err)
|
||||
continue
|
||||
}
|
||||
b.saveSentMetadata(ctx, false, evt.ID, email, &cfg)
|
||||
b.saveSentMetadata(ctx, false, evt.ID, eml, &cfg)
|
||||
}
|
||||
if len(tos) > 1 {
|
||||
b.SendNotice(ctx, evt.RoomID, "All emails were sent.")
|
||||
|
||||
29
bot/email.go
29
bot/email.go
@@ -9,6 +9,7 @@ import (
|
||||
"maunium.net/go/mautrix/format"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
)
|
||||
|
||||
@@ -85,7 +86,7 @@ func (b *Bot) GetMapping(mailbox string) (id.RoomID, bool) {
|
||||
}
|
||||
|
||||
// GetIFOptions returns incoming email filtering options (room settings)
|
||||
func (b *Bot) GetIFOptions(roomID id.RoomID) utils.IncomingFilteringOptions {
|
||||
func (b *Bot) GetIFOptions(roomID id.RoomID) email.IncomingFilteringOptions {
|
||||
cfg, err := b.getRoomSettings(roomID)
|
||||
if err != nil {
|
||||
b.log.Error("cannot retrieve room settings: %v", err)
|
||||
@@ -96,7 +97,7 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) utils.IncomingFilteringOptions {
|
||||
}
|
||||
|
||||
// IncomingEmail sends incoming email to matrix room
|
||||
func (b *Bot) IncomingEmail(ctx context.Context, email *utils.Email) error {
|
||||
func (b *Bot) IncomingEmail(ctx context.Context, email *email.Email) error {
|
||||
roomID, ok := b.GetMapping(email.Mailbox(true))
|
||||
if !ok {
|
||||
return errors.New("room not found")
|
||||
@@ -177,11 +178,11 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
|
||||
body := content.Body
|
||||
htmlBody := content.FormattedBody
|
||||
|
||||
meta.MessageID = utils.MessageID(evt.ID, domain)
|
||||
meta.MessageID = email.MessageID(evt.ID, domain)
|
||||
meta.References = meta.References + " " + meta.MessageID
|
||||
b.log.Debug("send email reply: %+v", meta)
|
||||
email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, htmlBody, nil)
|
||||
data := email.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||
eml := email.New(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, htmlBody, nil)
|
||||
data := eml.Compose(b.getBotSettings().DKIMPrivateKey())
|
||||
if data == "" {
|
||||
b.SendError(ctx, evt.RoomID, "email body is empty")
|
||||
return
|
||||
@@ -190,7 +191,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
|
||||
queued, err := b.Sendmail(evt.ID, meta.From, meta.To, data)
|
||||
if queued {
|
||||
b.log.Error("cannot send email: %v", err)
|
||||
b.saveSentMetadata(ctx, queued, meta.ThreadID, email, &cfg)
|
||||
b.saveSentMetadata(ctx, queued, meta.ThreadID, eml, &cfg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,7 +200,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
b.saveSentMetadata(ctx, queued, meta.ThreadID, email, &cfg)
|
||||
b.saveSentMetadata(ctx, queued, meta.ThreadID, eml, &cfg)
|
||||
}
|
||||
|
||||
type parentEmail struct {
|
||||
@@ -259,7 +260,7 @@ func (b *Bot) getParentEmail(evt *event.Event, domain string) parentEmail {
|
||||
return parent
|
||||
}
|
||||
|
||||
parent.MessageID = utils.MessageID(parentEvt.ID, domain)
|
||||
parent.MessageID = email.MessageID(parentEvt.ID, domain)
|
||||
parent.From = utils.EventField[string](&parentEvt.Content, eventFromKey)
|
||||
parent.To = utils.EventField[string](&parentEvt.Content, eventToKey)
|
||||
parent.InReplyTo = utils.EventField[string](&parentEvt.Content, eventMessageIDkey)
|
||||
@@ -283,14 +284,14 @@ func (b *Bot) getParentEmail(evt *event.Event, domain string) parentEmail {
|
||||
|
||||
// saveSentMetadata used to save metadata from !pm sent and thread reply events to a separate notice message
|
||||
// because that metadata is needed to determine email thread relations
|
||||
func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.EventID, email *utils.Email, cfg *roomSettings) {
|
||||
text := "Email has been sent to " + email.To
|
||||
func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.EventID, eml *email.Email, cfg *roomSettings) {
|
||||
text := "Email has been sent to " + eml.RcptTo
|
||||
if queued {
|
||||
text = "Email to " + email.To + " has been queued"
|
||||
text = "Email to " + eml.RcptTo + " has been queued"
|
||||
}
|
||||
|
||||
evt := eventFromContext(ctx)
|
||||
content := email.Content(threadID, cfg.ContentOptions())
|
||||
content := eml.Content(threadID, cfg.ContentOptions())
|
||||
notice := format.RenderMarkdown(text, true, true)
|
||||
msgContent, ok := content.Parsed.(*event.MessageEventContent)
|
||||
if !ok {
|
||||
@@ -307,8 +308,8 @@ func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.Eve
|
||||
return
|
||||
}
|
||||
domain := utils.SanitizeDomain(cfg.Domain())
|
||||
b.setThreadID(evt.RoomID, utils.MessageID(evt.ID, domain), threadID)
|
||||
b.setThreadID(evt.RoomID, utils.MessageID(msgID, domain), threadID)
|
||||
b.setThreadID(evt.RoomID, email.MessageID(evt.ID, domain), threadID)
|
||||
b.setThreadID(evt.RoomID, email.MessageID(msgID, domain), threadID)
|
||||
b.setLastEventID(evt.RoomID, threadID, msgID)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
)
|
||||
|
||||
@@ -143,8 +144,8 @@ func (s roomSettings) migrateSpamlistSettings() {
|
||||
}
|
||||
|
||||
// ContentOptions converts room display settings to content options
|
||||
func (s roomSettings) ContentOptions() *utils.ContentOptions {
|
||||
return &utils.ContentOptions{
|
||||
func (s roomSettings) ContentOptions() *email.ContentOptions {
|
||||
return &email.ContentOptions{
|
||||
HTML: !s.NoHTML(),
|
||||
Sender: !s.NoSender(),
|
||||
Recipient: !s.NoRecipient(),
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
package utils
|
||||
package email
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-msgauth/dkim"
|
||||
"github.com/jhillyerd/enmime"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/format"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
)
|
||||
|
||||
var styleRegex = regexp.MustCompile("<style((.|\n|\r)*?)<\\/style>")
|
||||
|
||||
// IncomingFilteringOptions for incoming mail
|
||||
type IncomingFilteringOptions interface {
|
||||
SpamcheckSMTP() bool
|
||||
SpamcheckMX() bool
|
||||
Spamlist() []string
|
||||
}
|
||||
|
||||
// Email object
|
||||
type Email struct {
|
||||
Date string
|
||||
@@ -39,54 +28,13 @@ type Email struct {
|
||||
Subject string
|
||||
Text string
|
||||
HTML string
|
||||
Files []*File
|
||||
Files []*utils.File
|
||||
}
|
||||
|
||||
// ContentOptions represents settings that specify how an email is to be converted to a Matrix message
|
||||
type ContentOptions struct {
|
||||
// On/Off
|
||||
Sender bool
|
||||
Recipient bool
|
||||
Subject bool
|
||||
HTML bool
|
||||
Threads bool
|
||||
|
||||
// Keys
|
||||
MessageIDKey string
|
||||
InReplyToKey string
|
||||
ReferencesKey string
|
||||
SubjectKey string
|
||||
FromKey string
|
||||
ToKey string
|
||||
CcKey string
|
||||
RcptToKey string
|
||||
}
|
||||
|
||||
// AddressValid checks if email address is valid
|
||||
func AddressValid(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// EmailDate returns Date in RFC1123 with numeric timezone
|
||||
func EmailDate(original ...time.Time) string {
|
||||
now := time.Now().UTC()
|
||||
if len(original) > 0 && !original[0].IsZero() {
|
||||
now = original[0]
|
||||
}
|
||||
|
||||
return now.Format(time.RFC1123Z)
|
||||
}
|
||||
|
||||
// MessageID generates email Message-Id from matrix event ID
|
||||
func MessageID(eventID id.EventID, domain string) string {
|
||||
return fmt.Sprintf("<%s@%s>", eventID, domain)
|
||||
}
|
||||
|
||||
// NewEmail constructs Email object
|
||||
func NewEmail(messageID, inReplyTo, references, subject, from, to, text, html string, files []*File) *Email {
|
||||
// New constructs Email object
|
||||
func New(messageID, inReplyTo, references, subject, from, to, text, html string, files []*utils.File) *Email {
|
||||
email := &Email{
|
||||
Date: EmailDate(),
|
||||
Date: dateNow(),
|
||||
MessageID: messageID,
|
||||
InReplyTo: inReplyTo,
|
||||
References: references,
|
||||
@@ -107,35 +55,33 @@ func NewEmail(messageID, inReplyTo, references, subject, from, to, text, html st
|
||||
return email
|
||||
}
|
||||
|
||||
func FromEnvelope(rcptto string, eml *enmime.Envelope) *Email {
|
||||
datetime, _ := eml.Date() //nolint:errcheck // handled in EmailDate()
|
||||
date := EmailDate(datetime)
|
||||
// FromEnvelope constructs Email object from envelope
|
||||
func FromEnvelope(rcptto string, envelope *enmime.Envelope) *Email {
|
||||
datetime, _ := envelope.Date() //nolint:errcheck // handled in dateNow()
|
||||
date := dateNow(datetime)
|
||||
|
||||
var html string
|
||||
if eml.HTML != "" {
|
||||
html = styleRegex.ReplaceAllString(eml.HTML, "")
|
||||
if envelope.HTML != "" {
|
||||
html = styleRegex.ReplaceAllString(envelope.HTML, "")
|
||||
}
|
||||
|
||||
files := make([]*File, 0, len(eml.Attachments))
|
||||
for _, attachment := range eml.Attachments {
|
||||
for _, err := range attachment.Errors {
|
||||
log.Warn("attachment error: %v", err)
|
||||
}
|
||||
file := NewFile(attachment.FileName, attachment.Content)
|
||||
files := make([]*utils.File, 0, len(envelope.Attachments))
|
||||
for _, attachment := range envelope.Attachments {
|
||||
file := utils.NewFile(attachment.FileName, attachment.Content)
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
email := &Email{
|
||||
Date: date,
|
||||
MessageID: eml.GetHeader("Message-Id"),
|
||||
InReplyTo: eml.GetHeader("In-Reply-To"),
|
||||
References: eml.GetHeader("References"),
|
||||
From: eml.GetHeader("From"),
|
||||
To: eml.GetHeader("To"),
|
||||
MessageID: envelope.GetHeader("Message-Id"),
|
||||
InReplyTo: envelope.GetHeader("In-Reply-To"),
|
||||
References: envelope.GetHeader("References"),
|
||||
From: envelope.GetHeader("From"),
|
||||
To: envelope.GetHeader("To"),
|
||||
RcptTo: rcptto,
|
||||
CC: eml.GetHeader("Cc"),
|
||||
Subject: eml.GetHeader("Subject"),
|
||||
Text: eml.Text,
|
||||
CC: envelope.GetHeader("Cc"),
|
||||
Subject: envelope.GetHeader("Subject"),
|
||||
Text: envelope.Text,
|
||||
HTML: html,
|
||||
Files: files,
|
||||
}
|
||||
@@ -146,9 +92,9 @@ func FromEnvelope(rcptto string, eml *enmime.Envelope) *Email {
|
||||
// Mailbox returns postmoogle's mailbox, parsing it from FROM (if incoming=false) or TO (incoming=true)
|
||||
func (e *Email) Mailbox(incoming bool) string {
|
||||
if incoming {
|
||||
return Mailbox(e.RcptTo)
|
||||
return utils.Mailbox(e.RcptTo)
|
||||
}
|
||||
return Mailbox(e.From)
|
||||
return utils.Mailbox(e.From)
|
||||
}
|
||||
|
||||
// Content converts the email object to a Matrix event content
|
||||
@@ -176,7 +122,7 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
|
||||
}
|
||||
|
||||
parsed := format.RenderMarkdown(text.String(), true, true)
|
||||
parsed.RelatesTo = RelatesTo(options.Threads, threadID)
|
||||
parsed.RelatesTo = utils.RelatesTo(options.Threads, threadID)
|
||||
|
||||
content := event.Content{
|
||||
Raw: map[string]interface{}{
|
||||
@@ -222,13 +168,11 @@ func (e *Email) Compose(privkey string) string {
|
||||
|
||||
root, err := mail.Build()
|
||||
if err != nil {
|
||||
log.Error("cannot compose email: %v", err)
|
||||
return ""
|
||||
}
|
||||
var data strings.Builder
|
||||
err = root.Encode(&data)
|
||||
if err != nil {
|
||||
log.Error("cannot encode email: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
28
email/options.go
Normal file
28
email/options.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package email
|
||||
|
||||
// IncomingFilteringOptions for incoming mail
|
||||
type IncomingFilteringOptions interface {
|
||||
SpamcheckSMTP() bool
|
||||
SpamcheckMX() bool
|
||||
Spamlist() []string
|
||||
}
|
||||
|
||||
// ContentOptions represents settings that specify how an email is to be converted to a Matrix message
|
||||
type ContentOptions struct {
|
||||
// On/Off
|
||||
Sender bool
|
||||
Recipient bool
|
||||
Subject bool
|
||||
HTML bool
|
||||
Threads bool
|
||||
|
||||
// Keys
|
||||
MessageIDKey string
|
||||
InReplyToKey string
|
||||
ReferencesKey string
|
||||
SubjectKey string
|
||||
FromKey string
|
||||
ToKey string
|
||||
CcKey string
|
||||
RcptToKey string
|
||||
}
|
||||
33
email/utils.go
Normal file
33
email/utils.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
var styleRegex = regexp.MustCompile("<style((.|\n|\r)*?)<\\/style>")
|
||||
|
||||
// AddressValid checks if email address is valid
|
||||
func AddressValid(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// MessageID generates email Message-Id from matrix event ID
|
||||
func MessageID(eventID id.EventID, domain string) string {
|
||||
return fmt.Sprintf("<%s@%s>", eventID, domain)
|
||||
}
|
||||
|
||||
// dateNow returns Date in RFC1123 with numeric timezone
|
||||
func dateNow(original ...time.Time) string {
|
||||
now := time.Now().UTC()
|
||||
if len(original) > 0 && !original[0].IsZero() {
|
||||
now = original[0]
|
||||
}
|
||||
|
||||
return now.Format(time.RFC1123Z)
|
||||
}
|
||||
2
go.mod
2
go.mod
@@ -22,7 +22,6 @@ require (
|
||||
gitlab.com/etke.cc/go/trysmtp v1.0.0
|
||||
gitlab.com/etke.cc/go/validator v1.0.4
|
||||
gitlab.com/etke.cc/linkpearl v0.0.0-20221116205701-65547c5608e6
|
||||
golang.org/x/net v0.2.0
|
||||
maunium.net/go/mautrix v0.12.3
|
||||
)
|
||||
|
||||
@@ -49,6 +48,7 @@ require (
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yuin/goldmark v1.5.3 // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/net v0.2.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"gitlab.com/etke.cc/go/logger"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -45,8 +45,8 @@ type matrixbot interface {
|
||||
IsBanned(net.Addr) bool
|
||||
Ban(net.Addr)
|
||||
GetMapping(string) (id.RoomID, bool)
|
||||
GetIFOptions(id.RoomID) utils.IncomingFilteringOptions
|
||||
IncomingEmail(context.Context, *utils.Email) error
|
||||
GetIFOptions(id.RoomID) email.IncomingFilteringOptions
|
||||
IncomingEmail(context.Context, *email.Email) error
|
||||
SetSendmail(func(string, string, string) error)
|
||||
GetDKIMprivkey() string
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"gitlab.com/etke.cc/go/logger"
|
||||
"gitlab.com/etke.cc/go/trysmtp"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -41,7 +41,7 @@ func (m *mailServer) Login(state *smtp.ConnectionState, username, password strin
|
||||
return nil, ErrBanned
|
||||
}
|
||||
|
||||
if !utils.AddressValid(username) {
|
||||
if !email.AddressValid(username) {
|
||||
m.log.Debug("address %s is invalid", username)
|
||||
m.bot.Ban(state.RemoteAddr)
|
||||
return nil, ErrBanned
|
||||
@@ -114,6 +114,6 @@ func (m *mailServer) SendEmail(from, to, data string) error {
|
||||
}
|
||||
|
||||
// ReceiveEmail - incoming mail into matrix room
|
||||
func (m *mailServer) ReceiveEmail(ctx context.Context, email *utils.Email) error {
|
||||
return m.bot.IncomingEmail(ctx, email)
|
||||
func (m *mailServer) ReceiveEmail(ctx context.Context, eml *email.Email) error {
|
||||
return m.bot.IncomingEmail(ctx, eml)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"gitlab.com/etke.cc/go/validator"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"gitlab.com/etke.cc/postmoogle/email"
|
||||
"gitlab.com/etke.cc/postmoogle/utils"
|
||||
)
|
||||
|
||||
@@ -20,8 +21,8 @@ import (
|
||||
type incomingSession struct {
|
||||
log *logger.Logger
|
||||
getRoomID func(string) (id.RoomID, bool)
|
||||
getFilters func(id.RoomID) utils.IncomingFilteringOptions
|
||||
receiveEmail func(context.Context, *utils.Email) error
|
||||
getFilters func(id.RoomID) email.IncomingFilteringOptions
|
||||
receiveEmail func(context.Context, *email.Email) error
|
||||
greylisted func(net.Addr) bool
|
||||
ban func(net.Addr)
|
||||
domains []string
|
||||
@@ -34,7 +35,7 @@ type incomingSession struct {
|
||||
|
||||
func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error {
|
||||
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
||||
if !utils.AddressValid(from) {
|
||||
if !email.AddressValid(from) {
|
||||
s.log.Debug("address %s is invalid", from)
|
||||
s.ban(s.addr)
|
||||
return ErrBanned
|
||||
@@ -84,15 +85,15 @@ func (s *incomingSession) Data(r io.Reader) error {
|
||||
}
|
||||
}
|
||||
parser := enmime.NewParser()
|
||||
eml, err := parser.ReadEnvelope(r)
|
||||
envelope, err := parser.ReadEnvelope(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
email := utils.FromEnvelope(s.tos[0], eml)
|
||||
eml := email.FromEnvelope(s.tos[0], envelope)
|
||||
for _, to := range s.tos {
|
||||
email.RcptTo = to
|
||||
err := s.receiveEmail(s.ctx, email)
|
||||
eml.RcptTo = to
|
||||
err := s.receiveEmail(s.ctx, eml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,7 +117,7 @@ type outgoingSession struct {
|
||||
|
||||
func (s *outgoingSession) Mail(from string, opts smtp.MailOptions) error {
|
||||
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
|
||||
if !utils.AddressValid(from) {
|
||||
if !email.AddressValid(from) {
|
||||
return errors.New("please, provide email address")
|
||||
}
|
||||
return nil
|
||||
@@ -132,14 +133,14 @@ func (s *outgoingSession) Rcpt(to string) error {
|
||||
|
||||
func (s *outgoingSession) Data(r io.Reader) error {
|
||||
parser := enmime.NewParser()
|
||||
eml, err := parser.ReadEnvelope(r)
|
||||
envelope, err := parser.ReadEnvelope(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
email := utils.FromEnvelope(s.tos[0], eml)
|
||||
eml := email.FromEnvelope(s.tos[0], envelope)
|
||||
for _, to := range s.tos {
|
||||
email.RcptTo = to
|
||||
err := s.sendmail(email.From, to, email.Compose(s.privkey))
|
||||
eml.RcptTo = to
|
||||
err := s.sendmail(eml.From, to, eml.Compose(s.privkey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,7 +151,7 @@ func (s *outgoingSession) Data(r io.Reader) error {
|
||||
func (s *outgoingSession) Reset() {}
|
||||
func (s *outgoingSession) Logout() error { return nil }
|
||||
|
||||
func validateEmail(from, to string, log *logger.Logger, options utils.IncomingFilteringOptions) bool {
|
||||
func validateEmail(from, to string, log *logger.Logger, options email.IncomingFilteringOptions) bool {
|
||||
enforce := validator.Enforce{
|
||||
Email: true,
|
||||
MX: options.SpamcheckMX(),
|
||||
|
||||
41
utils/mail.go
Normal file
41
utils/mail.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
// Mailbox returns mailbox part from email address
|
||||
func Mailbox(email string) string {
|
||||
index := strings.LastIndex(email, "@")
|
||||
if index == -1 {
|
||||
return email
|
||||
}
|
||||
return email[:index]
|
||||
}
|
||||
|
||||
// EmailsList returns human-readable list of mailbox's emails for all available domains
|
||||
func EmailsList(mailbox string, domain string) string {
|
||||
var msg strings.Builder
|
||||
domain = SanitizeDomain(domain)
|
||||
msg.WriteString(mailbox)
|
||||
msg.WriteString("@")
|
||||
msg.WriteString(domain)
|
||||
|
||||
count := len(domains) - 1
|
||||
for i, aliasDomain := range domains {
|
||||
if i < count {
|
||||
msg.WriteString(", ")
|
||||
}
|
||||
if aliasDomain == domain {
|
||||
continue
|
||||
}
|
||||
msg.WriteString(mailbox)
|
||||
msg.WriteString("@")
|
||||
msg.WriteString(aliasDomain)
|
||||
}
|
||||
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
// Hostname returns hostname part from email address
|
||||
func Hostname(email string) string {
|
||||
return email[strings.LastIndex(email, "@")+1:]
|
||||
}
|
||||
@@ -22,44 +22,6 @@ func SetDomains(slice []string) {
|
||||
domains = slice
|
||||
}
|
||||
|
||||
// Mailbox returns mailbox part from email address
|
||||
func Mailbox(email string) string {
|
||||
index := strings.LastIndex(email, "@")
|
||||
if index == -1 {
|
||||
return email
|
||||
}
|
||||
return email[:index]
|
||||
}
|
||||
|
||||
// EmailsList returns human-readable list of mailbox's emails for all available domains
|
||||
func EmailsList(mailbox string, domain string) string {
|
||||
var msg strings.Builder
|
||||
domain = SanitizeDomain(domain)
|
||||
msg.WriteString(mailbox)
|
||||
msg.WriteString("@")
|
||||
msg.WriteString(domain)
|
||||
|
||||
count := len(domains) - 1
|
||||
for i, aliasDomain := range domains {
|
||||
if i < count {
|
||||
msg.WriteString(", ")
|
||||
}
|
||||
if aliasDomain == domain {
|
||||
continue
|
||||
}
|
||||
msg.WriteString(mailbox)
|
||||
msg.WriteString("@")
|
||||
msg.WriteString(aliasDomain)
|
||||
}
|
||||
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
// Hostname returns hostname part from email address
|
||||
func Hostname(email string) string {
|
||||
return email[strings.LastIndex(email, "@")+1:]
|
||||
}
|
||||
|
||||
// SanitizeDomain checks that input domain is available for use
|
||||
func SanitizeDomain(domain string) string {
|
||||
domain = strings.TrimSpace(domain)
|
||||
|
||||
Reference in New Issue
Block a user