]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-78079: Fix UNC device path root normalization in pathlib (GH-102003)
authorBarney Gale <barney.gale@gmail.com>
Fri, 14 Apr 2023 20:55:41 +0000 (21:55 +0100)
committerGitHub <noreply@github.com>
Fri, 14 Apr 2023 20:55:41 +0000 (21:55 +0100)
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>
Lib/pathlib.py
Lib/test/test_ntpath.py
Lib/test/test_pathlib.py
Misc/NEWS.d/next/Library/2023-02-17-21-14-40.gh-issue-78079.z3Szr6.rst [new file with mode: 0644]

index 4ae1fae6f4b35807d406622f072dd87b8fa120cc..f43f01ef41a97fafae4acd67b114c4a8daf689ab 100644 (file)
@@ -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
 
index 42b9587ca18107d355f9991587daa67e83966cbc..0e57c165ca98ea8d6f2dc69fb10a77a71011f961 100644 (file)
@@ -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
index 3c6da94d094610f588ec54d68606867fe42afd86..120b70f2197385a61e822df8236af11138d6345a 100644 (file)
@@ -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 (file)
index 0000000..bbb9ac3
--- /dev/null
@@ -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.