(email: str)
| 12 | |
| 13 | |
| 14 | def split_email(email: str) -> Tuple[Optional[str], str, str, bool]: |
| 15 | # Return the display name, unescaped local part, and domain part |
| 16 | # of the address, and whether the local part was quoted. If no |
| 17 | # display name was present and angle brackets do not surround |
| 18 | # the address, display name will be None; otherwise, it will be |
| 19 | # set to the display name or the empty string if there were |
| 20 | # angle brackets but no display name. |
| 21 | |
| 22 | # Typical email addresses have a single @-sign and no quote |
| 23 | # characters, but the awkward "quoted string" local part form |
| 24 | # (RFC 5321 4.1.2) allows @-signs and escaped quotes to appear |
| 25 | # in the local part if the local part is quoted. |
| 26 | |
| 27 | # A `display name <addr>` format is also present in MIME messages |
| 28 | # (RFC 5322 3.4) and this format is also often recognized in |
| 29 | # mail UIs. It's not allowed in SMTP commands or in typical web |
| 30 | # login forms, but parsing it has been requested, so it's done |
| 31 | # here as a convenience. It's implemented in the spirit but not |
| 32 | # the letter of RFC 5322 3.4 because MIME messages allow newlines |
| 33 | # and comments as a part of the CFWS rule, but this is typically |
| 34 | # not allowed in mail UIs (although comment syntax was requested |
| 35 | # once too). |
| 36 | # |
| 37 | # Display names are either basic characters (the same basic characters |
| 38 | # permitted in email addresses, but periods are not allowed and spaces |
| 39 | # are allowed; see RFC 5322 Appendix A.1.2), or or a quoted string with |
| 40 | # the same rules as a quoted local part. (Multiple quoted strings might |
| 41 | # be allowed? Unclear.) Optional space (RFC 5322 3.4 CFWS) and then the |
| 42 | # email address follows in angle brackets. |
| 43 | # |
| 44 | # An initial quote is ambiguous between starting a display name or |
| 45 | # a quoted local part --- fun. |
| 46 | # |
| 47 | # We assume the input string is already stripped of leading and |
| 48 | # trailing CFWS. |
| 49 | |
| 50 | def split_string_at_unquoted_special(text: str, specials: Tuple[str, ...]) -> Tuple[str, str]: |
| 51 | # Split the string at the first character in specials (an @-sign |
| 52 | # or left angle bracket) that does not occur within quotes and |
| 53 | # is not followed by a Unicode combining character. |
| 54 | # If no special character is found, raise an error. |
| 55 | inside_quote = False |
| 56 | escaped = False |
| 57 | left_part = "" |
| 58 | for i, c in enumerate(text): |
| 59 | # < plus U+0338 (Combining Long Solidus Overlay) normalizes to |
| 60 | # ≮ U+226E (Not Less-Than), and it would be confusing to treat |
| 61 | # the < as the start of "<email>" syntax in that case. Likewise, |
| 62 | # if anything combines with an @ or ", we should probably not |
| 63 | # treat it as a special character. |
| 64 | if unicodedata.normalize("NFC", text[i:])[0] != c: |
| 65 | left_part += c |
| 66 | |
| 67 | elif inside_quote: |
| 68 | left_part += c |
| 69 | if c == '\\' and not escaped: |
| 70 | escaped = True |
| 71 | elif c == '"' and not escaped: |
no test coverage detected