Opened 3 weeks ago

Closed 3 weeks ago

Last modified 2 weeks ago

#36746 closed Bug (invalid)

IndexError in prep_address() when parsing invalid email addresses like 'to@' — at Version 6

Reported by: Mahdi Dehghan Owned by: Mahdi Dehghan
Component: Core (Mail) Version: dev
Severity: Normal Keywords:
Cc: Mahdi Dehghan Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Mike Edmunds)

When parsing invalid email addresses (such as 'to@') in the SMTP backend's prep_address() method, Python's email parser raises an IndexError instead of the expected ValueError. This causes the test test_avoids_sending_to_invalid_addresses to fail.

Steps to reproduce:

  1. Run Django's test suite: ./runtests.py mail.tests.SMTPBackendTests.test_avoids_sending_to_invalid_addresses
  2. The test fails with IndexError when trying to parse 'to@'

Root cause:
The prep_address() method at line 172 in django/core/mail/backends/smtp.py directly calls AddressHeader.value_parser(address) without exception handling. When parsing malformed addresses like 'to@', Python's email parser first raises HeaderParseError, then during exception handling attempts to parse the address differently, which leads to an IndexError when trying to access value[0] on an empty string.

Proposed solution:
Wrap the AddressHeader.value_parser() call in a try-except block to catch HeaderParseError, IndexError, and ValueError exceptions, converting them to ValueError with an appropriate message. This matches the pattern already used in the deprecated sanitize_address() function in django/core/mail/message.py (line 115).

Expected behavior:
The prep_address() method should catch parsing exceptions and raise ValueError with message "Invalid address" for invalid addresses, matching the test's expectation.

Actual behavior:
IndexError: string index out of range is raised from Python's email._header_value_parser when parsing addresses like 'to@'.

Error traceback:

ERROR: test_avoids_sending_to_invalid_addresses (mail.tests.SMTPBackendTests.test_avoids_sending_to_invalid_addresses) [<object object at 0x12d0aff70>] (email_address='to@')
Verify invalid addresses can't sneak into SMTP commands through
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1965, in get_address
    token, value = get_group(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1923, in get_group
    raise errors.HeaderParseError("expected ':' at end of group "
    ^^^^^^^^^^^^^^^^^
email.errors.HeaderParseError: expected ':' at end of group display name but found '@'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1791, in get_mailbox
    token, value = get_name_addr(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1777, in get_name_addr
    token, value = get_angle_addr(value)
      ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1702, in get_angle_addr
    raise errors.HeaderParseError(
    ^^^^^^^^^^^^^^^^^
email.errors.HeaderParseError: expected angle-addr but found '@'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/unittest/case.py", line 58, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/unittest/case.py", line 539, in subTest
    yield
  File "tests/mail/tests.py", line 3070, in test_avoids_sending_to_invalid_addresses
    backend.send_messages([email])
    ^^^^^^^^^^^^^^^^^
  File "django/core/mail/backends/smtp.py", line 138, in send_messages
    sent = self._send(message)
    ^^^^^^^^^^^^^^^^^
  File "django/core/mail/backends/smtp.py", line 151, in _send
    recipients = [self.prep_address(addr) for addr in email_message.recipients()]
    ^^^^^^^^^^^^^^^^^
  File "django/core/mail/backends/smtp.py", line 172, in prep_address
    parsed = AddressHeader.value_parser(address)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/headerregistry.py", line 333, in value_parser
    address_list, value = parser.get_address_list(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1988, in get_address_list
    token, value = get_address(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1968, in get_address
    token, value = get_mailbox(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1794, in get_mailbox
    token, value = get_addr_spec(value)
    ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1647, in get_addr_spec
    token, value = get_domain(value[1:])
      ^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/email/_header_value_parser.py", line 1604, in get_domain
    if value[0] in CFWS_LEADER:
      ^^^^^^^^^^^^^^^^^
IndexError: string index out of range

Change History (6)

comment:1 by Mahdi Dehghan, 3 weeks ago

I'd like to work on this issue. I have a fix ready.

comment:2 by Mahdi Dehghan, 3 weeks ago

Owner: set to Mahdi Dehghan
Status: newassigned

comment:3 by Mahdi Dehghan, 3 weeks ago

Last edited 3 weeks ago by Mahdi Dehghan (previous) (diff)

comment:4 by Mariusz Felisiak, 3 weeks ago

Description: modified (diff)

comment:5 by Jacob Walls, 3 weeks ago

Resolution: needsinfo
Status: assignedclosed

Thanks -- what version of Python 3.12 was this? We only officially support the latest point release, which at the time I write is 3.12.12.

comment:6 by Mike Edmunds, 2 weeks ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top