implements ability to view headers in message view

This commit is contained in:
Yash Srivastav 2019-06-07 13:56:14 +05:30 committed by Drew DeVault
parent 2279ac3ab3
commit b83e7c9fa6
6 changed files with 146 additions and 54 deletions

View file

@ -0,0 +1,25 @@
package msgview
import (
"errors"
"fmt"
"git.sr.ht/~sircmpwn/aerc/widgets"
)
func init() {
register("toggle-headers", ToggleHeaders)
}
func toggleHeadersUsage(cmd string) error {
return errors.New(fmt.Sprintf("Usage: %s", cmd))
}
func ToggleHeaders(aerc *widgets.Aerc, args []string) error {
if len(args) > 1 {
return toggleHeadersUsage(args[0])
}
mv, _ := aerc.SelectedTab().(*widgets.MessageViewer)
mv.ToggleHeaders()
return nil
}

View file

@ -44,6 +44,13 @@ pager=less -R
# Default: text/plain,text/html # Default: text/plain,text/html
alternatives=text/plain,text/html alternatives=text/plain,text/html
#
# Default setting to determine whether to show full headers or only parsed
# ones in message viewer.
#
# Default: false
show-headers=false
[compose] [compose]
# #
# Specifies the command to run the editor with. It will be shown in an embedded # Specifies the command to run the editor with. It will be shown in an embedded

View file

