FetchBodyParts: decode source in the workers

Previously the workers returned a mixture of decoded / encoded parts.
This lead to a whole bunch of issues.
This commit changes the msgviewer and the commands to assume parts to already
be decoded
This commit is contained in:
Reto Brunner 2020-01-04 21:13:51 +01:00 committed by Drew DeVault
parent 19dfc49481
commit 9096049f75
8 changed files with 29 additions and 117 deletions

View file

@ -10,9 +10,6 @@ import (
"path" "path"
"strings" "strings"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
"git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/models"
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
"git.sr.ht/~sircmpwn/getopt" "git.sr.ht/~sircmpwn/getopt"
@ -138,28 +135,9 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
// TODO: something more intelligent than fetching the 1st part // TODO: something more intelligent than fetching the 1st part
// TODO: add attachments! // TODO: add attachments!
store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { store.FetchBodyPart(msg.Uid, msg.BodyStructure, []int{1}, func(reader io.Reader) {
header := message.Header{}
header.SetText(
"Content-Transfer-Encoding", msg.BodyStructure.Encoding)
header.SetContentType(
msg.BodyStructure.MIMEType, msg.BodyStructure.Params)
header.SetText("Content-Description", msg.BodyStructure.Description)
entity, err := message.New(header, reader)
if err != nil {
// TODO: Do something with the error
addTab()
return
}
mreader := mail.NewReader(entity)
part, err := mreader.NextPart()
if err != nil {
// TODO: Do something with the error
addTab()
return
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(part.Body) buf.ReadFrom(reader)
defaults["Original"] = buf.String() defaults["Original"] = buf.String()
addTab() addTab()
}) })

View file

@ -1,13 +1,10 @@
package msg package msg
import ( import (
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"mime/quotedprintable"
"os/exec" "os/exec"
"strings"
"time" "time"
"git.sr.ht/~sircmpwn/aerc/commands" "git.sr.ht/~sircmpwn/aerc/commands"
@ -129,14 +126,7 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
}) })
} else if pipePart { } else if pipePart {
p := provider.SelectedMessagePart() p := provider.SelectedMessagePart()
p.Store.FetchBodyPart(p.Msg.Uid, p.Index, func(reader io.Reader) { p.Store.FetchBodyPart(p.Msg.Uid, p.Msg.BodyStructure, p.Index, func(reader io.Reader) {
// email parts are encoded as 7bit (plaintext), quoted-printable, or base64
if strings.EqualFold(p.Part.Encoding, "base64") {
reader = base64.NewDecoder(base64.StdEncoding, reader)
} else if strings.EqualFold(p.Part.Encoding, "quoted-printable") {
reader = quotedprintable.NewReader(reader)
}
if background { if background {
doExec(reader) doExec(reader)
} else { } else {

View file

@ -9,9 +9,6 @@ import (
"strings" "strings"
"git.sr.ht/~sircmpwn/getopt" "git.sr.ht/~sircmpwn/getopt"
"github.com/emersion/go-message"
_ "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail"
"git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/models"
"git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/widgets"
@ -155,56 +152,9 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
template = aerc.Config().Templates.QuotedReply template = aerc.Config().Templates.QuotedReply
} }
store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { store.FetchBodyPart(msg.Uid, msg.BodyStructure, []int{1}, func(reader io.Reader) {
header := message.Header{}
if len(msg.BodyStructure.Parts) > 0 {
partID := 0 // TODO: will we always choose first msg part?
header.SetText(
"Content-Transfer-Encoding", msg.BodyStructure.Parts[partID].Encoding)
if msg.BodyStructure.Parts[partID].MIMESubType == "" {
header.SetContentType(
msg.BodyStructure.Parts[partID].MIMEType,
msg.BodyStructure.Parts[partID].Params)
} else {
// include SubType if defined (text/plain, text/html, ...)
header.SetContentType(
fmt.Sprintf("%s/%s", msg.BodyStructure.Parts[partID].MIMEType,
msg.BodyStructure.Parts[partID].MIMESubType),
msg.BodyStructure.Parts[partID].Params)
}
header.SetText("Content-Description", msg.BodyStructure.Parts[partID].Description)
} else { // Parts has no headers, so we use global headers info
header.SetText(
"Content-Transfer-Encoding", msg.BodyStructure.Encoding)
if msg.BodyStructure.MIMESubType == "" {
header.SetContentType(
msg.BodyStructure.MIMEType,
msg.BodyStructure.Params)
} else {
// include SubType if defined (text/plain, text/html, ...)
header.SetContentType(
fmt.Sprintf("%s/%s", msg.BodyStructure.MIMEType,
msg.BodyStructure.MIMESubType),
msg.BodyStructure.Params)
}
header.SetText("Content-Description", msg.BodyStructure.Description)
}
entity, err := message.New(header, reader)
if err != nil {
// TODO: Do something with the error
addTab()
return
}
mreader := mail.NewReader(entity)
part, err := mreader.NextPart()
if err != nil {
// TODO: Do something with the error
addTab()
return
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(part.Body) buf.ReadFrom(reader)
defaults["Original"] = buf.String() defaults["Original"] = buf.String()
addTab() addTab()
}) })
@ -214,6 +164,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
} }
} }
//TODO (RPB): unused function
func findPlaintext(bs *models.BodyStructure, func findPlaintext(bs *models.BodyStructure,
path []int) (*models.BodyStructure, []int) { path []int) (*models.BodyStructure, []int) {

View file

@ -36,7 +36,7 @@ func (Open) Execute(aerc *widgets.Aerc, args []string) error {
mv := aerc.SelectedTab().(*widgets.MessageViewer) mv := aerc.SelectedTab().(*widgets.MessageViewer)
p := mv.SelectedMessagePart() p := mv.SelectedMessagePart()
p.Store.FetchBodyPart(p.Msg.Uid, p.Index, func(reader io.Reader) { p.Store.FetchBodyPart(p.Msg.Uid, p.Msg.BodyStructure, p.Index, func(reader io.Reader) {
// email parts are encoded as 7bit (plaintext), quoted-printable, or base64 // email parts are encoded as 7bit (plaintext), quoted-printable, or base64
if strings.EqualFold(p.Part.Encoding, "base64") { if strings.EqualFold(p.Part.Encoding, "base64") {

View file

@ -60,7 +60,7 @@ func (Save) Execute(aerc *widgets.Aerc, args []string) error {
mv := aerc.SelectedTab().(*widgets.MessageViewer) mv := aerc.SelectedTab().(*widgets.MessageViewer)
p := mv.SelectedMessagePart() p := mv.SelectedMessagePart()
p.Store.FetchBodyPart(p.Msg.Uid, p.Index, func(reader io.Reader) { p.Store.FetchBodyPart(p.Msg.Uid, p.Msg.BodyStructure, p.Index, func(reader io.Reader) {
// email parts are encoded as 7bit (plaintext), quoted-printable, or base64 // email parts are encoded as 7bit (plaintext), quoted-printable, or base64
if strings.EqualFold(p.Part.Encoding, "base64") { if strings.EqualFold(p.Part.Encoding, "base64") {

View file

@ -127,11 +127,22 @@ func (store *MessageStore) FetchFull(uids []uint32, cb func(io.Reader)) {
} }
func (store *MessageStore) FetchBodyPart( func (store *MessageStore) FetchBodyPart(
uid uint32, part []int, cb func(io.Reader)) { uid uint32, parent *models.BodyStructure, part []int, cb func(io.Reader)) {
partbs, err := parent.PartAtIndex(part)
if err != nil {
store.worker.Logger.Printf("FetchBodyPart: %v\n", err)
}
var charset string
var ok bool
if charset, ok = partbs.Params["charset"]; !ok {
charset = ""
}
store.worker.PostAction(&types.FetchMessageBodyPart{ store.worker.PostAction(&types.FetchMessageBodyPart{
Uid: uid, Uid: uid,
Part: part, Part: part,
Encoding: partbs.Encoding,
Charset: charset,
}, func(resp types.WorkerMessage) { }, func(resp types.WorkerMessage) {
msg, ok := resp.(*types.MessageBodyPart) msg, ok := resp.(*types.MessageBodyPart)
if !ok { if !ok {

View file

@ -10,9 +10,6 @@ import (
"strings" "strings"
"github.com/danwakefield/fnmatch" "github.com/danwakefield/fnmatch"
message "github.com/emersion/go-message"
_ "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/google/shlex" "github.com/google/shlex"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
@ -549,10 +546,6 @@ func (pv *PartViewer) SetSource(reader io.Reader) {
func (pv *PartViewer) attemptCopy() { func (pv *PartViewer) attemptCopy() {
if pv.source != nil && pv.pager != nil && pv.pager.Process != nil { if pv.source != nil && pv.pager != nil && pv.pager.Process != nil {
header := message.Header{}
header.SetText("Content-Transfer-Encoding", pv.part.Encoding)
header.SetContentType(fmt.Sprintf("%s/%s", pv.part.MIMEType, pv.part.MIMESubType), pv.part.Params)
header.SetText("Content-Description", pv.part.Description)
if pv.filter != nil { if pv.filter != nil {
stdout, _ := pv.filter.StdoutPipe() stdout, _ := pv.filter.StdoutPipe()
stderr, _ := pv.filter.StderrPipe() stderr, _ := pv.filter.StderrPipe()
@ -608,28 +601,15 @@ func (pv *PartViewer) attemptCopy() {
pv.pagerin.Write([]byte{'\n'}) pv.pagerin.Write([]byte{'\n'})
} }
entity, err := message.New(header, pv.source)
if err != nil {
pv.err = err
pv.Invalidate()
return
}
reader := mail.NewReader(entity)
part, err := reader.NextPart()
if err != nil {
pv.err = err
pv.Invalidate()
return
}
if pv.part.MIMEType == "text" { if pv.part.MIMEType == "text" {
scanner := bufio.NewScanner(part.Body) scanner := bufio.NewScanner(pv.source)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
text = ansi.ReplaceAllString(text, "") text = ansi.ReplaceAllString(text, "")
io.WriteString(pv.sink, text+"\n") io.WriteString(pv.sink, text+"\n")
} }
} else { } else {
io.Copy(pv.sink, part.Body) io.Copy(pv.sink, pv.source)
} }
pv.sink.Close() pv.sink.Close()
}() }()
@ -653,7 +633,7 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
return return
} }
if !pv.fetched { if !pv.fetched {
pv.store.FetchBodyPart(pv.msg.Uid, pv.index, pv.SetSource) pv.store.FetchBodyPart(pv.msg.Uid, pv.msg.BodyStructure, pv.index, pv.SetSource)
pv.fetched = true pv.fetched = true
} }
if pv.err != nil { if pv.err != nil {

View file

@ -104,8 +104,10 @@ type FetchFullMessages struct {
type FetchMessageBodyPart struct { type FetchMessageBodyPart struct {
Message Message
Uid uint32 Uid uint32
Part []int Part []int
Encoding string
Charset string
} }
type DeleteMessages struct { type DeleteMessages struct {