]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Raise BadTTL for non-decimal Unicode digits in dns.ttl.from_text(). (#1279)
authorSanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
Fri, 3 Jul 2026 14:48:24 +0000 (07:48 -0700)
committerGitHub <noreply@github.com>
Fri, 3 Jul 2026 14:48:24 +0000 (07:48 -0700)
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
doc/whatsnew.rst
tests/test_ttl.py

index 11f6015b6550c2471e7d8a75abd28412f91469c7..801fb946ddb4bad9622a5f2bbe5aaf2991018cce 100644 (file)
@@ -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
index 76dc07101dd5c7ee9f9fedb6706d5d3344a9024a..40a45a48c92f78671d198adaebb21adbf3284c70 100644 (file)
@@ -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
 -----
 
index 4d17628d24480c888aae05fcbb21b4e8d7907bb8..32537dee52f5d259c5f89f45f4c09fa522433ad9 100644 (file)
@@ -42,3 +42,12 @@ class TTLTestCase(unittest.TestCase):
     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)