summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Cox <dev@jasoncarloscox.com>2022-10-17 13:19:33 -0400
committerRobin Jarry <robin@jarry.cc>2022-10-17 22:41:00 +0200
commit7647dfb8b47edbcb8080bd0327529383142ec888 (patch)
tree80cdd42050598815e0fa872745def3cc7d2bea80
parent8ffcd3e5adfa008f33989d4589a47ae1cd1a5e68 (diff)
downloadaerc-7647dfb8b47edbcb8080bd0327529383142ec888.tar.gz
aerc-7647dfb8b47edbcb8080bd0327529383142ec888.zip
compose: warn before sending without attachment
Prevent the embarrassing forgotten attachment scenario by warning the user before sending a message that may need an attachment but does not have one. Whether a message needs an attachment is determined by testing a configurable regex against the message body. Signed-off-by: Jason Cox <dev@jasoncarloscox.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--CHANGELOG.md2
-rw-r--r--commands/compose/send.go39
-rw-r--r--config/aerc.conf12
-rw-r--r--config/config.go21
-rw-r--r--doc/aerc-config.5.scd14
-rw-r--r--widgets/aerc.go4
-rw-r--r--widgets/compose.go20
7 files changed, 106 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 107f8dfc..6554f61f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for attaching files with `mailto:`-links
- Filter commands now have the `AERC_MIME_TYPE` and `AERC_FILENAME` variables
defined in their environment.
+- Warn before sending emails that may need an attachment with
+ `no-attachment-warning` in `aerc.conf`.
### Changed
diff --git a/commands/compose/send.go b/commands/compose/send.go
index 3786721b..ccfe7886 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -100,6 +100,40 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
rcpts: rcpts,
}
+ warn, err := composer.ShouldWarnAttachment()
+ if err != nil || warn {
+ msg := "You may have forgotten an attachment."
+ if err != nil {
+ logging.Warnf("failed to check for a forgotten attachment: %v", err)
+ msg = "Failed to check for a forgotten attachment."
+ }
+
+ prompt := widgets.NewPrompt(aerc.Config(),
+ msg+" Abort send? [Y/n] ",
+ func(text string) {
+ if text == "n" || text == "N" {
+ send(aerc, composer, ctx, header, tabName)
+ }
+ }, func(cmd string) ([]string, string) {
+ if cmd == "" {
+ return []string{"y", "n"}, ""
+ }
+
+ return nil, ""
+ },
+ )
+
+ aerc.PushPrompt(prompt)
+ } else {
+ send(aerc, composer, ctx, header, tabName)
+ }
+
+ return nil
+}
+
+func send(aerc *widgets.Aerc, composer *widgets.Composer, ctx sendCtx,
+ header *mail.Header, tabName string,
+) {
// we don't want to block the UI thread while we are sending
// so we do everything in a goroutine and hide the composer from the user
aerc.RemoveTab(composer)
@@ -109,6 +143,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
mode.NoQuit()
var copyBuf bytes.Buffer // for the Sent folder content if CopyTo is set
+ config := composer.Config()
failCh := make(chan error)
// writer
@@ -116,6 +151,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
defer logging.PanicHandler()
var sender io.WriteCloser
+ var err error
switch ctx.scheme {
case "smtp":
fallthrough
@@ -151,7 +187,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
// leave no-quit mode
defer mode.NoQuitDone()
- err = <-failCh
+ err := <-failCh
if err != nil {
aerc.PushError(strings.ReplaceAll(err.Error(), "\n", " "))
aerc.NewTab(composer, tabName)
@@ -176,7 +212,6 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
composer.SetSent()
composer.Close()
}()
- return nil
}
func listRecipients(h *mail.Header) ([]*mail.Address, error) {
diff --git a/config/aerc.conf b/config/aerc.conf
index a980c70e..384a0db3 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -297,6 +297,18 @@ address-book-cmd=
# Default: true
reply-to-self=true
+#
+# Warn before sending an email that matches the specified regexp but does not
+# have any attachments. Leave empty to disable this feature.
+#
+# Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax. The
+# "(?im)" flags are set by default (case-insensitive and multi-line).
+#
+# Example:
+# no-attachment-warning=^[^>]*attach(ed|ment)
+#
+no-attachment-warning=
+
[filters]
#
# Filters allow you to pipe an email body through a shell command to render
diff --git a/config/config.go b/config/config.go
index e40d964a..0de1780c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -203,10 +203,11 @@ type BindingConfigContext struct {
}
type ComposeConfig struct {
- Editor string `ini:"editor"`
- HeaderLayout [][]string `ini:"-"`
- AddressBookCmd string `ini:"address-book-cmd"`
- ReplyToSelf bool `ini:"reply-to-self"`
+ Editor string `ini:"editor"`
+ HeaderLayout [][]string `ini:"-"`
+ AddressBookCmd string `ini:"address-book-cmd"`
+ ReplyToSelf bool `ini:"reply-to-self"`
+ NoAttachmentWarning *regexp.Regexp `ini:"-"`
}
type FilterConfig struct {
@@ -523,6 +524,18 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
if key == "header-layout" {
config.Compose.HeaderLayout = parseLayout(val)
}
+
+ if key == "no-attachment-warning" && len(val) > 0 {
+ re, err := regexp.Compile("(?im)" + val)
+ if err != nil {
+ return fmt.Errorf(
+ "Invalid no-attachment-warning '%s': %w",
+ val, err,
+ )
+ }
+
+ config.Compose.NoAttachmentWarning = re
+ }
}
}
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 2c5f809d..21472e6e 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -493,6 +493,20 @@ These options are configured in the *[compose]* section of aerc.conf.
Default: true
+*no-attachment-warning*
+ Specifies a regular expression against which an email's body should be
+ tested before sending an email with no attachment. If the regexp
+ matches, aerc will warn you before sending the message. Leave empty to
+ disable this feature.
+
+ Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax.
+ The "(?im)" flags are set by default (case-insensitive and multi-line).
+
+ Example:
+ no-attachment-warning=^[^>]\*attach(ed|ment)
+
+ Default: none
+
## FILTERS
Filters allow you to pipe an email body through a shell command to render
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 35c32fe3..81d07474 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -598,6 +598,10 @@ func (aerc *Aerc) BeginExCommand(cmd string) {
aerc.focus(exline)
}
+func (aerc *Aerc) PushPrompt(prompt *ExLine) {
+ aerc.prompts.Push(prompt)
+}
+
func (aerc *Aerc) RegisterPrompt(prompt string, cmd []string) {
p := NewPrompt(aerc.conf, prompt, func(text string) {
if text != "" {
diff --git a/widgets/compose.go b/widgets/compose.go
index dc0c21aa..6e34365a 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -776,6 +776,26 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
}
}
+func (c *Composer) ShouldWarnAttachment() (bool, error) {
+ regex := c.config.Compose.NoAttachmentWarning
+
+ if regex == nil || len(c.attachments) > 0 {
+ return false, nil
+ }
+
+ err := c.reloadEmail()
+ if err != nil {
+ return false, errors.Wrap(err, "reloadEmail")
+ }
+
+ body, err := io.ReadAll(c.email)
+ if err != nil {
+ return false, errors.Wrap(err, "io.ReadAll")
+ }
+
+ return regex.Match(body), nil
+}
+
func writeMsgImpl(c *Composer, header *mail.Header, writer io.Writer) error {
if len(c.attachments) == 0 && len(c.textParts) == 0 {
// no attachments