GH-126601: pathname2url(): handle NTFS alternate data streams (#126760)

Adjust `pathname2url()` to encode embedded colon characters in Windows
paths, rather than bailing out with an `OSError`.

Co-authored-by: Steve Dower <steve.dower@microsoft.com>
This commit is contained in:
Barney Gale 2024-11-22 00:29:05 +00:00 committed by GitHub
parent e8bb053941
commit fd133d4f21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 21 additions and 14 deletions

View file

@ -152,6 +152,11 @@ The :mod:`urllib.request` module defines the following functions:
the path component of a URL. This does not produce a complete URL. The return the path component of a URL. This does not produce a complete URL. The return
value will already be quoted using the :func:`~urllib.parse.quote` function. value will already be quoted using the :func:`~urllib.parse.quote` function.
.. versionchanged:: 3.14
On Windows, ``:`` characters not following a drive letter are quoted. In
previous versions, :exc:`OSError` was raised if a colon character was
found in any position other than the second character.
.. function:: url2pathname(path) .. function:: url2pathname(path)

View file

@ -40,6 +40,7 @@ def pathname2url(p):
# C:\foo\bar\spam.foo # C:\foo\bar\spam.foo
# becomes # becomes
# ///C:/foo/bar/spam.foo # ///C:/foo/bar/spam.foo
import ntpath
import urllib.parse import urllib.parse
# First, clean up some special forms. We are going to sacrifice # First, clean up some special forms. We are going to sacrifice
# the additional information anyway # the additional information anyway
@ -48,16 +49,13 @@ def pathname2url(p):
p = p[4:] p = p[4:]
if p[:4].upper() == 'UNC/': if p[:4].upper() == 'UNC/':
p = '//' + p[4:] p = '//' + p[4:]
elif p[1:2] != ':': drive, tail = ntpath.splitdrive(p)
raise OSError('Bad path: ' + p) if drive[1:] == ':':
if not ':' in p: # DOS drive specified. Add three slashes to the start, producing
# No DOS drive specified, just quote the pathname # an authority section with a zero-length authority, and a path
return urllib.parse.quote(p) # section starting with a single slash.
comp = p.split(':', maxsplit=2) drive = f'///{drive.upper()}'
if len(comp) != 2 or len(comp[0]) > 1:
error = 'Bad path: ' + p
raise OSError(error)
drive = urllib.parse.quote(comp[0].upper()) drive = urllib.parse.quote(drive, safe='/:')
tail = urllib.parse.quote(comp[1]) tail = urllib.parse.quote(tail)
return '///' + drive + ':' + tail return drive + tail

View file

@ -1429,8 +1429,9 @@ class Pathname_Tests(unittest.TestCase):
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")
# Long drive letter # NTFS alternate data streams
self.assertRaises(IOError, fn, "XX:\\") self.assertEqual(fn('C:\\foo:bar'), '///C:/foo%3Abar')
self.assertEqual(fn('foo:bar'), 'foo%3Abar')
# No drive letter # No drive letter
self.assertEqual(fn("\\folder\\test\\"), '/folder/test/') self.assertEqual(fn("\\folder\\test\\"), '/folder/test/')
self.assertEqual(fn("\\\\folder\\test\\"), '//folder/test/') self.assertEqual(fn("\\\\folder\\test\\"), '//folder/test/')

View file

@ -0,0 +1,3 @@
Fix issue where :func:`urllib.request.pathname2url` raised :exc:`OSError`
when given a Windows path containing a colon character not following a
drive letter, such as before an NTFS alternate data stream.