Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
99 lines
2.7 KiB
Go
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")
|
|
}
|