summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2023-04-14 21:55:41 +0100
committerGitHub <noreply@github.com>2023-04-14 21:55:41 +0100
commit8af8f52d175959f03cf4a2786b6a81ec09e15e95 (patch)
tree8848581a801af299c17343a997acb5f1a06c9f40
parent7c1b0a46c61f8bf8e2039bba1bff11b6ae20e56b (diff)
downloadcpython-git-8af8f52d175959f03cf4a2786b6a81ec09e15e95.tar.gz
GH-78079: Fix UNC device path root normalization in pathlib (GH-102003)
We no longer add a root to device paths such as `//./PhysicalDrive0`, `//?/BootPartition` and `//./c:` while normalizing. We also avoid adding a root to incomplete UNC share paths, like `//`, `//a` and `//a/`. Co-authored-by: Eryk Sun <eryksun@gmail.com>
-rw-r--r--Lib/pathlib.py11
-rw-r--r--Lib/test/test_ntpath.py8
-rw-r--r--Lib/test/test_pathlib.py24
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-17-21-14-40.gh-issue-78079.z3Szr6.rst3
4 files changed, 43 insertions, 3 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 4ae1fae6f4..f43f01ef41 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -322,9 +322,14 @@ class PurePath(object):
if altsep:
path = path.replace(altsep, sep)
drv, root, rel = cls._flavour.splitroot(path)
- if drv.startswith(sep):
- # pathlib assumes that UNC paths always have a root.
- root = sep
+ if not root and drv.startswith(sep) and not drv.endswith(sep):
+ drv_parts = drv.split(sep)
+ if len(drv_parts) == 4 and drv_parts[2] not in '?.':
+ # e.g. //server/share
+ root = sep
+ elif len(drv_parts) == 6:
+ # e.g. //?/unc/server/share
+ root = sep
parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.']
return drv, root, parsed
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index 42b9587ca1..0e57c165ca 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -169,6 +169,7 @@ class TestNtpath(NtpathTestCase):
# gh-81790: support device namespace, including UNC drives.
tester('ntpath.splitroot("//?/c:")', ("//?/c:", "", ""))
+ tester('ntpath.splitroot("//./c:")', ("//./c:", "", ""))
tester('ntpath.splitroot("//?/c:/")', ("//?/c:", "/", ""))
tester('ntpath.splitroot("//?/c:/dir")', ("//?/c:", "/", "dir"))
tester('ntpath.splitroot("//?/UNC")', ("//?/UNC", "", ""))
@@ -179,8 +180,12 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.splitroot("//?/VOLUME{00000000-0000-0000-0000-000000000000}/spam")',
('//?/VOLUME{00000000-0000-0000-0000-000000000000}', '/', 'spam'))
tester('ntpath.splitroot("//?/BootPartition/")', ("//?/BootPartition", "/", ""))
+ tester('ntpath.splitroot("//./BootPartition/")', ("//./BootPartition", "/", ""))
+ tester('ntpath.splitroot("//./PhysicalDrive0")', ("//./PhysicalDrive0", "", ""))
+ tester('ntpath.splitroot("//./nul")', ("//./nul", "", ""))
tester('ntpath.splitroot("\\\\?\\c:")', ("\\\\?\\c:", "", ""))
+ tester('ntpath.splitroot("\\\\.\\c:")', ("\\\\.\\c:", "", ""))
tester('ntpath.splitroot("\\\\?\\c:\\")', ("\\\\?\\c:", "\\", ""))
tester('ntpath.splitroot("\\\\?\\c:\\dir")', ("\\\\?\\c:", "\\", "dir"))
tester('ntpath.splitroot("\\\\?\\UNC")', ("\\\\?\\UNC", "", ""))
@@ -193,6 +198,9 @@ class TestNtpath(NtpathTestCase):
tester('ntpath.splitroot("\\\\?\\VOLUME{00000000-0000-0000-0000-000000000000}\\spam")',
('\\\\?\\VOLUME{00000000-0000-0000-0000-000000000000}', '\\', 'spam'))
tester('ntpath.splitroot("\\\\?\\BootPartition\\")', ("\\\\?\\BootPartition", "\\", ""))
+ tester('ntpath.splitroot("\\\\.\\BootPartition\\")', ("\\\\.\\BootPartition", "\\", ""))
+ tester('ntpath.splitroot("\\\\.\\PhysicalDrive0")', ("\\\\.\\PhysicalDrive0", "", ""))
+ tester('ntpath.splitroot("\\\\.\\nul")', ("\\\\.\\nul", "", ""))
# gh-96290: support partial/invalid UNC drives
tester('ntpath.splitroot("//")', ("//", "", "")) # empty server & missing share
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 3c6da94d09..120b70f219 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -810,6 +810,9 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
check(('c:/a',), 'c:', '\\', ('c:\\', 'a'))
check(('/a',), '', '\\', ('\\', 'a'))
# UNC paths.
+ check(('//',), '\\\\', '', ('\\\\',))
+ check(('//a',), '\\\\a', '', ('\\\\a',))
+ check(('//a/',), '\\\\a\\', '', ('\\\\a\\',))
check(('//a/b',), '\\\\a\\b', '\\', ('\\\\a\\b\\',))
check(('//a/b/',), '\\\\a\\b', '\\', ('\\\\a\\b\\',))
check(('//a/b/c',), '\\\\a\\b', '\\', ('\\\\a\\b\\', 'c'))
@@ -823,12 +826,26 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
# UNC paths.
check(('a', '//b/c//', 'd'), '\\\\b\\c', '\\', ('\\\\b\\c\\', 'd'))
# Extended paths.
+ check(('//./c:',), '\\\\.\\c:', '', ('\\\\.\\c:',))
check(('//?/c:/',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\',))
check(('//?/c:/a',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'a'))
check(('//?/c:/a', '/b'), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'b'))
# Extended UNC paths (format is "\\?\UNC\server\share").
+ check(('//?',), '\\\\?', '', ('\\\\?',))
+ check(('//?/',), '\\\\?\\', '', ('\\\\?\\',))
+ check(('//?/UNC',), '\\\\?\\UNC', '', ('\\\\?\\UNC',))
+ check(('//?/UNC/',), '\\\\?\\UNC\\', '', ('\\\\?\\UNC\\',))
+ check(('//?/UNC/b',), '\\\\?\\UNC\\b', '', ('\\\\?\\UNC\\b',))
+ check(('//?/UNC/b/',), '\\\\?\\UNC\\b\\', '', ('\\\\?\\UNC\\b\\',))
check(('//?/UNC/b/c',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',))
+ check(('//?/UNC/b/c/',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',))
check(('//?/UNC/b/c/d',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\', 'd'))
+ # UNC device paths
+ check(('//./BootPartition/',), '\\\\.\\BootPartition', '\\', ('\\\\.\\BootPartition\\',))
+ check(('//?/BootPartition/',), '\\\\?\\BootPartition', '\\', ('\\\\?\\BootPartition\\',))
+ check(('//./PhysicalDrive0',), '\\\\.\\PhysicalDrive0', '', ('\\\\.\\PhysicalDrive0',))
+ check(('//?/Volume{}/',), '\\\\?\\Volume{}', '\\', ('\\\\?\\Volume{}\\',))
+ check(('//./nul',), '\\\\.\\nul', '', ('\\\\.\\nul',))
# Second part has a root but not drive.
check(('a', '/b', 'c'), '', '\\', ('\\', 'b', 'c'))
check(('Z:/a', '/b', 'c'), 'Z:', '\\', ('Z:\\', 'b', 'c'))
@@ -1371,6 +1388,13 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase):
self.assertEqual(pp, P('C:/a/b/dd:s'))
pp = p.joinpath(P('E:d:s'))
self.assertEqual(pp, P('E:d:s'))
+ # Joining onto a UNC path with no root
+ pp = P('//').joinpath('server')
+ self.assertEqual(pp, P('//server'))
+ pp = P('//server').joinpath('share')
+ self.assertEqual(pp, P('//server/share'))
+ pp = P('//./BootPartition').joinpath('Windows')
+ self.assertEqual(pp, P('//./BootPartition/Windows'))
def test_div(self):
# Basically the same as joinpath().
diff --git a/Misc/NEWS.d/next/Library/2023-02-17-21-14-40.gh-issue-78079.z3Szr6.rst b/Misc/NEWS.d/next/Library/2023-02-17-21-14-40.gh-issue-78079.z3Szr6.rst
new file mode 100644
index 0000000000..bbb9ac3e3f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-17-21-14-40.gh-issue-78079.z3Szr6.rst
@@ -0,0 +1,3 @@
+Fix incorrect normalization of UNC device path roots, and partial UNC share
+path roots, in :class:`pathlib.PurePath`. Pathlib no longer appends a
+trailing slash to such paths.