about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/format/format.go20
-rw-r--r--worker/lib/parse.go73
2 files changed, 67 insertions, 26 deletions
diff --git a/lib/format/format.go b/lib/format/format.go
index 5f45e58..cc46da8 100644
--- a/lib/format/format.go
+++ b/lib/format/format.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	gomail "net/mail"
 	"strings"
+	"time"
 	"unicode"
 
 	"git.sr.ht/~sircmpwn/aerc/models"
@@ -111,13 +112,21 @@ func ParseMessageFormat(
 			retval = append(retval, 'd')
 			args = append(args, number)
 		case 'd':
+			date := msg.Envelope.Date
+			if date.IsZero() {
+				date = msg.InternalDate
+			}
 			retval = append(retval, 's')
 			args = append(args,
-				msg.InternalDate.Format(timestampformat))
+				dummyIfZeroDate(date, timestampformat))
 		case 'D':
+			date := msg.Envelope.Date
+			if date.IsZero() {
+				date = msg.InternalDate
+			}
 			retval = append(retval, 's')
 			args = append(args,
-				msg.InternalDate.Local().Format(timestampformat))
+				dummyIfZeroDate(date, timestampformat))
 		case 'f':
 			if msg.Envelope == nil {
 				return "", nil,
@@ -335,3 +344,10 @@ handle_end_error:
 	return "", nil,
 		errors.New("reached end of string while parsing message format")
 }
+
+func dummyIfZeroDate(date time.Time, format string) string {
+	if date.IsZero() {
+		return strings.Repeat("?", len(format))
+	}
+	return date.Format(format)
+}
diff --git a/worker/lib/parse.go b/worker/lib/parse.go
index a14a6d3..58327c9 100644
--- a/worker/lib/parse.go
+++ b/worker/lib/parse.go
@@ -102,11 +102,9 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
 	return &body, nil
 }
 
+var DateParseError = errors.New("date parsing failed")
+
 func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
-	date, err := parseDate(h)
-	if err != nil {
-		return nil, fmt.Errorf("could not parse date header: %v", err)
-	}
 	from, err := parseAddressList(h, "from")
 	if err != nil {
 		return nil, fmt.Errorf("could not read from address: %v", err)
@@ -135,6 +133,12 @@ func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
 	if err != nil {
 		return nil, fmt.Errorf("could not read message id: %v", err)
 	}
+	date, err := parseDate(h)
+	if err != nil {
+		// still return a valid struct plus a sentinel date parsing error
+		// if only the date parsing failed
+		err = fmt.Errorf("%w: %v", DateParseError, err)
+	}
 	return &models.Envelope{
 		Date:      date,
 		Subject:   subj,
@@ -144,28 +148,24 @@ func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
 		To:        to,
 		Cc:        cc,
 		Bcc:       bcc,
-	}, nil
+	}, err
 }
 
-// parseDate extends the built-in date parser with additional layouts which are
-// non-conforming but appear in the wild.
+// parseDate tries to parse the date from the Date header with non std formats
+// if this fails it tries to parse the received header as well
 func parseDate(h *mail.Header) (time.Time, error) {
-	t, parseErr := h.Date()
-	if parseErr == nil {
+	t, err := h.Date()
+	if err == nil {
 		return t, nil
 	}
 	text, err := h.Text("date")
-	if err != nil {
-		return time.Time{}, errors.New("no date header")
-	}
-	// sometimes, no error occurs but the date is empty. In this case, guess time from received header field
-	if text == "" {
-		guess, err := h.Text("received")
-		if err != nil {
-			return time.Time{}, errors.New("no received header")
+	// sometimes, no error occurs but the date is empty.
+	// In this case, guess time from received header field
+	if err != nil || text == "" {
+		t, err := parseReceivedHeader(h)
+		if err == nil {
+			return t, nil
 		}
-		t, _ := time.Parse(time.RFC1123Z, dateRe.FindString(guess))
-		return t, nil
 	}
 	layouts := []string{
 		// X-Mailer: EarthLink Zoo Mail 1.0
@@ -176,7 +176,21 @@ func parseDate(h *mail.Header) (time.Time, error) {
 			return t, nil
 		}
 	}
-	return time.Time{}, fmt.Errorf("unrecognized date format: %s", text)
+	// still no success, try the received header as a last resort
+	t, err = parseReceivedHeader(h)
+	if err != nil {
+		return time.Time{}, fmt.Errorf("unrecognized date format: %s", text)
+	}
+	return t, nil
+}
+
+func parseReceivedHeader(h *mail.Header) (time.Time, error) {
+	guess, err := h.Text("received")
+	if err != nil {
+		return time.Time{}, fmt.Errorf("received header not parseable: %v",
+			err)
+	}
+	return time.Parse(time.RFC1123Z, dateRe.FindString(guess))
 }
 
 func parseAddressList(h *mail.Header, key string) ([]*models.Address, error) {
@@ -231,9 +245,20 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
 	if err != nil {
 		return nil, fmt.Errorf("could not get structure: %v", err)
 	}
-	env, err := parseEnvelope(&mail.Header{msg.Header})
-	if err != nil {
-		return nil, fmt.Errorf("could not get envelope: %v", err)
+	h := &mail.Header{msg.Header}
+	env, err := parseEnvelope(h)
+	if err != nil && !errors.Is(err, DateParseError) {
+		return nil, fmt.Errorf("could not parse envelope: %v", err)
+		// if only the date parsing failed we still get the rest of the
+		// envelop structure in a valid state.
+		// Date parsing errors are fairly common and it's better to be
+		// slightly off than to not be able to read the mails at all
+		// hence we continue here
+	}
+	recDate, _ := parseReceivedHeader(h)
+	if recDate.IsZero() {
+		// better than nothing, if incorrect
+		recDate = env.Date
 	}
 	flags, err := raw.ModelFlags()
 	if err != nil {
@@ -248,7 +273,7 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
 		Envelope:      env,
 		Flags:         flags,
 		Labels:        labels,
-		InternalDate:  env.Date,
+		InternalDate:  recDate,
 		RFC822Headers: &mail.Header{msg.Header},
 		Size:          0,
 		Uid:           raw.UID(),