Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ In Development
* The `original` field of the returned validated email object incorrectly un-escaped the local part of the email address. Now it returns the original address, without any display name, as passed to validate_email.
* add support python3.15
* The IPv6 tag of a domain literal is now case-insensitive, as in the RFCs, so `me@[ipv6:::1]` is accepted (and normalized to `me@[IPv6:::1]`) when `allow_domain_literal=True`.
* Internationalized domains with a right-to-left label are now rejected if any other label breaks the IDNA Bidi Rule (RFC 5893), such as a label starting with a digit. Such domains, like `1.xn--4dbc.com` and `xn--4dbc.1.com` (`1.אב.com` and `אב.1.com` in Unicode), could be displayed identically.

2.3.0 (August 26, 2025)
-----------------------
Expand Down
14 changes: 14 additions & 0 deletions email_validator/syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,20 @@ def validate_email_domain_name(domain: str, test_environment: bool = False, glob
raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".")
check_unsafe_chars(domain_i18n)

# If any label has right-to-left characters, the Bidi Rule applies to
# every label in the domain, including left-to-right ones (RFC 5893
# Section 1.4 and 2, UTS #46 CheckBidi). The idna package only checks
# each label on its own, so a label like "1" next to a right-to-left
# label was accepted, and such domains can be displayed identically to
# a different domain (e.g. 1.xn--4dbc.com and xn--4dbc.1.com, which
# are 1.אב.com and אב.1.com in Unicode, with Hebrew alef bet).
if any(unicodedata.bidirectional(c) in ("R", "AL", "AN") for c in domain_i18n):
for label in domain_i18n.split("."):
try:
idna.check_bidi(label, check_ltr=True)
except idna.IDNAError as e:
raise EmailSyntaxError(f"The part after the @-sign is not valid IDNA ({e}).") from e

# Check that it can be encoded back to IDNA ASCII. We have no test
# case for this.
try:
Expand Down
7 changes: 6 additions & 1 deletion tests/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def test_domain_literal() -> None:
('meme@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.com', 'The email address is too long (4 characters too many).'),
('my.long.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444.info', 'The email address is too long (2 characters too many).'),
('my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (1-2 characters too many).'),
('my.long.address@\uFB2C111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (1-3 characters too many).'),
('my.long.address@\uFB2C111111111222222222233333333334444444444555555555.a666666666777777777788888888889999999999000000000.a111111111222222222233333333334444444444555555555.a666666666777777777788888888889999999999000000000.a111111111222222222233333333334444.info', 'The email address is too long (1-3 characters too many).'),
('my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444.info', 'The email address is too long (1 character too many).'),
('my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (1-2 characters too many).'),
('my.\u0073\u0323\u0307.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (1-2 characters too many).'),
Expand All @@ -489,6 +489,11 @@ def test_domain_literal() -> None:
('me@bad-tld-1', 'The part after the @-sign is not valid. It should have a period.'),
('me@bad.tld-2', 'The part after the @-sign is not valid. It is not within a valid top-level domain.'),
('me@xn--0.tld', 'The part after the @-sign is not valid IDNA (Invalid A-label).'),
# 1.xn--4dbc.com is 1.אב.com and xn--4dbc.1.com is אב.1.com
# (alef bet in Hebrew). Both display as the same string, so both are rejected.
('me@1.xn--4dbc.com', 'The part after the @-sign is not valid IDNA (First codepoint in label \'1\' must be directionality L, R or AL).'),
('me@xn--4dbc.1.com', 'The part after the @-sign is not valid IDNA (First codepoint in label \'1\' must be directionality L, R or AL).'),
('me@1.xn--4dbrk0ce', 'The part after the @-sign is not valid IDNA (First codepoint in label \'1\' must be directionality L, R or AL).'),
('me@yy--0.tld', 'An email address cannot have two letters followed by two dashes immediately after the @-sign or after a period, except Punycode.'),
('me@yy--0.tld', 'An email address cannot have two letters followed by two dashes immediately after the @-sign or after a period, except Punycode.'),
('me@[127.0.0.1]', 'A bracketed IP address after the @-sign is not allowed here.'),
Expand Down
Loading