Skip to content

Commit

Permalink
webmail: fix loading a "view" (messages in a mailbox) when the "initi…
Browse files Browse the repository at this point in the history
…al" message cannot be parsed

when we send a list of messages from the mox backend to the js frontend, we
include a parsed form of the "initial" message: the one we immediately show,
typically the top-most (unread) message. however, if that message could not be
parsed (due to invalid header syntax), we would fail the entire operation of
loading the view.

with this change, we simply don't return a parsed form of an initial message if
we cannot parse it. that will cause the webmail frontend to not select &
display a message immediately. if you then try to open the message, you'll
still get an error message as before. but at least the view has been loaded,
and you can open the raw message to inspect the contents.

for issue #219 by wneessen
  • Loading branch information
mjl- committed Oct 5, 2024
1 parent 5d97bf1 commit fb65ec0
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 8 deletions.
9 changes: 9 additions & 0 deletions message/part.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var Pedantic bool

var (
ErrBadContentType = errors.New("bad content-type")
ErrHeader = errors.New("bad message header")
)

var (
Expand Down Expand Up @@ -394,6 +395,8 @@ func newPart(log mlog.Log, strict bool, r io.ReaderAt, offset int64, parent *Par
}

// Header returns the parsed header of this part.
//
// Returns a ErrHeader for messages with invalid header syntax.
func (p *Part) Header() (textproto.MIMEHeader, error) {
if p.header != nil {
return p.header, nil
Expand Down Expand Up @@ -431,6 +434,12 @@ func parseHeader(r io.Reader) (textproto.MIMEHeader, error) {
}
msg, err := mail.ReadMessage(bytes.NewReader(buf))
if err != nil {
// Recognize parsing errors from net/mail.ReadMessage.
// todo: replace with own message parsing code that returns proper error types.
errstr := err.Error()
if strings.HasPrefix(errstr, "malformed initial line:") || strings.HasPrefix(errstr, "malformed header line:") {
err = fmt.Errorf("%w: %v", ErrHeader, err)
}
return zero, err
}
return textproto.MIMEHeader(msg.Header), nil
Expand Down
2 changes: 1 addition & 1 deletion webmail/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem
if full && state.part.BodyOffset > 0 {
hdrs, err := state.part.Header()
if err != nil {
return ParsedMessage{}, fmt.Errorf("parsing headers: %v", err)
return ParsedMessage{}, fmt.Errorf("parsing headers: %w", err)
}
pm.Headers = hdrs

Expand Down
23 changes: 16 additions & 7 deletions webmail/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -1486,16 +1486,19 @@ func queryMessages(ctx context.Context, log mlog.Log, acc *store.Account, tx *bs

var pm *ParsedMessage
if m.ID == page.DestMessageID || page.DestMessageID == 0 && have == 0 && page.AnchorMessageID == 0 {
// For threads, if there was not DestMessageID, we may be getting the newest
// For threads, if there was no DestMessageID, we may be getting the newest
// message. For an initial view, this isn't necessarily the first the user is
// expected to read first, that would be the first unread, which we'll get below
// when gathering the thread.
found = true
xpm, err := parsedMessage(log, m, &state, true, false)
if err != nil {
if err != nil && errors.Is(err, message.ErrHeader) {
log.Debug("not returning parsed message due to invalid headers", slog.Int64("msgid", m.ID), slog.Any("err", err))
} else if err != nil {
return fmt.Errorf("parsing message %d: %v", m.ID, err)
} else {
pm = &xpm
}
pm = &xpm
}

mi, err := messageItem(log, m, &state)
Expand Down Expand Up @@ -1613,10 +1616,13 @@ func gatherThread(log mlog.Log, tx *bstore.Tx, acc *store.Account, v view, m sto
if tm.ID == destMessageID || destMessageID == 0 && first && (pm == nil || !firstUnread && !tm.Seen) {
firstUnread = !tm.Seen
xpm, err := parsedMessage(log, tm, &xstate, true, false)
if err != nil {
if err != nil && errors.Is(err, message.ErrHeader) {
log.Debug("not returning parsed message due to invalid headers", slog.Int64("msgid", m.ID), slog.Any("err", err))
} else if err != nil {
return fmt.Errorf("parsing thread message %d: %v", tm.ID, err)
} else {
pm = &xpm
}
pm = &xpm
}
return nil
}()
Expand All @@ -1631,10 +1637,13 @@ func gatherThread(log mlog.Log, tx *bstore.Tx, acc *store.Account, v view, m sto
xstate := msgState{acc: acc}
defer xstate.clear()
xpm, err := parsedMessage(log, m, &xstate, true, false)
if err != nil {
if err != nil && errors.Is(err, message.ErrHeader) {
log.Debug("not returning parsed message due to invalid headers", slog.Int64("msgid", m.ID), slog.Any("err", err))
} else if err != nil {
return nil, nil, fmt.Errorf("parsing thread message %d: %v", m.ID, err)
} else {
pm = &xpm
}
pm = &xpm
}

return mil, pm, nil
Expand Down

0 comments on commit fb65ec0

Please sign in to comment.