Skip to content

standard library email.parser fails to recognize multipart message #135854

Open

Description

@mok0

Bug report

Bug description:

If an email message with attachments contain a boundary with the characters "<--- ... --->" the email.parser module fails to recognize the message as being multipart.

I have attached to this bug report an example mailbox file, and when the following snippet is run on it you will see that is_multipart() returns False, although the message is clearly a multipart message.

I found this boundary string used in a real life email I received.

from email.parser import Parser

# Create parser instance
P = Parser()

with open("message-error.mbox") as f:
    msg = P.parse(f)

print(msg.is_multipart())

message-error.txt

CPython versions tested on:

3.13

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Jun 23, 2025
  2. tanloong commented on Jun 23, 2025

    @tanloong
    Contributor

    Some findings:

    The surrounding angle brackets in <--- ... ---> are regarded as quotes and are removed by email.utils.unquote(str). The documentation also has a description of this.

    So, email.parser treats the entire message as a single part when it fails to find the stripped boundary --- ... ---. However, if the angle appears on only one side of the boundary (not both), the parser can correctly detect the multi-part message.

  3. tanloong commented on Jun 23, 2025

    @tanloong
    Contributor

    I think the boundary gets duplicate strippings. email.message.get_boundary() calls email.message.get_param() and utils.collapse_rfc2231_value() in sequence, both of the two functions perform their own stripping. The first stripping drops the double quotes, the second one drops the angle brackets.

  4. mok0 commented on Jun 24, 2025

    @mok0
    Author

    Your observation that email.utils.unquote eats < makes sense.

    I also observe this behavior in the From: field. If the "from" field in the message is given like "John Doe johndoe@example.com", msg["from"] will return"John Doe", however, if the "from" field in the message only contains "<johndoe@example.com>", msg["from"]` returns None.

    I believe this behavior is also a bug. The module must return the fields from the message unaltered. When I generate a list of my mail messages, some of them have no sender, and that is not an appropriate behavior.

  5. tanloong commented on Jun 25, 2025

    @tanloong
    Contributor

    @mok0 I can't reproduce it on my side, is there something I'm missing?

    from email.parser import Parser
    
    p = Parser()
    msg = p.parsestr('From: "<johndoe@example.com>"')
    
    print(msg["from"])  # <johndoe@example.com>
  6. mok0 commented on Jun 26, 2025

    @mok0
    Author

    Hm, I can't reproduce it either now, but in my work with the module I am creating an sqlite3 database of all headers, and I can see the From: field is missing in cases where sender field in the mbox file only has a <....> construct. I will try to investigate further.

  7. bitdancer commented on Aug 19, 2026

    @bitdancer
    Member

    Since the 'From' issue hasn't been reproduced, we'll confine this issue to the boundary issue from the title.

    The root of the problem is that utils.unquote is stripping <> "quotes". I have no idea why it is doing that, I can't see anywhere in the email library itself that that stripping would be appropriate. I can only conclude it is provided to facilitate stripping the <> off an addrspec (<a@b.com>), which as far as I can see the email library itself never uses utils.unquote to do.

    Even if unquote wasn't stripping <>, the double unquoting could be an issue: imagine a boundary specified as "\"foo\"". But as I commented on the PR, simply removing the double quoting would make an invalid boundary specification valid, which doesn't seem like a good idea (unless other email clients do likewise?)

    The best course I can see at the moment is to replace all the internal calls to unquote with calls to an internal function that does not strip <>, and apply the fix proposed in the PR as well. Unfortunately that could have backward incompatibility issues. Probably safe enough to do it in a feature release, though.

  8. bitdancer commented on Aug 19, 2026

    @bitdancer
    Member

    To clarify: <> are not valid parameter value "quoting" characters per RFC 2045, so they shouldn't be being stripped: such a value should be illegal for any mime parameter value, per the RFC.

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