diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py index 757fd01bec8223..0761fbf3474975 100644 --- a/Lib/nturl2path.py +++ b/Lib/nturl2path.py @@ -14,7 +14,9 @@ def url2pathname(url): # ///C:/foo/bar/spam.foo # become # C:\foo\bar\spam.foo - import string, urllib.parse + import string, sys, urllib.parse + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() if url[:3] == '///': # URL has an empty authority section, so the path begins on the third # character. @@ -30,13 +32,15 @@ def url2pathname(url): if not '|' in url: # No drive specifier, just convert slashes # make sure not to convert quoted slashes :-) - return urllib.parse.unquote(url.replace('/', '\\')) + return urllib.parse.unquote(url.replace('/', '\\'), + encoding=encoding, errors=errors) comp = url.split('|') if len(comp) != 2 or comp[0][-1] not in string.ascii_letters: error = 'Bad URL: ' + url raise OSError(error) drive = comp[0][-1].upper() - tail = urllib.parse.unquote(comp[1].replace('/', '\\')) + tail = urllib.parse.unquote(comp[1].replace('/', '\\'), + encoding=encoding, errors=errors) return drive + ':' + tail def pathname2url(p): @@ -46,7 +50,9 @@ def pathname2url(p): # C:\foo\bar\spam.foo # becomes # ///C:/foo/bar/spam.foo - import urllib.parse + import sys, urllib.parse + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() # First, clean up some special forms. We are going to sacrifice # the additional information anyway p = p.replace('\\', '/') @@ -58,12 +64,12 @@ def pathname2url(p): raise OSError('Bad path: ' + p) if not ':' in p: # No DOS drive specified, just quote the pathname - return urllib.parse.quote(p) + return urllib.parse.quote(p, encoding=encoding, errors=errors) comp = p.split(':', maxsplit=2) if len(comp) != 2 or len(comp[0]) > 1: error = 'Bad path: ' + p raise OSError(error) drive = urllib.parse.quote(comp[0].upper()) - tail = urllib.parse.quote(comp[1]) + tail = urllib.parse.quote(comp[1], encoding=encoding, errors=errors) return '///' + drive + ':' + tail diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index c6a3157698d3ad..d0f743178f7819 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -4,6 +4,7 @@ import urllib.request import urllib.error import http.client +import nturl2path import email.message import io import unittest @@ -1659,6 +1660,15 @@ def test_pathname2url_nonascii(self): url = urllib.parse.quote(os_helper.FS_NONASCII, encoding=encoding, errors=errors) self.assertEqual(urllib.request.pathname2url(os_helper.FS_NONASCII), url) + def test_pathname2url_surrogates(self): + # gh-156713: the filesystem encoding and error handler are used, + # so that paths containing surrogate characters can be converted. + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + tail = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors) + self.assertEqual(nturl2path.pathname2url('C:\\a\udcff'), + '///C:/' + tail) + @unittest.skipUnless(sys.platform == 'win32', 'test specific to Windows pathnames.') def test_url2pathname_win(self): @@ -1720,6 +1730,15 @@ def test_url2pathname_nonascii(self): url = urllib.parse.quote(url, encoding=encoding, errors=errors) self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII) + def test_url2pathname_surrogates(self): + # gh-156713: the filesystem encoding and error handler are used, so + # that URLs containing percent-encoded surrogates can be converted. + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + url = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors) + self.assertEqual(nturl2path.url2pathname('///C:/' + url), + 'C:\\a\udcff') + class Utility_Tests(unittest.TestCase): """Testcase to test the various utility functions in the urllib.""" diff --git a/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst b/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst new file mode 100644 index 00000000000000..1d21fc70fd9256 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst @@ -0,0 +1,4 @@ +Fix :func:`!nturl2path.pathname2url` and :func:`!nturl2path.url2pathname`: +the filesystem encoding and error handler are now used for percent-encoding +and decoding, as in :mod:`urllib.request`. Previously paths containing +surrogate characters raised :exc:`UnicodeEncodeError`.