(addrinfo: ValidatedEmail)
| 695 | |
| 696 | |
| 697 | def validate_email_length(addrinfo: ValidatedEmail) -> None: |
| 698 | # There are three forms of the email address whose length must be checked: |
| 699 | # |
| 700 | # 1) The original email address string. Since callers may continue to use |
| 701 | # this string, even though we recommend using the normalized form, we |
| 702 | # should not pass validation when the original input is not valid. This |
| 703 | # form is checked first because it is the original input. |
| 704 | # 2) The normalized email address. We perform Unicode NFC normalization of |
| 705 | # the local part, we normalize the domain to internationalized characters |
| 706 | # (if originally IDNA ASCII) which also includes Unicode normalization, |
| 707 | # and we may remove quotes in quoted local parts. We recommend that |
| 708 | # callers use this string, so it must be valid. |
| 709 | # 3) The email address with the IDNA ASCII representation of the domain |
| 710 | # name, since this string may be used with email stacks that don't |
| 711 | # support UTF-8. Since this is the least likely to be used by callers, |
| 712 | # it is checked last. Note that ascii_email will only be set if the |
| 713 | # local part is ASCII, but conceivably the caller may combine a |
| 714 | # internationalized local part with an ASCII domain, so we check this |
| 715 | # on that combination also. Since we only return the normalized local |
| 716 | # part, we use that (and not the unnormalized local part). |
| 717 | # |
| 718 | # In all cases, the length is checked in UTF-8 because the SMTPUTF8 |
| 719 | # extension to SMTP validates the length in bytes. |
| 720 | |
| 721 | addresses_to_check = [ |
| 722 | (addrinfo.original, None), |
| 723 | (addrinfo.normalized, "after normalization"), |
| 724 | ((addrinfo.ascii_local_part or addrinfo.local_part or "") + "@" + addrinfo.ascii_domain, "when the part after the @-sign is converted to IDNA ASCII"), |
| 725 | ] |
| 726 | |
| 727 | for addr, reason in addresses_to_check: |
| 728 | addr_len = len(addr) |
| 729 | addr_utf8_len = len(addr.encode("utf8")) |
| 730 | diff = addr_utf8_len - EMAIL_MAX_LENGTH |
| 731 | if diff > 0: |
| 732 | if reason is None and addr_len == addr_utf8_len: |
| 733 | # If there is no normalization or transcoding, |
| 734 | # we can give a simple count of the number of |
| 735 | # characters over the limit. |
| 736 | reason = get_length_reason(addr, limit=EMAIL_MAX_LENGTH) |
| 737 | elif reason is None: |
| 738 | # If there is no normalization but there is |
| 739 | # some transcoding to UTF-8, we can compute |
| 740 | # the minimum number of characters over the |
| 741 | # limit by dividing the number of bytes over |
| 742 | # the limit by the maximum number of bytes |
| 743 | # per character. |
| 744 | mbpc = max(len(c.encode("utf8")) for c in addr) |
| 745 | mchars = max(1, diff // mbpc) |
| 746 | suffix = "s" if diff > 1 else "" |
| 747 | if mchars == diff: |
| 748 | reason = f"({diff} character{suffix} too many)" |
| 749 | else: |
| 750 | reason = f"({mchars}-{diff} character{suffix} too many)" |
| 751 | else: |
| 752 | # Since there is normalization, the number of |
| 753 | # characters in the input that need to change is |
| 754 | # impossible to know. |
no test coverage detected