diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 4fe9d4aefc3b0c..c7256bf60e46eb 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -115,13 +115,14 @@ class _WindowsFlavour(_Flavour): is_supported = (os.name == 'nt') drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') - ext_namespace_prefix = '\\\\?\\' + # See https://bugs.python.org/issue33898 + ext_namespace_prefix = ('\\\\?\\', '\\\\.\\') reserved_names = ( {'CON', 'PRN', 'AUX', 'NUL'} | {'COM%d' % i for i in range(1, 10)} | {'LPT%d' % i for i in range(1, 10)} - ) + ) # Interesting findings about extended paths: # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported @@ -141,8 +142,8 @@ def splitroot(self, part, sep=sep): else: prefix = '' third = part[2:3] - if (second == sep and first == sep and third != sep): - # is a UNC path: + if first == second == sep and third != sep: + # is a UNC and/or Global path: # vvvvvvvvvvvvvvvvvvvvv root # \\machine\mountpoint\directory\etc\... # directory ^^^^^^^^^^^^^^ @@ -163,16 +164,32 @@ def splitroot(self, part, sep=sep): drv = part[:2] part = part[2:] first = third + # Except for UNC and Global paths, the drive should be + # the first component after the local-device prefix. + # See https://bugs.python.org/issue33898 + # All UNC and some of Global paths are parsed in if-condition above. + elif (part != sep and prefix and + self.casefold('global') not in self.casefold(prefix)): + index = part.find(sep) + if index != -1: + drv = part[:index] + part = part[index:] + first = part[0:1] + else: + drv = part + part = '' + first = '' if first == sep: root = first part = part.lstrip(sep) return prefix + drv, root, part def casefold(self, s): + # .lower() is not ideal. See https://bugs.python.org/issue32612 return s.lower() def casefold_parts(self, parts): - return [p.lower() for p in parts] + return [self.casefold(p) for p in parts] def resolve(self, path, strict=False): s = str(path) @@ -199,13 +216,31 @@ def resolve(self, path, strict=False): return None def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): + # See https://bugs.python.org/issue33898 prefix = '' if s.startswith(ext_prefix): prefix = s[:4] s = s[4:] - if s.startswith('UNC\\'): - prefix += s[:3] - s = '\\' + s[3:] + index = s.find('\\') + if index != -1: + # Do not assume case sensitivity. + # See https://docs.microsoft.com/windows/desktop/FileIO/naming-a-file + s1 = self.casefold(s[:index]) + if s1 == self.casefold('global'): + prefix += s[:6] + # For example, Path('//?/Global/Z:/').drive + if s[8:9] == ':': + prefix += s[6:7] + s = s[7:] + # For example, r'\\?\Global\UNC\server\share' + elif self.casefold(s[7:11]) == self.casefold('unc\\'): + prefix += s[6:10] + s = '\\' + s[10:] + else: + s = '\\' + s[6:] + elif s1 == self.casefold('unc'): + prefix += s[:3] + s = '\\' + s[3:] return prefix, s def _ext_to_normal(self, s): diff --git a/Misc/NEWS.d/next/Windows/2018-08-04-15-04-10.bpo-33898.psrYKd.rst b/Misc/NEWS.d/next/Windows/2018-08-04-15-04-10.bpo-33898.psrYKd.rst new file mode 100644 index 00000000000000..4f481eb5a30e32 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2018-08-04-15-04-10.bpo-33898.psrYKd.rst @@ -0,0 +1,2 @@ + Fix pathlib issues with parsing Windows device paths + starting with //?/ or //./. Patch by Albina Giliazova.