From 2b8df4155de3f26b2b1bac72da3b167295d0a1a2 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 11 Oct 2026 04:09:21 +0800 Subject: [PATCH] Reject a trailing newline in email local parts --- src/validators/email.py | 2 +- tests/test_email.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/validators/email.py b/src/validators/email.py index cba44533..46cc5946 100644 --- a/src/validators/email.py +++ b/src/validators/email.py @@ -83,7 +83,7 @@ def email( rfc_2782=rfc_2782, ) ) - if re.match( + if re.fullmatch( # 2026-10-11: Validate the complete local part. # extended latin r"(^[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF]" # dot-atom diff --git a/tests/test_email.py b/tests/test_email.py index 56c95f37..990ef8ee 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -56,3 +56,13 @@ def test_returns_true_on_valid_email(value: str): def test_returns_failed_validation_on_invalid_email(value: str): """Test returns failed validation on invalid email.""" assert isinstance(email(value), ValidationError) + + +@pytest.mark.parametrize( + "local_part", ["person", "person.name", "\u0141\u00f3ka\u015b\u0107", '"person"'] +) +def test_rejects_newline_after_local_part(local_part: str): + """Test the whole local part, including its final character.""" + # 2026-10-11: A newline before the at-sign is not part of a valid local part. + assert email(f"{local_part}@example.com") is True + assert isinstance(email(f"{local_part}\n@example.com"), ValidationError)