Files
Andrew S Erwin b9721ff13f
release gate / check (push) Failing after 8m35s
initial commit on Federted servers
2026-10-19 16:23:55 -05:00

121 lines
4.3 KiB
Go

package handlers
import (
"net/url"
"testing"
)
func TestMailboxRejectsUnusualPort(t *testing.T) {
t.Setenv("INTEGRATIONS_ALLOW_PRIVATE", "")
t.Setenv("INTEGRATIONS_ALLOW_INSECURE_IMAP", "")
if _, e := mailboxData(object{"host": "mail.example.com", "username": "u", "password": "p", "port": float64(22), "tls": true}); e == nil {
t.Fatal("mailbox accepted a non-IMAP port")
}
if _, e := mailboxData(object{"host": "mail.example.com", "username": "u", "password": "p", "port": float64(993), "tls": true}); e != nil {
t.Fatalf("normal IMAP port rejected: %v", e)
}
}
// TestResolveCardDAVTarget pins review CR-01: the COMPLETE resolved
// destination is revalidated, so a same-host absolute http:// path cannot
// downgrade an https base and carry Basic credentials over plaintext.
func TestResolveCardDAVTarget(t *testing.T) {
base, e := url.Parse("https://cloud.example.com/remote.php/dav")
if e != nil {
t.Fatal(e)
}
accepted := []string{
"/remote.php/dav/addressbooks/users/alice/contacts/",
"addressbooks/users/alice/contacts/",
"https://cloud.example.com/remote.php/dav/addressbooks/users/alice/contacts/",
}
for _, path := range accepted {
target, e := resolveCardDAVTarget(base, path)
if e != nil {
t.Fatalf("resolveCardDAVTarget(%q) rejected: %v", path, e)
}
if target.Scheme != "https" || target.Host != "cloud.example.com" {
t.Fatalf("resolveCardDAVTarget(%q) resolved to %s", path, target)
}
}
rejected := []string{
// Same host, downgraded scheme — the CR-01 credential-leak vector.
"http://cloud.example.com/remote.php/dav/addressbooks/users/alice/contacts/",
// Cross-host absolute URLs.
"https://evil.example.com/remote.php/dav/",
"http://evil.example.com/remote.php/dav/",
// Scheme-relative and userinfo tricks.
"//evil.example.com/dav/",
"https://alice:pw@cloud.example.com/dav/",
}
for _, path := range rejected {
if target, e := resolveCardDAVTarget(base, path); e == nil {
t.Fatalf("resolveCardDAVTarget(%q) accepted, resolved to %s", path, target)
}
}
}
// TestParseVCardSubset pins the documented parser subset (review CR-08):
// repeated properties are preserved, PREF selection is deterministic,
// groups/params/escapes/folding are handled, and base64-encoded properties
// are dropped instead of silently misparsed.
func TestParseVCardSubset(t *testing.T) {
card := "BEGIN:VCARD\r\n" +
"VERSION:3.0\r\n" +
"UID:uid-1\r\n" +
"item1.FN:Preferred Name\r\n" +
"item1.X-ABLabel:Main\r\n" +
"EMAIL;TYPE=WORK:work@computer.test\r\n" +
"EMAIL;TYPE=PREF:pref@computer.test\r\n" +
"EMAIL:plain@computer.test\r\n" +
"TEL;TYPE=HOME,VOICE:+1 555 0100\r\n" +
"NOTE:folded line\r\n" +
" continuation\r\n" +
"X-ESCAPED:a\\,b\\;c\\\\d\\ne\r\n" +
"PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRg==\r\n" +
"END:VCARD"
v := parseVCard(card)
if got := vCardPick(v["UID"]); got != "uid-1" {
t.Fatalf("UID = %q", got)
}
// Group prefix stripped (item1.FN -> FN).
if got := vCardPick(v["FN"]); got != "Preferred Name" {
t.Fatalf("FN = %q", got)
}
// All three EMAIL occurrences survive; TYPE=PREF wins deterministically.
if len(v["EMAIL"]) != 3 {
t.Fatalf("repeated EMAILs not preserved: %d", len(v["EMAIL"]))
}
if got := vCardPick(v["EMAIL"]); got != "pref@computer.test" {
t.Fatalf("PREF selection = %q", got)
}
// vCard 4.0 numeric PREF ranks below any TYPE=PREF-free occurrence.
num := parseVCard("EMAIL:a@x.test\r\nEMAIL;PREF=2:b@x.test\r\nEMAIL;PREF=1:c@x.test\r\n")
if got := vCardPick(num["EMAIL"]); got != "c@x.test" {
t.Fatalf("numeric PREF selection = %q", got)
}
// Multi-value parameters (TYPE=HOME,VOICE) are split.
tel := v["TEL"]
if len(tel) != 1 || tel[0].params["TYPE"][0] != "HOME" || tel[0].params["TYPE"][1] != "VOICE" {
t.Fatalf("TEL params = %+v", tel)
}
// CRLF-space folding unfolds into one value: the first space is the fold
// marker, the second is content.
if got := vCardPick(v["NOTE"]); got != "folded line continuation" {
t.Fatalf("NOTE unfolding = %q", got)
}
// Escapes decode.
if got := vCardPick(v["X-ESCAPED"]); got != "a,b;c\\d\ne" {
t.Fatalf("escapes = %q", got)
}
// Base64-encoded properties are dropped, not stored as garbage.
if _, ok := v["PHOTO"]; ok {
t.Fatal("base64-encoded PHOTO was parsed instead of dropped")
}
// Missing properties pick to "".
if got := vCardPick(v["ORG"]); got != "" {
t.Fatalf("missing property = %q", got)
}
}