feat: contact form backend for linux and freebsd servers
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user