]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-96290: Support partial/invalid UNC drives in ntpath.normpath() and splitdrive...
authorBarney Gale <barney.gale@gmail.com>
Thu, 12 Jan 2023 19:24:57 +0000 (19:24 +0000)
committerGitHub <noreply@github.com>
Thu, 12 Jan 2023 19:24:57 +0000 (19:24 +0000)
This brings the Python implementation of `ntpath.normpath()` in line with the C implementation added in 99fcf15

Co-authored-by: Eryk Sun <eryksun@gmail.com>
Lib/ntpath.py
Lib/test/test_ntpath.py
Lib/test/test_zipfile/test_core.py
Misc/NEWS.d/next/Library/2022-12-19-23-19-26.gh-issue-96290.qFjsi6.rst [new file with mode: 0644]

index 265eaa8d4b953f3bb7a4b671492b26fee62c254c..cd7fb58a88de67d22fd09f985c6aef2adf02d86d 100644 (file)
@@ -87,16 +87,20 @@ except ImportError:
 def isabs(s):
     """Test whether a path is absolute"""
     s = os.fspath(s)
-    # Paths beginning with \\?\ are always absolute, but do not
-    # necessarily contain a drive.
     if isinstance(s, bytes):
-        if s.replace(b'/', b'\\').startswith(b'\\\\?\\'):
-            return True
+        sep = b'\\'
+        altsep = b'/'
+        colon_sep = b':\\'
     else:
-        if s.replace('/', '\\').startswith('\\\\?\\'):
-            return True
-    s = splitdrive(s)[1]
-    return len(s) > 0 and s[0] and s[0] in _get_bothseps(s)
+        sep = '\\'
+        altsep = '/'
+        colon_sep = ':\\'
+    s = s[:3].replace(altsep, sep)
+    # Absolute: UNC, device, and paths with a drive and root.
+    # LEGACY BUG: isabs("/x") should be false since the path has no drive.
+    if s.startswith(sep) or s.startswith(colon_sep, 1):
+        return True
+    return False
 
 
 # Join two (or more) paths.
@@ -172,34 +176,26 @@ def splitdrive(p):
             sep = b'\\'
             altsep = b'/'
             colon = b':'
-            unc_prefix = b'\\\\?\\UNC'
+            unc_prefix = b'\\\\?\\UNC\\'
         else:
             sep = '\\'
             altsep = '/'
             colon = ':'
-            unc_prefix = '\\\\?\\UNC'
+            unc_prefix = '\\\\?\\UNC\\'
         normp = p.replace(altsep, sep)
-        if (normp[0:2] == sep*2) and (normp[2:3] != sep):
-            # is a UNC path:
-            # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
-            # \\machine\mountpoint\directory\etc\...
-            #           directory ^^^^^^^^^^^^^^^
-            if normp[:8].upper().rstrip(sep) == unc_prefix:
-                start = 8
-            else:
-                start = 2
+        if normp[0:2] == sep * 2:
+            # UNC drives, e.g. \\server\share or \\?\UNC\server\share
+            # Device drives, e.g. \\.\device or \\?\device
+            start = 8 if normp[:8].upper() == unc_prefix else 2
             index = normp.find(sep, start)
             if index == -1:
-                return p[:0], p
+                return p, p[:0]
             index2 = normp.find(sep, index + 1)
-            # a UNC path can't have two slashes in a row
-            # (after the initial two)
-            if index2 == index + 1:
-                return p[:0], p
             if index2 == -1:
-                index2 = len(p)
+                return p, p[:0]
             return p[:index2], p[index2:]
         if normp[1:2] == colon:
+            # Drive-letter drives, e.g. X:
             return p[:2], p[2:]
     return p[:0], p
 
@@ -523,20 +519,11 @@ except ImportError:
             altsep = b'/'
             curdir = b'.'
             pardir = b'..'
-            special_prefixes = (b'\\\\.\\', b'\\\\?\\')
         else:
             sep = '\\'
             altsep = '/'
             curdir = '.'
             pardir = '..'
