Validates the syntax of the domain part of an email address.
(domain: str, test_environment: bool = False, globally_deliverable: bool = True)
| 496 | |
| 497 | |
| 498 | def validate_email_domain_name(domain: str, test_environment: bool = False, globally_deliverable: bool = True) -> DomainNameValidationResult: |
| 499 | """Validates the syntax of the domain part of an email address.""" |
| 500 | |
| 501 | # Check for invalid characters. |
| 502 | # (RFC 952 plus RFC 6531 section 3.3 for internationalized addresses) |
| 503 | bad_chars = { |
| 504 | safe_character_display(c) |
| 505 | for c in domain |
| 506 | if not ATEXT_HOSTNAME_INTL.match(c) |
| 507 | } |
| 508 | if bad_chars: |
| 509 | raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".") |
| 510 | |
| 511 | # Check for unsafe characters. |
| 512 | # Some of this may be redundant with the range U+0080 to U+10FFFF that is checked |
| 513 | # by DOT_ATOM_TEXT_INTL. Other characters may be permitted by the email specs, but |
| 514 | # they may not be valid, safe, or sensible Unicode strings. |
| 515 | check_unsafe_chars(domain) |
| 516 | |
| 517 | # Reject characters that would be rejected by UTS-46 normalization next but |
| 518 | # with an error message under our control. |
| 519 | bad_chars = { |
| 520 | safe_character_display(c) for c in domain |
| 521 | if not uts46_valid_char(c) |
| 522 | } |
| 523 | if bad_chars: |
| 524 | raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".") |
| 525 | |
| 526 | # Perform UTS-46 normalization, which includes casefolding, NFC normalization, |
| 527 | # and converting all label separators (the period/full stop, fullwidth full stop, |
| 528 | # ideographic full stop, and halfwidth ideographic full stop) to regular dots. |
| 529 | # It will also raise an exception if there is an invalid character in the input, |
| 530 | # such as "⒈" which is invalid because it would expand to include a dot and |
| 531 | # U+1FEF which normalizes to a backtick, which is not an allowed hostname character. |
| 532 | # Since several characters *are* normalized to a dot, this has to come before |
| 533 | # checks related to dots, like check_dot_atom which comes next. |
| 534 | original_domain = domain |
| 535 | try: |
| 536 | domain = idna.uts46_remap(domain, std3_rules=False, transitional=False) |
| 537 | except idna.IDNAError as e: |
| 538 | raise EmailSyntaxError(f"The part after the @-sign contains invalid characters ({e}).") from e |
| 539 | |
| 540 | # Check for invalid characters after Unicode normalization which are not caught |
| 541 | # by uts46_remap (see tests for examples). |
| 542 | bad_chars = { |
| 543 | safe_character_display(c) |
| 544 | for c in domain |
| 545 | if not ATEXT_HOSTNAME_INTL.match(c) |
| 546 | } |
| 547 | if bad_chars: |
| 548 | raise EmailSyntaxError("The part after the @-sign contains invalid characters after Unicode normalization: " + ", ".join(sorted(bad_chars)) + ".") |
| 549 | |
| 550 | # The domain part is made up dot-separated "labels." Each label must |
| 551 | # have at least one character and cannot start or end with dashes, which |
| 552 | # means there are some surprising restrictions on periods and dashes. |
| 553 | # Check that before we do IDNA encoding because the IDNA library gives |
| 554 | # unfriendly errors for these cases, but after UTS-46 normalization because |
| 555 | # it can insert periods and hyphens (from fullwidth characters). |
no test coverage detected