summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2022-06-28 23:42:08 +0200
committerRobin Jarry <robin@jarry.cc>2022-07-02 17:53:06 +0200
commit60052c607011ab09fe204cf5adc0cc9e29b34cdd (patch)
treeda5bfd63f8aa074e4ab56353ddf32daef1c6a495
parent9d90b70b4edfd2af81ac686e16f8bfd9b6ebfa9c (diff)
downloadaerc-60052c607011ab09fe204cf5adc0cc9e29b34cdd.zip
forward: provide option to append all attachments
Append all non-multipart attachments with the -A flag. Rename the flag for forwarding a full message as an RFC2822 attachments to -F. Suggested-by: psykose Signed-off-by: Koni Marti <koni.marti@gmail.com> Tested-by: Tim Culverhouse <tim@timculverhouse.com>
-rw-r--r--commands/msg/forward.go67
-rw-r--r--doc/aerc.1.scd8
-rw-r--r--lib/structure_helpers.go27
-rw-r--r--lib/structure_helpers_test.go46
4 files changed, 131 insertions, 17 deletions
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index 9b234ef..bc5953f 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -6,9 +6,11 @@ import (
"fmt"
"io"
"io/ioutil"
+ "math/rand"
"os"
"path"
"strings"
+ "sync"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/format"
@@ -35,29 +37,28 @@ func (forward) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (forward) Execute(aerc *widgets.Aerc, args []string) error {
- opts, optind, err := getopt.Getopts(args, "AT:")
+ opts, optind, err := getopt.Getopts(args, "AFT:")
if err != nil {
return err
}
- attach := false
+ attachAll := false
+ attachFull := false
template := ""
- var tolist []*mail.Address
for _, opt := range opts {
switch opt.Option {
case 'A':
- attach = true
- to := strings.Join(args[optind:], ", ")
- if strings.Contains(to, "@") {
- tolist, err = mail.ParseAddressList(to)
- if err != nil {
- return fmt.Errorf("invalid to address(es): %v", err)
- }
- }
+ attachAll = true
+ case 'F':
+ attachFull = true
case 'T':
template = opt.Value
}
}
+ if attachAll && attachFull {
+ return errors.New("Options -A and -F are mutually exclusive")
+ }
+
widget := aerc.SelectedTab().(widgets.ProvidesMessage)
acct := widget.SelectedAccount()
if acct == nil {
@@ -77,6 +78,14 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
subject := "Fwd: " + msg.Envelope.Subject
h.SetSubject(subject)
+ var tolist []*mail.Address
+ to := strings.Join(args[optind:], ", ")
+ if strings.Contains(to, "@") {
+ tolist, err = mail.ParseAddressList(to)
+ if err != nil {
+ return fmt.Errorf("invalid to address(es): %v", err)
+ }
+ }
if len(tolist) > 0 {
h.SetAddressList("to", tolist)
}
@@ -112,7 +121,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
return composer, nil
}
- if attach {
+ if attachFull {
tmpDir, err := ioutil.TempDir("", "aerc-tmp-attachment")
if err != nil {
return err
@@ -144,7 +153,6 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
template = aerc.Config().Templates.Forwards
}
- // TODO: add attachments!
part := lib.FindPlaintext(msg.BodyStructure, nil)
if part == nil {
part = lib.FindFirstNonMultipart(msg.BodyStructure, nil)
@@ -158,7 +166,38 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
original.Text = buf.String()
- addTab()
+
+ // create composer
+ composer, err := addTab()
+ if err != nil {
+ return
+ }
+
+ // add attachments
+ if attachAll {
+ var mu sync.Mutex
+ parts := lib.FindAllNonMultipart(msg.BodyStructure, nil, nil)
+ for _, p := range parts {
+ if lib.EqualParts(p, part) {
+ continue
+ }
+ bs, err := msg.BodyStructure.PartAtIndex(p)
+ if err != nil {
+ acct.Logger().Println("forward: PartAtIndex:", err)
+ continue
+ }
+ store.FetchBodyPart(msg.Uid, p, func(reader io.Reader) {
+ mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
+ name, ok := bs.Params["name"]
+ if !ok {
+ name = fmt.Sprintf("%s_%s_%d", bs.MIMEType, bs.MIMESubType, rand.Uint64())
+ }
+ mu.Lock()
+ composer.AddPartAttachment(name, mime, bs.Params, reader)
+ mu.Unlock()
+ })
+ }
+ }
})
}
return nil
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 2c4fd31..f1a6a5b 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -135,14 +135,16 @@ message list, the message in the message viewer, etc).
directory. The original message will be deleted only if it is in the
postpone directory.
-*forward* [-A] [-T <template-file>] [address...]
+*forward* [-A | -F] [-T <template-file>] [address...]
Opens the composer to forward the selected message to another recipient.
- *-A*: Forward the message as an RFC 2822 attachment.
+ *-A*: Forward the message and all attachments.
+
+ *-F*: Forward the full message as an RFC 2822 attachment.
*-T* <template-file>
Use the specified template file for creating the initial
- message body. Unless *-A* is specified, this defaults to what
+ message body. Unless *-F* is specified, this defaults to what
is set as _forwards_ in the _[templates]_ section of
_aerc.conf_.
diff --git a/lib/structure_helpers.go b/lib/structure_helpers.go
index ac6950a..1b4a6e6 100644
--- a/lib/structure_helpers.go
+++ b/lib/structure_helpers.go
@@ -52,3 +52,30 @@ func FindFirstNonMultipart(bs *models.BodyStructure, path []int) []int {
}
return nil
}
+
+func FindAllNonMultipart(bs *models.BodyStructure, path []int, pathlist [][]int) [][]int {
+ for i, part := range bs.Parts {
+ cur := append(path, i+1)
+ mimetype := strings.ToLower(part.MIMEType)
+ if mimetype != "multipart" {
+ pathlist = append(pathlist, cur)
+ } else if mimetype == "multipart" {
+ if sub := FindAllNonMultipart(part, cur, nil); len(sub) > 0 {
+ pathlist = append(pathlist, sub...)
+ }
+ }
+ }
+ return pathlist
+}
+
+func EqualParts(a []int, b []int) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := 0; i < len(a); i++ {
+ if a[i] != b[i] {
+ return false
+ }
+ }
+ return true
+}
diff --git a/lib/structure_helpers_test.go b/lib/structure_helpers_test.go
new file mode 100644
index 0000000..f670735
--- /dev/null
+++ b/lib/structure_helpers_test.go
@@ -0,0 +1,46 @@
+package lib_test
+
+import (
+ "testing"
+
+ "git.sr.ht/~rjarry/aerc/lib"
+ "git.sr.ht/~rjarry/aerc/models"
+)
+
+func TestLib_FindAllNonMultipart(t *testing.T) {
+
+ testStructure := &models.BodyStructure{
+ MIMEType: "multipart",
+ Parts: []*models.BodyStructure{
+ &models.BodyStructure{},
+ &models.BodyStructure{
+ MIMEType: "multipart",
+ Parts: []*models.BodyStructure{
+ &models.BodyStructure{},
+ &models.BodyStructure{},
+ },
+ },
+ &models.BodyStructure{},
+ },
+ }
+
+ expected := [][]int{
+ []int{1},
+ []int{2, 1},
+ []int{2, 2},
+ []int{3},
+ }
+
+ parts := lib.FindAllNonMultipart(testStructure, nil, nil)
+
+ if len(expected) != len(parts) {
+ t.Errorf("incorrect dimensions; expected: %v, got: %v", expected, parts)
+ }
+
+ for i := 0; i < len(parts); i++ {
+ if !lib.EqualParts(expected[i], parts[i]) {
+ t.Errorf("incorrect values; expected: %v, got: %v", expected[i], parts[i])
+ }
+ }
+
+}