From acf69b7490f4848066f2df4b3c2f675a8d57661a Mon Sep 17 00:00:00 2001 From: Reto Brunner Date: Thu, 23 Apr 2020 20:55:18 +0200 Subject: [PATCH] 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 --- commands/account/compose.go | 2 +- lib/templates/template.go | 9 +- widgets/compose.go | 191 ++++++++---------------------------- 3 files changed, 49 insertions(+), 153 deletions(-) diff --git a/commands/account/compose.go b/commands/account/compose.go index c214441..8115faa 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -46,7 +46,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error { } tab.Content.Invalidate() }) - go composer.PrependContents(strings.NewReader(body)) + go composer.AppendContents(strings.NewReader(body)) return nil } diff --git a/lib/templates/template.go b/lib/templates/template.go index 9df594e..e18328c 100644 --- a/lib/templates/template.go +++ b/lib/templates/template.go @@ -3,6 +3,7 @@ package templates import ( "bytes" "errors" + "io" "net/mail" "os" "os/exec" @@ -185,7 +186,7 @@ func findTemplate(templateName string, templateDirs []string) (string, error) { 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) if err != nil { return nil, err @@ -196,11 +197,11 @@ func ParseTemplateFromFile(templateName string, templateDirs []string, data inte return nil, err } - var outString bytes.Buffer - if err := emailTemplate.Execute(&outString, data); err != nil { + var body bytes.Buffer + if err := emailTemplate.Execute(&body, data); err != nil { return nil, err } - return outString.Bytes(), nil + return &body, nil } func ParseTemplate(templateText string, data interface{}) ([]byte, error) { diff --git a/widgets/compose.go b/widgets/compose.go index a97e5fe..9ceabf6 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "github.com/emersion/go-message" "github.com/emersion/go-message/mail" "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" @@ -99,10 +98,10 @@ func NewComposer(aerc *Aerc, conf *config.AercConfig, completer: cmpl, } - c.AddSignature() if err := c.AddTemplate(template, templateData); err != nil { return nil, err } + c.AddSignature() c.updateGrid() c.ShowTerminal() @@ -172,16 +171,6 @@ func (c *Composer) SetContents(reader io.Reader) *Composer { 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) { c.email.Seek(0, io.SeekEnd) io.Copy(c.email, reader) @@ -198,67 +187,29 @@ func (c *Composer) AddTemplate(template string, data interface{}) error { if err != nil { return err } - return c.addTemplate(templateText) -} -func (c *Composer) AddTemplateFromString(template string, data interface{}) error { - if template == "" { - return nil - } - - templateText, err := templates.ParseTemplate(template, data) + mr, err := mail.CreateReader(templateText) if err != nil { - return err + return fmt.Errorf("Template loading failed: %v", err) } - return c.addTemplate(templateText) -} -func (c *Composer) addTemplate(templateText []byte) error { - reader, err := mail.CreateReader(bytes.NewReader(templateText)) - if err != nil { - // encountering an error when reading the template probably - // 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) + // add the headers contained in the template to the default headers + 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 := reader.NextPart() + part, err := mr.NextPart() 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" + return fmt.Errorf("Could not get body of template: %v", err) } - // prepend header fields without editors to message body - c.PrependContents(bytes.NewReader([]byte(headers))) + c.AppendContents(part.Body) return nil } @@ -411,114 +362,58 @@ func (c *Composer) Worker() *types.Worker { } func (c *Composer) PrepareHeader() (*mail.Header, []string, error) { - // Extract headers from the email, if present - if err := c.reloadEmail(); err != nil { - return nil, nil, err + header := &mail.Header{} + for h, val := range c.defaults { + if val == "" { + continue + } + header.SetText(h, val) } - var ( - 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 - mhdr := (*message.Header)(&header.Header) - mhdr.SetText("Message-Id", c.msgId) + header.SetText("Message-Id", c.msgId) + header.SetDate(c.date) headerKeys := make([]string, 0, len(c.editors)) for key := range c.editors { 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 { - val := "" - editor, ok := c.editors[h] - if ok { - val = editor.input.String() - } else { - val, _ = mhdr.Text(h) + var rcpts []string + for h, editor := range c.editors { + val := editor.input.String() + if val == "" { + continue } 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 - if val != "" { - hdrRcpts, err := gomail.ParseAddressList(val) - if err != nil { - return nil, nil, errors.Wrapf(err, "ParseAddressList(%s)", val) - } - edRcpts := make([]*mail.Address, len(hdrRcpts)) - for i, addr := range hdrRcpts { - edRcpts[i] = (*mail.Address)(addr) - } - header.SetAddressList(h, edRcpts) - if h != "From" { - for _, addr := range edRcpts { - rcpts = append(rcpts, addr.Address) - } + hdrRcpts, err := gomail.ParseAddressList(val) + if err != nil { + return nil, nil, errors.Wrapf(err, "ParseAddressList(%s)", val) + } + edRcpts := make([]*mail.Address, len(hdrRcpts)) + for i, addr := range hdrRcpts { + edRcpts[i] = (*mail.Address)(addr) + } + header.SetAddressList(h, edRcpts) + if h != "From" { + for _, addr := range edRcpts { + rcpts = append(rcpts, addr.Address) } } default: - // Handle user configured header editors. - if ok && !mhdr.Header.Has(h) { - if val := editor.input.String(); val != "" { - mhdr.SetText(h, val) - } - } + header.SetText(h, val) } } - - // 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 + return header, rcpts, nil } func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error { if err := c.reloadEmail(); err != nil { 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 { // 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, @@ -529,7 +424,7 @@ func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error { } defer w.Close() - if err := writeMultipartBody(body, w); err != nil { + if err := writeMultipartBody(c.email, w); err != nil { return errors.Wrap(err, "writeMultipartBody") }