c26d08103b
aerc.SelectedAccount() is used in lots of places. Most of them without checking the return value. In some cases, the currently selected tab is not related to any account (widget.Terminal for example). This can lead to unexpected crashes when accessing account specific configuration. When possible, return an error when no account is currently selected. If no error can be returned, fallback to non-account specific configuration. Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Koni Marti <koni.marti@gmail.com>
130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package msg
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/widgets"
|
|
"github.com/emersion/go-message/mail"
|
|
)
|
|
|
|
// Unsubscribe helps people unsubscribe from mailing lists by way of the
|
|
// List-Unsubscribe header.
|
|
type Unsubscribe struct{}
|
|
|
|
func init() {
|
|
register(Unsubscribe{})
|
|
}
|
|
|
|
// Aliases returns a list of aliases for the :unsubscribe command
|
|
func (Unsubscribe) Aliases() []string {
|
|
return []string{"unsubscribe"}
|
|
}
|
|
|
|
// Complete returns a list of completions
|
|
func (Unsubscribe) Complete(aerc *widgets.Aerc, args []string) []string {
|
|
return nil
|
|
}
|
|
|
|
// Execute runs the Unsubscribe command
|
|
func (Unsubscribe) Execute(aerc *widgets.Aerc, args []string) error {
|
|
if len(args) != 1 {
|
|
return errors.New("Usage: unsubscribe")
|
|
}
|
|
widget := aerc.SelectedTab().(widgets.ProvidesMessage)
|
|
msg, err := widget.SelectedMessage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
headers := msg.RFC822Headers
|
|
if !headers.Has("list-unsubscribe") {
|
|
return errors.New("No List-Unsubscribe header found")
|
|
}
|
|
text, err := headers.Text("list-unsubscribe")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
methods := parseUnsubscribeMethods(text)
|
|
aerc.Logger().Printf("found %d unsubscribe methods", len(methods))
|
|
for _, method := range methods {
|
|
aerc.Logger().Printf("trying to unsubscribe using %v", method)
|
|
switch method.Scheme {
|
|
case "mailto":
|
|
return unsubscribeMailto(aerc, method)
|
|
case "http", "https":
|
|
return unsubscribeHTTP(method)
|
|
default:
|
|
aerc.Logger().Printf("skipping unrecognized scheme: %s", method.Scheme)
|
|
}
|
|
}
|
|
return errors.New("no supported unsubscribe methods found")
|
|
}
|
|
|
|
// parseUnsubscribeMethods reads the list-unsubscribe header and parses it as a
|
|
// list of angle-bracket <> deliminated URLs. See RFC 2369.
|
|
func parseUnsubscribeMethods(header string) (methods []*url.URL) {
|
|
r := bufio.NewReader(strings.NewReader(header))
|
|
for {
|
|
// discard until <
|
|
_, err := r.ReadSlice('<')
|
|
if err != nil {
|
|
return
|
|
}
|
|
// read until <
|
|
m, err := r.ReadSlice('>')
|
|
if err != nil {
|
|
return
|
|
}
|
|
m = m[:len(m)-1]
|
|
if u, err := url.Parse(string(m)); err == nil {
|
|
methods = append(methods, u)
|
|
}
|
|
}
|
|
}
|
|
|
|
func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
|
|
widget := aerc.SelectedTab().(widgets.ProvidesMessage)
|
|
acct := widget.SelectedAccount()
|
|
if acct == nil {
|
|
return errors.New("No account selected")
|
|
}
|
|
|
|
h := &mail.Header{}
|
|
h.SetSubject(u.Query().Get("subject"))
|
|
if to, err := mail.ParseAddressList(u.Opaque); err == nil {
|
|
h.SetAddressList("to", to)
|
|
}
|
|
|
|
composer, err := widgets.NewComposer(
|
|
aerc,
|
|
acct,
|
|
aerc.Config(),
|
|
acct.AccountConfig(),
|
|
acct.Worker(),
|
|
"",
|
|
h,
|
|
models.OriginalMail{},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
composer.SetContents(strings.NewReader(u.Query().Get("body")))
|
|
tab := aerc.NewTab(composer, "unsubscribe")
|
|
composer.OnHeaderChange("Subject", func(subject string) {
|
|
if subject == "" {
|
|
tab.Name = "unsubscribe"
|
|
} else {
|
|
tab.Name = subject
|
|
}
|
|
tab.Content.Invalidate()
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func unsubscribeHTTP(u *url.URL) error {
|
|
return lib.NewXDGOpen(u.String()).Start()
|
|
}
|