Smail/server/utils/validation/email.go

96 lines
2.8 KiB
Go

package validation
import (
"errors"
"net/mail"
"strings"
"git.pcux.cn/xuans/Smail/dto/parsemail"
)
var (
ErrInvalidEmail = errors.New("invalid email")
ErrTooManyRecipients = errors.New("too many recipients")
ErrTooManyAttachments = errors.New("too many attachments")
ErrAttachmentTooLarge = errors.New("attachment too large")
ErrEmailTooLarge = errors.New("email too large")
)
type EmailLimits struct {
MaxRecipients int
MaxAttachments int
MaxAttachmentBytes int64
MaxTotalAttachmentBytes int64
MaxBodyBytes int64
MaxEstimatedBytes int64
}
func ValidateEmail(email *parsemail.Email, limits EmailLimits) error {
if email == nil || email.From == nil || email.Sender == nil {
return ErrInvalidEmail
}
recipients := len(email.To) + len(email.Cc) + len(email.Bcc) + len(email.ReplyTo)
if recipients == 0 {
return ErrInvalidEmail
}
if limits.MaxRecipients > 0 && recipients > limits.MaxRecipients {
return ErrTooManyRecipients
}
for _, list := range [][]*parsemail.User{email.To, email.Cc, email.Bcc, email.ReplyTo} {
for _, user := range list {
if user == nil || !IsMailboxAddress(user.EmailAddress) {
return ErrInvalidEmail
}
}
}
bodyBytes := int64(len(email.Text)) + int64(len(email.HTML))
if limits.MaxBodyBytes > 0 && bodyBytes > limits.MaxBodyBytes {
return ErrEmailTooLarge
}
if limits.MaxAttachments > 0 && len(email.Attachments) > limits.MaxAttachments {
return ErrTooManyAttachments
}
totalAttachmentBytes := int64(0)
estimatedBytes := base64Size(int64(len(email.Text))) + base64Size(int64(len(email.HTML))) + 64<<10
for _, attachment := range email.Attachments {
if attachment == nil || strings.ContainsAny(attachment.ContentType, "\r\n") {
return ErrInvalidEmail
}
size := int64(len(attachment.Content))
if limits.MaxAttachmentBytes > 0 && size > limits.MaxAttachmentBytes {
return ErrAttachmentTooLarge
}
if size > 0 && totalAttachmentBytes > int64(^uint64(0)>>1)-size {
return ErrEmailTooLarge
}
totalAttachmentBytes += size
if limits.MaxTotalAttachmentBytes > 0 && totalAttachmentBytes > limits.MaxTotalAttachmentBytes {
return ErrEmailTooLarge
}
estimatedBytes += base64Size(size) + int64(len(attachment.Filename)) + int64(len(attachment.ContentType)) + 1024
if limits.MaxEstimatedBytes > 0 && estimatedBytes > limits.MaxEstimatedBytes {
return ErrEmailTooLarge
}
}
if limits.MaxEstimatedBytes > 0 && estimatedBytes > limits.MaxEstimatedBytes {
return ErrEmailTooLarge
}
return nil
}
func IsMailboxAddress(address string) bool {
address = strings.TrimSpace(address)
parsed, err := mail.ParseAddress(address)
return err == nil && parsed.Address == address
}
func base64Size(size int64) int64 {
if size <= 0 {
return 0
}
return ((size + 2) / 3) * 4
}