Skip to content

Commit b871e39

Browse files
[3.13] gh-156713: Use the filesystem encoding in nturl2path (GH-156717) (GH-157425)
urllib.request.pathname2url() and url2pathname() use the filesystem encoding and error handler since gh-85168, but nturl2path, which implements them on Windows before 3.14, was left unchanged. Paths containing surrogate characters raised UnicodeEncodeError. (cherry picked from commit 01599e2)
1 parent 5583f36 commit b871e39

3 files changed

Lines changed: 35 additions & 6 deletions

File tree

Lib/nturl2path.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ def url2pathname(url):
1414
# ///C:/foo/bar/spam.foo
1515
# become
1616
# C:\foo\bar\spam.foo
17-
import string, urllib.parse
17+
import string, sys, urllib.parse
18+
encoding = sys.getfilesystemencoding()
19+
errors = sys.getfilesystemencodeerrors()
1820
if url[:3] == '///':
1921
# URL has an empty authority section, so the path begins on the third
2022
# character.
@@ -30,13 +32,15 @@ def url2pathname(url):
3032
if not '|' in url:
3133
# No drive specifier, just convert slashes
3234
# make sure not to convert quoted slashes :-)
33-
return urllib.parse.unquote(url.replace('/', '\\'))
35+
return urllib.parse.unquote(url.replace('/', '\\'),
36+
encoding=encoding, errors=errors)
3437
comp = url.split('|')
3538
if len(comp) != 2 or comp[0][-1] not in string.ascii_letters:
3639
error = 'Bad URL: ' + url
3740
raise OSError(error)
3841
drive = comp[0][-1].upper()
39-
tail = urllib.parse.unquote(comp[1].replace('/', '\\'))
42+
tail = urllib.parse.unquote(comp[1].replace('/', '\\'),
43+
encoding=encoding, errors=errors)
4044
return drive + ':' + tail
4145

4246
def pathname2url(p):
@@ -46,7 +50,9 @@ def pathname2url(p):
4650
# C:\foo\bar\spam.foo
4751
# becomes
4852
# ///C:/foo/bar/spam.foo
49-
import urllib.parse
53+
import sys, urllib.parse
54+
encoding = sys.getfilesystemencoding()
55+
errors = sys.getfilesystemencodeerrors()
5056
# First, clean up some special forms. We are going to sacrifice
5157
# the additional information anyway
5258
p = p.replace('\\', '/')
@@ -58,12 +64,12 @@ def pathname2url(p):
5864
raise OSError('Bad path: ' + p)
5965
if not ':' in p:
6066
# No DOS drive specified, just quote the pathname
61-
return urllib.parse.quote(p)
67+
return urllib.parse.quote(p, encoding=encoding, errors=errors)
6268
comp = p.split(':', maxsplit=2)
6369
if len(comp) != 2 or len(comp[0]) > 1:
6470
error = 'Bad path: ' + p
6571
raise OSError(error)
6672

6773
drive = urllib.parse.quote(comp[0].upper())
68-
tail = urllib.parse.quote(comp[1])
74+
tail = urllib.parse.quote(comp[1], encoding=encoding, errors=errors)
6975
return '///' + drive + ':' + tail

Lib/test/test_urllib.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import urllib.request
55
import urllib.error
66
import http.client
7+
import nturl2path
78
import email.message
89
import io
910
import unittest
@@ -1659,6 +1660,15 @@ def test_pathname2url_nonascii(self):
16591660
url = urllib.parse.quote(os_helper.FS_NONASCII, encoding=encoding, errors=errors)
16601661
self.assertEqual(urllib.request.pathname2url(os_helper.FS_NONASCII), url)
16611662

1663+
def test_pathname2url_surrogates(self):
1664+
# gh-156713: the filesystem encoding and error handler are used,
1665+
# so that paths containing surrogate characters can be converted.
1666+
encoding = sys.getfilesystemencoding()
1667+
errors = sys.getfilesystemencodeerrors()
1668+
tail = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors)
1669+
self.assertEqual(nturl2path.pathname2url('C:\\a\udcff'),
1670+
'///C:/' + tail)
1671+
16621672
@unittest.skipUnless(sys.platform == 'win32',
16631673
'test specific to Windows pathnames.')
16641674
def test_url2pathname_win(self):
@@ -1720,6 +1730,15 @@ def test_url2pathname_nonascii(self):
17201730
url = urllib.parse.quote(url, encoding=encoding, errors=errors)
17211731
self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII)
17221732

1733+
def test_url2pathname_surrogates(self):
1734+
# gh-156713: the filesystem encoding and error handler are used, so
1735+
# that URLs containing percent-encoded surrogates can be converted.
1736+
encoding = sys.getfilesystemencoding()
1737+
errors = sys.getfilesystemencodeerrors()
1738+
url = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors)
1739+
self.assertEqual(nturl2path.url2pathname('///C:/' + url),
1740+
'C:\\a\udcff')
1741+
17231742
class Utility_Tests(unittest.TestCase):
17241743
"""Testcase to test the various utility functions in the urllib."""
17251744

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`!nturl2path.pathname2url` and :func:`!nturl2path.url2pathname`:
2+
the filesystem encoding and error handler are now used for percent-encoding
3+
and decoding, as in :mod:`urllib.request`. Previously paths containing
4+
surrogate characters raised :exc:`UnicodeEncodeError`.

0 commit comments

Comments
 (0)