Skip to content

Commit 5afbb60

Browse files
gh-93251: Decode localized socket error messages from the locale encoding (GH-154683)
The gai_strerror() and hstrerror() messages were decoded as UTF-8, so gaierror and herror could be replaced with UnicodeDecodeError if the message is localized and the locale encoding is not UTF-8. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 306a35c commit 5afbb60

3 files changed

Lines changed: 33 additions & 2 deletions

File tree

Lib/test/test_socket.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,6 +1787,23 @@ def testGetaddrinfo(self):
17871787
except socket.gaierror:
17881788
pass
17891789

1790+
@unittest.skipUnless(hasattr(socket, 'AI_NUMERICSERV'),
1791+
'needs socket.AI_NUMERICSERV')
1792+
@support.thread_unsafe('setlocale is not thread-safe')
1793+
@support.run_with_locales('LC_ALL',
1794+
'uk_UA.KOI8-U', 'uk_UA', 'ja_JP.eucJP', 'ja_JP.SJIS', 'ja_JP',
1795+
'ko_KR.eucKR', 'zh_CN.GB18030', 'el_GR.ISO8859-7',
1796+
'de_DE.ISO8859-1', 'ja_JP.UTF-8',
1797+
'')
1798+
def test_getaddrinfo_localized_error(self):
1799+
# gh-93251: the localized gai_strerror() message could fail to be
1800+
# decoded as UTF-8, so UnicodeDecodeError was raised
1801+
# instead of gaierror.
1802+
with self.assertRaises(socket.gaierror) as cm:
1803+
socket.getaddrinfo("localhost", "http",
1804+
flags=socket.AI_NUMERICSERV)
1805+
str(cm.exception)
1806+
17901807
@unittest.skipIf(_testcapi is None, "requires _testcapi")
17911808
def test_getaddrinfo_int_port_overflow(self):
17921809
# gh-74895: Test that getaddrinfo does not raise OverflowError on port.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :exc:`UnicodeDecodeError` in :mod:`socket` functions
2+
(such as :func:`~socket.getaddrinfo` and :func:`~socket.gethostbyaddr`)
3+
when the localized error message of the C library is not UTF-8:
4+
decode it from the locale encoding.

Modules/socketmodule.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,14 +745,24 @@ set_error(void)
745745
}
746746

747747

748+
#if defined(HAVE_HSTRERROR) || defined(HAVE_GAI_STRERROR)
749+
/* Decode a locale-encoded error message from the C library.
750+
It can be localized and use a non-UTF-8 encoding. */
751+
static PyObject *
752+
decode_error_message(const char *str)
753+
{
754+
return PyUnicode_DecodeLocale(str, "surrogateescape");
755+
}
756+
#endif
757+
748758
#if defined(HAVE_GETHOSTBYNAME_R) || defined (HAVE_GETHOSTBYNAME) || defined (HAVE_GETHOSTBYADDR)
749759
static PyObject *
750760
set_herror(socket_state *state, int h_error)
751761
{
752762
PyObject *v;
753763

754764
#ifdef HAVE_HSTRERROR
755-
v = Py_BuildValue("(is)", h_error, hstrerror(h_error));
765+
v = Py_BuildValue("(iN)", h_error, decode_error_message(hstrerror(h_error)));
756766
#else
757767
v = Py_BuildValue("(is)", h_error, "host not found");
758768
#endif
@@ -779,7 +789,7 @@ set_gaierror(socket_state *state, int error)
779789
#endif
780790

781791
#ifdef HAVE_GAI_STRERROR
782-
v = Py_BuildValue("(is)", error, gai_strerror(error));
792+
v = Py_BuildValue("(iN)", error, decode_error_message(gai_strerror(error)));
783793
#else
784794
v = Py_BuildValue("(is)", error, "getaddrinfo failed");
785795
#endif

0 commit comments

Comments
 (0)