-            special_prefixes = ('\\\\.\\', '\\\\?\\')
-        if path.startswith(special_prefixes):
-            # in the case of paths with these prefixes:
-            # \\.\ -> device names
-            # \\?\ -> literal paths
-            # do not do any normalization, but return the path
-            # unchanged apart from the call to os.fspath()
-            return path
         path = path.replace(altsep, sep)
         prefix, path = splitdrive(path)
 
index 336648273b6cf170e19f4c5041825b04cb9d6d58..f56de0be772105b82ab6e78e7e3e96f00b0dd072 100644 (file)
@@ -107,13 +107,13 @@ class TestNtpath(NtpathTestCase):
         tester('ntpath.splitdrive("//conky/mountpoint/foo/bar")',
                ('//conky/mountpoint', '/foo/bar'))
         tester('ntpath.splitdrive("\\\\\\conky\\mountpoint\\foo\\bar")',
-            ('', '\\\\\\conky\\mountpoint\\foo\\bar'))
+            ('\\\\\\conky', '\\mountpoint\\foo\\bar'))
         tester('ntpath.splitdrive("///conky/mountpoint/foo/bar")',
-            ('', '///conky/mountpoint/foo/bar'))
+            ('///conky', '/mountpoint/foo/bar'))
         tester('ntpath.splitdrive("\\\\conky\\\\mountpoint\\foo\\bar")',
-               ('', '\\\\conky\\\\mountpoint\\foo\\bar'))
+               ('\\\\conky\\', '\\mountpoint\\foo\\bar'))
         tester('ntpath.splitdrive("//conky//mountpoint/foo/bar")',
-               ('', '//conky//mountpoint/foo/bar'))
+               ('//conky/', '/mountpoint/foo/bar'))
         # Issue #19911: UNC part containing U+0130
         self.assertEqual(ntpath.splitdrive('//conky/MOUNTPOİNT/foo/bar'),
                          ('//conky/MOUNTPOİNT', '/foo/bar'))
@@ -121,8 +121,8 @@ class TestNtpath(NtpathTestCase):
         tester('ntpath.splitdrive("//?/c:")', ("//?/c:", ""))
         tester('ntpath.splitdrive("//?/c:/")', ("//?/c:", "/"))
         tester('ntpath.splitdrive("//?/c:/dir")', ("//?/c:", "/dir"))
-        tester('ntpath.splitdrive("//?/UNC")', ("", "//?/UNC"))
-        tester('ntpath.splitdrive("//?/UNC/")', ("", "//?/UNC/"))
+        tester('ntpath.splitdrive("//?/UNC")', ("//?/UNC", ""))
+        tester('ntpath.splitdrive("//?/UNC/")', ("//?/UNC/", ""))
         tester('ntpath.splitdrive("//?/UNC/server/")', ("//?/UNC/server/", ""))
         tester('ntpath.splitdrive("//?/UNC/server/share")', ("//?/UNC/server/share", ""))
         tester('ntpath.splitdrive("//?/UNC/server/share/dir")', ("//?/UNC/server/share", "/dir"))
@@ -133,8 +133,8 @@ class TestNtpath(NtpathTestCase):
         tester('ntpath.splitdrive("\\\\?\\c:")', ("\\\\?\\c:", ""))
         tester('ntpath.splitdrive("\\\\?\\c:\\")', ("\\\\?\\c:", "\\"))
         tester('ntpath.splitdrive("\\\\?\\c:\\dir")', ("\\\\?\\c:", "\\dir"))
-        tester('ntpath.splitdrive("\\\\?\\UNC")', ("", "\\\\?\\UNC"))
-        tester('ntpath.splitdrive("\\\\?\\UNC\\")', ("", "\\\\?\\UNC\\"))
+        tester('ntpath.splitdrive("\\\\?\\UNC")', ("\\\\?\\UNC", ""))
+        tester('ntpath.splitdrive("\\\\?\\UNC\\")', ("\\\\?\\UNC\\", ""))
         tester('ntpath.splitdrive("\\\\?\\UNC\\server\\")', ("\\\\?\\UNC\\server\\", ""))
         tester('ntpath.splitdrive("\\\\?\\UNC\\server\\share")', ("\\\\?\\UNC\\server\\share", ""))
         tester('ntpath.splitdrive("\\\\?\\UNC\\server\\share\\dir")',
@@ -143,6 +143,13 @@ class TestNtpath(NtpathTestCase):
                ('\\\\?\\VOLUME{00000000-0000-0000-0000-000000000000}', '\\spam'))
         tester('ntpath.splitdrive("\\\\?\\BootPartition\\")', ("\\\\?\\BootPartition", "\\"))
 
