From 7eae86a7e5cdd728998bf54e30fdd27c7198a945 Mon Sep 17 00:00:00 2001 From: neha khan Date: Fri, 6 Nov 2026 14:02:08 +0530 Subject: [PATCH] Avoid quadratic backtracking in email and idn-email format regexes Both regexes run on the validated data and backtrack quadratically on long invalid strings. After the @, [^@]+\.[^@]+\Z retries every dot when the string ends in a second @, and the email lookahead (?!.*\.\..*@) rescans the rest of the string for each '..'. Rejecting 'a@' + 'a.' * 40000 + '@' (80 KB) took 24 s for email, and 'a@a' + '.' * 80000 + '@' took 61 s for idn-email. Match the local part as dot-separated runs of [^@.] and check for a dot inside the domain with a single lookahead, so both regexes run in linear time. idn-email accepts the same strings as before. email now also rejects '..' in the local part when a newline sits between it and the @, which the old lookahead's '.' could not see. --- CHANGELOG.txt | 1 + fastjsonschema/draft04.py | 2 +- fastjsonschema/draft07.py | 2 +- tests/test_format.py | 55 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index dd8f8fd..95b7b5b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,7 @@ === UNRELEASED * Fixed `$` in `patternProperties` matching before a trailing newline, which also made a `pattern` with the same regex accept a trailing newline +* Fixed quadratic backtracking in the `email` and `idn-email` format regexes on long invalid strings === 2.22.3 (2026-11-01) diff --git a/fastjsonschema/draft04.py b/fastjsonschema/draft04.py index cba1e48..aa4e1f3 100644 --- a/fastjsonschema/draft04.py +++ b/fastjsonschema/draft04.py @@ -34,7 +34,7 @@ class CodeGeneratorDraft04(CodeGenerator): # library. Some regexps are not 100% precise but good enough, fast and without dependencies. FORMAT_REGEXS = { 'date-time': r'^(?:' + DATE_REGEX + r')(t|T)(?:[01]\d|2[0-3]):[0-5]\d:(?:[0-5]\d|60)(?:\.\d+)?(?:[+-](?:[01]\d|2[0-3]):[0-5]\d|[+-](?:[01]\d|2[0-3])[0-5]\d|z|Z)\Z', - 'email': r'^(?!.*\.\..*@)[^@.][^@]*(?