Files
mesh-catalog/modules/mesh-mail-web/cmd/mesh-mail-web/mail_test.go
T

110 lines
4.8 KiB
Go

package main
// The mail page and attachment download (novox/hq ADR 0440 §5, issue 1266).
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// 1266/AC1, AC2: a mail whose body holds <script>alert(1)</script> and <img src="http://remote/x"> shows that
// text escaped, carries no script or img element, and nothing on the page names another origin.
func TestMailPageEscapesScriptAndRemoteImageInTheBody(t *testing.T) {
raw, _ := json.Marshal(map[string]any{
"account": "mesh", "folder": "INBOX", "uid": 7, "subject": "hi", "date": "2026-01-01T00:00:00Z",
"from": []map[string]any{{"address": "bob@example.org"}}, "to": []map[string]any{{"address": "me@example.org"}},
"text": `<script>alert(1)</script> and <img src="http://remote.example/x">`,
})
b := &Bundle{Read: func(context.Context, map[string]any) (json.RawMessage, error) { return raw, nil }}
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/mail?account=mesh&folder=INBOX&uid=7", nil)
b.mailPage(w, r)
body := w.Body.String()
if strings.Contains(body, "<script>") || strings.Contains(body, "<img") {
t.Fatalf("the page carries a script or img element:\n%s", body)
}
if !strings.Contains(body, "&lt;script&gt;") || !strings.Contains(body, "&lt;img") {
t.Fatalf("the body is not shown escaped as text:\n%s", body)
}
if strings.Contains(body, `src="http`) || strings.Contains(body, `href="http`) {
t.Fatalf("the page names another origin in a src or href:\n%s", body)
}
}
// Also 1266/AC2: a mail's attachment link names only this server's own path, never another origin.
func TestMailPageAttachmentLinksNameOnlyThisServersOwnPath(t *testing.T) {
raw, _ := json.Marshal(map[string]any{
"account": "mesh", "folder": "INBOX", "uid": 7, "subject": "hi", "date": "2026-01-01T00:00:00Z",
"attachments": []map[string]any{{"key": "abc123", "name": "invoice.pdf", "size": 10, "type": "application/pdf"}},
})
b := &Bundle{Read: func(context.Context, map[string]any) (json.RawMessage, error) { return raw, nil }}
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/mail?account=mesh&folder=INBOX&uid=7", nil)
b.mailPage(w, r)
body := w.Body.String()
if !strings.Contains(body, `href="/attachment?account=mesh&amp;folder=INBOX&amp;uid=7&amp;key=abc123"`) {
t.Fatalf("no same-origin attachment link:\n%s", body)
}
}
// 1266/AC3: an attachment asked carries its bytes with Content-Disposition attachment and its name escaped,
// Content-Type application/octet-stream, and X-Content-Type-Options nosniff.
func TestAttachmentDownloadCarriesTheRightHeadersAndBytes(t *testing.T) {
data := []byte("the attachment's own bytes")
raw, _ := json.Marshal(map[string]any{
"attachment": map[string]any{"key": "abc123", "name": `weird, name.pdf`, "size": len(data), "type": "application/pdf"},
"base64": base64.StdEncoding.EncodeToString(data),
})
b := &Bundle{Read: func(context.Context, map[string]any) (json.RawMessage, error) { return raw, nil }}
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/attachment?account=mesh&folder=INBOX&uid=7&key=abc123", nil)
b.attachmentPage(w, r)
if w.Header().Get("Content-Type") != "application/octet-stream" {
t.Fatalf("Content-Type: %q", w.Header().Get("Content-Type"))
}
if w.Header().Get("X-Content-Type-Options") != "nosniff" {
t.Fatalf("X-Content-Type-Options: %q", w.Header().Get("X-Content-Type-Options"))
}
cd := w.Header().Get("Content-Disposition")
if !strings.HasPrefix(cd, "attachment;") || !strings.Contains(cd, "weird, name.pdf") {
t.Fatalf("Content-Disposition: %q", cd)
}
if w.Body.String() != string(data) {
t.Fatalf("body: %q, want %q", w.Body.String(), data)
}
}
// 1266/AC4: a mail mail-store refuses (an account not granted) is answered 404, naming nothing of the mail.
func TestMailPageAndAttachmentAre404WhenMailStoreRefuses(t *testing.T) {
b := &Bundle{Read: func(context.Context, map[string]any) (json.RawMessage, error) {
return nil, errors.New("refused: mesh-web is not granted the mail account secret-project")
}}
w1 := httptest.NewRecorder()
b.mailPage(w1, httptest.NewRequest(http.MethodGet, "/mail?account=secret-project&folder=INBOX&uid=1", nil))
if w1.Code != http.StatusNotFound {
t.Fatalf("mail page status %d, want 404", w1.Code)
}
if strings.Contains(w1.Body.String(), "secret-project") {
t.Fatalf("the 404 names the account: %s", w1.Body.String())
}
w2 := httptest.NewRecorder()
b.attachmentPage(w2, httptest.NewRequest(http.MethodGet, "/attachment?account=secret-project&folder=INBOX&uid=1&key=k", nil))
if w2.Code != http.StatusNotFound {
t.Fatalf("attachment status %d, want 404", w2.Code)
}
if strings.Contains(w2.Body.String(), "secret-project") {
t.Fatalf("the 404 names the account: %s", w2.Body.String())
}
}