+        # gh-96290: support partial/invalid UNC drives
+        tester('ntpath.splitdrive("//")', ("//", ""))  # empty server & missing share
+        tester('ntpath.splitdrive("///")', ("///", ""))  # empty server & empty share
+        tester('ntpath.splitdrive("///y")', ("///y", ""))  # empty server & non-empty share
+        tester('ntpath.splitdrive("//x")', ("//x", ""))  # non-empty server & missing share
+        tester('ntpath.splitdrive("//x/")', ("//x/", ""))  # non-empty server & empty share
+
     def test_split(self):
         tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
         tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
@@ -161,6 +168,10 @@ class TestNtpath(NtpathTestCase):
         tester('ntpath.isabs("\\foo")', 1)
         tester('ntpath.isabs("\\foo\\bar")', 1)
 
+        # gh-96290: normal UNC paths and device paths without trailing backslashes
+        tester('ntpath.isabs("\\\\conky\\mountpoint")', 1)
+        tester('ntpath.isabs("\\\\.\\C:")', 1)
+
     def test_commonprefix(self):
         tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
                "/home/swen")
@@ -270,6 +281,12 @@ class TestNtpath(NtpathTestCase):
         tester("ntpath.normpath('//server/share/../..')",  '\\\\server\\share\\')
         tester("ntpath.normpath('//server/share/../../')", '\\\\server\\share\\')
 
+        # gh-96290: don't normalize partial/invalid UNC drives as rooted paths.
+        tester("ntpath.normpath('\\\\foo\\\\')", '\\\\foo\\\\')
+        tester("ntpath.normpath('\\\\foo\\')", '\\\\foo\\')
+        tester("ntpath.normpath('\\\\foo')", '\\\\foo')
+        tester("ntpath.normpath('\\\\')", '\\\\')
+
     def test_realpath_curdir(self):
         expected = ntpath.normpath(os.getcwd())
         tester("ntpath.realpath('.')", expected)
index bb0f1467735bcf6271097ccb4ca952983cd8a396..cf41d0e8cb8d53b475ec1db63826e31d21861ef4 100644 (file)
@@ -1469,10 +1469,10 @@ class ExtractTests(unittest.TestCase):
             (r'C:\foo\bar', 'foo/bar'),
             (r'//conky/mountpoint/foo/bar', 'foo/bar'),
             (r'\\conky\mountpoint\foo\bar', 'foo/bar'),
-            (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
-            (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
-            (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
-            (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
+            (r'///conky/mountpoint/foo/bar', 'mountpoint/foo/bar'),
+            (r'\\\conky\mountpoint\foo\bar', 'mountpoint/foo/bar'),
+            (r'//conky//mountpoint/foo/bar', 'mountpoint/foo/bar'),
+            (r'\\conky\\mountpoint\foo\bar', 'mountpoint/foo/bar'),
             (r'//?/C:/foo/bar', 'foo/bar'),
             (r'\\?\C:\foo\bar', 'foo/bar'),
             (r'C:/../C:/foo/bar', 'C_/foo/bar'),
diff --git a/Misc/NEWS.d/next/Library/2022-12-19-23-19-26.gh-issue-96290.qFjsi6.rst b/Misc/NEWS.d/next/Library/2022-12-19-23-19-26.gh-issue-96290.qFjsi6.rst
new file mode 100644 (file)
index 0000000..33f9860
--- /dev/null
@@ -0,0 +1,5 @@
+Fix handling of partial and invalid UNC drives in ``ntpath.splitdrive()``, and in
+``ntpath.normpath()`` on non-Windows systems. Paths such as '\\server' and '\\' are now considered
+by ``splitdrive()`` to contain only a drive, and consequently are not modified by ``normpath()`` on
+non-Windows systems. The behaviour of ``normpath()`` on Windows systems is unaffected, as native
+OS APIs are used. Patch by Eryk Sun, with contributions by Barney Gale.