mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-03-02 18:23:33 +01:00
Go has evolved significantly over the years and has introduced some handy helper functions that make the code easier to read. Use helper functions like slices.Contains, map.Copy, and strings.CutPrefix, when appropriate. Signed-off-by: Moritz Poldrack <git@moritz.sh> Acked-by: Robin Jarry <robin@jarry.cc>
182 lines
4.2 KiB
Go
182 lines
4.2 KiB
Go
package lib
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
_ "github.com/emersion/go-message/charset"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib/crypto"
|
|
"git.sr.ht/~rjarry/aerc/lib/log"
|
|
"git.sr.ht/~rjarry/aerc/lib/rfc822"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
|
)
|
|
|
|
// This is an abstraction for viewing a message with semi-transparent PGP
|
|
// support.
|
|
type MessageView interface {
|
|
// Returns the MessageInfo for this message
|
|
MessageInfo() *models.MessageInfo
|
|
|
|
// Returns the BodyStructure for this message
|
|
BodyStructure() *models.BodyStructure
|
|
|
|
// Returns the message store that this message was originally sourced from
|
|
Store() *MessageStore
|
|
|
|
// Fetches the full message
|
|
FetchFull(cb func(io.Reader))
|
|
|
|
// Fetches a specific body part for this message
|
|
FetchBodyPart(part []int, cb func(io.Reader))
|
|
|
|
MessageDetails() *models.MessageDetails
|
|
|
|
// SeenFlagSet returns true if the "seen" flag has been set
|
|
SeenFlagSet() bool
|
|
}
|
|
|
|
func usePGP(info *models.BodyStructure) bool {
|
|
if info == nil {
|
|
return false
|
|
}
|
|
if info.MIMEType == "application" {
|
|
if info.MIMESubType == "pgp-encrypted" ||
|
|
info.MIMESubType == "pgp-signature" {
|
|
|
|
return true
|
|
}
|
|
}
|
|
return slices.ContainsFunc(info.Parts, usePGP)
|
|
}
|
|
|
|
type MessageStoreView struct {
|
|
messageInfo *models.MessageInfo
|
|
messageStore *MessageStore
|
|
message []byte
|
|
details *models.MessageDetails
|
|
bodyStructure *models.BodyStructure
|
|
setSeen bool
|
|
}
|
|
|
|
func NewMessageStoreView(messageInfo *models.MessageInfo, setSeen bool,
|
|
store *MessageStore, pgp crypto.Provider, decryptKeys openpgp.PromptFunction,
|
|
innerCb func(MessageView, error),
|
|
) {
|
|
cb := func(msv MessageView, err error) {
|
|
if msv != nil && setSeen && err == nil &&
|
|
!messageInfo.Flags.Has(models.SeenFlag) {
|
|
store.Flag([]models.UID{messageInfo.Uid}, models.SeenFlag, true, nil)
|
|
}
|
|
innerCb(msv, err)
|
|
}
|
|
|
|
if messageInfo == nil {
|
|
// Call nils to the callback, the split view will use this to
|
|
// display an empty view
|
|
cb(nil, nil)
|
|
return
|
|
}
|
|
|
|
msv := &MessageStoreView{
|
|
messageInfo, store,
|
|
nil, nil, messageInfo.BodyStructure,
|
|
setSeen,
|
|
}
|
|
|
|
if usePGP(messageInfo.BodyStructure) {
|
|
msv.FetchFull(func(fm io.Reader) {
|
|
reader := rfc822.NewCRLFReader(fm)
|
|
md, err := pgp.Decrypt(reader, decryptKeys)
|
|
if err != nil {
|
|
cb(nil, err)
|
|
return
|
|
}
|
|
msv.message, err = io.ReadAll(md.Body)
|
|
if err != nil {
|
|
cb(nil, err)
|
|
return
|
|
}
|
|
decrypted, err := rfc822.ReadMessage(bytes.NewBuffer(msv.message))
|
|
if err != nil {
|
|
cb(nil, err)
|
|
return
|
|
}
|
|
bs, err := rfc822.ParseEntityStructure(decrypted)
|
|
if rfc822.IsMultipartError(err) {
|
|
log.Warnf("MessageView: %v", err)
|
|
bs = rfc822.CreateTextPlainBody()
|
|
} else if err != nil {
|
|
cb(nil, err)
|
|
return
|
|
}
|
|
msv.bodyStructure = bs
|
|
msv.details = md
|
|
cb(msv, nil)
|
|
})
|
|
} else {
|
|
cb(msv, nil)
|
|
}
|
|
}
|
|
|
|
func (msv *MessageStoreView) SeenFlagSet() bool {
|
|
return msv.setSeen
|
|
}
|
|
|
|
func (msv *MessageStoreView) MessageInfo() *models.MessageInfo {
|
|
return msv.messageInfo
|
|
}
|
|
|
|
func (msv *MessageStoreView) BodyStructure() *models.BodyStructure {
|
|
return msv.bodyStructure
|
|
}
|
|
|
|
func (msv *MessageStoreView) Store() *MessageStore {
|
|
return msv.messageStore
|
|
}
|
|
|
|
func (msv *MessageStoreView) MessageDetails() *models.MessageDetails {
|
|
return msv.details
|
|
}
|
|
|
|
func (msv *MessageStoreView) FetchFull(cb func(io.Reader)) {
|
|
if msv.message == nil && msv.messageStore != nil {
|
|
msv.messageStore.FetchFull([]models.UID{msv.messageInfo.Uid},
|
|
func(fm *types.FullMessage) {
|
|
cb(fm.Content.Reader)
|
|
})
|
|
return
|
|
}
|
|
cb(bytes.NewReader(msv.message))
|
|
}
|
|
|
|
func (msv *MessageStoreView) FetchBodyPart(part []int, cb func(io.Reader)) {
|
|
if msv.message == nil && msv.messageStore != nil {
|
|
msv.messageStore.FetchBodyPart(msv.messageInfo.Uid, part, cb)
|
|
return
|
|
}
|
|
|
|
buf := bytes.NewBuffer(msv.message)
|
|
msg, err := rfc822.ReadMessage(buf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
reader, err := rfc822.FetchEntityPartReader(msg, part)
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("Failed to fetch message part: %w", err)
|
|
log.Errorf(errMsg.Error())
|
|
if msv.message != nil {
|
|
log.Warnf("Displaying raw message part")
|
|
reader = bytes.NewReader(msv.message)
|
|
} else {
|
|
reader = strings.NewReader(errMsg.Error())
|
|
}
|
|
}
|
|
cb(reader)
|
|
}
|