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.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user