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.
:rtype: int
"""
- if text.isdigit():
+ if text.isdecimal():
total = int(text)
elif len(text) == 0:
raise BadTTL
current = 0
need_digit = True
for c in text:
- if c.isdigit():
+ if c.isdecimal():
current *= 10
current += int(c)
need_digit = False
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
-----
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)