Skip to content

Commit 2ba0fd5

Browse files
authored
gh-81790: support "UNC" device paths in ntpath.splitdrive() (GH-91882)
1 parent 53a8b17 commit 2ba0fd5

File tree

5 files changed

+39
-66
lines changed

5 files changed

+39
-66
lines changed

Doc/library/os.path.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ the :mod:`glob` module.)
469469
("c:", "/dir")
470470

471471
If the path contains a UNC path, drive will contain the host name
472-
and share, up to but not including the fourth separator::
472+
and share::
473473

474474
>>> splitdrive("//host/computer/dir")
475475
("//host/computer", "/dir")

Lib/ntpath.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,23 @@ def splitdrive(p):
172172
sep = b'\\'
173173
altsep = b'/'
174174
colon = b':'
175+
unc_prefix = b'\\\\?\\UNC'
175176
else:
176177
sep = '\\'
177178
altsep = '/'
178179
colon = ':'
180+
unc_prefix = '\\\\?\\UNC'
179181
normp = p.replace(altsep, sep)
180182
if (normp[0:2] == sep*2) and (normp[2:3] != sep):
181183
# is a UNC path:
182184
# vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
183185
# \\machine\mountpoint\directory\etc\...
184186
# directory ^^^^^^^^^^^^^^^
185-
index = normp.find(sep, 2)
187+
if normp[:8].upper().rstrip(sep) == unc_prefix:
188+
start = 8
189+
else:
190+
start = 2
191+
index = normp.find(sep, start)
186192
if index == -1:
187193
return p[:0], p
188194
index2 = normp.find(sep, index + 1)

Lib/pathlib.py

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -120,68 +120,18 @@ class _WindowsFlavour(_Flavour):
120120

121121
is_supported = (os.name == 'nt')
122122

