diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 263fdafbad86d78..4b29273b7a240a0 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1741,6 +1741,23 @@ def testGetaddrinfo(self): except socket.gaierror: pass + @unittest.skipUnless(hasattr(socket, 'AI_NUMERICSERV'), + 'needs socket.AI_NUMERICSERV') + @support.thread_unsafe('setlocale is not thread-safe') + @support.run_with_locales('LC_ALL', + 'uk_UA.KOI8-U', 'uk_UA', 'ja_JP.eucJP', 'ja_JP.SJIS', 'ja_JP', + 'ko_KR.eucKR', 'zh_CN.GB18030', 'el_GR.ISO8859-7', + 'de_DE.ISO8859-1', 'ja_JP.UTF-8', + '') + def test_getaddrinfo_localized_error(self): + # gh-93251: the localized gai_strerror() message could fail to be + # decoded as UTF-8, so UnicodeDecodeError was raised + # instead of gaierror. + with self.assertRaises(socket.gaierror) as cm: + socket.getaddrinfo("localhost", "http", + flags=socket.AI_NUMERICSERV) + str(cm.exception) + @unittest.skipIf(_testcapi is None, "requires _testcapi") def test_getaddrinfo_int_port_overflow(self): # gh-74895: Test that getaddrinfo does not raise OverflowError on port. diff --git a/Misc/NEWS.d/next/Library/2026-07-25-13-30-00.gh-issue-93251.gAiErr.rst b/Misc/NEWS.d/next/Library/2026-07-25-13-30-00.gh-issue-93251.gAiErr.rst new file mode 100644 index 000000000000000..3f313030802f875 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-25-13-30-00.gh-issue-93251.gAiErr.rst @@ -0,0 +1,4 @@ +Fix :exc:`UnicodeDecodeError` in :mod:`socket` functions +(such as :func:`~socket.getaddrinfo` and :func:`~socket.gethostbyaddr`) +when the localized error message of the C library is not UTF-8: +decode it from the locale encoding. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 73f55ff05742a25..f297a5f9027164c 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -735,6 +735,16 @@ set_error(void) } +#if defined(HAVE_HSTRERROR) || defined(HAVE_GAI_STRERROR) +/* Decode a locale-encoded error message from the C library. + It can be localized and use a non-UTF-8 encoding. */ +static PyObject * +decode_error_message(const char *str) +{ + return PyUnicode_DecodeLocale(str, "surrogateescape"); +} +#endif + #if defined(HAVE_GETHOSTBYNAME_R) || defined (HAVE_GETHOSTBYNAME) || defined (HAVE_GETHOSTBYADDR) static PyObject * set_herror(socket_state *state, int h_error) @@ -742,7 +752,7 @@ set_herror(socket_state *state, int h_error) PyObject *v; #ifdef HAVE_HSTRERROR - v = Py_BuildValue("(is)", h_error, hstrerror(h_error)); + v = Py_BuildValue("(iN)", h_error, decode_error_message(hstrerror(h_error))); #else v = Py_BuildValue("(is)", h_error, "host not found"); #endif @@ -769,7 +779,7 @@ set_gaierror(socket_state *state, int error) #endif #ifdef HAVE_GAI_STRERROR - v = Py_BuildValue("(is)", error, gai_strerror(error)); + v = Py_BuildValue("(iN)", error, decode_error_message(gai_strerror(error))); #else v = Py_BuildValue("(is)", error, "getaddrinfo failed"); #endif