diff --git a/pydantic/networks.py b/pydantic/networks.py index 920088b6e5..380c32b1a6 100644 --- a/pydantic/networks.py +++ b/pydantic/networks.py @@ -376,7 +376,15 @@ def _validate(cls, __input_value: NetworkType, _: core_schema.ValidationInfo) -> return cls(__input_value) # type: ignore[return-value] -pretty_email_regex = re.compile(r' *([\w ]*?) *<(.+?)> *') +def _build_pretty_email_regex() -> re.Pattern: + name_chars = r'[\w!#$%&\'*+\-/=?^_`{|}~]' + unquoted_name_group = fr'((?:{name_chars}+\s+)*{name_chars}+)' + quoted_name_group = r'"((?:[^"]|\")+)"' + email_group = r'<\s*(.+)\s*>' + return re.compile(rf'\s*(?:{unquoted_name_group}|{quoted_name_group})?\s*{email_group}\s*') + + +pretty_email_regex = _build_pretty_email_regex() def validate_email(value: str) -> tuple[str, str]: @@ -395,7 +403,8 @@ def validate_email(value: str) -> tuple[str, str]: m = pretty_email_regex.fullmatch(value) name: str | None = None if m: - name, value = m.groups() + unquoted_name, quoted_name, value = m.groups() + name = unquoted_name or quoted_name email = value.strip() diff --git a/tests/test_networks.py b/tests/test_networks.py index 92a1115757..f636d908cc 100644 --- a/tests/test_networks.py +++ b/tests/test_networks.py @@ -751,6 +751,8 @@ class Model(BaseModel): ('аррӏе@example.com', 'аррӏе', 'аррӏе@example.com'), ('xn--80ak6aa92e@example.com', 'xn--80ak6aa92e', 'xn--80ak6aa92e@example.com'), ('葉士豪@臺網中心.tw', '葉士豪', '葉士豪@臺網中心.tw'), + ('"first.last" ', 'first.last', 'first.last@example.com'), + ("Shaquille O'Neal ", "Shaquille O'Neal", 'shaq@example.com'), ], ) def test_address_valid(value, name, email): @@ -785,6 +787,7 @@ def test_address_valid(value, name, email): ('foobar >', None), ('foobar <', None), ('foobar <>', None), + ('first.last ', None), ], ) def test_address_invalid(value, reason):