package webui import ( "net/http" "net/http/httptest" "net/url" "strconv" "strings" "testing" ) // TestWebmailMarkAsJunkAddsToBlocklistNotRules reproduces the intended behavior // change: "Mark as Junk" used to create a filter rule (mark_as_spam action); it now // adds the sender to the mailbox's own Blocklist ("junk" allowblock entries) instead, // leaving the Rules list untouched. func TestWebmailMarkAsJunkAddsToBlocklistNotRules(t *testing.T) { app := newTestApp(t) mux := app.Mux() domains, _ := app.DB.ListDomains() mailboxID := createTestMailboxWithPassword(t, app, "junker@example.com", domains[0].ID, "junker-password-1!") cookie := webmailLoginSession(t, app, mailboxID) uid := storeTestMessage(t, app, mailboxID, "INBOX", "spammer@example.com", "buy now", "body") req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/INBOX/"+strconv.FormatInt(uid, 10)+"/mark-junk", nil) req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("mark as junk: status=%d body=%s", rec.Code, rec.Body.String()) } junked, err := app.DB.IsJunked(mailboxID, "spammer@example.com") if err != nil || !junked { t.Fatalf("expected spammer@example.com added to the blocklist, junked=%v err=%v", junked, err) } rules, err := app.DB.ListRulesForMailbox(mailboxID) if err != nil { t.Fatal(err) } if len(rules) != 0 { t.Fatalf("expected no filter rule created (blocklist replaces the old rule-based flow), got %+v", rules) } msgs, err := app.DB.ListMessagesInFolder(mailboxID, "Junk") if err != nil || len(msgs) != 1 { t.Fatalf("expected the message itself moved to Junk, got %d (err=%v)", len(msgs), err) } } // TestWebmailBlocklistAddAndRemove exercises the self-service Blocklist/Whitelist // page's add+remove flow for both list types. func TestWebmailBlocklistAddAndRemove(t *testing.T) { app := newTestApp(t) mux := app.Mux() domains, _ := app.DB.ListDomains() mailboxID := createTestMailboxWithPassword(t, app, "lister@example.com", domains[0].ID, "lister-password-1!") cookie := webmailLoginSession(t, app, mailboxID) add := func(pattern, listType string) *httptest.ResponseRecorder { form := url.Values{"pattern": {pattern}, "list_type": {listType}} req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/add", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.AddCookie(cookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) return rec } if rec := add("bad@example.com", "junk"); rec.Code != http.StatusFound { t.Fatalf("add junk: status=%d", rec.Code) } if rec := add("good@example.com", "allow"); rec.Code != http.StatusFound { t.Fatalf("add allow: status=%d", rec.Code) } entries, err := app.DB.ListAllowBlock(mailboxID) if err != nil || len(entries) != 2 { t.Fatalf("expected 2 entries, got %d (err=%v)", len(entries), err) } // "block" (admin's hard-reject list) must not be settable from this self-service // endpoint. if rec := add("someone@example.com", "block"); rec.Code != http.StatusFound { t.Fatalf("status=%d", rec.Code) } entries, err = app.DB.ListAllowBlock(mailboxID) if err != nil || len(entries) != 2 { t.Fatalf("expected 'block' rejected (still 2 entries), got %d (err=%v)", len(entries), err) } var junkID int64 for _, e := range entries { if e.ListType == "junk" { junkID = e.ID } } rmReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/"+strconv.FormatInt(junkID, 10)+"/remove", nil) rmReq.AddCookie(cookie) rmRec := httptest.NewRecorder() mux.ServeHTTP(rmRec, rmReq) if rmRec.Code != http.StatusFound { t.Fatalf("remove: status=%d", rmRec.Code) } remaining, err := app.DB.ListAllowBlock(mailboxID) if err != nil || len(remaining) != 1 || remaining[0].ListType != "allow" { t.Fatalf("expected only the allow entry left, got %+v (err=%v)", remaining, err) } } // TestWebmailBlocklistScopedToOwnMailbox confirms one mailbox owner can't remove // another mailbox's blocklist entry by guessing its ID. func TestWebmailBlocklistScopedToOwnMailbox(t *testing.T) { app := newTestApp(t) mux := app.Mux() domains, _ := app.DB.ListDomains() victimID := createTestMailboxWithPassword(t, app, "victim3@example.com", domains[0].ID, "victim-password-1!") attackerID := createTestMailboxWithPassword(t, app, "attacker3@example.com", domains[0].ID, "attacker-password-1!") entryID, err := app.DB.AddAllowBlockEntry(victimID, "junk", "spam@example.com") if err != nil { t.Fatal(err) } attackerCookie := webmailLoginSession(t, app, attackerID) req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/blocklist/"+strconv.FormatInt(entryID, 10)+"/remove", nil) req.AddCookie(attackerCookie) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status=%d", rec.Code) } stillThere, err := app.DB.ListAllowBlock(victimID) if err != nil || len(stillThere) != 1 { t.Fatalf("expected the victim's entry untouched, got %d (err=%v)", len(stillThere), err) } }