package mailer import "fmt" // headerWriter schreibt E-Mail-Header ausschließlich über diese // strukturierte API (Akzeptanzkriterium 2) — nie über freie // Stringkonkatenation von Feldname und -wert. Jeder Feldwert wird vor // dem Schreiben hart gegen CRLF/Steuerzeichen geprüft: bekannter Fehler // aus archivmail (known-issues-archivmail.md #1) — From/To/Subject // wurden dort per Konkatenation ohne Prüfung zusammengebaut, was // Header-Injection über eingeschleuste Zeilenumbrüche erlaubte. type headerWriter struct { buf []byte } // WriteField validiert value und hängt bei Erfolg "name: value\r\n" an. // Ein Fehler lässt buf unverändert. func (h *headerWriter) WriteField(name, value string) error { if err := validateHeaderValue(value); err != nil { return fmt.Errorf("mailer: feld %q: %w", name, err) } h.buf = append(h.buf, name...) h.buf = append(h.buf, ':', ' ') h.buf = append(h.buf, value...) h.buf = append(h.buf, '\r', '\n') return nil } func (h *headerWriter) Bytes() []byte { return h.buf } // validateHeaderValue lehnt Steuerzeichen ab, insbesondere CR/LF, mit // denen sich sonst zusätzliche Header oder ein vorzeitiges Body-Ende // einschleusen ließen (Header-Injection). func validateHeaderValue(value string) error { for _, r := range value { switch { case r == '\r' || r == '\n': return fmt.Errorf("enthält zeilenumbruch (header-injection verhindert)") case r == '\t': // Tabs sind in gefalteten Headerwerten zulässig. case r < 0x20: return fmt.Errorf("enthält steuerzeichen 0x%02x", r) } } return nil }