aerc/commands/compose/postpone.go
Robin Jarry c26d08103b 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>
2022-02-25 13:56:53 +01:00

128 lines
2.8 KiB
Go

package compose
import (
"io"
"io/ioutil"
"time"
"github.com/miolini/datacounter"
"github.com/pkg/errors"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/widgets"
"git.sr.ht/~rjarry/aerc/worker/types"
)
type Postpone struct{}
func init() {
register(Postpone{})
}
func (Postpone) Aliases() []string {
return []string{"postpone"}
}
func (Postpone) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
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()]
if config.Postpone == "" {
return errors.New("No Postpone location configured")
}
aerc.Logger().Println("Postponing mail")
header, err := composer.PrepareHeader()
if err != nil {
return errors.Wrap(err, "PrepareHeader")
}
header.SetContentType("text/plain", map[string]string{"charset": "UTF-8"})
header.Set("Content-Transfer-Encoding", "quoted-printable")
worker := composer.Worker()
dirs := acct.Directories().List()
alreadyCreated := false
for _, dir := range dirs {
if dir == config.Postpone {
alreadyCreated = true
break
}
}
errChan := make(chan string)
// run this as a goroutine so we can make other progress. The message
// will be saved once the directory is created.
go func() {
errStr := <-errChan
if errStr != "" {
aerc.PushError(errStr)
return
}
handleErr := func(err error) {
aerc.PushError(err.Error())
aerc.Logger().Println("Postponing failed:", err)
aerc.NewTab(composer, tabName)
}
aerc.RemoveTab(composer)
ctr := datacounter.NewWriterCounter(ioutil.Discard)
err = composer.WriteMessage(header, ctr)
if err != nil {
handleErr(errors.Wrap(err, "WriteMessage"))
return
}
nbytes := int(ctr.Count())
r, w := io.Pipe()
worker.PostAction(&types.AppendMessage{
Destination: config.Postpone,
Flags: []models.Flag{models.SeenFlag},
Date: time.Now(),
Reader: r,
Length: int(nbytes),
}, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
aerc.PushStatus("Message postponed.", 10*time.Second)
r.Close()
composer.Close()
case *types.Error:
r.Close()
handleErr(msg.Error)
}
})
composer.WriteMessage(header, w)
w.Close()
}()
if !alreadyCreated {
// to synchronise the creating of the directory
worker.PostAction(&types.CreateDirectory{
Directory: config.Postpone,
}, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
errChan <- ""
case *types.Error:
errChan <- msg.Error.Error()
}
})
} else {
errChan <- ""
}
return nil
}