https://github.com/python/cpython/commit/01599e203e25610421bb5df413f32fa0848f3ade
commit: 01599e203e25610421bb5df413f32fa0848f3ade
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-13T15:44:46+03:00
summary:
gh-156713: Use the filesystem encoding in nturl2path (GH-156717)
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.
files:
A Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst
M Lib/nturl2path.py
M Lib/test/test_nturl2path.py
diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py
index 57c7858dff0b81..47c49ca2020c5c 100644
--- a/Lib/nturl2path.py
+++ b/Lib/nturl2path.py
@@ -22,7 +22,10 @@ def url2pathname(url):
# ///C:/foo/bar/spam.foo
# become
# C:\foo\bar\spam.foo
+ import sys
import 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.
@@ -40,7 +43,8 @@ def url2pathname(url):
if url[1:2] == '|':
# Older URLs use a pipe after a drive letter
url = url[:1] + ':' + url[2:]
- return urllib.parse.unquote(url.replace('/', '\\'))
+ return urllib.parse.unquote(url.replace('/', '\\'),
+ encoding=encoding, errors=errors)
def pathname2url(p):
"""OS-specific conversion from a file system path to a relative URL
@@ -50,7 +54,10 @@ def pathname2url(p):
# becomes
# ///C:/foo/bar/spam.foo
import ntpath
+ import sys
import 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('\\', '/')
@@ -65,10 +72,11 @@ def pathname2url(p):
# an authority section with a zero-length authority, and a path
# section starting with a single slash.
drive = f'///{drive}'
- drive = urllib.parse.quote(drive, safe='/:')
+ drive = urllib.parse.quote(drive, encoding=encoding, errors=errors,
+ safe='/:')
elif root:
# Add explicitly empty authority to path beginning with one slash.
root = f'//{root}'
- tail = urllib.parse.quote(tail)
+ tail = urllib.parse.quote(tail, encoding=encoding, errors=errors)
return drive + root + tail
diff --git a/Lib/test/test_nturl2path.py b/Lib/test/test_nturl2path.py
index a6a3422a0f75b2..b4532137968d6e 100644
--- a/Lib/test/test_nturl2path.py
+++ b/Lib/test/test_nturl2path.py
@@ -1,4 +1,6 @@
+import sys
import unittest
+import urllib.parse
from test.support import warnings_helper
@@ -58,6 +60,15 @@ def test_pathname2url(self):
for url in urls:
self.assertEqual(fn(nturl2path.url2pathname(url)), 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)
+
def test_url2pathname(self):
fn = nturl2path.url2pathname
self.assertEqual(fn('/'), '\\')
@@ -103,5 +114,15 @@ def test_url2pathname(self):
self.assertEqual(fn(nturl2path.pathname2url(path)), path)
+ 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')
+
+
if __name__ == '__main__':
unittest.main()
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`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]