From b83e7c9fa6a0d187a0f20d98d522cff792053cdd Mon Sep 17 00:00:00 2001 From: Yash Srivastav Date: Fri, 7 Jun 2019 13:56:14 +0530 Subject: [PATCH] implements ability to view headers in message view --- commands/msgview/toggle-headers.go | 25 +++++ config/aerc.conf.in | 7 ++ config/config.go | 1 + widgets/msgviewer.go | 144 ++++++++++++++++++----------- worker/imap/fetch.go | 21 ++++- worker/types/messages.go | 2 + 6 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 commands/msgview/toggle-headers.go diff --git a/commands/msgview/toggle-headers.go b/commands/msgview/toggle-headers.go new file mode 100644 index 0000000..fc29042 --- /dev/null +++ b/commands/msgview/toggle-headers.go @@ -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 +} diff --git a/config/aerc.conf.in b/config/aerc.conf.in index 090e624..0bf114f 100644 --- a/config/aerc.conf.in +++ b/config/aerc.conf.in @@ -44,6 +44,13 @@ pager=less -R # Default: 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] # # Specifies the command to run the editor with. It will be shown in an embedded diff --git a/config/config.go b/config/config.go index 889a63d..3b7edbb 100644 --- a/config/config.go +++ b/config/config.go @@ -72,6 +72,7 @@ type FilterConfig struct { type ViewerConfig struct { Pager string Alternatives []string + ShowHeaders bool `ini:"show-headers"` } type AercConfig struct { diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index 45a5ed0..b82fa8d 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -35,8 +35,9 @@ type MessageViewer struct { type PartSwitcher struct { ui.Invalidatable - parts []*PartViewer - selected int + parts []*PartViewer + selected int + showHeaders bool } func formatAddresses(addrs []*imap.Address) string { @@ -98,33 +99,10 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig, }).At(2, 0).Span(1, 2) headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2) - var err error switcher := &PartSwitcher{} - if len(msg.BodyStructure.Parts) == 0 { - pv, err := NewPartViewer(conf, store, msg, msg.BodyStructure, []int{1}) - if err != nil { - 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 - } - } + err := createSwitcher(switcher, conf, store, msg, conf.Viewer.ShowHeaders) + if err != nil { + goto handle_error } grid.AddChild(headers).At(0, 0) @@ -132,6 +110,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig, return &MessageViewer{ acct: acct, + conf: conf, grid: grid, msg: msg, store: store, @@ -148,7 +127,7 @@ handle_error: func enumerateParts(conf *config.AercConfig, store *lib.MessageStore, msg *types.MessageInfo, body *imap.BodyStructure, - index []int) ([]*PartViewer, error) { + showHeaders bool, index []int) ([]*PartViewer, error) { var parts []*PartViewer for i, part := range body.Parts { @@ -158,14 +137,14 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore, pv := &PartViewer{part: part} parts = append(parts, pv) subParts, err := enumerateParts( - conf, store, msg, part, curindex) + conf, store, msg, part, showHeaders, curindex) if err != nil { return nil, err } parts = append(parts, subParts...) continue } - pv, err := NewPartViewer(conf, store, msg, part, curindex) + pv, err := NewPartViewer(conf, store, msg, part, showHeaders, curindex) if err != nil { return nil, err } @@ -174,6 +153,44 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore, 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) { if mv.err != nil { ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) @@ -205,6 +222,15 @@ func (mv *MessageViewer) SelectedMessage() *types.MessageInfo { 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 { switcher := mv.switcher part := switcher.parts[switcher.selected] @@ -295,18 +321,19 @@ func (mv *MessageViewer) Focus(focus bool) { type PartViewer struct { ui.Invalidatable - err error - fetched bool - filter *exec.Cmd - index []int - msg *types.MessageInfo - pager *exec.Cmd - pagerin io.WriteCloser - part *imap.BodyStructure - sink io.WriteCloser - source io.Reader - store *lib.MessageStore - term *Terminal + err error + fetched bool + filter *exec.Cmd + index []int + msg *types.MessageInfo + pager *exec.Cmd + pagerin io.WriteCloser + part *imap.BodyStructure + showHeaders bool + sink io.WriteCloser + source io.Reader + store *lib.MessageStore + term *Terminal } type PartInfo struct { @@ -318,7 +345,8 @@ type PartInfo struct { func NewPartViewer(conf *config.AercConfig, store *lib.MessageStore, msg *types.MessageInfo, - part *imap.BodyStructure, index []int) (*PartViewer, error) { + part *imap.BodyStructure, showHeaders bool, + index []int) (*PartViewer, error) { var ( filter *exec.Cmd @@ -375,15 +403,16 @@ func NewPartViewer(conf *config.AercConfig, } pv := &PartViewer{ - filter: filter, - index: index, - msg: msg, - pager: pager, - pagerin: pagerin, - part: part, - sink: pipe, - store: store, - term: term, + filter: filter, + index: index, + msg: msg, + pager: pager, + pagerin: pagerin, + part: part, + showHeaders: showHeaders, + sink: pipe, + store: store, + term: term, } if term != nil { @@ -439,6 +468,15 @@ func (pv *PartViewer) attemptCopy() { }() } 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) if err != nil { pv.err = err diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go index ac9d009..49c9ac5 100644 --- a/worker/imap/fetch.go +++ b/worker/imap/fetch.go @@ -1,7 +1,12 @@ package imap import ( + "bufio" + "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" ) @@ -10,15 +15,22 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders( msg *types.FetchMessageHeaders) { imapw.worker.Logger.Printf("Fetching message headers") + section := &imap.BodySectionName{ + BodyPartName: imap.BodyPartName{ + Specifier: imap.HeaderSpecifier, + }, + } + items := []imap.FetchItem{ imap.FetchBodyStructure, imap.FetchEnvelope, imap.FetchInternalDate, imap.FetchFlags, imap.FetchUid, + section.FetchItem(), } - imapw.handleFetchMessages(msg, &msg.Uids, items, nil) + imapw.handleFetchMessages(msg, &msg.Uids, items, section) } func (imapw *IMAPWorker) handleFetchMessageBodyPart( @@ -54,12 +66,19 @@ func (imapw *IMAPWorker) handleFetchMessages( imapw.seqMap[_msg.SeqNum-1] = _msg.Uid switch msg.(type) { 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{ Message: types.RespondTo(msg), BodyStructure: _msg.BodyStructure, Envelope: _msg.Envelope, Flags: _msg.Flags, InternalDate: _msg.InternalDate, + RFC822Headers: header, Uid: _msg.Uid, }, nil) case *types.FetchFullMessages: diff --git a/worker/types/messages.go b/worker/types/messages.go index 4e46cbf..29d3d9f 100644 --- a/worker/types/messages.go +++ b/worker/types/messages.go @@ -5,6 +5,7 @@ import ( "time" "github.com/emersion/go-imap" + "github.com/emersion/go-message/mail" "git.sr.ht/~sircmpwn/aerc/config" ) @@ -145,6 +146,7 @@ type MessageInfo struct { Envelope *imap.Envelope Flags []string InternalDate time.Time + RFC822Headers *mail.Header Size uint32 Uid uint32 }