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:
parent
fe1cabb077
commit
c846307144
7 changed files with 67 additions and 62 deletions
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc/lib"
|
"git.sr.ht/~sircmpwn/aerc/lib"
|
||||||
|
"git.sr.ht/~sircmpwn/aerc/lib/format"
|
||||||
"git.sr.ht/~sircmpwn/aerc/models"
|
"git.sr.ht/~sircmpwn/aerc/models"
|
||||||
"git.sr.ht/~sircmpwn/aerc/widgets"
|
"git.sr.ht/~sircmpwn/aerc/widgets"
|
||||||
"git.sr.ht/~sircmpwn/aerc/worker/types"
|
"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) {
|
addTab := func() (*widgets.Composer, error) {
|
||||||
if template != "" {
|
if template != "" {
|
||||||
original.From = models.FormatAddresses(msg.Envelope.From)
|
original.From = format.FormatAddresses(msg.Envelope.From)
|
||||||
original.Date = msg.Envelope.Date
|
original.Date = msg.Envelope.Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,46 @@ package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"mime"
|
||||||
gomail "net/mail"
|
gomail "net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc/models"
|
"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)
|
addrs, err := gomail.ParseAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*models.Address)(addrs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrs
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
func ParseMessageFormat(
|
||||||
|
@ -29,7 +53,10 @@ func ParseMessageFormat(
|
||||||
retval := make([]byte, 0, len(format))
|
retval := make([]byte, 0, len(format))
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
accountFromAddress := parseAddress(fromAddress)
|
accountFromAddress, err := ParseAddress(fromAddress)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var c rune
|
var c rune
|
||||||
for i, ni := 0, 0; i < len(format); {
|
for i, ni := 0, 0; i < len(format); {
|
||||||
|
@ -87,8 +114,7 @@ func ParseMessageFormat(
|
||||||
}
|
}
|
||||||
addr := msg.Envelope.From[0]
|
addr := msg.Envelope.From[0]
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args,
|
args = append(args, addr.Address)
|
||||||
fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
|
|
||||||
case 'A':
|
case 'A':
|
||||||
if msg.Envelope == nil {
|
if msg.Envelope == nil {
|
||||||
return "", nil,
|
return "", nil,
|
||||||
|
@ -106,8 +132,7 @@ func ParseMessageFormat(
|
||||||
addr = msg.Envelope.ReplyTo[0]
|
addr = msg.Envelope.ReplyTo[0]
|
||||||
}
|
}
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args,
|
args = append(args, addr.Address)
|
||||||
fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
|
|
||||||
case 'C':
|
case 'C':
|
||||||
retval = append(retval, 'd')
|
retval = append(retval, 'd')
|
||||||
args = append(args, number)
|
args = append(args, number)
|
||||||
|
@ -158,7 +183,7 @@ func ParseMessageFormat(
|
||||||
if addr.Name != "" {
|
if addr.Name != "" {
|
||||||
val = addr.Name
|
val = addr.Name
|
||||||
} else {
|
} else {
|
||||||
val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
|
val = addr.Address
|
||||||
}
|
}
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args, val)
|
args = append(args, val)
|
||||||
|
@ -188,7 +213,7 @@ func ParseMessageFormat(
|
||||||
if addr.Name != "" {
|
if addr.Name != "" {
|
||||||
val = addr.Name
|
val = addr.Name
|
||||||
} else {
|
} else {
|
||||||
val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
|
val = addr.Address
|
||||||
}
|
}
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args, val)
|
args = append(args, val)
|
||||||
|
@ -197,7 +222,7 @@ func ParseMessageFormat(
|
||||||
return "", nil,
|
return "", nil,
|
||||||
errors.New("no envelope available for this message")
|
errors.New("no envelope available for this message")
|
||||||
}
|
}
|
||||||
addrs := models.FormatAddresses(msg.Envelope.To)
|
addrs := FormatAddresses(msg.Envelope.To)
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args, addrs)
|
args = append(args, addrs)
|
||||||
case 'R':
|
case 'R':
|
||||||
|
@ -205,7 +230,7 @@ func ParseMessageFormat(
|
||||||
return "", nil,
|
return "", nil,
|
||||||
errors.New("no envelope available for this message")
|
errors.New("no envelope available for this message")
|
||||||
}
|
}
|
||||||
addrs := models.FormatAddresses(msg.Envelope.Cc)
|
addrs := FormatAddresses(msg.Envelope.Cc)
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args, addrs)
|
args = append(args, addrs)
|
||||||
case 's':
|
case 's':
|
||||||
|
@ -226,8 +251,7 @@ func ParseMessageFormat(
|
||||||
}
|
}
|
||||||
addr := msg.Envelope.To[0]
|
addr := msg.Envelope.To[0]
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args,
|
args = append(args, addr.Address)
|
||||||
fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
|
|
||||||
case 'T':
|
case 'T':
|
||||||
retval = append(retval, 's')
|
retval = append(retval, 's')
|
||||||
args = append(args, accountName)
|
args = append(args, accountName)
|
||||||
|
@ -241,8 +265,12 @@ func ParseMessageFormat(
|
||||||
errors.New("found no address for sender")
|
errors.New("found no address for sender")
|
||||||
}
|
}
|
||||||
addr := msg.Envelope.From[0]
|
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')
|
retval = append(retval, 's')
|
||||||
args = append(args, addr.Mailbox)
|
args = append(args, mailbox)
|
||||||
case 'v':
|
case 'v':
|
||||||
if msg.Envelope == nil {
|
if msg.Envelope == nil {
|
||||||
return "", nil,
|
return "", nil,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
gomail "net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -134,40 +134,26 @@ type Envelope struct {
|
||||||
MessageId string
|
MessageId string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Address struct {
|
type Address gomail.Address
|
||||||
Name string
|
|
||||||
Mailbox string
|
|
||||||
Host string
|
|
||||||
}
|
|
||||||
|
|
||||||
var atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$")
|
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 a.Name != "" {
|
||||||
if atom.MatchString(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 {
|
} else {
|
||||||
return fmt.Sprintf("\"%s\" <%s@%s>",
|
return fmt.Sprintf("\"%s\" <%s>",
|
||||||
strings.ReplaceAll(a.Name, "\"", "'"),
|
strings.ReplaceAll(a.Name, "\"", "'"), a.Address)
|
||||||
a.Mailbox, a.Host)
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// OriginalMail is helper struct used for reply/forward
|
||||||
type OriginalMail struct {
|
type OriginalMail struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc/config"
|
"git.sr.ht/~sircmpwn/aerc/config"
|
||||||
"git.sr.ht/~sircmpwn/aerc/lib"
|
"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/lib/ui"
|
||||||
"git.sr.ht/~sircmpwn/aerc/models"
|
"git.sr.ht/~sircmpwn/aerc/models"
|
||||||
)
|
)
|
||||||
|
@ -130,13 +131,13 @@ func NewMessageViewer(acct *AccountView,
|
||||||
func fmtHeader(msg *models.MessageInfo, header string, timefmt string) string {
|
func fmtHeader(msg *models.MessageInfo, header string, timefmt string) string {
|
||||||
switch header {
|
switch header {
|
||||||
case "From":
|
case "From":
|
||||||
return models.FormatAddresses(msg.Envelope.From)
|
return format.FormatAddresses(msg.Envelope.From)
|
||||||
case "To":
|
case "To":
|
||||||
return models.FormatAddresses(msg.Envelope.To)
|
return format.FormatAddresses(msg.Envelope.To)
|
||||||
case "Cc":
|
case "Cc":
|
||||||
return models.FormatAddresses(msg.Envelope.Cc)
|
return format.FormatAddresses(msg.Envelope.Cc)
|
||||||
case "Bcc":
|
case "Bcc":
|
||||||
return models.FormatAddresses(msg.Envelope.Bcc)
|
return format.FormatAddresses(msg.Envelope.Bcc)
|
||||||
case "Date":
|
case "Date":
|
||||||
return msg.Envelope.Date.Local().Format(timefmt)
|
return msg.Envelope.Date.Local().Format(timefmt)
|
||||||
case "Subject":
|
case "Subject":
|
||||||
|
@ -496,11 +497,11 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig,
|
||||||
case "subject":
|
case "subject":
|
||||||
header = info.Envelope.Subject
|
header = info.Envelope.Subject
|
||||||
case "from":
|
case "from":
|
||||||
header = models.FormatAddresses(info.Envelope.From)
|
header = format.FormatAddresses(info.Envelope.From)
|
||||||
case "to":
|
case "to":
|
||||||
header = models.FormatAddresses(info.Envelope.To)
|
header = format.FormatAddresses(info.Envelope.To)
|
||||||
case "cc":
|
case "cc":
|
||||||
header = models.FormatAddresses(info.Envelope.Cc)
|
header = format.FormatAddresses(info.Envelope.Cc)
|
||||||
}
|
}
|
||||||
if f.Regex.Match([]byte(header)) {
|
if f.Regex.Match([]byte(header)) {
|
||||||
filter = exec.Command("sh", "-c", f.Command)
|
filter = exec.Command("sh", "-c", f.Command)
|
||||||
|
|
|
@ -64,8 +64,7 @@ func translateAddresses(addrs []*imap.Address) []*models.Address {
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
converted = append(converted, &models.Address{
|
converted = append(converted, &models.Address{
|
||||||
Name: addr.PersonalName,
|
Name: addr.PersonalName,
|
||||||
Mailbox: addr.MailboxName,
|
Address: addr.Address(),
|
||||||
Host: addr.HostName,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return converted
|
return converted
|
||||||
|
|
|
@ -205,18 +205,9 @@ func parseAddressList(h *mail.Header, key string) ([]*models.Address, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, addr := range addrs {
|
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{
|
converted = append(converted, &models.Address{
|
||||||
Name: addr.Name,
|
Name: addr.Name,
|
||||||
Mailbox: mbox,
|
Address: addr.Address,
|
||||||
Host: host,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return converted, nil
|
return converted, nil
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package lib
|
package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -83,7 +82,7 @@ func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCrit
|
||||||
if addr.Name != "" {
|
if addr.Name != "" {
|
||||||
return addr.Name
|
return addr.Name
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
|
return addr.Address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getName(firstI) < getName(firstJ)
|
return getName(firstI) < getName(firstJ)
|
||||||
|
|
Loading…
Reference in a new issue