TestComposeMessageSubjectCRLFNeutralized confirms that even if a CRLF reaches ComposeMessage (e.g. some future caller skips the API-layer validation in handleSendEmail), the Q-encoding step neutralizes it so no header smuggling can occur on the SMTP envelope. Belt + suspenders with the API-layer rej
(t *testing.T)
| 88 | // no header smuggling can occur on the SMTP envelope. Belt + suspenders |
| 89 | // with the API-layer reject in handleSendEmail. |
| 90 | func TestComposeMessageSubjectCRLFNeutralized(t *testing.T) { |
| 91 | smuggled := "Hello\r\nBcc: attacker@evil.com\r\nX-Smuggled: yes" |
| 92 | raw, err := ComposeMessage( |
| 93 | "from@test.com", []string{"to@test.com"}, nil, |
| 94 | smuggled, "Body", "text/plain", "", nil, "test.dev", "", "", |
| 95 | ) |
| 96 | if err != nil { |
| 97 | t.Fatalf("ComposeMessage failed: %v", err) |
| 98 | } |
| 99 | headerEnd := strings.Index(string(raw), "\r\n\r\n") |
| 100 | if headerEnd < 0 { |
| 101 | t.Fatal("no header/body separator") |
| 102 | } |
| 103 | headers := string(raw)[:headerEnd] |
| 104 | // No smuggled headers should appear on their own line. |
| 105 | for _, smuggled := range []string{"\r\nBcc: attacker@evil.com", "\r\nX-Smuggled:"} { |
| 106 | if strings.Contains(headers, smuggled) { |
| 107 | t.Errorf("composed headers contain smuggled line: %q\nfull headers:\n%s", smuggled, headers) |
| 108 | } |
| 109 | } |
| 110 | // The Subject header should round-trip back to the original literal |
| 111 | // via Go's WordDecoder, confirming the bytes survived encode/decode. |
| 112 | msg, err := mail.ReadMessage(strings.NewReader(string(raw))) |
| 113 | if err != nil { |
| 114 | t.Fatalf("parse: %v", err) |
| 115 | } |
| 116 | dec := new(mime.WordDecoder) |
| 117 | got, err := dec.DecodeHeader(msg.Header.Get("Subject")) |
| 118 | if err != nil { |
| 119 | t.Fatalf("decode subject: %v", err) |
| 120 | } |
| 121 | if got != smuggled { |
| 122 | t.Errorf("decoded subject = %q, want %q", got, smuggled) |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | func TestComposeMessageASCIISubjectUnchanged(t *testing.T) { |
| 127 | // Pure-ASCII subjects should pass through without encoded-word wrapping. |
nothing calls this directly
no test coverage detected