Skip to content

email.utils.formataddr does not respect double spaces #91400

Open

Description

@AlecRosenbaum
BPO 47244
Nosy @warsaw, @bitdancer, @AlecRosenbaum

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2022-04-06.17:45:12.930>
labels = ['type-bug', '3.8', 'expert-email', '3.10', '3.11', '3.7', '3.9']
title = 'email.utils.formataddr does not respect double spaces'
updated_at = <Date 2022-04-06.17:45:12.930>
user = 'https://github.com/AlecRosenbaum'

bugs.python.org fields:

activity = <Date 2022-04-06.17:45:12.930>
actor = 'AlecRosenbaum'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['email']
creation = <Date 2022-04-06.17:45:12.930>
creator = 'AlecRosenbaum'
dependencies = []
files = []
hgrepos = []
issue_num = 47244
keywords = []
message_count = 1.0
messages = ['416891']
nosy_count = 3.0
nosy_names = ['barry', 'r.david.murray', 'AlecRosenbaum']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue47244'
versions = ['Python 3.7', 'Python 3.8', 'Python 3.9', 'Python 3.10', 'Python 3.11']

Linked PRs

Activity

  1. AlecRosenbaum commented on Apr 6, 2022

    AlecRosenbaummannequin
    MannequinAuthor

    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:

    from email.utils import formataddr, parseaddr
    
    identity = '"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.

  2. transferred this issue fromon Apr 10, 2022
  3. added a commit that references this issue on May 10, 2022
  4. self-assigned this
    on Mar 17, 2023
  5. bitdancer commented on Mar 6, 2025

    @bitdancer
    Member

    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.

  6. added
    stdlibStandard Library Python modules in the Lib/ directory
    and removed
    3.11only security fixes
    on Mar 7, 2025
  7. removed their assignment
    on Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-emailtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions