[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 54c63a32d0)

Co-authored-by: Barney Gale <barney.gale@gmail.com>
This commit is contained in:
Miss Islington (bot) 2024-11-08 18:31:44 +01:00 committed by GitHub
parent b1a406d923
commit 20043d5cf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 18 additions and 21 deletions

View file

@ -24,23 +24,15 @@ def url2pathname(url):
# convert this to \\host\path\on\remote\host # convert this to \\host\path\on\remote\host
# (notice halving of slashes at the start of the path) # (notice halving of slashes at the start of the path)
url = url[2:] url = url[2:]
components = url.split('/')
# make sure not to convert quoted slashes :-) # make sure not to convert quoted slashes :-)
return urllib.parse.unquote('\\'.join(components)) return urllib.parse.unquote(url.replace('/', '\\'))
comp = url.split('|') comp = url.split('|')
if len(comp) != 2 or comp[0][-1] not in string.ascii_letters: if len(comp) != 2 or comp[0][-1] not in string.ascii_letters:
error = 'Bad URL: ' + url error = 'Bad URL: ' + url
raise OSError(error) raise OSError(error)
drive = comp[0][-1].upper() drive = comp[0][-1].upper()
components = comp[1].split('/') tail = urllib.parse.unquote(comp[1].replace('/', '\\'))
path = drive + ':' return drive + ':' + tail
for comp in components:
if comp:
path = path + '\\' + urllib.parse.unquote(comp)
# Issue #11474 - handing url such as |c/|
if path.endswith(':') and url.endswith('/'):
path += '\\'
return path
def pathname2url(p): def pathname2url(p):
"""OS-specific conversion from a file system path to a relative URL """OS-specific conversion from a file system path to a relative URL
@ -60,17 +52,12 @@ def pathname2url(p):
raise OSError('Bad path: ' + p) raise OSError('Bad path: ' + p)
if not ':' in p: if not ':' in p:
# No drive specifier, just convert slashes and quote the name # No drive specifier, just convert slashes and quote the name
components = p.split('\\') return urllib.parse.quote(p.replace('\\', '/'))
return urllib.parse.quote('/'.join(components))
comp = p.split(':', maxsplit=2) comp = p.split(':', maxsplit=2)
if len(comp) != 2 or len(comp[0]) > 1: if len(comp) != 2 or len(comp[0]) > 1:
error = 'Bad path: ' + p error = 'Bad path: ' + p
raise OSError(error) raise OSError(error)
drive = urllib.parse.quote(comp[0].upper()) drive = urllib.parse.quote(comp[0].upper())
components = comp[1].split('\\') tail = urllib.parse.quote(comp[1].replace('\\', '/'))
path = '///' + drive + ':' return '///' + drive + ':' + tail
for comp in components:
if comp:
path = path + '/' + urllib.parse.quote(comp)
return path

View file

@ -1526,8 +1526,10 @@ class Pathname_Tests(unittest.TestCase):
self.assertEqual(fn('\\\\?\\C:\\dir'), '///C:/dir') self.assertEqual(fn('\\\\?\\C:\\dir'), '///C:/dir')
self.assertEqual(fn('\\\\?\\unc\\server\\share\\dir'), '//server/share/dir') self.assertEqual(fn('\\\\?\\unc\\server\\share\\dir'), '//server/share/dir')
self.assertEqual(fn("C:"), '///C:') self.assertEqual(fn("C:"), '///C:')
self.assertEqual(fn("C:\\"), '///C:') self.assertEqual(fn("C:\\"), '///C:/')
self.assertEqual(fn('C:\\a\\b.c'), '///C:/a/b.c') self.assertEqual(fn('C:\\a\\b.c'), '///C:/a/b.c')
self.assertEqual(fn('C:\\a\\b.c\\'), '///C:/a/b.c/')
self.assertEqual(fn('C:\\a\\\\b.c'), '///C:/a//b.c')
self.assertEqual(fn('C:\\a\\b%#c'), '///C:/a/b%25%23c') self.assertEqual(fn('C:\\a\\b%#c'), '///C:/a/b%25%23c')
self.assertEqual(fn('C:\\a\\b\xe9'), '///C:/a/b%C3%A9') self.assertEqual(fn('C:\\a\\b\xe9'), '///C:/a/b%C3%A9')
self.assertEqual(fn('C:\\foo\\bar\\spam.foo'), "///C:/foo/bar/spam.foo") self.assertEqual(fn('C:\\foo\\bar\\spam.foo'), "///C:/foo/bar/spam.foo")
@ -1563,13 +1565,15 @@ class Pathname_Tests(unittest.TestCase):
self.assertEqual(fn("///C|"), 'C:') self.assertEqual(fn("///C|"), 'C:')
self.assertEqual(fn("///C:"), 'C:') self.assertEqual(fn("///C:"), 'C:')
self.assertEqual(fn('///C:/'), 'C:\\') self.assertEqual(fn('///C:/'), 'C:\\')
self.assertEqual(fn('/C|//'), 'C:\\') self.assertEqual(fn('/C|//'), 'C:\\\\')
self.assertEqual(fn('///C|/path'), 'C:\\path') self.assertEqual(fn('///C|/path'), 'C:\\path')
# No DOS drive # No DOS drive
self.assertEqual(fn("///C/test/"), '\\\\\\C\\test\\') self.assertEqual(fn("///C/test/"), '\\\\\\C\\test\\')
self.assertEqual(fn("////C/test/"), '\\\\C\\test\\') self.assertEqual(fn("////C/test/"), '\\\\C\\test\\')
# DOS drive paths # DOS drive paths
self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file')
self.assertEqual(fn('C:/path/to/file/'), 'C:\\path\\to\\file\\')
self.assertEqual(fn('C:/path/to//file'), 'C:\\path\\to\\\\file')
self.assertEqual(fn('C|/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('C|/path/to/file'), 'C:\\path\\to\\file')
self.assertEqual(fn('/C|/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('/C|/path/to/file'), 'C:\\path\\to\\file')
self.assertEqual(fn('///C|/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('///C|/path/to/file'), 'C:\\path\\to\\file')
@ -1583,6 +1587,9 @@ class Pathname_Tests(unittest.TestCase):
# Localhost paths # Localhost paths
self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file')
self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file')
# Percent-encoded forward slashes are preserved for backwards compatibility
self.assertEqual(fn('C:/foo%2fbar'), 'C:\\foo/bar')
self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar')
# Round-tripping # Round-tripping
paths = ['C:', paths = ['C:',
r'\\\C\test\\', r'\\\C\test\\',

View file

@ -0,0 +1,3 @@
Fix issue where :func:`urllib.request.pathname2url` and
:func:`~urllib.request.url2pathname` removed slashes from Windows DOS drive
paths and URLs.