From 5d3e4136ae7e8225c853128af5c114e14f221e48 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Thu, 11 Sep 2025 00:08:53 -0700 Subject: [PATCH] [3.13] gh-138432: Improved invalid path checking in zoneinfo.reset_tzpath() (GH-138433) * Improve error messages for path-like relative paths and path-like bytes paths. * TZPATH is now always a tuple of strings. (cherry picked from commit 859aecc33b82f45e5b7ae30138d28f2a2f33a575) Co-authored-by: Stephen Morton --- Lib/test/test_zoneinfo/test_zoneinfo.py | 10 ++++++++++ Lib/zoneinfo/_tzpath.py | 7 +++++++ .../2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst | 6 ++++++ 3 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index f7bb2aa151c04a..b4b2b984b1bf36 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -18,6 +18,7 @@ from functools import cached_property from test.support import MISSING_C_DOCSTRINGS, requires_gil_enabled +from test.support.os_helper import FakePath from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase from test.support.import_helper import import_module, CleanImport @@ -1798,6 +1799,7 @@ def test_reset_tzpath_relative_paths(self): ("/usr/share/zoneinfo", "../relative/path",), ("path/to/somewhere", "../relative/path",), ("/usr/share/zoneinfo", "path/to/somewhere", "../relative/path",), + (FakePath("path/to/somewhere"),) ] for input_paths in bad_values: with self.subTest(input_paths=input_paths): @@ -1809,6 +1811,9 @@ def test_tzpath_type_error(self): "/etc/zoneinfo:/usr/share/zoneinfo", b"/etc/zoneinfo:/usr/share/zoneinfo", 0, + (b"/bytes/path", "/valid/path"), + (FakePath(b"/bytes/path"),), + (0,), ] for bad_value in bad_values: @@ -1819,6 +1824,7 @@ def test_tzpath_type_error(self): def test_tzpath_attribute(self): tzpath_0 = [f"{DRIVE}/one", f"{DRIVE}/two"] tzpath_1 = [f"{DRIVE}/three"] + tzpath_pathlike = (FakePath(f"{DRIVE}/usr/share/zoneinfo"),) with self.tzpath_context(tzpath_0): query_0 = self.module.TZPATH @@ -1826,8 +1832,12 @@ def test_tzpath_attribute(self): with self.tzpath_context(tzpath_1): query_1 = self.module.TZPATH + with self.tzpath_context(tzpath_pathlike): + query_pathlike = self.module.TZPATH + self.assertSequenceEqual(tzpath_0, query_0) self.assertSequenceEqual(tzpath_1, query_1) + self.assertSequenceEqual(tuple([os.fspath(p) for p in tzpath_pathlike]), query_pathlike) class CTzPathTest(TzPathTest): diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 5db17bea045d8c..d8ccec82059391 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -13,6 +13,13 @@ def _reset_tzpath(to=None, stacklevel=4): + f"not {type(tzpaths)}: {tzpaths!r}" ) + tzpaths = [os.fspath(p) for p in tzpaths] + if not all(isinstance(p, str) for p in tzpaths): + raise TypeError( + "All elements of a tzpath sequence must be strings or " + "os.PathLike objects which convert to strings." + ) + if not all(map(os.path.isabs, tzpaths)): raise ValueError(_get_invalid_paths_message(tzpaths)) base_tzpath = tzpaths diff --git a/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst new file mode 100644 index 00000000000000..b48b978a6d5c75 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-03-15-20-10.gh-issue-138432.RMc7UX.rst @@ -0,0 +1,6 @@ +:meth:`zoneinfo.reset_tzpath` will now convert any :class:`os.PathLike` objects +it receives into strings before adding them to ``TZPATH``. It will raise +``TypeError`` if anything other than a string is found after this conversion. +If given an :class:`os.PathLike` object that represents a relative path, it +will now raise ``ValueError`` instead of ``TypeError``, and present a more +informative error message.