From 99c2eaa6d4012bd949b4528817c5a475bbe6ed5f Mon Sep 17 00:00:00 2001 From: yuanx749 Date: Sat, 4 Jul 2026 00:26:06 +0300 Subject: [PATCH 1/4] Add test --- Lib/test/test_tarfile.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 2998a3667b4d17..692f97d1f71130 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -254,6 +254,14 @@ def test_add_dir_getmember(self): self.add_dir_and_getmember('bar') self.add_dir_and_getmember('a'*101) + def test_extract_name_with_trailing_slash(self): + with tarfile.open(tmpname, 'w') as tar: + tar.addfile(tarfile.TarInfo('./mydir/')) + with tarfile.open(tmpname) as tar: + names = tar.getnames() + self.assertEqual(names, ['./mydir/']) + tar.extract(names[0], TEMPDIR, filter='fully_trusted') + @unittest.skipUnless(hasattr(os, "getuid") and hasattr(os, "getgid"), "Missing getuid or getgid implementation") def add_dir_and_getmember(self, name): From 83bf44e05b0742a3e11e68ef5b1d9d54aa6c58ec Mon Sep 17 00:00:00 2001 From: yuanx749 Date: Sat, 4 Jul 2026 00:26:15 +0300 Subject: [PATCH 2/4] Fix lookup --- Lib/tarfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 385dbb536d8a7d..844a2aa25fdccf 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2174,7 +2174,9 @@ def getmember(self, name): than once in the archive, its last occurrence is assumed to be the most up-to-date version. """ - tarinfo = self._getmember(name.rstrip('/')) + tarinfo = self._getmember(name) + if tarinfo is None and name.endswith('/'): + tarinfo = self._getmember(name.rstrip('/')) if tarinfo is None: raise KeyError("filename %r not found" % name) return tarinfo From 05aa6389673ee20fa12a1be17a5b815de41f4410 Mon Sep 17 00:00:00 2001 From: yuanx749 Date: Sat, 4 Jul 2026 00:26:26 +0300 Subject: [PATCH 3/4] Add NEWS --- .../Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst b/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst new file mode 100644 index 00000000000000..51957a73b5603b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst @@ -0,0 +1,3 @@ +Fix :meth:`tarfile.TarFile.extract` to accept archive member names with a +trailing forward slash, including those returned by +:meth:`tarfile.TarFile.getnames`. Contributed by Xiao Yuan. From 14689d50128406f73ffd5eb383ad9de6d4a2bafa Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 8 Sep 2026 03:53:10 +0000 Subject: [PATCH 4/4] gh-127636: Clarify the trailing-slash tarfile test Note that './mydir/' is a regular-file member, not DIRTYPE, so a future reader does not turn it into a directory and lose the regression coverage. Also assert the member actually extracts as a file rather than only checking that extract() does not raise. --- Lib/test/test_tarfile.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index e6f21f14fd45aa..89f37dd47dcdd4 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -255,12 +255,17 @@ def test_add_dir_getmember(self): self.add_dir_and_getmember('a'*101) def test_extract_name_with_trailing_slash(self): + # gh-127636: './mydir/' is deliberately a regular-file member + # (REGTYPE, not DIRTYPE) whose stored name ends in a slash. It + # extracts as a file. Do not "fix" this by setting DIRTYPE; the + # trailing-slash name on a non-directory is what is being tested. with tarfile.open(tmpname, 'w') as tar: tar.addfile(tarfile.TarInfo('./mydir/')) - with tarfile.open(tmpname) as tar: + with os_helper.temp_dir() as tmpdir, tarfile.open(tmpname) as tar: names = tar.getnames() self.assertEqual(names, ['./mydir/']) - tar.extract(names[0], TEMPDIR, filter='fully_trusted') + tar.extract(names[0], tmpdir, filter='fully_trusted') + self.assertTrue(os.path.isfile(os.path.join(tmpdir, 'mydir'))) @unittest.skipUnless(hasattr(os, "getuid") and hasattr(os, "getgid"), "Missing getuid or getgid implementation")