Some checks are pending
Security Checks / security (push) Waiting to run
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
176 lines
4.4 KiB
Go
176 lines
4.4 KiB
Go
package parsemail
|
|
|
|
import (
|
|
crand "crypto/rand"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.pcux.cn/xuans/Smail/config"
|
|
)
|
|
|
|
type controlledWriteCloser struct {
|
|
write func([]byte) (int, error)
|
|
closeErr error
|
|
}
|
|
|
|
func (w *controlledWriteCloser) Write(p []byte) (int, error) {
|
|
return w.write(p)
|
|
}
|
|
|
|
func (w *controlledWriteCloser) Close() error {
|
|
return w.closeErr
|
|
}
|
|
|
|
type failingReader struct {
|
|
err error
|
|
}
|
|
|
|
func (r failingReader) Read([]byte) (int, error) {
|
|
return 0, r.err
|
|
}
|
|
|
|
func TestWriteMessagePartPreservesWriteAndCloseErrors(t *testing.T) {
|
|
writeErr := errors.New("write failed")
|
|
closeErr := errors.New("close failed")
|
|
writer := &controlledWriteCloser{
|
|
write: func([]byte) (int, error) {
|
|
return 0, writeErr
|
|
},
|
|
closeErr: closeErr,
|
|
}
|
|
|
|
err := writeMessagePart(writer, []byte("message"))
|
|
if !errors.Is(err, writeErr) {
|
|
t.Fatalf("write error was not preserved: %v", err)
|
|
}
|
|
if !errors.Is(err, closeErr) {
|
|
t.Fatalf("close error was not preserved: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriteMessagePartRejectsShortWrite(t *testing.T) {
|
|
closeErr := errors.New("close failed")
|
|
writer := &controlledWriteCloser{
|
|
write: func(p []byte) (int, error) {
|
|
return len(p) - 1, nil
|
|
},
|
|
closeErr: closeErr,
|
|
}
|
|
|
|
err := writeMessagePart(writer, []byte("message"))
|
|
if !errors.Is(err, io.ErrShortWrite) {
|
|
t.Fatalf("short write was not detected: %v", err)
|
|
}
|
|
if !errors.Is(err, closeErr) {
|
|
t.Fatalf("close error was not preserved: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGenerateMsgIDRejectsInvalidDomain(t *testing.T) {
|
|
for _, domain := range []string{"", "example.com\r\nBcc: attacker@example.net", "bad domain.example", "bad@example.com"} {
|
|
if _, err := GenerateMsgID(domain); err == nil {
|
|
t.Fatalf("invalid domain was accepted: %q", domain)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateMsgIDPropagatesRandomFailure(t *testing.T) {
|
|
readErr := errors.New("random source failed")
|
|
oldReader := crand.Reader
|
|
crand.Reader = failingReader{err: readErr}
|
|
t.Cleanup(func() { crand.Reader = oldReader })
|
|
|
|
if _, err := GenerateMsgID("example.com"); !errors.Is(err, readErr) {
|
|
t.Fatalf("random error was not propagated: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGenerateMsgIDUsesConfiguredDomain(t *testing.T) {
|
|
msgID, err := GenerateMsgID(" example.com ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasSuffix(msgID, "@example.com") {
|
|
t.Fatalf("unexpected message ID: %q", msgID)
|
|
}
|
|
}
|
|
|
|
func TestBuildBytesRejectsInvalidPointers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
email *Email
|
|
}{
|
|
{
|
|
name: "missing sender",
|
|
email: &Email{
|
|
MsgID: "missing-sender@example.com",
|
|
To: []*User{{EmailAddress: "recipient@example.com"}},
|
|
},
|
|
},
|
|
{
|
|
name: "nil recipient",
|
|
email: &Email{
|
|
MsgID: "nil-recipient@example.com",
|
|
From: &User{EmailAddress: "sender@example.com"},
|
|
To: []*User{nil},
|
|
},
|
|
},
|
|
{
|
|
name: "nil attachment",
|
|
email: &Email{
|
|
MsgID: "nil-attachment@example.com",
|
|
From: &User{EmailAddress: "sender@example.com"},
|
|
To: []*User{{EmailAddress: "recipient@example.com"}},
|
|
Attachments: []*Attachment{nil},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if data, err := tt.email.BuildBytes(nil, false); err == nil || len(data) != 0 {
|
|
t.Fatalf("invalid email was built: bytes=%d err=%v", len(data), err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildBytesFailsClosedWithoutDKIMSigner(t *testing.T) {
|
|
oldInstance := instance
|
|
instance = nil
|
|
t.Cleanup(func() { instance = oldInstance })
|
|
|
|
email := &Email{
|
|
MsgID: "unsigned@example.com",
|
|
From: &User{EmailAddress: "sender@example.com"},
|
|
To: []*User{{EmailAddress: "recipient@example.com"}},
|
|
Text: []byte("message"),
|
|
}
|
|
if data, err := email.BuildBytes(nil, true); err == nil || len(data) != 0 {
|
|
t.Fatalf("message was returned without a signer: bytes=%d err=%v", len(data), err)
|
|
}
|
|
}
|
|
|
|
func TestBuildBytesDoesNotReturnUnsignedMessageOnDKIMFailure(t *testing.T) {
|
|
oldConfig := config.Instance
|
|
oldInstance := instance
|
|
config.Instance = &config.Config{Domain: "example.com"}
|
|
instance = &Dkim{}
|
|
t.Cleanup(func() {
|
|
config.Instance = oldConfig
|
|
instance = oldInstance
|
|
})
|
|
|
|
email := &Email{
|
|
MsgID: "sign-failure@example.com",
|
|
From: &User{EmailAddress: "sender@example.com"},
|
|
To: []*User{{EmailAddress: "recipient@example.com"}},
|
|
Text: []byte("message"),
|
|
}
|
|
if data, err := email.BuildBytes(nil, true); err == nil || len(data) != 0 {
|
|
t.Fatalf("unsigned message was returned after signing failure: bytes=%d err=%v", len(data), err)
|
|
}
|
|
}
|