Remove ability to specify headers in the editor

Due to headers being essentially free text, we constantly run into issues
with parts of the body being interpreted as headers.

Remove the ability to overwrite headers to avoid that, while keeping the ability
to specify headers in the template files.

Fixes #383
This commit is contained in:
Reto Brunner 2020-04-23 20:55:18 +02:00 committed by Drew DeVault
parent 3fa9ae3edb
commit acf69b7490
3 changed files with 49 additions and 153 deletions

View File

@ -46,7 +46,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
} }
tab.Content.Invalidate() tab.Content.Invalidate()
}) })
go composer.PrependContents(strings.NewReader(body)) go composer.AppendContents(strings.NewReader(body))
return nil return nil
} }

View File

@ -3,6 +3,7 @@ package templates
import ( import (
"bytes" "bytes"
"errors" "errors"
"io"
"net/mail" "net/mail"
"os" "os"
"os/exec" "os/exec"
@ -185,7 +186,7 @@ func findTemplate(templateName string, templateDirs []string) (string, error) {
return "", errors.New("Can't find template - " + templateName) return "", errors.New("Can't find template - " + templateName)
} }
func ParseTemplateFromFile(templateName string, templateDirs []string, data interface{}) ([]byte, error) { func ParseTemplateFromFile(templateName string, templateDirs []string, data interface{}) (io.Reader, error) {
templateFile, err := findTemplate(templateName, templateDirs) templateFile, err := findTemplate(templateName, templateDirs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -196,11 +197,11 @@ func ParseTemplateFromFile(templateName string, templateDirs []string, data inte
return nil, err return nil, err
} }
var outString bytes.Buffer var body bytes.Buffer
if err := emailTemplate.Execute(&outString, data); err != nil { if err := emailTemplate.Execute(&body, data); err != nil {
return nil, err return nil, err
} }
return outString.Bytes(), nil return &body, nil
} }
func ParseTemplate(templateText string, data interface{}) ([]byte, error) { func ParseTemplate(templateText string, data interface{}) ([]byte, error) {

View File

@ -15,7 +15,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
@ -99,10 +98,10 @@ func NewComposer(aerc *Aerc, conf *config.AercConfig,
completer: cmpl, completer: cmpl,
} }
c.AddSignature()
if err := c.AddTemplate(template, templateData); err != nil { if err := c.AddTemplate(template, templateData); err != nil {
return nil, err return nil, err
} }
c.AddSignature()
c.updateGrid() c.updateGrid()
c.ShowTerminal() c.ShowTerminal()
@ -172,16 +171,6 @@ func (c *Composer) SetContents(reader io.Reader) *Composer {
return c return c
} }
func (c *Composer) PrependContents(reader io.Reader) {
buf := bytes.NewBuffer(nil)
c.email.Seek(0, io.SeekStart)
io.Copy(buf, c.email)
c.email.Seek(0, io.SeekStart)
io.Copy(c.email, reader)
io.Copy(c.email, buf)
c.email.Sync()
}
func (c *Composer) AppendContents(reader io.Reader) { func (c *Composer) AppendContents(reader io.Reader) {
c.email.Seek(0, io.SeekEnd) c.email.Seek(0, io.SeekEnd)
io.Copy(c.email, reader) io.Copy(c.email, reader)
@ -198,67 +187,29 @@ func (c *Composer) AddTemplate(template string, data interface{}) error {
if err != nil { if err != nil {
return err return err
} }
return c.addTemplate(templateText)
}
func (c *Composer) AddTemplateFromString(template string, data interface{}) error { mr, err := mail.CreateReader(templateText)
if template == "" {
return nil
}
templateText, err := templates.ParseTemplate(template, data)
if err != nil { if err != nil {
return err return fmt.Errorf("Template loading failed: %v", err)
} }
return c.addTemplate(templateText)
}
func (c *Composer) addTemplate(templateText []byte) error { // add the headers contained in the template to the default headers
reader, err := mail.CreateReader(bytes.NewReader(templateText)) hf := mr.Header.Fields()
if err != nil { for hf.Next() {
// encountering an error when reading the template probably var val string
// means the template didn't evaluate to a properly formatted var err error
// mail file. if val, err = hf.Text(); err != nil {
// This is fine, we still want to support simple body templates val = hf.Value()
// that don't include headers.
//
// Just prepend the rendered template in that case. This
// basically equals the previous behavior.
c.PrependContents(bytes.NewReader(templateText))
return nil
}
defer reader.Close()
// populate header editors
header := reader.Header
mhdr := (*message.Header)(&header.Header)
for _, editor := range c.editors {
if mhdr.Has(editor.name) {
editor.input.Set(mhdr.Get(editor.name))
// remove header fields that have editors
mhdr.Del(editor.name)
} }
c.defaults[hf.Key()] = val
} }
part, err := reader.NextPart() part, err := mr.NextPart()
if err != nil { if err != nil {
return errors.Wrap(err, "reader.NextPart") return fmt.Errorf("Could not get body of template: %v", err)
}
c.PrependContents(part.Body)
var (
headers string
fds = mhdr.Fields()
)
for fds.Next() {
headers += fmt.Sprintf("%s: %s\n", fds.Key(), fds.Value())
}
if headers != "" {
headers += "\n"
} }
// prepend header fields without editors to message body c.AppendContents(part.Body)
c.PrependContents(bytes.NewReader([]byte(headers)))
return nil return nil
} }
@ -411,114 +362,58 @@ func (c *Composer) Worker() *types.Worker {
} }
func (c *Composer) PrepareHeader() (*mail.Header, []string, error) { func (c *Composer) PrepareHeader() (*mail.Header, []string, error) {
// Extract headers from the email, if present header := &mail.Header{}
if err := c.reloadEmail(); err != nil { for h, val := range c.defaults {
return nil, nil, err if val == "" {
continue
}
header.SetText(h, val)
} }
var ( header.SetText("Message-Id", c.msgId)
rcpts []string header.SetDate(c.date)
header mail.Header
)
reader, err := mail.CreateReader(c.email)
if err == nil {
header = reader.Header
defer reader.Close()
} else {
c.email.Seek(0, io.SeekStart)
}
// Update headers
mhdr := (*message.Header)(&header.Header)
mhdr.SetText("Message-Id", c.msgId)
headerKeys := make([]string, 0, len(c.editors)) headerKeys := make([]string, 0, len(c.editors))
for key := range c.editors { for key := range c.editors {
headerKeys = append(headerKeys, key) headerKeys = append(headerKeys, key)
} }
// Ensure headers which require special processing are included.
for _, key := range []string{"To", "From", "Cc", "Bcc", "Subject", "Date"} {
if _, ok := c.editors[key]; !ok {
headerKeys = append(headerKeys, key)
}
}
for _, h := range headerKeys { var rcpts []string
val := "" for h, editor := range c.editors {
editor, ok := c.editors[h] val := editor.input.String()
if ok { if val == "" {
val = editor.input.String() continue
} else {
val, _ = mhdr.Text(h)
} }
switch h { switch h {
case "Subject":
if subject, _ := header.Subject(); subject == "" {
header.SetSubject(val)
}
case "Date":
if date, err := header.Date(); err != nil || date == (time.Time{}) {
header.SetDate(c.date)
}
case "From", "To", "Cc", "Bcc": // Address headers case "From", "To", "Cc", "Bcc": // Address headers
if val != "" { hdrRcpts, err := gomail.ParseAddressList(val)
hdrRcpts, err := gomail.ParseAddressList(val) if err != nil {
if err != nil { return nil, nil, errors.Wrapf(err, "ParseAddressList(%s)", val)
return nil, nil, errors.Wrapf(err, "ParseAddressList(%s)", val) }
} edRcpts := make([]*mail.Address, len(hdrRcpts))
edRcpts := make([]*mail.Address, len(hdrRcpts)) for i, addr := range hdrRcpts {
for i, addr := range hdrRcpts { edRcpts[i] = (*mail.Address)(addr)
edRcpts[i] = (*mail.Address)(addr) }
} header.SetAddressList(h, edRcpts)
header.SetAddressList(h, edRcpts) if h != "From" {
if h != "From" { for _, addr := range edRcpts {
for _, addr := range edRcpts { rcpts = append(rcpts, addr.Address)
rcpts = append(rcpts, addr.Address)
}
} }
} }
default: default:
// Handle user configured header editors. header.SetText(h, val)
if ok && !mhdr.Header.Has(h) {
if val := editor.input.String(); val != "" {
mhdr.SetText(h, val)
}
}
} }
} }
return header, rcpts, nil
// Merge in additional headers
txthdr := mhdr.Header
for key, value := range c.defaults {
if !txthdr.Has(key) && value != "" {
mhdr.SetText(key, value)
}
}
return &header, rcpts, nil
} }
func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error { func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
if err := c.reloadEmail(); err != nil { if err := c.reloadEmail(); err != nil {
return err return err
} }
var body io.Reader
reader, err := mail.CreateReader(c.email)
if err == nil {
// TODO: Do we want to let users write a full blown multipart email
// into the editor? If so this needs to change
part, err := reader.NextPart()
if err != nil {
return errors.Wrap(err, "reader.NextPart")
}
body = part.Body
defer reader.Close()
} else {
c.email.Seek(0, io.SeekStart)
body = c.email
}
if len(c.attachments) == 0 { if len(c.attachments) == 0 {
// don't create a multipart email if we only have text // don't create a multipart email if we only have text
return writeInlineBody(header, body, writer) return writeInlineBody(header, c.email, writer)
} }
// otherwise create a multipart email, // otherwise create a multipart email,
@ -529,7 +424,7 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
} }
defer w.Close() defer w.Close()
if err := writeMultipartBody(body, w); err != nil { if err := writeMultipartBody(c.email, w); err != nil {
return errors.Wrap(err, "writeMultipartBody") return errors.Wrap(err, "writeMultipartBody")
} }