Skip to content

Commit 20043d5

Browse files
[3.13] GH-126212: Fix removal of slashes in file URIs on Windows (GH-126214) (#126590)
GH-126212: Fix removal of slashes in file URIs on Windows (GH-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. (cherry picked from commit 54c63a3) Co-authored-by: Barney Gale <[email protected]>
1 parent b1a406d commit 20043d5

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)