From 1ead58173cfa246334202cbc1d3f399b3477710d Mon Sep 17 00:00:00 2001 From: StasonJatham Date: Sun, 20 Sep 2026 15:54:50 +0200 Subject: [PATCH] Tighten parser email validation with regression tests --- collectionparser/parser.py | 13 ++++++------- tests/parser_test.py | 5 +++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/collectionparser/parser.py b/collectionparser/parser.py index 852c67b..0450ade 100644 --- a/collectionparser/parser.py +++ b/collectionparser/parser.py @@ -16,19 +16,18 @@ from collectionparser.utils import PasswordType, filter_printable, find_files, s # Separators tried in order; the duplicate ',' of the original is dropped. VALID_SEPARATORS = (":", ";", " ", "\t", " ", " ", ", ", ",", ";;", "::") -# Same shape as the old `validate_email` dependency: rejects '<', '/', '>' etc. -_EMAIL_RE = re.compile( - r"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*" - r"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$", - re.IGNORECASE, -) +# Email check: word/hyphen/dot local part, dotted word/hyphen domain labels, +# 2..8 char TLD. `re.ASCII` keeps `\w` to [A-Za-z0-9_], so symbol-laden junk +# like `!!!!@yahoo.com` is rejected. +_EMAIL_RE = re.compile(r"^[\w.-]+@([\w-]+\.)+[\w-]{2,8}$", re.ASCII) _IP_RE = re.compile(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") _HEX = frozenset(string.hexdigits) _HASH_TYPES = {32: PasswordType.MD5, 40: PasswordType.SHA1, 64: PasswordType.SHA256} def valid_email(candidate: str) -> bool: - return candidate.count("@") == 1 and _EMAIL_RE.match(candidate) is not None + """True for a plausible address (``^[\\w.-]+@([\\w-]+\\.)+[\\w-]{2,8}$``).""" + return _EMAIL_RE.match(candidate) is not None def password_type(password: str) -> PasswordType: diff --git a/tests/parser_test.py b/tests/parser_test.py index 854e3b4..1447181 100644 --- a/tests/parser_test.py +++ b/tests/parser_test.py @@ -56,6 +56,11 @@ class ParserTest(unittest.TestCase): self.assertIsNone(p("no at sign at all")) self.assertIsNone(p("plain text line:with:colons")) self.assertIsNotNone(p("user%40domain.com:password")) # %40 still becomes '@' + # Symbol-laden local parts are rejected by the strict ASCII regex. + self.assertIsNone(p("!!!!!!!!!!!!!!!!!!!!!!!!@yahoo.com:12abcd")) + self.assertIsNone(p("john!doe@example.com:pw")) + self.assertIsNone(p("user+tag@gmail.com:pw")) # '+' not allowed by the regex + self.assertIsNotNone(p("a_b@example.com:pw")) self.assertIsNotNone( p("hookturn54@hotmail.com:3318flindo2003@yahoo.ca:victory") )