feat(mail): ING-02 POP3-Server (RFC 1939) mit Zustandsmaschine
Vollständiger POP3-Server von Grund auf implementiert, analog zum bestehenden IMAP-Server (ING-01): TCP-Listener mit einer Goroutine pro Verbindung, CRLF/Byte-Stuffing-sichere Response-Writer, Zustandsmaschine (Authorization/Transaction/Update), Kommandos USER, PASS, STAT, LIST, RETR, DELE, QUIT. Zentrale Designentscheidungen: - USER antwortet immer +OK (RFC-konform), Prüfung erst bei PASS - Fehlgeschlagene Anmeldung liefert für unbekannten Benutzer und falsches Passwort denselben generischen Text (keine Informationspreisgabe, Akzeptanzkriterium 3) - DELE markiert Nachrichten nur sitzungslokal; store.Delete wird strukturell ausschließlich in QUIT (Transaction -> Update) aufgerufen, wodurch ein Verbindungsabbruch ohne QUIT nichts endgültig löscht (Pflichtprüfung 3) Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt: Zustandsübergangs-Tests gegen realen TCP-Server, manuelle Session mit Python-Standardbibliothek poplib (echtes Transkript im Prüfprotokoll), automatisierter Test für DELE-ohne-QUIT. Zusätzlich: 20 parallele reale Sessions (Akzeptanzkriterium 1), vollständiger RETR+DELE+QUIT-Zyklus (Akzeptanzkriterium 2). go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~24 Pakete) regressionsfrei getestet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5dcfa36f99
commit
bd1f52648c
@@ -0,0 +1,58 @@
|
||||
package pop3
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// sanitizeResponseText entfernt eingebettete CR/LF aus text, BEVOR er in
|
||||
// eine Antwortzeile eingebettet wird (Bekannter Fehler vermeiden — gleiche
|
||||
// Konvention wie mail/internal/imap/response.go: archivmail erlaubte
|
||||
// Header-/Zeilen-Injection durch Stringkonkatenation ohne CRLF-Prüfung).
|
||||
func sanitizeResponseText(text string) string {
|
||||
text = strings.ReplaceAll(text, "\r", "")
|
||||
text = strings.ReplaceAll(text, "\n", "")
|
||||
return text
|
||||
}
|
||||
|
||||
func writeOK(w *bufio.Writer, text string) error {
|
||||
_, err := w.WriteString("+OK " + sanitizeResponseText(text) + "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func writeErr(w *bufio.Writer, text string) error {
|
||||
_, err := w.WriteString("-ERR " + sanitizeResponseText(text) + "\r\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// writeMultiline schreibt eine mehrzeilige POP3-Antwort (LIST/RETR):
|
||||
// "+OK ...\r\n" gefolgt von den Datenzeilen und einer abschließenden
|
||||
// "." -Zeile (RFC 1939 §3). content wird an "\n" in Zeilen zerlegt; jede
|
||||
// Zeile, die selbst mit "." beginnt, wird per "Byte-Stuffing" verdoppelt
|
||||
// (RFC-Pflicht UND zusätzlicher Schutz gegen eine vorzeitig wirkende
|
||||
// Terminierungszeile durch Nachrichteninhalt).
|
||||
func writeMultiline(w *bufio.Writer, okText, content string) error {
|
||||
if _, err := w.WriteString("+OK " + sanitizeResponseText(okText) + "\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
normalized := strings.ReplaceAll(content, "\r\n", "\n")
|
||||
for _, line := range strings.Split(normalized, "\n") {
|
||||
line = strings.TrimSuffix(line, "\r")
|
||||
if strings.HasPrefix(line, ".") {
|
||||
line = "." + line
|
||||
}
|
||||
if _, err := w.WriteString(line + "\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := w.WriteString(".\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
Reference in New Issue
Block a user