Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions internal/delivery/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1181,8 +1181,14 @@ func ReadDataCommand(r *bufio.Reader, maxSize int64) ([]byte, error) {
// multi-line continuations. Only lines starting with space/tab extend the previous header,
// preventing malformed headers from corrupting adjacent fields.
func extractAllHeaders(rawMessage string) []MessageHeader {
rawHeaderMessage := rawMessage
if headerEnd := strings.Index(rawMessage, "\r\n\r\n"); headerEnd >= 0 {
rawHeaderMessage = rawMessage[:headerEnd]
} else if headerEnd := strings.Index(rawMessage, "\n\n"); headerEnd >= 0 {
rawHeaderMessage = rawMessage[:headerEnd]
}
var headers []MessageHeader
lines := strings.Split(rawMessage, "\n")
lines := strings.Split(rawHeaderMessage, "\n")
sequence := 0
var currentHeaderName string
var currentHeaderValue strings.Builder
Expand All @@ -1192,14 +1198,6 @@ func extractAllHeaders(rawMessage string) []MessageHeader {

// Empty line marks end of headers section
if line == "" {
// Save last header if exists
if currentHeaderName != "" {
headers = append(headers, MessageHeader{
Name: currentHeaderName,
Value: currentHeaderValue.String(),
Sequence: sequence,
})
}
break
}

Expand Down Expand Up @@ -1237,5 +1235,13 @@ func extractAllHeaders(rawMessage string) []MessageHeader {
currentHeaderValue.WriteString(strings.TrimSpace(line[colonIdx+1:]))
}

if currentHeaderName != "" {
headers = append(headers, MessageHeader{
Name: currentHeaderName,
Value: currentHeaderValue.String(),
Sequence: sequence,
})
}

return headers
}
71 changes: 71 additions & 0 deletions internal/delivery/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,77 @@ Body starts here.`
}
}

func TestExtractAllHeadersExcludesBody(t *testing.T) {
tests := []struct {
name string
rawMessage string
}{
{
name: "CRLF separator",
rawMessage: "From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: Test Subject\r\n" +
"\r\n" +
"This is the body.\r\n" +
"X-Injected: evil\r\n" +
"More body text.\r\n",
},
{
name: "LF separator",
rawMessage: "From: sender@example.com\n" +
"To: recipient@example.com\n" +
"Subject: Test Subject\n" +
"\n" +
"This is the body.\n" +
"X-Injected: evil\n" +
"More body text.\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
msg, err := parser.ParseMIMEMessage(tt.rawMessage)
if err != nil {
t.Fatalf("Failed to parse message: %v", err)
}

foundSubject := false
for _, header := range msg.Headers {
if header.Name == "X-Injected" {
t.Errorf("Expected no X-Injected header from body, got %q", header.Value)
}
if header.Name == "Subject" {
foundSubject = true
}
}

if !foundSubject {
t.Error("Expected Subject header to be extracted from the header section")
}
})
}
}

func TestExtractAllHeadersStopsAtFirstBlankLine(t *testing.T) {
rawMessage := "From: sender@example.com\r\n" +
"To: recipient@example.com\n" +
"\n" +
"X-Injected: evil\r\n" +
"\r\n" +
"Body text.\r\n"

msg, err := parser.ParseMIMEMessage(rawMessage)
if err != nil {
t.Fatalf("Failed to parse message: %v", err)
}

for _, header := range msg.Headers {
if header.Name == "X-Injected" {
t.Errorf("Expected no X-Injected header from body, got %q", header.Value)
}
}
}

func TestIsValidEmail(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading