Skip to content

Commit cae9d9d

Browse files
authored
GH-126766: url2pathname(): handle empty authority section. (#126767)
Discard two leading slashes from the beginning of a `file:` URI if they introduce an empty authority section. As a result, file URIs like `///etc/hosts` are correctly parsed as `/etc/hosts`.
1 parent 47cbf03 commit cae9d9d

File tree

4 files changed

+14
-9
lines changed

4 files changed

+14
-9
lines changed

Lib/nturl2path.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ def url2pathname(url):
1919
url = url.replace(':', '|')
2020
if not '|' in url:
2121
# No drive specifier, just convert slashes
22-
if url[:4] == '////':
23-
# path is something like ////host/path/on/remote/host
24-
# convert this to \\host\path\on\remote\host
25-
# (notice halving of slashes at the start of the path)
22+
if url[:3] == '///':
23+
# URL has an empty authority section, so the path begins on the
24+
# third character.
2625
url = url[2:]
2726
# make sure not to convert quoted slashes :-)
2827
return urllib.parse.unquote(url.replace('/', '\\'))

Lib/test/test_urllib.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ def test_pathname2url_win(self):
15491549
self.assertEqual(fn('//?/unc/server/share/dir'), '//server/share/dir')
15501550
# Round-tripping
15511551
urls = ['///C:',
1552-
'///folder/test/',
1552+
'/folder/test/',
15531553
'///C:/foo/bar/spam.foo']
15541554
for url in urls:
15551555
self.assertEqual(fn(urllib.request.url2pathname(url)), url)
@@ -1573,7 +1573,7 @@ def test_url2pathname_win(self):
15731573
self.assertEqual(fn('/C|//'), 'C:\\\\')
15741574
self.assertEqual(fn('///C|/path'), 'C:\\path')
15751575
# No DOS drive
1576-
self.assertEqual(fn("///C/test/"), '\\\\\\C\\test\\')
1576+
self.assertEqual(fn("///C/test/"), '\\C\\test\\')
15771577
self.assertEqual(fn("////C/test/"), '\\\\C\\test\\')
15781578
# DOS drive paths
15791579
self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file')
@@ -1597,7 +1597,7 @@ def test_url2pathname_win(self):
15971597
self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar')
15981598
# Round-tripping
15991599
paths = ['C:',
1600-
r'\\\C\test\\',
1600+
r'\C\test\\',
16011601
r'C:\foo\bar\spam.foo']
16021602
for path in paths:
16031603
self.assertEqual(fn(urllib.request.pathname2url(path)), path)
@@ -1608,8 +1608,8 @@ def test_url2pathname_posix(self):
16081608
fn = urllib.request.url2pathname
16091609
self.assertEqual(fn('/foo/bar'), '/foo/bar')
16101610
self.assertEqual(fn('//foo/bar'), '//foo/bar')
1611-
self.assertEqual(fn('///foo/bar'), '///foo/bar')
1612-
self.assertEqual(fn('////foo/bar'), '////foo/bar')
1611+
self.assertEqual(fn('///foo/bar'), '/foo/bar')
1612+
self.assertEqual(fn('////foo/bar'), '//foo/bar')
16131613
self.assertEqual(fn('//localhost/foo/bar'), '//localhost/foo/bar')
16141614

16151615
class Utility_Tests(unittest.TestCase):

Lib/urllib/request.py

+4
Original file line numberDiff line numberDiff line change
@@ -1656,6 +1656,10 @@ def data_open(self, req):
16561656
def url2pathname(pathname):
16571657
"""OS-specific conversion from a relative URL of the 'file' scheme
16581658
to a file system path; not recommended for general use."""
1659+
if pathname[:3] == '///':
1660+
# URL has an empty authority section, so the path begins on the
1661+
# third character.
1662+
pathname = pathname[2:]
16591663
return unquote(pathname)
16601664

16611665
def pathname2url(pathname):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix issue where :func:`urllib.request.url2pathname` failed to discard two
2+
leading slashes introducing an empty authority section.

0 commit comments

Comments
 (0)