Skip to content

Commit aa42b83

Browse files
barneygaleebonnal
authored andcommitted
pythonGH-126212: Fix removal of slashes in file URIs on Windows (python#126214)
Adjust `urllib.request.pathname2url()` and `url2pathname()` so that they don't remove slashes from Windows DOS drive paths and URLs. There was no basis for this behaviour, and it conflicts with how UNC and POSIX paths are handled.
1 parent ece74b0 commit aa42b83

File tree

3 files changed

+18
-21
lines changed

3 files changed

+18
-21
lines changed

Lib/nturl2path.py

+6-19
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,15 @@ def url2pathname(url):
2424
# convert this to \\host\path\on\remote\host
2525
# (notice halving of slashes at the start of the path)
2626
url = url[2:]
27-
components = url.split('/')
2827
# make sure not to convert quoted slashes :-)
29-
return urllib.parse.unquote('\\'.join(components))
28+
return urllib.parse.unquote(url.replace('/', '\\'))
3029
comp = url.split('|')
3130
if len(comp) != 2 or comp[0][-1] not in string.ascii_letters:
3231
error = 'Bad URL: ' + url
3332
raise OSError(error)
3433
drive = comp[0][-1].upper()
35-
components = comp[1].split('/')
36-
path = drive + ':'
37-
for comp in components:
38-
if comp:
39-
path = path + '\\' + urllib.parse.unquote(comp)
40-
# Issue #11474 - handing url such as |c/|
41-
if path.endswith(':') and url.endswith('/'):
42-
path += '\\'
43-
return path
34+
tail = urllib.parse.unquote(comp[1].replace('/', '\\'))
35+
return drive + ':' + tail
4436

4537
def pathname2url(p):
4638
"""OS-specific conversion from a file system path to a relative URL
@@ -60,17 +52,12 @@ def pathname2url(p):
6052
raise OSError('Bad path: ' + p)
6153
if not ':' in p:
6254
# No drive specifier, just convert slashes and quote the name
63-
components = p.split('\\')
64-
return urllib.parse.quote('/'.join(components))
55+
return urllib.parse.quote(p.replace('\\', '/'))
6556
comp = p.split(':', maxsplit=2)
6657
if len(comp) != 2 or len(comp[0]) > 1:
6758
error = 'Bad path: ' + p
6859
raise OSError(error)
6960

7061
drive = urllib.parse.quote(comp[0].upper())
71-
components = comp[1].split('\\')
72-
path = '///' + drive + ':'
73-
for comp in components:
74-
if comp:
75-
path = path + '/' + urllib.parse.quote(comp)
76-
return path
62+
tail = urllib.parse.quote(comp[1].replace('\\', '/'))
63+
return '///' + drive + ':' + tail

Lib/test/test_urllib.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1526,8 +1526,10 @@ def test_pathname2url_win(self):
15261526
self.assertEqual(fn('\\\\?\\C:\\dir'), '///C:/dir')
15271527
self.assertEqual(fn('\\\\?\\unc\\server\\share\\dir'), '//server/share/dir')
15281528
self.assertEqual(fn("C:"), '///C:')
1529-
self.assertEqual(fn("C:\\"), '///C:')
1529+
self.assertEqual(fn("C:\\"), '///C:/')
15301530
self.assertEqual(fn('C:\\a\\b.c'), '///C:/a/b.c')
1531+
self.assertEqual(fn('C:\\a\\b.c\\'), '///C:/a/b.c/')
1532+
self.assertEqual(fn('C:\\a\\\\b.c'), '///C:/a//b.c')
15311533
self.assertEqual(fn('C:\\a\\b%#c'), '///C:/a/b%25%23c')
15321534
self.assertEqual(fn('C:\\a\\b\xe9'), '///C:/a/b%C3%A9')
15331535
self.assertEqual(fn('C:\\foo\\bar\\spam.foo'), "///C:/foo/bar/spam.foo")
@@ -1563,13 +1565,15 @@ def test_url2pathname_win(self):
15631565
self.assertEqual(fn("///C|"), 'C:')
15641566
self.assertEqual(fn("///C:"), 'C:')
15651567
self.assertEqual(fn('///C:/'), 'C:\\')
1566-
self.assertEqual(fn('/C|//'), 'C:\\')
1568+
self.assertEqual(fn('/C|//'), 'C:\\\\')
15671569
self.assertEqual(fn('///C|/path'), 'C:\\path')
15681570
# No DOS drive
15691571
self.assertEqual(fn("///C/test/"), '\\\\\\C\\test\\')
15701572
self.assertEqual(fn("////C/test/"), '\\\\C\\test\\')
15711573
# DOS drive paths
15721574
self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file')
1575+
self.assertEqual(fn('C:/path/to/file/'), 'C:\\path\\to\\file\\')
1576+
self.assertEqual(fn('C:/path/to//file'), 'C:\\path\\to\\\\file')
15731577
self.assertEqual(fn('C|/path/to/file'), 'C:\\path\\to\\file')
15741578
self.assertEqual(fn('/C|/path/to/file'), 'C:\\path\\to\\file')
15751579
self.assertEqual(fn('///C|/path/to/file'), 'C:\\path\\to\\file')
@@ -1583,6 +1587,9 @@ def test_url2pathname_win(self):
15831587
# Localhost paths
15841588
self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file')
15851589
self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file')
1590+
# Percent-encoded forward slashes are preserved for backwards compatibility
1591+
self.assertEqual(fn('C:/foo%2fbar'), 'C:\\foo/bar')
1592+
self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar')
15861593
# Round-tripping
15871594
paths = ['C:',
15881595
r'\\\C\test\\',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix issue where :func:`urllib.request.pathname2url` and
2+
:func:`~urllib.request.url2pathname` removed slashes from Windows DOS drive
3+
paths and URLs.

0 commit comments

Comments
 (0)