diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 3ff0194..0c6b0e0 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -11,6 +11,7 @@ import ( "strings" "git.sr.ht/~sircmpwn/aerc/lib" + "git.sr.ht/~sircmpwn/aerc/lib/format" "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/widgets" "git.sr.ht/~sircmpwn/aerc/worker/types" @@ -77,7 +78,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error { addTab := func() (*widgets.Composer, error) { if template != "" { - original.From = models.FormatAddresses(msg.Envelope.From) + original.From = format.FormatAddresses(msg.Envelope.From) original.Date = msg.Envelope.Date } diff --git a/lib/format/format.go b/lib/format/format.go index cc46da8..06b4d3f 100644 --- a/lib/format/format.go +++ b/lib/format/format.go @@ -2,22 +2,46 @@ package format import ( "errors" - "fmt" + "mime" gomail "net/mail" "strings" "time" "unicode" "git.sr.ht/~sircmpwn/aerc/models" + "github.com/emersion/go-message" ) -func parseAddress(address string) *gomail.Address { +func ParseAddress(address string) (*models.Address, error) { addrs, err := gomail.ParseAddress(address) if err != nil { - return nil + return nil, err + } + return (*models.Address)(addrs), nil +} + +func ParseAddressList(s string) ([]*models.Address, error) { + parser := gomail.AddressParser{ + &mime.WordDecoder{message.CharsetReader}, + } + list, err := parser.ParseList(s) + if err != nil { + return nil, err } - return addrs + addrs := make([]*models.Address, len(list)) + for i, a := range list { + addrs[i] = (*models.Address)(a) + } + return addrs, nil +} + +func FormatAddresses(l []*models.Address) string { + formatted := make([]string, len(l)) + for i, a := range l { + formatted[i] = a.Format() + } + return strings.Join(formatted, ", ") } func ParseMessageFormat( @@ -29,7 +53,10 @@ func ParseMessageFormat( retval := make([]byte, 0, len(format)) var args []interface{} - accountFromAddress := parseAddress(fromAddress) + accountFromAddress, err := ParseAddress(fromAddress) + if err != nil { + return "", nil, err + } var c rune for i, ni := 0, 0; i < len(format); { @@ -87,8 +114,7 @@ func ParseMessageFormat( } addr := msg.Envelope.From[0] retval = append(retval, 's') - args = append(args, - fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)) + args = append(args, addr.Address) case 'A': if msg.Envelope == nil { return "", nil, @@ -106,8 +132,7 @@ func ParseMessageFormat( addr = msg.Envelope.ReplyTo[0] } retval = append(retval, 's') - args = append(args, - fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)) + args = append(args, addr.Address) case 'C': retval = append(retval, 'd') args = append(args, number) @@ -158,7 +183,7 @@ func ParseMessageFormat( if addr.Name != "" { val = addr.Name } else { - val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host) + val = addr.Address } retval = append(retval, 's') args = append(args, val) @@ -188,7 +213,7 @@ func ParseMessageFormat( if addr.Name != "" { val = addr.Name } else { - val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host) + val = addr.Address } retval = append(retval, 's') args = append(args, val) @@ -197,7 +222,7 @@ func ParseMessageFormat( return "", nil, errors.New("no envelope available for this message") } - addrs := models.FormatAddresses(msg.Envelope.To) + addrs := FormatAddresses(msg.Envelope.To) retval = append(retval, 's') args = append(args, addrs) case 'R': @@ -205,7 +230,7 @@ func ParseMessageFormat( return "", nil, errors.New("no envelope available for this message") } - addrs := models.FormatAddresses(msg.Envelope.Cc) + addrs := FormatAddresses(msg.Envelope.Cc) retval = append(retval, 's') args = append(args, addrs) case 's': @@ -226,8 +251,7 @@ func ParseMessageFormat( } addr := msg.Envelope.To[0] retval = append(retval, 's') - args = append(args, - fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)) + args = append(args, addr.Address) case 'T': retval = append(retval, 's') args = append(args, accountName) @@ -241,8 +265,12 @@ func ParseMessageFormat( errors.New("found no address for sender") } addr := msg.Envelope.From[0] + mailbox := addr.Address // fallback if there's no @ sign + if split := strings.SplitN(addr.Address, "@", 2); len(split) == 2 { + mailbox = split[1] + } retval = append(retval, 's') - args = append(args, addr.Mailbox) + args = append(args, mailbox) case 'v': if msg.Envelope == nil { return "", nil, diff --git a/models/models.go b/models/models.go index d61b774..b7d7e05 100644 --- a/models/models.go +++ b/models/models.go @@ -1,9 +1,9 @@ package models import ( - "bytes" "fmt" "io" + gomail "net/mail" "regexp" "strings" "time" @@ -134,40 +134,26 @@ type Envelope struct { MessageId string } -type Address struct { - Name string - Mailbox string - Host string -} +type Address gomail.Address var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$") -func (a Address) Format() string { +// String formats the address. If the address's name +// contains non-ASCII characters it will be quoted but not encoded. +// Meant for display purposes to the humans, not for sending over the wire. +func (a *Address) Format() string { if a.Name != "" { if atom.MatchString(a.Name) { - return fmt.Sprintf("%s <%s@%s>", a.Name, a.Mailbox, a.Host) + return fmt.Sprintf("%s <%s>", a.Name, a.Address) } else { - return fmt.Sprintf("\"%s\" <%s@%s>", - strings.ReplaceAll(a.Name, "\"", "'"), - a.Mailbox, a.Host) + return fmt.Sprintf("\"%s\" <%s>", + strings.ReplaceAll(a.Name, "\"", "'"), a.Address) } } else { - return fmt.Sprintf("<%s@%s>", a.Mailbox, a.Host) + return fmt.Sprintf("<%s>", a.Address) } } -// FormatAddresses formats a list of addresses, separating each by a comma -func FormatAddresses(addrs []*Address) string { - val := bytes.Buffer{} - for i, addr := range addrs { - val.WriteString(addr.Format()) - if i != len(addrs)-1 { - val.WriteString(", ") - } - } - return val.String() -} - // OriginalMail is helper struct used for reply/forward type OriginalMail struct { Date time.Time diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index 6a91741..587959b 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -17,6 +17,7 @@ import ( "git.sr.ht/~sircmpwn/aerc/config" "git.sr.ht/~sircmpwn/aerc/lib" + "git.sr.ht/~sircmpwn/aerc/lib/format" "git.sr.ht/~sircmpwn/aerc/lib/ui" "git.sr.ht/~sircmpwn/aerc/models" ) @@ -130,13 +131,13 @@ func NewMessageViewer(acct *AccountView, func fmtHeader(msg *models.MessageInfo, header string, timefmt string) string { switch header { case "From": - return models.FormatAddresses(msg.Envelope.From) + return format.FormatAddresses(msg.Envelope.From) case "To": - return models.FormatAddresses(msg.Envelope.To) + return format.FormatAddresses(msg.Envelope.To) case "Cc": - return models.FormatAddresses(msg.Envelope.Cc) + return format.FormatAddresses(msg.Envelope.Cc) case "Bcc": - return models.FormatAddresses(msg.Envelope.Bcc) + return format.FormatAddresses(msg.Envelope.Bcc) case "Date": return msg.Envelope.Date.Local().Format(timefmt) case "Subject": @@ -496,11 +497,11 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig, case "subject": header = info.Envelope.Subject case "from": - header = models.FormatAddresses(info.Envelope.From) + header = format.FormatAddresses(info.Envelope.From) case "to": - header = models.FormatAddresses(info.Envelope.To) + header = format.FormatAddresses(info.Envelope.To) case "cc": - header = models.FormatAddresses(info.Envelope.Cc) + header = format.FormatAddresses(info.Envelope.Cc) } if f.Regex.Match([]byte(header)) { filter = exec.Command("sh", "-c", f.Command) diff --git a/worker/imap/imap.go b/worker/imap/imap.go index 7afab02..aa1854d 100644 --- a/worker/imap/imap.go +++ b/worker/imap/imap.go @@ -64,8 +64,7 @@ func translateAddresses(addrs []*imap.Address) []*models.Address { for _, addr := range addrs { converted = append(converted, &models.Address{ Name: addr.PersonalName, - Mailbox: addr.MailboxName, - Host: addr.HostName, + Address: addr.Address(), }) } return converted diff --git a/worker/lib/parse.go b/worker/lib/parse.go index 58327c9..b003d96 100644 --- a/worker/lib/parse.go +++ b/worker/lib/parse.go @@ -205,18 +205,9 @@ func parseAddressList(h *mail.Header, key string) ([]*models.Address, error) { return nil, err } for _, addr := range addrs { - parts := strings.Split(addr.Address, "@") - var mbox, host string - if len(parts) > 1 { - mbox = strings.Join(parts[0:len(parts)-1], "@") - host = parts[len(parts)-1] - } else { - mbox = addr.Address - } converted = append(converted, &models.Address{ Name: addr.Name, - Mailbox: mbox, - Host: host, + Address: addr.Address, }) } return converted, nil diff --git a/worker/lib/sort.go b/worker/lib/sort.go index ac8ed07..9d1f50a 100644 --- a/worker/lib/sort.go +++ b/worker/lib/sort.go @@ -1,7 +1,6 @@ package lib import ( - "fmt" "sort" "strings" @@ -83,7 +82,7 @@ func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCrit if addr.Name != "" { return addr.Name } else { - return fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host) + return addr.Address } } return getName(firstI) < getName(firstJ)