]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-152248: Reject a POSIX TZ abbreviation with non-ASCII-letters in pure-Pytho...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 30 Jun 2026 08:57:58 +0000 (10:57 +0200)
committerGitHub <noreply@github.com>
Tue, 30 Jun 2026 08:57:58 +0000 (08:57 +0000)
(cherry picked from commit 449122ed0dbdb6a545af4927c59f4c80ee15c515)

Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
Co-authored-by: Stan Ulbrych <stan@python.org>
Lib/test/test_zoneinfo/test_zoneinfo.py
Lib/zoneinfo/_zoneinfo.py
Misc/NEWS.d/next/Library/2026-06-26-13-39-11.gh-issue-152248.N2Rmaf.rst [new file with mode: 0644]
Modules/_zoneinfo.c

index cf3f82db75b0b60e9edc0494034b5982db1b499d..813b56501308f9e0e37578560b5ffa3cba3cf9e8 100644 (file)
@@ -1009,14 +1009,14 @@ class TZStrTest(ZoneInfoTestBase):
 
         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 @@ class TZStrTest(ZoneInfoTestBase):
             "+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 @@ class TZStrTest(ZoneInfoTestBase):
                 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
index 52832f600c304487808b5d5c1d4d23ab6d5c7ddb..90cf2bbf8f5d0d1bddfb3aa404449350fcc04d8b 100644 (file)
@@ -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 (file)
index 0000000..8921ad1
--- /dev/null
@@ -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.
index eaffd020ed97c09cb028f418017e09ddbd301590..2a7ac4498261e08c4e28c41f1fe285b2391d5241 100644 (file)
@@ -1762,6 +1762,9 @@ parse_abbr(const char **p, PyObject **abbr)
             ptr++;
         }
         str_end = ptr;
+        if (str_end == str_start) {
+            return -1;
+        }
         ptr++;
     }
     else {