Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

99 lines
2.7 KiB
Go

package email
import (
"context"
"fmt"
"net"
"net/smtp"
"strings"
)
var smtpSendMail = smtp.SendMail
type smtpTransport struct {
host string
port string
user string
password string
}
func newSMTPTransport(host, port, user, password string) *smtpTransport {
if strings.TrimSpace(port) == "" {
port = "587"
}
return &smtpTransport{host: host, port: port, user: user, password: password}
}
func (s *smtpTransport) Name() string { return ProviderSMTP }
func (s *smtpTransport) Send(ctx context.Context, from FromIdentity, msg Outbound) error {
_ = ctx
to := strings.TrimSpace(msg.To)
if to == "" {
return ErrInvalidRecipient
}
if hasHeaderBreak(to) {
return ErrInvalidRecipient
}
fromAddr := strings.TrimSpace(from.Email)
if fromAddr == "" {
return ErrInvalidFrom
}
if hasHeaderBreak(fromAddr) || hasHeaderBreak(from.Name) {
return ErrInvalidFrom
}
if hasHeaderBreak(msg.Subject) {
return fmt.Errorf("invalid subject")
}
replyTo := strings.TrimSpace(from.ReplyTo)
if replyTo != "" && hasHeaderBreak(replyTo) {
return fmt.Errorf("invalid reply-to")
}
addr := net.JoinHostPort(s.host, s.port)
boundary := "descrybe_mkt_7f3a"
var body strings.Builder
body.WriteString(fmt.Sprintf("From: %s\r\n", from.Formatted()))
body.WriteString(fmt.Sprintf("To: %s\r\n", to))
body.WriteString(fmt.Sprintf("Subject: %s\r\n", msg.Subject))
if replyTo != "" {
body.WriteString(fmt.Sprintf("Reply-To: %s\r\n", replyTo))
}
for k, v := range msg.Headers {
k = strings.TrimSpace(k)
v = strings.TrimSpace(v)
if k == "" || v == "" {
continue
}
if hasHeaderBreak(k) || strings.Contains(k, ":") {
return fmt.Errorf("invalid header name")
}
if hasHeaderBreak(v) {
return fmt.Errorf("invalid header value")
}
body.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
body.WriteString("MIME-Version: 1.0\r\n")
if strings.TrimSpace(msg.HTML) != "" {
body.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=%s\r\n\r\n", boundary))
body.WriteString(fmt.Sprintf("--%s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s\r\n", boundary, msg.Text))
body.WriteString(fmt.Sprintf("--%s\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n", boundary, msg.HTML))
body.WriteString(fmt.Sprintf("--%s--\r\n", boundary))
} else {
body.WriteString("Content-Type: text/plain; charset=UTF-8\r\n\r\n")
body.WriteString(msg.Text)
}
var auth smtp.Auth
if s.user != "" {
auth = smtp.PlainAuth("", s.user, s.password, s.host)
}
if err := smtpSendMail(addr, auth, fromAddr, []string{to}, []byte(body.String())); err != nil {
return fmt.Errorf("smtp send failed")
}
return nil
}
func hasHeaderBreak(v string) bool {
return strings.ContainsAny(v, "\r\n")
}