123-
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
124-
ext_namespace_prefix = '\\\\?\\'
125-
126123
reserved_names = (
127124
{'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} |
128125
{'COM%s' % c for c in '123456789\xb9\xb2\xb3'} |
129126
{'LPT%s' % c for c in '123456789\xb9\xb2\xb3'}
130127
)
131128

132-
# Interesting findings about extended paths:
133-
# * '\\?\c:\a' is an extended path, which bypasses normal Windows API
134-
# path processing. Thus relative paths are not resolved and slash is not
135-
# translated to backslash. It has the native NT path limit of 32767
136-
# characters, but a bit less after resolving device symbolic links,
137-
# such as '\??\C:' => '\Device\HarddiskVolume2'.
138-
# * '\\?\c:/a' looks for a device named 'C:/a' because slash is a
139-
# regular name character in the object namespace.
140-
# * '\\?\c:\foo/bar' is invalid because '/' is illegal in NT filesystems.
141-
# The only path separator at the filesystem level is backslash.
142-
# * '//?/c:\a' and '//?/c:/a' are effectively equivalent to '\\.\c:\a' and
143-
# thus limited to MAX_PATH.
144-
# * Prior to Windows 8, ANSI API bytes paths are limited to MAX_PATH,
145-
# even with the '\\?\' prefix.
146-
147129
def splitroot(self, part, sep=sep):
148-
first = part[0:1]
149-
second = part[1:2]
150-
if (second == sep and first == sep):
151-
# XXX extended paths should also disable the collapsing of "."
152-
# components (according to MSDN docs).
153-
prefix, part = self._split_extended_path(part)
154-
first = part[0:1]
155-
second = part[1:2]
130+
drv, rest = self.pathmod.splitdrive(part)
131+
if drv[:1] == sep or rest[:1] == sep:
132+
return drv, sep, rest.lstrip(sep)
156133
else:
157-
prefix = ''
158-
third = part[2:3]
159-
if (second == sep and first == sep and third != sep):
160-
# is a UNC path:
161-
# vvvvvvvvvvvvvvvvvvvvv root
162-
# \\machine\mountpoint\directory\etc\...
163-
# directory ^^^^^^^^^^^^^^
164-
index = part.find(sep, 2)
165-
if index != -1:
166-
index2 = part.find(sep, index + 1)
167-
# a UNC path can't have two slashes in a row
168-
# (after the initial two)
169-
if index2 != index + 1:
170-
if index2 == -1:
171-
index2 = len(part)
172-
if prefix:
173-
return prefix + part[1:index2], sep, part[index2+1:]
174-
else:
175-
return part[:index2], sep, part[index2+1:]
176-
drv = root = ''
177-
if second == ':' and first in self.drive_letters:
178-
drv = part[:2]
179-
part = part[2:]
180-
first = third
181-
if first == sep:
182-
root = first
183-
part = part.lstrip(sep)
184-
return prefix + drv, root, part
134+
return drv, '', rest
185135

186136
def casefold(self, s):
187137
return s.lower()
@@ -192,16 +142,6 @@ def casefold_parts(self, parts):
192142
def compile_pattern(self, pattern):
193143
return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
194144

195-
def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
196-
prefix = ''
197-
if s.startswith(ext_prefix):
198-
prefix = s[:4]
199-
s = s[4:]
200-
if s.startswith('UNC\\'):
201-
prefix += s[:3]
202-
s = '\\' + s[3:]
203-
return prefix, s
204-
205145
def is_reserved(self, parts):
206146
# NOTE: the rules for reserved names seem somewhat complicated
207147
# (e.g. r"..\NUL" is reserved but not r"foo\NUL" if "foo" does not

Lib/test/test_ntpath.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,31 @@ def test_splitdrive(self):
117117
# Issue #19911: UNC part containing U+0130
118118
self.assertEqual(ntpath.splitdrive('//conky/MOUNTPOİNT/foo/bar'),
119119
('//conky/MOUNTPOİNT', '/foo/bar'))
120+
# gh-81790: support device namespace, including UNC drives.
121+
tester('ntpath.splitdrive("//?/c:")', ("//?/c:", ""))
122+
tester('ntpath.splitdrive("//?/c:/")', ("//?/c:", "/"))
123+
tester('ntpath.splitdrive("//?/c:/dir")', ("//?/c:", "/dir"))
124+
tester('ntpath.splitdrive("//?/UNC")', ("", "//?/UNC"))
125+
tester('ntpath.splitdrive("//?/UNC/")', ("", "//?/UNC/"))
126+
tester('ntpath.splitdrive("//?/UNC/server/")', ("//?/UNC/server/", ""))
127+
tester('ntpath.splitdrive("//?/UNC/server/share")', ("//?/UNC/server/share", ""))
128+
tester('ntpath.splitdrive("//?/UNC/server/share/dir")', ("//?/UNC/server/share", "/dir"))
129+
tester('ntpath.splitdrive("//?/VOLUME{00000000-0000-0000-0000-000000000000}/spam")',
130+
('//?/VOLUME{00000000-0000-0000-0000-000000000000}', '/spam'))
131+
tester('ntpath.splitdrive("//?/BootPartition/")', ("//?/BootPartition", "/"))
132+
133+
tester('ntpath.splitdrive("\\\\?\\c:")', ("\\\\?\\c:", ""))
134+
tester('ntpath.splitdrive("\\\\?\\c:\\")', ("\\\\?\\c:", "\\"))
135+
tester('ntpath.splitdrive("\\\\?\\c:\\dir")', ("\\\\?\\c:", "\\dir"))
136+
tester('ntpath.splitdrive("\\\\?\\UNC")', ("", "\\\\?\\UNC"))
137+
tester('ntpath.splitdrive("\\\\?\\UNC\\")', ("", "\\\\?\\UNC\\"))
138+
tester('ntpath.splitdrive("\\\\?\\UNC\\server\\")', ("\\\\?\\UNC\\server\\", ""))
139+
tester('ntpath.splitdrive("\\\\?\\UNC\\server\\share")', ("\\\\?\\UNC\\server\\share", ""))
140+
tester('ntpath.splitdrive("\\\\?\\UNC\\server\\share\\dir")',
141+
("\\\\?\\UNC\\server\\share", "\\dir"))
142+
tester('ntpath.splitdrive("\\\\?\\VOLUME{00000000-0000-0000-0000-000000000000}\\spam")',
143+
('\\\\?\\VOLUME{00000000-0000-0000-0000-000000000000}', '\\spam'))
144+
tester('ntpath.splitdrive("\\\\?\\BootPartition\\")', ("\\\\?\\BootPartition", "\\"))
120145

121146
def test_split(self):
122147
tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`os.path.splitdrive` now understands DOS device paths with UNC
2+
links (beginning ``\\?\UNC\``). Contributed by Barney Gale.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy