base models.Address on the mail.Address type

This allows us to hook into the std libs implementation of parsing related stuff.
For this, we need to get rid of the distinction between a mailbox and a host
to just a single "address" field.

However this is already the common case. All but one users immediately
concatenated the mbox/domain to a single address.

So this in effects makes it simpler for most cases and we simply do the
transformation in the special case.
This commit is contained in:
Reto Brunner 2020-08-19 12:01:45 +02:00
parent fe1cabb077
commit c846307144
7 changed files with 67 additions and 62 deletions

View File

@ -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
}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)