diff --git a/commands/msg/reply.go b/commands/msg/reply.go index bde9194..8503979 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -13,6 +13,7 @@ import ( "git.sr.ht/~sircmpwn/aerc/lib/format" "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/widgets" + "github.com/emersion/go-message/mail" ) type reply struct{} @@ -97,8 +98,8 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error { } var ( - to []*models.Address - cc []*models.Address + to []*mail.Address + cc []*mail.Address ) recSet := newAddrSet() // used for de-duping @@ -117,7 +118,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error { // we add our from address, so that we don't self address ourselves recSet.Add(from) - envTos := make([]*models.Address, 0, len(msg.Envelope.To)) + envTos := make([]*mail.Address, 0, len(msg.Envelope.To)) for _, addr := range msg.Envelope.To { if recSet.Contains(addr) { continue @@ -147,7 +148,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error { defaults := map[string]string{ "To": format.FormatAddresses(to), "Cc": format.FormatAddresses(cc), - "From": from.Format(), + "From": format.AddressForHumans(from), "Subject": subject, "In-Reply-To": msg.Envelope.MessageId, } @@ -231,17 +232,17 @@ func newAddrSet() addrSet { return addrSet(s) } -func (s addrSet) Add(a *models.Address) { +func (s addrSet) Add(a *mail.Address) { s[a.Address] = struct{}{} } -func (s addrSet) AddList(al []*models.Address) { +func (s addrSet) AddList(al []*mail.Address) { for _, a := range al { s[a.Address] = struct{}{} } } -func (s addrSet) Contains(a *models.Address) bool { +func (s addrSet) Contains(a *mail.Address) bool { _, ok := s[a.Address] return ok } diff --git a/lib/format/format.go b/lib/format/format.go index 787821a..e19ca31 100644 --- a/lib/format/format.go +++ b/lib/format/format.go @@ -2,25 +2,28 @@ package format import ( "errors" + "fmt" "mime" gomail "net/mail" + "regexp" "strings" "time" "unicode" "git.sr.ht/~sircmpwn/aerc/models" "github.com/emersion/go-message" + "github.com/emersion/go-message/mail" ) -func ParseAddress(address string) (*models.Address, error) { +func ParseAddress(address string) (*mail.Address, error) { addrs, err := gomail.ParseAddress(address) if err != nil { return nil, err } - return (*models.Address)(addrs), nil + return (*mail.Address)(addrs), nil } -func ParseAddressList(s string) ([]*models.Address, error) { +func ParseAddressList(s string) ([]*mail.Address, error) { if len(s) == 0 { // we don't consider an empty list to be an error return nil, nil @@ -33,17 +36,35 @@ func ParseAddressList(s string) ([]*models.Address, error) { return nil, err } - addrs := make([]*models.Address, len(list)) + addrs := make([]*mail.Address, len(list)) for i, a := range list { - addrs[i] = (*models.Address)(a) + addrs[i] = (*mail.Address)(a) } return addrs, nil } -func FormatAddresses(l []*models.Address) string { +// AddressForHumans 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 AddressForHumans(a *mail.Address) string { + if a.Name != "" { + if atom.MatchString(a.Name) { + return fmt.Sprintf("%s <%s>", a.Name, a.Address) + } else { + return fmt.Sprintf("\"%s\" <%s>", + strings.ReplaceAll(a.Name, "\"", "'"), a.Address) + } + } else { + return fmt.Sprintf("<%s>", a.Address) + } +} + +var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$") + +func FormatAddresses(l []*mail.Address) string { formatted := make([]string, len(l)) for i, a := range l { - formatted[i] = a.Format() + formatted[i] = AddressForHumans(a) } return strings.Join(formatted, ", ") } @@ -130,7 +151,7 @@ func ParseMessageFormat(format string, timeFmt string, ctx Ctx) (string, return "", nil, errors.New("no envelope available for this message") } - var addr *models.Address + var addr *mail.Address if len(envelope.ReplyTo) == 0 { if len(envelope.From) == 0 { return "", nil, @@ -171,7 +192,7 @@ func ParseMessageFormat(format string, timeFmt string, ctx Ctx) (string, return "", nil, errors.New("found no address for sender") } - addr := envelope.From[0].Format() + addr := AddressForHumans(envelope.From[0]) retval = append(retval, 's') args = append(args, addr) case 'F': diff --git a/models/models.go b/models/models.go index a93db72..bfe0ff3 100644 --- a/models/models.go +++ b/models/models.go @@ -3,9 +3,6 @@ package models import ( "fmt" "io" - gomail "net/mail" - "regexp" - "strings" "time" "github.com/emersion/go-message/mail" @@ -127,34 +124,14 @@ func (bs *BodyStructure) PartAtIndex(index []int) (*BodyStructure, error) { type Envelope struct { Date time.Time Subject string - From []*Address - ReplyTo []*Address - To []*Address - Cc []*Address - Bcc []*Address + From []*mail.Address + ReplyTo []*mail.Address + To []*mail.Address + Cc []*mail.Address + Bcc []*mail.Address MessageId string } -type Address gomail.Address - -var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$") - -// 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>", a.Name, a.Address) - } else { - return fmt.Sprintf("\"%s\" <%s>", - strings.ReplaceAll(a.Name, "\"", "'"), a.Address) - } - } else { - return fmt.Sprintf("<%s>", a.Address) - } -} - // OriginalMail is helper struct used for reply/forward type OriginalMail struct { Date time.Time diff --git a/worker/imap/imap.go b/worker/imap/imap.go index aa1854d..e296e9b 100644 --- a/worker/imap/imap.go +++ b/worker/imap/imap.go @@ -5,6 +5,7 @@ import ( "git.sr.ht/~sircmpwn/aerc/models" "github.com/emersion/go-message/charset" + "github.com/emersion/go-message/mail" ) func init() { @@ -59,10 +60,10 @@ func translateEnvelope(e *imap.Envelope) *models.Envelope { } } -func translateAddresses(addrs []*imap.Address) []*models.Address { - var converted []*models.Address +func translateAddresses(addrs []*imap.Address) []*mail.Address { + var converted []*mail.Address for _, addr := range addrs { - converted = append(converted, &models.Address{ + converted = append(converted, &mail.Address{ Name: addr.PersonalName, Address: addr.Address(), }) diff --git a/worker/lib/parse.go b/worker/lib/parse.go index edd3649..9fa539f 100644 --- a/worker/lib/parse.go +++ b/worker/lib/parse.go @@ -193,19 +193,19 @@ func parseReceivedHeader(h *mail.Header) (time.Time, error) { return time.Parse(time.RFC1123Z, dateRe.FindString(guess)) } -func parseAddressList(h *mail.Header, key string) ([]*models.Address, error) { - var converted []*models.Address +func parseAddressList(h *mail.Header, key string) ([]*mail.Address, error) { + var converted []*mail.Address addrs, err := h.AddressList(key) if err != nil { if hdr, err := h.Text(key); err == nil { - return []*models.Address{&models.Address{ + return []*mail.Address{&mail.Address{ Name: hdr, }}, nil } return nil, err } for _, addr := range addrs { - converted = append(converted, &models.Address{ + converted = append(converted, &mail.Address{ Name: addr.Name, Address: addr.Address, }) diff --git a/worker/lib/sort.go b/worker/lib/sort.go index 9d1f50a..09bcf77 100644 --- a/worker/lib/sort.go +++ b/worker/lib/sort.go @@ -6,6 +6,7 @@ import ( "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/types" + "github.com/emersion/go-message/mail" ) func Sort(messageInfos []*models.MessageInfo, @@ -20,7 +21,7 @@ func Sort(messageInfos []*models.MessageInfo, }) case types.SortCc: sortAddresses(messageInfos, criterion, - func(msgInfo *models.MessageInfo) []*models.Address { + func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.Cc }) case types.SortDate: @@ -29,7 +30,7 @@ func Sort(messageInfos []*models.MessageInfo, }) case types.SortFrom: sortAddresses(messageInfos, criterion, - func(msgInfo *models.MessageInfo) []*models.Address { + func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.From }) case types.SortRead: @@ -47,7 +48,7 @@ func Sort(messageInfos []*models.MessageInfo, }) case types.SortTo: sortAddresses(messageInfos, criterion, - func(msgInfo *models.MessageInfo) []*models.Address { + func(msgInfo *models.MessageInfo) []*mail.Address { return msgInfo.Envelope.To }) } @@ -61,10 +62,10 @@ func Sort(messageInfos []*models.MessageInfo, } func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCriterion, - getValue func(*models.MessageInfo) []*models.Address) { + getValue func(*models.MessageInfo) []*mail.Address) { sortSlice(criterion, messageInfos, func(i, j int) bool { addressI, addressJ := getValue(messageInfos[i]), getValue(messageInfos[j]) - var firstI, firstJ *models.Address + var firstI, firstJ *mail.Address if len(addressI) > 0 { firstI = addressI[0] } @@ -78,7 +79,7 @@ func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCrit } else if firstI != nil && firstJ == nil { return true } else /* firstI != nil && firstJ != nil */ { - getName := func(addr *models.Address) string { + getName := func(addr *mail.Address) string { if addr.Name != "" { return addr.Name } else {