handle message unknown charset error

This change handles message parse errors by printing the error when the
user tries to view the message. Specifically only handling unknown
charset errors in this patch, but there are many types of invalid
messages that can be handled in this way.

aerc currently leaves certain messages in the msglist in the pending
(spinner) state, and I'm unable to view or modify the message. aerc also
only prints parse errors with message when they are initially loaded.
This UX is a little better, because you can still see the header info
about the message, and if you try to view it, you will see the specific
error.
This commit is contained in:
Jeff Martin 2020-08-21 11:58:30 -07:00 committed by Reto Brunner
parent 43c4f2f3ab
commit 0acb28645f
5 changed files with 104 additions and 2 deletions

View file

@ -38,6 +38,10 @@ func (ViewMessage) Execute(aerc *widgets.Aerc, args []string) error {
if deleted { if deleted {
return nil return nil
} }
if msg.Error != nil {
aerc.PushError(msg.Error.Error())
return nil
}
lib.NewMessageStoreView(msg, store, aerc.DecryptKeys, lib.NewMessageStoreView(msg, store, aerc.DecryptKeys,
func(view lib.MessageView, err error) { func(view lib.MessageView, err error) {
if err != nil { if err != nil {

View file

@ -65,6 +65,7 @@ type MessageInfo struct {
RFC822Headers *mail.Header RFC822Headers *mail.Header
Size uint32 Size uint32
Uid uint32 Uid uint32
Error error
} }
// A MessageBodyPart can be displayed in the message viewer // A MessageBodyPart can be displayed in the message viewer

View file

@ -94,7 +94,7 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
} }
ps, err := ParseEntityStructure(part) ps, err := ParseEntityStructure(part)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse child entity structure: %v", err) return nil, fmt.Errorf("could not parse child entity structure: %w", err)
} }
body.Parts = append(body.Parts, ps) body.Parts = append(body.Parts, ps)
} }
@ -224,6 +224,7 @@ type RawMessage interface {
// MessageInfo populates a models.MessageInfo struct for the message. // MessageInfo populates a models.MessageInfo struct for the message.
// based on the reader returned by NewReader // based on the reader returned by NewReader
func MessageInfo(raw RawMessage) (*models.MessageInfo, error) { func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
var parseErr error
r, err := raw.NewReader() r, err := raw.NewReader()
if err != nil { if err != nil {
return nil, err return nil, err
@ -233,7 +234,9 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
return nil, fmt.Errorf("could not read message: %v", err) return nil, fmt.Errorf("could not read message: %v", err)
} }
bs, err := ParseEntityStructure(msg) bs, err := ParseEntityStructure(msg)
if err != nil { if errors.As(err, new(message.UnknownEncodingError)) {
parseErr = err
} else if err != nil {
return nil, fmt.Errorf("could not get structure: %v", err) return nil, fmt.Errorf("could not get structure: %v", err)
} }
h := &mail.Header{msg.Header} h := &mail.Header{msg.Header}
@ -268,5 +271,6 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
RFC822Headers: &mail.Header{msg.Header}, RFC822Headers: &mail.Header{msg.Header},
Size: 0, Size: 0,
Uid: raw.UID(), Uid: raw.UID(),
Error: parseErr,
}, nil }, nil
} }

65
worker/lib/parse_test.go Normal file
View file

@ -0,0 +1,65 @@
package lib
import (
"bytes"
"io"
"io/ioutil"
"path/filepath"
"testing"
"git.sr.ht/~sircmpwn/aerc/models"
)
func TestMessageInfoHandledError(t *testing.T) {
rootDir := "testdata/message/invalid"
msgFiles, err := ioutil.ReadDir(rootDir)
die(err)
for _, fi := range msgFiles {
if fi.IsDir() {
continue
}
p := fi.Name()
t.Run(p, func(t *testing.T) {
m := newMockRawMessageFromPath(filepath.Join(rootDir, p))
mi, err := MessageInfo(m)
if err != nil {
t.Fatal(err)
}
if perr := mi.Error; perr == nil {
t.Fatal("Expected MessageInfo.Error, got none")
}
})
}
}
type mockRawMessage struct {
body []byte
}
func newMockRawMessage(body []byte) *mockRawMessage {
return &mockRawMessage{
body: body,
}
}
func newMockRawMessageFromPath(p string) *mockRawMessage {
b, err := ioutil.ReadFile(p)
die(err)
return newMockRawMessage(b)
}
func (m *mockRawMessage) NewReader() (io.Reader, error) {
return bytes.NewReader(m.body), nil
}
func (m *mockRawMessage) ModelFlags() ([]models.Flag, error) { return nil, nil }
func (m *mockRawMessage) Labels() ([]string, error) { return nil, nil }
func (m *mockRawMessage) UID() uint32 { return 0 }
func die(err error) {
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,28 @@
Subject: Confirmation Needed gUdVJQBhsd
Content-Type: multipart/mixed; boundary="Nextpart_1Q2YJhd197991794467076Pgfa"
To: <BORK@example.com>
From: ""REGISTRAR"" <zdglopi-1Q2YJhd-noReply@example.com>
--Nextpart_1Q2YJhd197991794467076Pgfa
Content-Type: multipart/parallel; boundary="sg54sd54g54sdg54"
--sg54sd54g54sdg54
Content-Type: multipart/alternative; boundary="54qgf54q546f46qsf46qsf"
--54qgf54q546f46qsf46qsf
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: Hexa
--54qgf54q546f46qsf46qsf
Content-Type: text/html; charset=utf-8
<CeNteR><a hRef="https://example.com-ap-southeast-example.com.com/example.com#qs=r-acacaeehdiebadgdhgghcaegckhabababaggacihaccajfbacccgaehhbkacb"><b><h2>Congratulations Netflix Customer!</h2></b></a><br>
<HeaD>
<ObJECT>
--Nextpart_1Q2YJhd197991794467076Pgfa--