normalize localparts with unicode nfc when parsing

both when parsing our configs, and for incoming on smtp or in messages.
so we properly compare things like é and e+accent as equal, and accept the
different encodings of that same address.
This commit is contained in:
Mechiel Lukkien
2024-03-08 21:08:40 +01:00
parent 4fbd7abb57
commit 8e6fe7459b
23 changed files with 134 additions and 59 deletions

View File

@ -1347,7 +1347,7 @@ func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string,
acc.WithRLock(func() {
xdbread(ctx, acc, func(tx *bstore.Tx) {
type key struct {
localpart smtp.Localpart
localpart string
domain string
}
seen := map[key]bool{}
@ -1360,7 +1360,7 @@ func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string,
return nil
}
// todo: we should have the address including name available in the database for searching. Will result in better matching, and also for the name.
address := fmt.Sprintf("<%s@%s>", r.Localpart.String(), r.Domain)
address := fmt.Sprintf("<%s@%s>", r.Localpart, r.Domain)
if !strings.Contains(strings.ToLower(address), search) {
return nil
}
@ -1382,7 +1382,7 @@ func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string,
xcheckf(ctx, err, "parsing domain of recipient")
var found bool
lp := r.Localpart.String()
lp := r.Localpart
checkAddrs := func(l []message.Address) {
if found {
return

View File

@ -1009,7 +1009,7 @@
},
{
"Name": "User",
"Docs": "Localpart.",
"Docs": "Localpart, encoded as string. Must be parsed before using as Localpart.",
"Typewords": [
"string"
]
@ -1063,7 +1063,7 @@
},
{
"Name": "Unicode",
"Docs": "Name as U-labels. Empty if this is an ASCII-only domain. No trailing dot.",
"Docs": "Name as U-labels, in Unicode NFC. Empty if this is an ASCII-only domain. No trailing dot.",
"Typewords": [
"string"
]
@ -2817,7 +2817,7 @@
},
{
"Name": "Localpart",
"Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.",
"Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.\nLocalparts are in Unicode NFC.",
"Values": null
}
],

View File

@ -106,7 +106,7 @@ export interface Envelope {
// Address as used in From and To headers.
export interface Address {
Name: string // Free-form name for display in mail applications.
User: string // Localpart.
User: string // Localpart, encoded as string. Must be parsed before using as Localpart.
Host: string // Domain in ASCII.
}
@ -124,7 +124,7 @@ export interface MessageAddress {
// trailing dot. When using with StrictResolver, add the trailing dot.
export interface Domain {
ASCII: string // A non-unicode domain, e.g. with A-labels (xn--...) or NR-LDH (non-reserved letters/digits/hyphens) labels. Always in lower case. No trailing dot.
Unicode: string // Name as U-labels. Empty if this is an ASCII-only domain. No trailing dot.
Unicode: string // Name as U-labels, in Unicode NFC. Empty if this is an ASCII-only domain. No trailing dot.
}
// SubmitMessage is an email message to be sent to one or more recipients.
@ -516,6 +516,7 @@ export enum SecurityResult {
// Localpart is a decoded local part of an email address, before the "@".
// For quoted strings, values do not hold the double quote or escaping backslashes.
// An empty string can be a valid localpart.
// Localparts are in Unicode NFC.
export type Localpart = string
export const structTypes: {[typename: string]: boolean} = {"Address":true,"Attachment":true,"ChangeMailboxAdd":true,"ChangeMailboxCounts":true,"ChangeMailboxKeywords":true,"ChangeMailboxRemove":true,"ChangeMailboxRename":true,"ChangeMailboxSpecialUse":true,"ChangeMsgAdd":true,"ChangeMsgFlags":true,"ChangeMsgRemove":true,"ChangeMsgThread":true,"Domain":true,"DomainAddressConfig":true,"Envelope":true,"EventStart":true,"EventViewChanges":true,"EventViewErr":true,"EventViewMsgs":true,"EventViewReset":true,"File":true,"Filter":true,"Flags":true,"ForwardAttachments":true,"Mailbox":true,"Message":true,"MessageAddress":true,"MessageEnvelope":true,"MessageItem":true,"NotFilter":true,"Page":true,"ParsedMessage":true,"Part":true,"Query":true,"RecipientSecurity":true,"Request":true,"SpecialUse":true,"SubmitMessage":true}