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:
parent
3fa9ae3edb
commit
acf69b7490
3 changed files with 49 additions and 153 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
|
for hf.Next() {
|
||||||
|
var val string
|
||||||
|
var err error
|
||||||
|
if val, err = hf.Text(); err != nil {
|
||||||
|
val = hf.Value()
|
||||||
|
}
|
||||||
|
c.defaults[hf.Key()] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
part, err := mr.NextPart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// encountering an error when reading the template probably
|
return fmt.Errorf("Could not get body of template: %v", err)
|
||||||
// means the template didn't evaluate to a properly formatted
|
|
||||||
// mail file.
|
|
||||||
// This is fine, we still want to support simple body templates
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
part, err := reader.NextPart()
|
c.AppendContents(part.Body)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "reader.NextPart")
|
|
||||||
}
|
|
||||||
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.PrependContents(bytes.NewReader([]byte(headers)))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,55 +362,29 @@ 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
|
||||||
}
|
}
|
||||||
var (
|
header.SetText(h, val)
|
||||||
rcpts []string
|
|
||||||
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
|
header.SetText("Message-Id", c.msgId)
|
||||||
mhdr := (*message.Header)(&header.Header)
|
header.SetDate(c.date)
|
||||||
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)
|
||||||
|
@ -474,51 +399,21 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) {
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue