You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
email.utils.formataddr does not respect double spaces #91400
It seems that email.utils.formataddr and email.utils.parseaddr are not directly inverse from each other, because formataddr does not respect double spaces within the "realname" section.
For example:
fromemail.utilsimportformataddr, parseaddridentity='"foo bar" <foo@example.com>'print(formataddr(parseaddr(identity)))
# 'foo bar <foo@example.com>'print(formataddr(parseaddr(formataddr(parseaddr(identity)))))
# 'foo bar <foo@example.com>'
The first round trip strips the quotes, and the second round trip strips the double space in the "realname" section.
The utility function only seems to check for special characters, but it should also check for double spaces.
According the RFCs, "foo bar" and foo bar are semantically equivalent. Both should be interpreted in the same way by an email client (that is, the two spaces should be preserved or compressed in both cases). Of, course, that isn't what happens in practice. For practical reasons there is value in preserving the state of the quoted string exactly as it appears, since that's what humans expect ;) And that's what both the old and new email API do.
formataddr, however, doesn't actually know if the original string was quoted or not. formataddr is a low level interface that takes a realname field and an address, and produces an rfc valid rendering of that address. The only consideration it can or should make in that case is what characters being present in a name field require quoting, which is what it does. That is, there is no bug in formataddr here:
>>> from email.utils import parseaddr, formataddr
>>> identity ='"foo bar" <foo@example.com>'
>>> one = parseaddr(identity)
>>> print(one)
('foo bar', 'foo@example.com')
>>> fone = formataddr(one)
>>> print(fone)
foo bar <foo@example.com>
>>> two = parseaddr(fone)
>>> print(two)
('foo bar', 'foo@example.com')
As you can see, parseaddr, while it does the expected thing when doing the unquoting, does not do the same thing when presented with a realname containing internal whitespace substrings. Semantically this is correct, since semantically runs of whitespace are equivalent to a single space (per the RFC: "Runs of FWS, comment or CFWS that occur between lexical tokens in a structured field header are semantically interpreted as a single space character", and a quoted string is a token while individual words (atoms) in a realname are individually tokens). The new email API has the same behavior: when parsing unquoted display names internal whitespace is compressed to single spaces.
Now, you might consider it to be an issue with formataddr that there is no way to tell it "I want a quoted string here even if it isn't needed". That's actually a feature rather than a bug, given the purpose of the API. which is to quote given source strings according to the RFC specification. The new API's function for building an address from the individual pieces (headerregistry.Address) has equivalent behavior to formataddr: there's no way to tell Address that you want it to quote the display name if it doesn't inherently need quoting.
Is it worth fixing the parsing side of this? it might cause more backward compatibility issues than it solves problems, though most likely it wouldn't. I wouldn't object to a fix, but I don't consider it very important. Also, in the new API this notion that, outside quoted strings, runs of whitespace are equivalent to a single space is pretty embedded in the parser, so it may not be easy to change. I haven't looked at how hard it would be to fix parseaddr, but I wouldn't change it without also making the same behavior change to the new API.
Activity
AlecRosenbaum commentedon Apr 6, 2022
It seems that
email.utils.formataddrandemail.utils.parseaddrare not directly inverse from each other, becauseformataddrdoes not respect double spaces within the "realname" section.For example:
The first round trip strips the quotes, and the second round trip strips the double space in the "realname" section.
The utility function only seems to check for special characters, but it should also check for double spaces.
pythongh-91400: make sure email parsing dont unquote realnames with s…
bitdancer commentedon Mar 6, 2025
According the RFCs,
"foo bar"andfoo barare semantically equivalent. Both should be interpreted in the same way by an email client (that is, the two spaces should be preserved or compressed in both cases). Of, course, that isn't what happens in practice. For practical reasons there is value in preserving the state of the quoted string exactly as it appears, since that's what humans expect ;) And that's what both the old and new email API do.formataddr, however, doesn't actually know if the original string was quoted or not. formataddr is a low level interface that takes a realname field and an address, and produces an rfc valid rendering of that address. The only consideration it can or should make in that case is what characters being present in a name field require quoting, which is what it does. That is, there is no bug in formataddr here:
As you can see, parseaddr, while it does the expected thing when doing the unquoting, does not do the same thing when presented with a realname containing internal whitespace substrings. Semantically this is correct, since semantically runs of whitespace are equivalent to a single space (per the RFC: "Runs of FWS, comment or CFWS that occur between lexical tokens in a structured field header are semantically interpreted as a single space character", and a quoted string is a token while individual words (atoms) in a realname are individually tokens). The new email API has the same behavior: when parsing unquoted display names internal whitespace is compressed to single spaces.
Now, you might consider it to be an issue with formataddr that there is no way to tell it "I want a quoted string here even if it isn't needed". That's actually a feature rather than a bug, given the purpose of the API. which is to quote given source strings according to the RFC specification. The new API's function for building an address from the individual pieces (
headerregistry.Address) has equivalent behavior to formataddr: there's no way to tellAddressthat you want it to quote the display name if it doesn't inherently need quoting.Is it worth fixing the parsing side of this? it might cause more backward compatibility issues than it solves problems, though most likely it wouldn't. I wouldn't object to a fix, but I don't consider it very important. Also, in the new API this notion that, outside quoted strings, runs of whitespace are equivalent to a single space is pretty embedded in the parser, so it may not be easy to change. I haven't looked at how hard it would be to fix parseaddr, but I wouldn't change it without also making the same behavior change to the new API.