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
7 changes: 7 additions & 0 deletions internal/mime/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ var dateFormats = []string{
"2006-01-02T15:04:05-07:00", // ISO 8601 with offset
"2006-01-02 15:04:05 -0700", // SQL-like format
"2006-01-02 15:04:05", // SQL-like without TZ
"Mon, Jan 2 2006 15:04:05 -0700", // Weekday, US month-day order, no comma after day
}

// numericOffsetRe matches numeric timezone offsets like +0000, -0700, +00:00, -07:00.
Expand Down Expand Up @@ -257,6 +258,12 @@ func parseDate(s string) time.Time {
// Normalize whitespace efficiently: split on whitespace runs and rejoin
s = strings.Join(strings.Fields(s), " ")

// Some mbox-derived sources have a stray, unindented continuation line
// (a lone ".") directly after the Date header. enmime folds it onto the
// header value instead of treating it as a parse error, leaving a
// trailing " ." that no real Date header would ever contain.
s = strings.TrimSuffix(s, " .")

// Strip trailing timezone name in parentheses like "(UTC)" or "(PST)"
// but keep the numeric offset for parsing
baseStr := s
Expand Down
28 changes: 28 additions & 0 deletions internal/mime/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ func TestParseDate(t *testing.T) {
{"numeric offset with different paren TZ", "Mon, 02 Jan 2006 15:04:05 +0700 (UTC)",
time.Date(2006, 1, 2, 8, 4, 5, 0, time.UTC)},

// mbox-derived Date headers with a stray unindented continuation
// line ("."), which enmime folds onto the header value
{"trailing malformed continuation dot", "Thu, 27 May 2004 17:06:42 -0500 .",
time.Date(2004, 5, 27, 22, 6, 42, 0, time.UTC)},

// US-style weekday + month-day order, no comma after day
{"weekday US month-day order", "Tue, Oct 17 2000 02:15:24 -0700",
time.Date(2000, 10, 17, 9, 15, 24, 0, time.UTC)},

// Invalid/unparseable dates should return zero time
{"empty", "", time.Time{}},
{"garbage", "not a date", time.Time{}},
Expand Down Expand Up @@ -344,6 +353,25 @@ func TestParse_MinimalMessage(t *testing.T) {
assert.Equal(t, "Body text", msg.BodyText)
}

// TestParse_MalformedContinuationLineAfterDate reproduces an mbox-derived
// message where a stray, unindented "." line follows the Date header.
// enmime folds it onto the Date value as a continuation instead of erroring,
// so Parse must still recover the sent date.
func TestParse_MalformedContinuationLineAfterDate(t *testing.T) {
raw := []byte("X-Mozilla-Status: 7001\n" +
"X-Mozilla-Status2: 00000000\n" +
"Date: Thu, 27 May 2004 17:06:42 -0500\n" +
".\n" +
"Content-Type: text/plain; charset=ISO-8859-1\n" +
"\n" +
"Body text.")

msg := mustParse(t, raw)

assert.True(t, msg.Date.Equal(time.Date(2004, 5, 27, 22, 6, 42, 0, time.UTC)),
"msg.Date = %v", msg.Date)
}

// TestParse_InvalidCharset verifies enmime handles malformed charsets gracefully.
// Enmime should not fail on invalid charset - it attempts conversion and collects errors.
func TestParse_InvalidCharset(t *testing.T) {
Expand Down