// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT //go:build linux || freebsd package email import ( "strings" "testing" "sourcedock.dev/petrbalvin/nuntius/internal/config" ) func ackForm() *config.Form { return &config.Form{ Name: "contact", Type: "contact", To: "owner@example.com", From: "noreply@example.com", SubjectPrefix: new("nuntius"), EmailBrand: new("nuntius"), } } func TestComposeAcknowledgement(t *testing.T) { msg, err := composeAcknowledgement(ackForm(), "jane@example.com") if err != nil { t.Fatalf("compose: %v", err) } raw := string(msg) for _, want := range []string{ "From: noreply@example.com\r\n", "To: jane@example.com\r\n", "Subject: [nuntius/contact] Message received\r\n", // The reply lands in the owner's inbox, not in the void. "Reply-To: owner@example.com\r\n", "Content-Type: multipart/alternative; boundary=nuntius-", "your message to contact was received", "Delivered by nuntius", } { if !strings.Contains(raw, want) { t.Errorf("message missing %q", want) } } // The submitted values are deliberately never echoed back. if strings.Contains(raw, "jane@example.com\r\n\r\n") && strings.Count(raw, "jane@example.com") != 1 { t.Errorf("message echoes the submitter context beyond the To header") } } func TestComposeAcknowledgementHeaderInjection(t *testing.T) { msg, err := composeAcknowledgement(ackForm(), "jane@example.com\r\nBcc: victim@example.com") if err != nil { t.Fatalf("compose: %v", err) } raw := string(msg) // The whole string collapses into one To header line: no line starts // with the injected header name. for line := range strings.SplitSeq(raw, "\r\n") { if strings.HasPrefix(line, "Bcc:") { t.Errorf("a CR/LF in the recipient injected a header line %q", line) } } if !strings.Contains(raw, "To: jane@example.com Bcc: victim@example.com\r\n") { t.Errorf("the CR/LF was not neutralised into spaces") } } func TestComposeAcknowledgementSubjectWithoutPrefix(t *testing.T) { form := ackForm() form.SubjectPrefix = new("") msg, err := composeAcknowledgement(form, "jane@example.com") if err != nil { t.Fatalf("compose: %v", err) } if !strings.Contains(string(msg), "Subject: Message received\r\n") { t.Errorf("subject with an empty prefix = %q, want the bare subject", subjectOf(t, string(msg))) } } func subjectOf(t *testing.T, raw string) string { t.Helper() for line := range strings.SplitSeq(raw, "\r\n") { if after, ok := strings.CutPrefix(line, "Subject: "); ok { return after } } t.Fatalf("no subject line in message") return "" }