@ -72,6 +72,7 @@ type FilterConfig struct {
type ViewerConfig struct { type ViewerConfig struct {
Pager string Pager string
Alternatives []string Alternatives []string
ShowHeaders bool `ini:"show-headers"`
} }
type AercConfig struct { type AercConfig struct {

View file

@ -35,8 +35,9 @@ type MessageViewer struct {
type PartSwitcher struct { type PartSwitcher struct {
ui.Invalidatable ui.Invalidatable
parts []*PartViewer parts []*PartViewer
selected int selected int
showHeaders bool
} }
func formatAddresses(addrs []*imap.Address) string { func formatAddresses(addrs []*imap.Address) string {
@ -98,33 +99,10 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
}).At(2, 0).Span(1, 2) }).At(2, 0).Span(1, 2)
headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2) headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2)
var err error
switcher := &PartSwitcher{} switcher := &PartSwitcher{}
if len(msg.BodyStructure.Parts) == 0 { err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders)
pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure, []int{1}) if err != nil {
if err != nil { goto handle_error
goto handle_error
}
switcher.parts = []*PartViewer{pv}
pv.OnInvalidate(func(_ ui.Drawable) {
switcher.Invalidate()
})
} else {
switcher.parts, err = enumerateParts(conf, store,
msg, msg.BodyStructure, []int{})
if err != nil {
goto handle_error
}
switcher.selected = -1
for i, pv := range switcher.parts {
pv.OnInvalidate(func(_ ui.Drawable) {
switcher.Invalidate()
})
// TODO: switch to user's preferred mimetype, if configured
if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
switcher.selected = i
}
}
} }
grid.AddChild(headers).At(0, 0) grid.AddChild(headers).At(0, 0)
@ -132,6 +110,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
return &MessageViewer{ return &MessageViewer{
acct: acct, acct: acct,
conf: conf,
grid: grid, grid: grid,
msg: msg, msg: msg,
store: store, store: store,
@ -148,7 +127,7 @@ handle_error:
func enumerateParts(conf *config.AercConfig, store *lib.MessageStore, func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
msg *types.MessageInfo, body *imap.BodyStructure, msg *types.MessageInfo, body *imap.BodyStructure,
index []int) ([]*PartViewer, error) { showHeaders bool, index []int) ([]*PartViewer, error) {
var parts []*PartViewer var parts []*PartViewer
for i, part := range body.Parts { for i, part := range body.Parts {
@ -158,14 +137,14 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
pv := &PartViewer{part: part} pv := &PartViewer{part: part}
parts = append(parts, pv) parts = append(parts, pv)
subParts, err := enumerateParts( subParts, err := enumerateParts(
conf, store, msg, part, curindex) conf, store, msg, part, showHeaders, curindex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
parts = append(parts, subParts...) parts = append(parts, subParts...)
continue continue
} }
pv, err := NewPartViewer(conf, store, msg, part, curindex) pv, err := NewPartViewer(conf, store, msg, part, showHeaders, curindex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,6 +153,44 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
return parts, nil return parts, nil
} }
func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
store *lib.MessageStore, msg *types.MessageInfo, showHeaders bool) error {
var err error
switcher.showHeaders = showHeaders
if showHeaders {
}
if len(msg.BodyStructure.Parts) == 0 {
pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure,
showHeaders, []int{1})
if err != nil {
return err
}
switcher.parts = []*PartViewer{pv}
pv.OnInvalidate(func(_ ui.Drawable) {
switcher.Invalidate()
})
} else {
switcher.parts, err = enumerateParts(conf, store,
msg, msg.BodyStructure, showHeaders, []int{})
if err != nil {
return err
}
switcher.selected = -1
for i, pv := range switcher.parts {
pv.OnInvalidate(func(_ ui.Drawable) {
switcher.Invalidate()
})
// TODO: switch to user's preferred mimetype, if configured
if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
switcher.selected = i
}
}
}
return nil
}
func (mv *MessageViewer) Draw(ctx *ui.Context) { func (mv *MessageViewer) Draw(ctx *ui.Context) {
if mv.err != nil { if mv.err != nil {
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
@ -205,6 +222,15 @@ func (mv *MessageViewer) SelectedMessage() *types.MessageInfo {
return mv.msg return mv.msg
} }
func (mv *MessageViewer) ToggleHeaders() {
switcher := mv.switcher
err := createSwitcher(switcher, mv.conf, mv.store, mv.msg, !switcher.showHeaders)
if err != nil {
mv.acct.Logger().Printf("warning: error during create switcher - %v", err)
}
switcher.Invalidate()
}
func (mv *MessageViewer) CurrentPart() *PartInfo { func (mv *MessageViewer) CurrentPart() *PartInfo {
switcher := mv.switcher switcher := mv.switcher
part := switcher.parts[switcher.selected] part := switcher.parts[switcher.selected]
@ -295,18 +321,19 @@ func (mv *MessageViewer) Focus(focus bool) {
type PartViewer struct { type PartViewer struct {
ui.Invalidatable ui.Invalidatable
err error err error
fetched bool fetched bool
filter *exec.Cmd filter *exec.Cmd
index []int index []int
msg *types.MessageInfo msg *types.MessageInfo
pager *exec.Cmd pager *exec.Cmd
pagerin io.WriteCloser pagerin io.WriteCloser
part *imap.BodyStructure part *imap.BodyStructure
sink io.WriteCloser showHeaders bool
source io.Reader sink io.WriteCloser
store *lib.MessageStore source io.Reader
term *Terminal store *lib.MessageStore
term *Terminal
} }
type PartInfo struct { type PartInfo struct {
@ -318,7 +345,8 @@ type PartInfo struct {
func NewPartViewer(conf *config.AercConfig, func NewPartViewer(conf *config.AercConfig,
store *lib.MessageStore, msg *types.MessageInfo, store *lib.MessageStore, msg *types.MessageInfo,
part *imap.BodyStructure, index []int) (*PartViewer, error) { part *imap.BodyStructure, showHeaders bool,
index []int) (*PartViewer, error) {
var ( var (
filter *exec.Cmd filter *exec.Cmd
@ -375,15 +403,16 @@ func NewPartViewer(conf *config.AercConfig,
} }
pv := &PartViewer{ pv := &PartViewer{
filter: filter, filter: filter,
index: index, index: index,
msg: msg, msg: msg,
pager: pager, pager: pager,
pagerin: pagerin, pagerin: pagerin,
part: part, part: part,
sink: pipe, showHeaders: showHeaders,
store: store, sink: pipe,
term: term, store: store,
term: term,
} }
if term != nil { if term != nil {
@ -439,6 +468,15 @@ func (pv *PartViewer) attemptCopy() {
}() }()
} }
go func() { go func() {
if pv.showHeaders && pv.msg.RFC822Headers != nil {
fields := pv.msg.RFC822Headers.Fields()
for fields.Next() {
field := fmt.Sprintf("%s: %s\n", fields.Key(), fields.Value())
pv.sink.Write([]byte(field))
}
pv.sink.Write([]byte{'\n'})
}
entity, err := message.New(header, pv.source) entity, err := message.New(header, pv.source)
if err != nil { if err != nil {
pv.err = err pv.err = err

View file

@ -1,7 +1,12 @@
package imap package imap
import ( import (
"bufio"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-message/textproto"
"git.sr.ht/~sircmpwn/aerc/worker/types" "git.sr.ht/~sircmpwn/aerc/worker/types"
) )
@ -10,15 +15,22 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
msg *types.FetchMessageHeaders) { msg *types.FetchMessageHeaders) {
imapw.worker.Logger.Printf("Fetching message headers") imapw.worker.Logger.Printf("Fetching message headers")
section := &imap.BodySectionName{
BodyPartName: imap.BodyPartName{
Specifier: imap.HeaderSpecifier,
},
}
items := []imap.FetchItem{ items := []imap.FetchItem{
imap.FetchBodyStructure, imap.FetchBodyStructure,
imap.FetchEnvelope, imap.FetchEnvelope,
imap.FetchInternalDate, imap.FetchInternalDate,
imap.FetchFlags, imap.FetchFlags,
imap.FetchUid, imap.FetchUid,
section.FetchItem(),
} }
imapw.handleFetchMessages(msg, &msg.Uids, items, nil) imapw.handleFetchMessages(msg, &msg.Uids, items, section)
} }
func (imapw *IMAPWorker) handleFetchMessageBodyPart( func (imapw *IMAPWorker) handleFetchMessageBodyPart(
@ -54,12 +66,19 @@ func (imapw *IMAPWorker) handleFetchMessages(
imapw.seqMap[_msg.SeqNum-1] = _msg.Uid imapw.seqMap[_msg.SeqNum-1] = _msg.Uid
switch msg.(type) { switch msg.(type) {
case *types.FetchMessageHeaders: case *types.FetchMessageHeaders:
reader := _msg.GetBody(section)
textprotoHeader, err := textproto.ReadHeader(bufio.NewReader(reader))
var header *mail.Header
if err == nil {
header = &mail.Header{message.Header{textprotoHeader}}
}
imapw.worker.PostMessage(&types.MessageInfo{ imapw.worker.PostMessage(&types.MessageInfo{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
BodyStructure: _msg.BodyStructure, BodyStructure: _msg.BodyStructure,
Envelope: _msg.Envelope, Envelope: _msg.Envelope,
Flags: _msg.Flags, Flags: _msg.Flags,
InternalDate: _msg.InternalDate, InternalDate: _msg.InternalDate,
RFC822Headers: header,
Uid: _msg.Uid, Uid: _msg.Uid,
}, nil) }, nil)
case *types.FetchFullMessages: case *types.FetchFullMessages:

View file

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"github.com/emersion/go-message/mail"
"git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/config"
) )
@ -145,6 +146,7 @@ type MessageInfo struct {
Envelope *imap.Envelope Envelope *imap.Envelope
Flags []string Flags []string
InternalDate time.Time InternalDate time.Time
RFC822Headers *mail.Header
Size uint32 Size uint32
Uid uint32 Uid uint32
} }