diff --git a/dns/ttl.py b/dns/ttl.py index 11f6015b..801fb946 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 76dc0710..40a45a48 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 4d17628d..32537dee 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)