https://github.com/python/cpython/commit/f71ce97db92d7f22542271bd49bdba392b27792e commit: f71ce97db92d7f22542271bd49bdba392b27792e branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: StanFromIreland <[email protected]> date: 2026-06-30T08:54:16Z summary:
[3.14] gh-152248: Reject a POSIX TZ abbreviation with non-ASCII-letters in pure-Python `zoneinfo` (GH-152249) (#152651) (cherry picked from commit 449122ed0dbdb6a545af4927c59f4c80ee15c515) Co-authored-by: tonghuaroot (童话) <[email protected]> Co-authored-by: Stan Ulbrych <[email protected]> files: A Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst M Lib/test/test_zoneinfo/test_zoneinfo.py M Lib/zoneinfo/_zoneinfo.py M Modules/_zoneinfo.c diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 0769f90786a1e5..b37ab0cd02df2c 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -1009,14 +1009,14 @@ def populate_tzstr_header(cls): cls._tzif_header = bytes(out) - def zone_from_tzstr(self, tzstr): + def zone_from_tzstr(self, tzstr, encoding="ascii"): """Creates a zoneinfo file following a POSIX rule.""" zonefile = io.BytesIO(self._tzif_header) zonefile.seek(0, 2) # Write the footer zonefile.write(b"\x0A") - zonefile.write(tzstr.encode("ascii")) + zonefile.write(tzstr.encode(encoding)) zonefile.write(b"\x0A") zonefile.seek(0) @@ -1150,6 +1150,13 @@ def test_invalid_tzstr(self): "+11", # Unquoted alphanumeric "GMT,M3.2.0/2,M11.1.0/3", # Transition rule but no DST "GMT0+11,M3.2.0/2,M11.1.0/3", # Unquoted alphanumeric in DST + # Unquoted abbreviation with embedded or leading whitespace + "AB C3", + " A B 3", + "AAA4BB B,J60/2,J300/2", # Embedded whitespace in DST + # Empty quoted abbreviation + "<>5", + "AAA4<>,M3.2.0/2,M11.1.0/3", "PST8PDT,M3.2.0/2", # Only one transition rule # Invalid offset hours "AAA168", @@ -1232,6 +1239,15 @@ def test_invalid_tzstr(self): with self.assertRaisesRegex(ValueError, tzstr_regex): self.zone_from_tzstr(invalid_tzstr) + def test_invalid_tzstr_non_ascii_abbr(self): + tzstr = "ABÀC3" + if self.module is py_zoneinfo: + expected = re.escape(tzstr) + else: + expected = re.escape(repr(tzstr.encode("utf-8"))) + with self.assertRaisesRegex(ValueError, expected): + self.zone_from_tzstr(tzstr, encoding="utf-8") + @classmethod def _populate_test_cases(cls): # This method uses a somewhat unusual style in that it populates the diff --git a/Lib/zoneinfo/_zoneinfo.py b/Lib/zoneinfo/_zoneinfo.py index 52832f600c3044..90cf2bbf8f5d0d 100644 --- a/Lib/zoneinfo/_zoneinfo.py +++ b/Lib/zoneinfo/_zoneinfo.py @@ -640,11 +640,11 @@ def _parse_tz_str(tz_str): parser_re = re.compile( r""" - (?P<std>[^<0-9:.+-]+|<[a-zA-Z0-9+-]+>) + (?P<std>[a-zA-Z]+|<[a-zA-Z0-9+-]+>) (?: (?P<stdoff>[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?) (?: - (?P<dst>[^0-9:.+-]+|<[a-zA-Z0-9+-]+>) + (?P<dst>[a-zA-Z]+|<[a-zA-Z0-9+-]+>) (?P<dstoff>[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?)? )? # dst )? # stdoff diff --git a/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst b/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst new file mode 100644 index 00000000000000..8921ad17349e97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst @@ -0,0 +1,3 @@ +Make the C and pure-Python :mod:`zoneinfo` parsers validate POSIX TZ +abbreviations consistently, rejecting unquoted abbreviations with non-letter +characters and empty quoted abbreviations. Patch by tonghuaroot. diff --git a/Modules/_zoneinfo.c b/Modules/_zoneinfo.c index 8dcf4252956d2a..f3fb5127773fa2 100644 --- a/Modules/_zoneinfo.c +++ b/Modules/_zoneinfo.c @@ -1759,6 +1759,9 @@ parse_abbr(const char **p, PyObject **abbr) ptr++; } str_end = ptr; + if (str_end == str_start) { + return -1; + } ptr++; } else { _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
