summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/compose/attach-key.go3
-rw-r--r--lib/attachment.go136
-rw-r--r--widgets/compose.go232
3 files changed, 220 insertions, 151 deletions
diff --git a/commands/compose/attach-key.go b/commands/compose/attach-key.go
index c12df44..c9212e3 100644
--- a/commands/compose/attach-key.go
+++ b/commands/compose/attach-key.go
@@ -27,6 +27,5 @@ func (AttachKey) Execute(aerc *widgets.Aerc, args []string) error {
composer, _ := aerc.SelectedTab().(*widgets.Composer)
- composer.SetAttachKey(!composer.AttachKey())
- return nil
+ return composer.SetAttachKey(!composer.AttachKey())
}
diff --git a/lib/attachment.go b/lib/attachment.go
new file mode 100644
index 0000000..d7cbf3f
--- /dev/null
+++ b/lib/attachment.go
@@ -0,0 +1,136 @@
+package lib
+
+import (
+ "bufio"
+ "io"
+ "mime"
+ "net/http"
+ "os"
+ "path/filepath"
+
+ "github.com/emersion/go-message/mail"
+ "github.com/pkg/errors"
+)
+
+type Part struct {
+ MimeType string
+ Params map[string]string
+ Body io.Reader
+}
+
+func NewPart(mimetype string, params map[string]string, body io.Reader) *Part {
+ return &Part{
+ MimeType: mimetype,
+ Params: params,
+ Body: body,
+ }
+}
+
+type Attachment interface {
+ Name() string
+ WriteTo(w *mail.Writer) error
+}
+
+type FileAttachment struct {
+ path string
+}
+
+func NewFileAttachment(path string) *FileAttachment {
+ return &FileAttachment{
+ path,
+ }
+}
+
+func (fa *FileAttachment) Name() string {
+ return fa.path
+}
+
+func (fa *FileAttachment) WriteTo(w *mail.Writer) error {
+ f, err := os.Open(fa.path)
+ if err != nil {
+ return errors.Wrap(err, "os.Open")
+ }
+ defer f.Close()
+
+ reader := bufio.NewReader(f)
+
+ // if we have an extension, prefer that instead of trying to sniff the header.
+ // That's generally more accurate than sniffing as lots of things are zip files
+ // under the hood, e.g. most office file types
+ ext := filepath.Ext(fa.path)
+ var mimeString string
+ if mimeString = mime.TypeByExtension(ext); mimeString != "" {
+ // found it in the DB
+ } else {
+ // Sniff the mime type instead
+ // http.DetectContentType only cares about the first 512 bytes
+ head, err := reader.Peek(512)
+ if err != nil && err != io.EOF {
+ return errors.Wrap(err, "Peek")
+ }
+ mimeString = http.DetectContentType(head)
+ }
+
+ // mimeString can contain type and params (like text encoding),
+ // so we need to break them apart before passing them to the headers
+ mimeType, params, err := mime.ParseMediaType(mimeString)
+ if err != nil {
+ return errors.Wrap(err, "ParseMediaType")
+ }
+ filename := filepath.Base(fa.path)
+ params["name"] = filename
+
+ // set header fields
+ ah := mail.AttachmentHeader{}
+ ah.SetContentType(mimeType, params)
+ // setting the filename auto sets the content disposition
+ ah.SetFilename(filename)
+
+ aw, err := w.CreateAttachment(ah)
+ if err != nil {
+ return errors.Wrap(err, "CreateAttachment")
+ }
+ defer aw.Close()
+
+ if _, err := reader.WriteTo(aw); err != nil {
+ return errors.Wrap(err, "reader.WriteTo")
+ }
+
+ return nil
+}
+
+type PartAttachment struct {
+ part *Part
+ name string
+}
+
+func NewPartAttachment(part *Part, name string) *PartAttachment {
+ return &PartAttachment{
+ part,
+ name,
+ }
+}
+
+func (pa *PartAttachment) Name() string {
+ return pa.name
+}
+
+func (pa *PartAttachment) WriteTo(w *mail.Writer) error {
+ // set header fields
+ ah := mail.AttachmentHeader{}
+ ah.SetContentType(pa.part.MimeType, pa.part.Params)
+
+ // setting the filename auto sets the content disposition
+ ah.SetFilename(pa.Name())
+
+ aw, err := w.CreateAttachment(ah)
+ if err != nil {
+ return errors.Wrap(err, "CreateAttachment")
+ }
+ defer aw.Close()
+
+ if _, err := io.Copy(aw, pa.part.Body); err != nil {
+ return errors.Wrap(err, "io.Copy")
+ }
+ return nil
+}
diff --git a/widgets/compose.go b/widgets/compose.go
index 2016abb..cbd4266 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -1,17 +1,13 @@
package widgets
import (
- "bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
- "mime"
- "net/http"
"net/textproto"
"os"
"os/exec"
- "path/filepath"
"strings"
"time"
@@ -23,6 +19,7 @@ import (
"git.sr.ht/~rjarry/aerc/completer"
"git.sr.ht/~rjarry/aerc/config"
+ "git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/format"
"git.sr.ht/~rjarry/aerc/lib/templates"
"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -30,12 +27,6 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type Part struct {
- MimeType string
- Params map[string]string
- Body io.Reader
-}
-
type Composer struct {
editors map[string]*headerEditor // indexes in lower case (from / cc / bcc)
header *mail.Header
@@ -46,7 +37,7 @@ type Composer struct {
acct *AccountView
aerc *Aerc
- attachments []string
+ attachments []lib.Attachment
editor *Terminal
email *os.File
grid *ui.Grid
@@ -68,7 +59,7 @@ type Composer struct {
width int
- textParts []*Part
+ textParts []*lib.Part
}
func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
@@ -193,7 +184,58 @@ func (c *Composer) Sent() bool {
}
func (c *Composer) SetAttachKey(attach bool) error {
+ if !attach {
+ name := c.crypto.signKey + ".asc"
+ found := false
+ for _, a := range c.attachments {
+ if a.Name() == name {
+ found = true
+ }
+ }
+ if found {
+ c.DeleteAttachment(name)
+ } else {
+ attach = !attach
+ }
+ }
+ if attach {
+ var s string
+ var err error
+ if c.crypto.signKey == "" {
+ if c.acctConfig.PgpKeyId != "" {
+ s = c.acctConfig.PgpKeyId
+ } else {
+ s, err = getSenderEmail(c)
+ if err != nil {
+ return err
+ }
+ }
+ c.crypto.signKey, err = c.aerc.Crypto.GetSignerKeyId(s)
+ if err != nil {
+ return err
+ }
+ }
+
+ r, err := c.aerc.Crypto.ExportKey(c.crypto.signKey)
+ if err != nil {
+ return err
+ }
+
+ c.attachments = append(c.attachments,
+ lib.NewPartAttachment(
+ lib.NewPart(
+ "application/pgp-keys",
+ map[string]string{"charset": "UTF-8"},
+ r,
+ ),
+ c.crypto.signKey+".asc",
+ ),
+ )
+
+ }
+
c.attachKey = attach
+
c.resetReview()
return nil
}
@@ -309,7 +351,8 @@ func (c *Composer) AppendPart(mimetype string, params map[string]string, body io
if !strings.HasPrefix(mimetype, "text") {
return fmt.Errorf("can only append text mimetypes")
}
- c.textParts = append(c.textParts, &Part{MimeType: mimetype, Params: params, Body: body})
+ c.textParts = append(c.textParts, lib.NewPart(mimetype, params, body))
+ c.resetReview()
return nil
}
@@ -611,36 +654,30 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
}
func writeMsgImpl(c *Composer, header *mail.Header, writer io.Writer) error {
- if len(c.attachments) == 0 && !c.attachKey && len(c.textParts) == 0 {
- // no attachements
+ if len(c.attachments) == 0 && len(c.textParts) == 0 {
+ // no attachments
return writeInlineBody(header, c.email, writer)
} else {
- // with attachements
+ // with attachments
w, err := mail.CreateWriter(writer, *header)
if err != nil {
return errors.Wrap(err, "CreateWriter")
}
- parts := []*Part{
- &Part{
- MimeType: "text/plain",
- Params: map[string]string{"Charset": "UTF-8"},
- Body: c.email,
- },
+ parts := []*lib.Part{
+ lib.NewPart(
+ "text/plain",
+ map[string]string{"Charset": "UTF-8"},
+ c.email,
+ ),
}
if err := writeMultipartBody(append(parts, c.textParts...), w); err != nil {
return errors.Wrap(err, "writeMultipartBody")
}
for _, a := range c.attachments {
- if err := writeAttachment(a, w); err != nil {
+ if err := a.WriteTo(w); err != nil {
return errors.Wrap(err, "writeAttachment")
}
}
- if c.attachKey {
- err := c.writeKeyAttachment(w)
- if err != nil {
- return err
- }
- }
w.Close()
}
return nil
@@ -660,7 +697,7 @@ func writeInlineBody(header *mail.Header, body io.Reader, writer io.Writer) erro
}
// write the message body to the multipart message
-func writeMultipartBody(parts []*Part, w *mail.Writer) error {
+func writeMultipartBody(parts []*lib.Part, w *mail.Writer) error {
bi, err := w.CreateInline()
if err != nil {
return errors.Wrap(err, "CreateInline")
@@ -683,73 +720,29 @@ func writeMultipartBody(parts []*Part, w *mail.Writer) error {
return nil
}
-// write the attachment specified by path to the message
-func writeAttachment(path string, writer *mail.Writer) error {
- f, err := os.Open(path)
- if err != nil {
- return errors.Wrap(err, "os.Open")
- }
- defer f.Close()
-
- reader := bufio.NewReader(f)
-
- // if we have an extension, prefer that instead of trying to sniff the header.
- // That's generally more accurate than sniffing as lots of things are zip files
- // under the hood, e.g. most office file types
- ext := filepath.Ext(path)
- var mimeString string
- if mimeString = mime.TypeByExtension(ext); mimeString != "" {
- // found it in the DB
- } else {
- // Sniff the mime type instead
- // http.DetectContentType only cares about the first 512 bytes
- head, err := reader.Peek(512)
- if err != nil && err != io.EOF {
- return errors.Wrap(err, "Peek")
- }
- mimeString = http.DetectContentType(head)
- }
-
- // mimeString can contain type and params (like text encoding),
- // so we need to break them apart before passing them to the headers
- mimeType, params, err := mime.ParseMediaType(mimeString)
- if err != nil {
- return errors.Wrap(err, "ParseMediaType")
- }
- filename := filepath.Base(path)
- params["name"] = filename
-
- // set header fields
- ah := mail.AttachmentHeader{}
- ah.SetContentType(mimeType, params)
- // setting the filename auto sets the content disposition
- ah.SetFilename(filename)
-
- aw, err := writer.CreateAttachment(ah)
- if err != nil {
- return errors.Wrap(err, "CreateAttachment")
- }
- defer aw.Close()
-
- if _, err := reader.WriteTo(aw); err != nil {
- return errors.Wrap(err, "reader.WriteTo")
+func (c *Composer) GetAttachments() []string {
+ var names []string
+ for _, a := range c.attachments {
+ names = append(names, a.Name())
}
-
- return nil
+ return names
}
-func (c *Composer) GetAttachments() []string {
- return c.attachments
+func (c *Composer) AddAttachment(path string) {
+ c.attachments = append(c.attachments, lib.NewFileAttachment(path))
+ c.resetReview()
}
-func (c *Composer) AddAttachment(path string) {
- c.attachments = append(c.attachments, path)
+func (c *Composer) AddPartAttachment(name string, mimetype string, params map[string]string, body io.Reader) {
+ c.attachments = append(c.attachments, lib.NewPartAttachment(
+ lib.NewPart(mimetype, params, body), name,
+ ))
c.resetReview()
}
-func (c *Composer) DeleteAttachment(path string) error {
+func (c *Composer) DeleteAttachment(name string) error {
for i, a := range c.attachments {
- if a == path {
+ if a.Name() == name {
c.attachments = append(c.attachments[:i], c.attachments[i+1:]...)
c.resetReview()
return nil
@@ -1109,9 +1102,6 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage {
for i := 0; i < len(composer.attachments)-1; i++ {
spec = append(spec, ui.GridSpec{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)})
}
- if composer.attachKey {
- spec = append(spec, ui.GridSpec{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)})
- }
if len(composer.textParts) > 0 {
spec = append(spec, ui.GridSpec{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)})
spec = append(spec, ui.GridSpec{Strategy: ui.SIZE_EXACT, Size: ui.Const(1)})
@@ -1144,18 +1134,13 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage {
grid.AddChild(ui.NewText("Attachments:",
uiConfig.GetStyle(config.STYLE_TITLE))).At(i, 0)
i += 1
- if composer.attachKey {
- grid.AddChild(ui.NewText(composer.crypto.signKey+".asc",
- uiConfig.GetStyle(config.STYLE_DEFAULT))).At(i, 0)
- i += 1
- }
- if len(composer.attachments) == 0 && !composer.attachKey {
+ if len(composer.attachments) == 0 {
grid.AddChild(ui.NewText("(none)",
uiConfig.GetStyle(config.STYLE_DEFAULT))).At(i, 0)
i += 1
} else {
for _, a := range composer.attachments {
- grid.AddChild(ui.NewText(a, uiConfig.GetStyle(config.STYLE_DEFAULT))).
+ grid.AddChild(ui.NewText(a.Name(), uiConfig.GetStyle(config.STYLE_DEFAULT))).
At(i, 0)
i += 1
}
@@ -1262,54 +1247,3 @@ func (c *Composer) checkEncryptionKeys(_ string) bool {
c.updateCrypto()
return true
}
-
-func (c *Composer) writeKeyAttachment(w *mail.Writer) error {
- // Verify key exists and get keyid
- cp := c.aerc.Crypto
- var (
- err error
- s string
- )
- if c.crypto.signKey == "" {
- if c.acctConfig.PgpKeyId != "" {
- s = c.acctConfig.PgpKeyId
- } else {
- s, err = getSenderEmail(c)
- if err != nil {
- return err
- }
- }
- c.crypto.signKey, err = cp.GetSignerKeyId(s)
- if err != nil {
- return err
- }
- }
- // Get the key in armor format
- r, err := cp.ExportKey(c.crypto.signKey)
- if err != nil {
- c.aerc.PushError(err.Error())
- return err
- }
- filename := c.crypto.signKey + ".asc"
- mimeType := "application/pgp-keys"
- params := map[string]string{
- "charset": "UTF-8",
- "name": filename,
- }
- // set header fields
- ah := mail.AttachmentHeader{}
- ah.SetContentType(mimeType, params)
- // setting the filename auto sets the content disposition
- ah.SetFilename(filename)
-
- aw, err := w.CreateAttachment(ah)
- if err != nil {
- return errors.Wrap(err, "CreateKeyAttachment")
- }
- defer aw.Close()
-
- if _, err := io.Copy(aw, r); err != nil {
- return errors.Wrap(err, "io.Copy")
- }
- return nil
-}