python-pydantic/CVE-2024-3772.patch

121 lines
4.2 KiB
Diff

Index: pydantic-1.10.7/pydantic/networks.py
===================================================================
--- pydantic-1.10.7.orig/pydantic/networks.py
+++ pydantic-1.10.7/pydantic/networks.py
@@ -701,7 +701,19 @@ class IPvAnyNetwork(_BaseNetwork): # ty
raise errors.IPvAnyNetworkError()
-pretty_email_regex = re.compile(r'([\w ]*?) *<(.*)> *')
+def _build_pretty_email_regex() -> re.Pattern[str]:
+ name_chars = r'[\w!#$%&\'*+\-/=?^_`{|}~]'
+ unquoted_name_group = rf'((?:{name_chars}+\s+)*{name_chars}+)'
+ quoted_name_group = r'"((?:[^"]|\")+)"'
+ email_group = r'<(.+)>'
+ return re.compile(rf'\s*(?:{unquoted_name_group}|{quoted_name_group})?\s*{email_group}\s*')
+
+pretty_email_regex = _build_pretty_email_regex()
+
+MAX_EMAIL_LENGTH = 2048
+"""Maximum length for an email.
+A somewhat arbitrary but very generous number compared to what is allowed by most implementations.
+"""
def validate_email(value: Union[str]) -> Tuple[str, str]:
@@ -718,10 +730,14 @@ def validate_email(value: Union[str]) ->
if email_validator is None:
import_email_validator()
+ if len(value) > MAX_EMAIL_LENGTH:
+ raise errors.EmailError()
+
m = pretty_email_regex.fullmatch(value)
- name: Optional[str] = None
+ 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()
Index: pydantic-1.10.7/tests/test_networks.py
===================================================================
--- pydantic-1.10.7.orig/tests/test_networks.py
+++ pydantic-1.10.7/tests/test_networks.py
@@ -1,3 +1,5 @@
+from typing import Union
+
import pytest
from pydantic import (
@@ -15,6 +17,7 @@ from pydantic import (
PostgresDsn,
RedisDsn,
ValidationError,
+ EmailError,
stricturl,
)
from pydantic.networks import validate_email
@@ -768,31 +771,37 @@ def test_address_valid(value, name, emai
@pytest.mark.skipif(not email_validator, reason='email_validator not installed')
@pytest.mark.parametrize(
- 'value',
+ 'value,reason',
[
- 'f oo.bar@example.com ',
- 'foo.bar@exam\nple.com ',
- 'foobar',
- 'foobar <foobar@example.com',
- '@example.com',
- 'foobar@.example.com',
- 'foobar@.com',
- 'foo bar@example.com',
- 'foo@bar@example.com',
- '\n@example.com',
- '\r@example.com',
- '\f@example.com',
- ' @example.com',
- '\u0020@example.com',
- '\u001f@example.com',
- '"@example.com',
- '\"@example.com',
- ',@example.com',
- 'foobar <foobar<@example.com>',
+ ('@example.com', 'There must be something before the @-sign.'),
+ ('f oo.bar@example.com', 'The email address contains invalid characters before the @-sign'),
+ ('foobar', 'An email address must have an @-sign.'),
+ ('foobar@localhost', 'The part after the @-sign is not valid. It should have a period.'),
+ ('foobar@127.0.0.1', 'The part after the @-sign is not valid. It is not within a valid top-level domain.'),
+ ('foo.bar@exam\nple.com ', None),
+ ('foobar <foobar@example.com', None),
+ ('foobar@.example.com', None),
+ ('foobar@.com', None),
+ ('foo bar@example.com', None),
+ ('foo@bar@example.com', None),
+ ('\n@example.com', None),
+ ('\r@example.com', None),
+ ('\f@example.com', None),
+ (' @example.com', None),
+ ('\u0020@example.com', None),
+ ('\u001f@example.com', None),
+ ('"@example.com', None),
+ (',@example.com', None),
+ ('foobar <foobar<@example.com>', None),
+ ('foobar <foobar@example.com>>', None),
+ ('foobar <<foobar<@example.com>', None),
+ ('foobar <>', None),
+ ('first.last <first.last@example.com>', None),
+ pytest.param('foobar <' + 'a' * 4096 + '@example.com>', 'Length must not exceed 2048 characters', id='long'),
],
)
-def test_address_invalid(value):
- with pytest.raises(ValueError):
+def test_address_invalid(value: str, reason: Union[str, None]):
+ with pytest.raises(EmailError):
validate_email(value)