diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 4ae1fae6f4b358..f43f01ef41a97f 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -322,9 +322,14 @@ def _parse_path(cls, path): 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 42b9587ca18107..0e57c165ca98ea 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -169,6 +169,7 @@ def test_splitroot(self): # 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 @@ def test_splitroot(self): 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 @@ def test_splitroot(self): 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 3c6da94d094610..120b70f2197385 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -810,6 +810,9 @@ def test_drive_root_parts(self): 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 @@ def test_drive_root_parts(self): # 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 @@ def test_join(self): 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 00000000000000..bbb9ac3e3f8faa --- /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.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: