From 5461f89264b08eb03703408c7d8dfe89230eae85 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Fri, 3 Jul 2026 06:55:57 -0700 Subject: [PATCH] Raise BadTTL for non-decimal Unicode digits in dns.ttl.from_text(). from_text() gated its integer parsing on str.isdigit(), then handed the text to int(). str.isdigit() is True for Unicode "digit" characters whose category is No (e.g. the superscript "\u00b2" or the Ethiopic "\u1369"), but int() only accepts decimal digits, so such input escaped the parser's validation and raised a bare ValueError instead of the documented dns.ttl.BadTTL. This was reachable through the public zone-file parser: a resource-record TTL or a $TTL directive containing such a character crashed dns.zone.from_text() with a ValueError rather than a clean dns.exception.SyntaxError. Switch both isdigit() checks to isdecimal(). For a single character, int()-acceptance is exactly equivalent to isdecimal(), and isdecimal() is a strict subset of isdigit(), so every previously valid TTL still parses while the non-decimal digits are now rejected as BadTTL. Add a regression test covering the fast (all-digit) path and the BIND-style units path. --- dns/ttl.py | 4 ++-- doc/whatsnew.rst | 6 ++++++ tests/test_ttl.py | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/dns/ttl.py b/dns/ttl.py index 11f6015b6..801fb946d 100644 --- a/dns/ttl.py +++ b/dns/ttl.py @@ -42,7 +42,7 @@ def from_text(text: str) -> int: :rtype: int """ - if text.isdigit(): + if text.isdecimal(): total = int(text) elif len(text) == 0: raise BadTTL @@ -51,7 +51,7 @@ def from_text(text: str) -> int: current = 0 need_digit = True for c in text: - if c.isdigit(): + if c.isdecimal(): current *= 10 current += int(c) need_digit = False diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 76dc07101..40a45a48c 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -19,6 +19,12 @@ TBD is the bit position, and dns.flags.from_text() / edns_from_text() parse that form back, so the conversions round-trip. See issue #1264. +* dns.ttl.from_text() now raises dns.ttl.BadTTL, rather than leaking a bare + ValueError, when the text contains a non-decimal Unicode "digit" (e.g. the + superscript ``\u00b2``). Such characters are accepted by ``str.isdigit()`` but + rejected by ``int()``, so they previously escaped the parser's validation. This + also makes zone files with such a TTL fail with a clean dns.exception.SyntaxError. + 2.8.0 ----- diff --git a/tests/test_ttl.py b/tests/test_ttl.py index 4d17628d2..32537dee5 100644 --- a/tests/test_ttl.py +++ b/tests/test_ttl.py @@ -42,3 +42,12 @@ def test_ttl_technically_too_big(self): def test_empty(self): with self.assertRaises(dns.ttl.BadTTL): dns.ttl.from_text("") + + def test_non_decimal_unicode_digits(self): + # str.isdigit() is True for Unicode "digit" characters (e.g. the + # superscript "\u00b2" or the Ethiopic "\u1369") that int() cannot + # convert, so these must be rejected as a BadTTL rather than leaking a + # bare ValueError. + for text in ("\u00b2", "1\u00b2", "\u00b2w", "1\u00b2s", "\u1369"): + with self.assertRaises(dns.ttl.BadTTL): + dns.ttl.from_text(text)