(s: str, allow_space: bool = False)
| 390 | |
| 391 | |
| 392 | def check_unsafe_chars(s: str, allow_space: bool = False) -> None: |
| 393 | # Check for unsafe characters or characters that would make the string |
| 394 | # invalid or non-sensible Unicode. |
| 395 | bad_chars = set() |
| 396 | for i, c in enumerate(s): |
| 397 | category = unicodedata.category(c) |
| 398 | if category[0] in ("L", "N", "P", "S"): |
| 399 | # Letters, numbers, punctuation, and symbols are permitted. |
| 400 | pass |
| 401 | elif category[0] == "M": |
| 402 | # Combining character in first position would combine with something |
| 403 | # outside of the email address if concatenated, so they are not safe. |
| 404 | # We also check if this occurs after the @-sign, which would not be |
| 405 | # sensible because it would modify the @-sign. |
| 406 | if i == 0: |
| 407 | bad_chars.add(c) |
| 408 | elif category == "Zs": |
| 409 | # Spaces outside of the ASCII range are not specifically disallowed in |
| 410 | # internationalized addresses as far as I can tell, but they violate |
| 411 | # the spirit of the non-internationalized specification that email |
| 412 | # addresses do not contain ASCII spaces when not quoted. Excluding |
| 413 | # ASCII spaces when not quoted is handled directly by the atom regex. |
| 414 | # |
| 415 | # In quoted-string local parts, spaces are explicitly permitted, and |
| 416 | # the ASCII space has category Zs, so we must allow it here, and we'll |
| 417 | # allow all Unicode spaces to be consistent. |
| 418 | if not allow_space: |
| 419 | bad_chars.add(c) |
| 420 | elif category[0] == "Z": |
| 421 | # The two line and paragraph separator characters (in categories Zl and Zp) |
| 422 | # are not specifically disallowed in internationalized addresses |
| 423 | # as far as I can tell, but they violate the spirit of the non-internationalized |
| 424 | # specification that email addresses do not contain line breaks when not quoted. |
| 425 | bad_chars.add(c) |
| 426 | elif category[0] == "C": |
| 427 | # Control, format, surrogate, private use, and unassigned code points (C) |
| 428 | # are all unsafe in various ways. Control and format characters can affect |
| 429 | # text rendering if the email address is concatenated with other text. |
| 430 | # Bidirectional format characters are unsafe, even if used properly, because |
| 431 | # they cause an email address to render as a different email address. |
| 432 | # Private use characters do not make sense for publicly deliverable |
| 433 | # email addresses. |
| 434 | bad_chars.add(c) |
| 435 | else: |
| 436 | # All categories should be handled above, but in case there is something new |
| 437 | # to the Unicode specification in the future, reject all other categories. |
| 438 | bad_chars.add(c) |
| 439 | if bad_chars: |
| 440 | raise EmailSyntaxError("The email address contains unsafe characters: " |
| 441 | + ", ".join(safe_character_display(c) for c in sorted(bad_chars)) + ".") |
| 442 | |
| 443 | |
| 444 | def check_dot_atom(label: str, start_descr: str, end_descr: str, is_hostname: bool) -> None: |
no test coverage detected