~sircmpwn/aerc

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH] imap: Remove FetchMessageBodyPart.{Encoding,Charset}

Details
Message ID
<20200516181041.97338-1-reto@labrat.space>
DKIM signature
pass
Download raw message
Patch: +111 -123
Fixes https://todo.sr.ht/~sircmpwn/aerc2/352 exactly as suggested by emersion.
---
Please do test. I didn't notice any breakage during testing but I'm not usually
using the imap worker so I may have missed some edge case.


 lib/msgstore.go          |  15 +--
 worker/imap/fetch.go     | 213 ++++++++++++++++++++-------------------
 worker/types/messages.go |   6 +-
 3 files changed, 111 insertions(+), 123 deletions(-)

diff --git a/lib/msgstore.go b/lib/msgstore.go
index b3a86b3..3fe26cb 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -138,21 +138,10 @@ func (store *MessageStore) FetchFull(uids []uint32, cb func(*types.FullMessage))

func (store *MessageStore) FetchBodyPart(
	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{
		Uid:      uid,
		Part:     part,
		Encoding: partbs.Encoding,
		Charset:  charset,
		Uid:  uid,
		Part: part,
	}, func(resp types.WorkerMessage) {
		msg, ok := resp.(*types.MessageBodyPart)
		if !ok {
diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go
index 9591ced..def0da8 100644
--- a/worker/imap/fetch.go
+++ b/worker/imap/fetch.go
@@ -2,11 +2,7 @@ package imap

import (
	"bufio"
	"encoding/base64"
	"fmt"
	"io"
	"mime/quotedprintable"
	"strings"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-message"
@@ -37,21 +33,90 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
		imap.FetchUid,
		section.FetchItem(),
	}
	imapw.handleFetchMessages(msg, msg.Uids, items, section)
	imapw.handleFetchMessages(msg, msg.Uids, items,
		func(_msg *imap.Message) error {
			reader := _msg.GetBody(section)
			textprotoHeader, err := textproto.ReadHeader(bufio.NewReader(reader))
			if err != nil {
				return fmt.Errorf("could not read header: %v", err)
			}
			header := &mail.Header{message.Header{textprotoHeader}}
			imapw.worker.PostMessage(&types.MessageInfo{
				Message: types.RespondTo(msg),
				Info: &models.MessageInfo{
					BodyStructure: translateBodyStructure(_msg.BodyStructure),
					Envelope:      translateEnvelope(_msg.Envelope),
					Flags:         translateImapFlags(_msg.Flags),
					InternalDate:  _msg.InternalDate,
					RFC822Headers: header,
					Uid:           _msg.Uid,
				},
			}, nil)
			return nil
		})
}

func (imapw *IMAPWorker) handleFetchMessageBodyPart(
	msg *types.FetchMessageBodyPart) {

	imapw.worker.Logger.Printf("Fetching message part")
	section := &imap.BodySectionName{}
	section.Path = msg.Part

	var partHeaderSection imap.BodySectionName
	partHeaderSection.Peek = true
	if len(msg.Part) > 0 {
		partHeaderSection.Specifier = imap.MIMESpecifier
	} else {
		partHeaderSection.Specifier = imap.HeaderSpecifier
	}
	partHeaderSection.Path = msg.Part

	var partBodySection imap.BodySectionName
	if len(msg.Part) > 0 {
		partBodySection.Specifier = imap.EntireSpecifier
	} else {
		partBodySection.Specifier = imap.TextSpecifier
	}
	partBodySection.Path = msg.Part

	items := []imap.FetchItem{
		imap.FetchFlags,
		imap.FetchEnvelope,
		imap.FetchUid,
		section.FetchItem(),
		imap.FetchBodyStructure,
		imap.FetchFlags,
		partHeaderSection.FetchItem(),
		partBodySection.FetchItem(),
	}
	imapw.handleFetchMessages(msg, []uint32{msg.Uid}, items, section)
	imapw.handleFetchMessages(msg, []uint32{msg.Uid}, items,
		func(_msg *imap.Message) error {
			headerReader := bufio.NewReader(_msg.GetBody(&partHeaderSection))
			h, err := textproto.ReadHeader(headerReader)
			if err != nil {
				return fmt.Errorf("failed to read part header: %v", err)
			}

			part, err := message.New(message.Header{h},
				_msg.GetBody(&partBodySection))
			if err != nil {
				return fmt.Errorf("failed to create message reader: %v", err)
			}

			imapw.worker.PostMessage(&types.MessageBodyPart{
				Message: types.RespondTo(msg),
				Part: &models.MessageBodyPart{
					Reader: part.Body,
					Uid:    _msg.Uid,
				},
			}, nil)
			// Update flags (to mark message as read)
			imapw.worker.PostMessage(&types.MessageInfo{
				Message: types.RespondTo(msg),
				Info: &models.MessageInfo{
					Flags: translateImapFlags(_msg.Flags),
					Uid:   _msg.Uid,
				},
			}, nil)
			return nil
		})
}

func (imapw *IMAPWorker) handleFetchFullMessages(
@@ -65,85 +130,53 @@ func (imapw *IMAPWorker) handleFetchFullMessages(
		imap.FetchUid,
		section.FetchItem(),
	}
	imapw.handleFetchMessages(msg, msg.Uids, items, section)
	imapw.handleFetchMessages(msg, msg.Uids, items,
		func(_msg *imap.Message) error {
			r := _msg.GetBody(section)
			if r == nil {
				return fmt.Errorf("could not get section %#v", section)
			}
			imapw.worker.PostMessage(&types.FullMessage{
				Message: types.RespondTo(msg),
				Content: &models.FullMessage{
					Reader: bufio.NewReader(r),
					Uid:    _msg.Uid,
				},
			}, nil)
			// Update flags (to mark message as read)
			imapw.worker.PostMessage(&types.MessageInfo{
				Message: types.RespondTo(msg),
				Info: &models.MessageInfo{
					Flags: translateImapFlags(_msg.Flags),
					Uid:   _msg.Uid,
				},
			}, nil)
			return nil
		})
}

func (imapw *IMAPWorker) handleFetchMessages(
	msg types.WorkerMessage, uids []uint32, items []imap.FetchItem,
	section *imap.BodySectionName) {
	procFunc func(*imap.Message) error) {

	messages := make(chan *imap.Message)
	done := make(chan error)

	go func() {
		var reterr error
		for _msg := range messages {
			imapw.seqMap[_msg.SeqNum-1] = _msg.Uid
			switch msg := msg.(type) {
			case *types.FetchMessageHeaders:
				reader := _msg.GetBody(section)
				textprotoHeader, err := textproto.ReadHeader(bufio.NewReader(reader))
				if err != nil {
					done <- fmt.Errorf("could not read header: %v", err)
					return
			err := procFunc(_msg)
			if err != nil {
				if reterr == nil {
					reterr = err
				}
				header := &mail.Header{message.Header{textprotoHeader}}
				imapw.worker.PostMessage(&types.MessageInfo{
					Message: types.RespondTo(msg),
					Info: &models.MessageInfo{
						BodyStructure: translateBodyStructure(_msg.BodyStructure),
						Envelope:      translateEnvelope(_msg.Envelope),
						Flags:         translateImapFlags(_msg.Flags),
						InternalDate:  _msg.InternalDate,
						RFC822Headers: header,
						Uid:           _msg.Uid,
					},
				}, nil)
			case *types.FetchFullMessages:
				r := _msg.GetBody(section)
				if r == nil {
					done <- fmt.Errorf("could not get section %#v", section)
					return
				}

				imapw.worker.PostMessage(&types.FullMessage{
					Message: types.RespondTo(msg),
					Content: &models.FullMessage{
						Reader: bufio.NewReader(r),
						Uid:    _msg.Uid,
					},
				}, nil)
				// Update flags (to mark message as read)
				imapw.worker.PostMessage(&types.MessageInfo{
					Message: types.RespondTo(msg),
					Info: &models.MessageInfo{
						Flags: translateImapFlags(_msg.Flags),
						Uid:   _msg.Uid,
					},
				}, nil)
			case *types.FetchMessageBodyPart:
				reader, err := getDecodedPart(msg, _msg, section)
				if err != nil {
					done <- err
					return
				// drain the channel upon error
				for range messages {
				}
				imapw.worker.PostMessage(&types.MessageBodyPart{
					Message: types.RespondTo(msg),
					Part: &models.MessageBodyPart{
						Reader: reader,
						Uid:    _msg.Uid,
					},
				}, nil)
				// Update flags (to mark message as read)
				imapw.worker.PostMessage(&types.MessageInfo{
					Message: types.RespondTo(msg),
					Info: &models.MessageInfo{
						Flags: translateImapFlags(_msg.Flags),
						Uid:   _msg.Uid,
					},
				}, nil)
			}
		}
		done <- nil
		done <- reterr
	}()

	emitErr := func(err error) {
@@ -165,35 +198,3 @@ func (imapw *IMAPWorker) handleFetchMessages(
	imapw.worker.PostMessage(
		&types.Done{types.RespondTo(msg)}, nil)
}

func getDecodedPart(task *types.FetchMessageBodyPart, msg *imap.Message,
	section *imap.BodySectionName) (io.Reader, error) {
	var r io.Reader
	var err error

	r = msg.GetBody(section)

	if r == nil {
		return nil, nil
	}
	r = encodingReader(task.Encoding, r)
	if task.Charset != "" {
		r, err = message.CharsetReader(task.Charset, r)
	}
	if err != nil {
		return nil, err
	}

	return r, err
}

func encodingReader(encoding string, r io.Reader) io.Reader {
	reader := r
	// email parts are encoded as 7bit (plaintext), quoted-printable, or base64
	if strings.EqualFold(encoding, "base64") {
		reader = base64.NewDecoder(base64.StdEncoding, r)
	} else if strings.EqualFold(encoding, "quoted-printable") {
		reader = quotedprintable.NewReader(r)
	}
	return reader
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 6422ef5..f1ef36e 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -104,10 +104,8 @@ type FetchFullMessages struct {

type FetchMessageBodyPart struct {
	Message
	Uid      uint32
	Part     []int
	Encoding string
	Charset  string
	Uid  uint32
	Part []int
}

type DeleteMessages struct {
-- 
2.26.2
Review patch Export thread (mbox)