aerc: always check SelectedAccount return value

aerc.SelectedAccount() is used in lots of places. Most of them without
checking the return value.

In some cases, the currently selected tab is not related to any account
(widget.Terminal for example). This can lead to unexpected crashes when
accessing account specific configuration.

When possible, return an error when no account is currently selected.
If no error can be returned, fallback to non-account specific
configuration.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Reviewed-by: Koni Marti <koni.marti@gmail.com>
This commit is contained in:
Robin Jarry 2022-02-25 00:21:06 +01:00
parent 91ead11c47
commit c26d08103b
9 changed files with 65 additions and 29 deletions

View file

@ -30,6 +30,9 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
return err
}
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
if template == "" {
template = aerc.Config().Templates.NewMessage
}

View file

@ -26,6 +26,9 @@ func (ViewMessage) Execute(aerc *widgets.Aerc, args []string) error {
return errors.New("Usage: view-message")
}
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
if acct.Messages().Empty() {
return nil
}

View file

@ -117,11 +117,15 @@ func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string {
func GetFolders(aerc *widgets.Aerc, args []string) []string {
out := make([]string, 0)
if len(args) == 0 {
return aerc.SelectedAccount().Directories().List()
acct := aerc.SelectedAccount()
if acct == nil {
return out
}
for _, dir := range aerc.SelectedAccount().Directories().List() {
if foundInString(dir, args[0], aerc.SelectedAccount().UiConfig().FuzzyFolderComplete) {
if len(args) == 0 {
return acct.Directories().List()
}
for _, dir := range acct.Directories().List() {
if foundInString(dir, args[0], acct.UiConfig().FuzzyFolderComplete) {
out = append(out, dir)
}
}
@ -144,8 +148,12 @@ func CompletionFromList(valid []string, args []string) []string {
}
func GetLabels(aerc *widgets.Aerc, args []string) []string {
acct := aerc.SelectedAccount()
if acct == nil {
return make([]string, 0)
}
if len(args) == 0 {
return aerc.SelectedAccount().Labels()
return acct.Labels()
}
// + and - are used to denote tag addition / removal and need to be striped
@ -165,7 +173,7 @@ func GetLabels(aerc *widgets.Aerc, args []string) []string {
trimmed := strings.TrimLeft(last, "+-")
out := make([]string, 0)
for _, label := range aerc.SelectedAccount().Labels() {
for _, label := range acct.Labels() {
if hasCaseSmartPrefix(label, trimmed) {
var prev string
if len(others) > 0 {

View file

@ -31,6 +31,10 @@ func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
if len(args) != 1 {
return errors.New("Usage: postpone")
}
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
composer, _ := aerc.SelectedTab().(*widgets.Composer)
config := composer.Config()
tabName := aerc.TabNames()[aerc.SelectedTabIndex()]
@ -48,7 +52,7 @@ func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
header.SetContentType("text/plain", map[string]string{"charset": "UTF-8"})
header.Set("Content-Transfer-Encoding", "quoted-printable")
worker := composer.Worker()
dirs := aerc.SelectedAccount().Directories().List()
dirs := acct.Directories().List()
alreadyCreated := false
for _, dir := range dirs {
if dir == config.Postpone {

View file

@ -89,6 +89,9 @@ func parseUnsubscribeMethods(header string) (methods []*url.URL) {
func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
widget := aerc.SelectedTab().(widgets.ProvidesMessage)
acct := widget.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
h := &mail.Header{}
h.SetSubject(u.Query().Get("subject"))

View file

@ -1,6 +1,8 @@
package msgview
import (
"errors"
"git.sr.ht/~rjarry/aerc/commands/account"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/widgets"
@ -27,6 +29,9 @@ func (NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
}
mv, _ := aerc.SelectedTab().(*widgets.MessageViewer)
acct := mv.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
store := mv.Store()
err = account.ExecuteNextPrevMessage(args, acct, pct, n)
if err != nil {

View file

@ -309,6 +309,14 @@ func (aerc *Aerc) SelectedAccount() *AccountView {
return nil
}
func (aerc *Aerc) SelectedAccountUiConfig() config.UIConfig {
acct := aerc.SelectedAccount()
if acct == nil {
return aerc.conf.Ui
}
return acct.UiConfig()
}
func (aerc *Aerc) SelectedTab() ui.Drawable {
return aerc.tabs.Tabs[aerc.tabs.Selected].Content
}

View file

@ -127,15 +127,15 @@ func (c *Composer) buildComposeHeader(aerc *Aerc, cmpl *completer.Completer) {
c.layout = aerc.conf.Compose.HeaderLayout
c.editors = make(map[string]*headerEditor)
c.focusable = make([]ui.MouseableDrawableInteractive, 0)
uiConfig := aerc.SelectedAccountUiConfig()
for i, row := range c.layout {
for j, h := range row {
h = strings.ToLower(h)
c.layout[i][j] = h // normalize to lowercase
e := newHeaderEditor(h, c.header, aerc.SelectedAccount().UiConfig())
e := newHeaderEditor(h, c.header, uiConfig)
if aerc.conf.Ui.CompletionPopovers {
e.input.TabComplete(cmpl.ForHeader(h),
aerc.SelectedAccount().UiConfig().CompletionDelay)
e.input.TabComplete(cmpl.ForHeader(h), uiConfig.CompletionDelay)
}
c.editors[h] = e
switch h {
@ -152,9 +152,9 @@ func (c *Composer) buildComposeHeader(aerc *Aerc, cmpl *completer.Completer) {
for _, h := range []string{"cc", "bcc"} {
if c.header.Has(h) {
if _, ok := c.editors[h]; !ok {
e := newHeaderEditor(h, c.header, aerc.SelectedAccount().UiConfig())
e := newHeaderEditor(h, c.header, uiConfig)
if aerc.conf.Ui.CompletionPopovers {
e.input.TabComplete(cmpl.ForHeader(h), aerc.SelectedAccount().UiConfig().CompletionDelay)
e.input.TabComplete(cmpl.ForHeader(h), uiConfig.CompletionDelay)
}
c.editors[h] = e
c.focusable = append(c.focusable, e)
@ -741,11 +741,10 @@ func (c *Composer) AddEditor(header string, value string, appendHeader bool) {
e.storeValue() // flush modifications from the user to the header
editor = e
} else {
e := newHeaderEditor(header, c.header,
c.aerc.SelectedAccount().UiConfig())
if c.config.Ui.CompletionPopovers {
e.input.TabComplete(c.completer.ForHeader(header),
c.config.Ui.CompletionDelay)
uiConfig := c.aerc.SelectedAccountUiConfig()
e := newHeaderEditor(header, c.header, uiConfig)
if uiConfig.CompletionPopovers {
e.input.TabComplete(c.completer.ForHeader(header), uiConfig.CompletionDelay)
}
c.editors[header] = e
c.layout = append(c.layout, []string{header})

View file

@ -53,11 +53,13 @@ func (ml *MessageList) Invalidate() {
func (ml *MessageList) Draw(ctx *ui.Context) {
ml.height = ctx.Height()
uiConfig := ml.aerc.SelectedAccountUiConfig()
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
ml.aerc.SelectedAccount().UiConfig().GetStyle(config.STYLE_MSGLIST_DEFAULT))
uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT))
acct := ml.aerc.SelectedAccount()
store := ml.Store()
if store == nil {
if store == nil || acct == nil {
if ml.isInitalizing {
ml.spinner.Draw(ctx)
return
@ -89,7 +91,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
row int = 0
)
if ml.aerc.SelectedAccount().UiConfig().ThreadingEnabled || store.BuildThreads() {
if uiConfig.ThreadingEnabled || store.BuildThreads() {
threads := store.Threads
counter := len(store.Uids())
@ -119,8 +121,8 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
}
}
fmtCtx := format.Ctx{
FromAddress: ml.aerc.SelectedAccount().acct.From,
AccountName: ml.aerc.SelectedAccount().Name(),
FromAddress: acct.acct.From,
AccountName: acct.Name(),
MsgInfo: msg,
MsgNum: row,
MsgIsMarked: store.IsMarked(t.Uid),
@ -144,8 +146,8 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
uid := uids[i]
msg := store.Messages[uid]
fmtCtx := format.Ctx{
FromAddress: ml.aerc.SelectedAccount().acct.From,
AccountName: ml.aerc.SelectedAccount().Name(),
FromAddress: acct.acct.From,
AccountName: acct.Name(),
MsgInfo: msg,
MsgNum: row,
MsgIsMarked: store.IsMarked(uid),
@ -183,8 +185,9 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row int, needsHeaders *[]uint32, fmtCtx format.Ctx) bool {
store := ml.store
msg := store.Messages[uid]
acct := ml.aerc.SelectedAccount()
if row >= ctx.Height() {
if row >= ctx.Height() || acct == nil {
return true
}
@ -195,8 +198,8 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
}
confParams := map[config.ContextType]string{
config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name,
config.UI_CONTEXT_FOLDER: ml.aerc.SelectedAccount().Directories().Selected(),
config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name,
config.UI_CONTEXT_FOLDER: acct.Directories().Selected(),
}
if msg.Envelope != nil {
confParams[config.UI_CONTEXT_SUBJECT] = msg.Envelope.Subject
@ -288,7 +291,7 @@ func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) {
if ok {
ml.Select(selectedMsg)
acct := ml.aerc.SelectedAccount()
if acct.Messages().Empty() {
if acct == nil || acct.Messages().Empty() {
return
}
store := acct.Messages().Store()
@ -433,7 +436,7 @@ func (ml *MessageList) ensureScroll() {
}
func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
uiConfig := ml.aerc.SelectedAccount().UiConfig()
uiConfig := ml.aerc.SelectedAccountUiConfig()
msg := uiConfig.EmptyMessage
ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg)