From 13b302c70282620d21f72fbc1cd43b0adf784096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel=20Pieroli=20da=20Silva?= Date: Tue, 13 Oct 2026 20:24:45 -0300 Subject: [PATCH] gh-159044: Restore string conversion for HTTP tunnel header values (GH-159216) (cherry picked from commit 8138bf11a5d32e8db003a86606be8e4c61e14d4b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Gabriel Pieroli da Silva Co-authored-by: LorenaMBorges --- Doc/library/http.client.rst | 3 ++- Lib/http/client.py | 2 +- Lib/test/test_httplib.py | 17 +++++++++++++++++ ...26-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst index 4002fc697ceaa5..ab557f1805e104 100644 --- a/Doc/library/http.client.rst +++ b/Doc/library/http.client.rst @@ -370,7 +370,8 @@ HTTPConnection Objects proxy server). The *headers* argument should be a mapping of extra HTTP headers to send with - the CONNECT request. + the CONNECT request. Non-string header values are converted to strings via + :func:`str`. As HTTP/1.1 is used for HTTP CONNECT tunnelling request, `as per the RFC `_, a HTTP ``Host:`` diff --git a/Lib/http/client.py b/Lib/http/client.py index 7ef99e7201c005..3fdf3a36f180ef 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1013,7 +1013,7 @@ def _tunnel(self): headers = [connect] for header, value in self._tunnel_headers.items(): header_bytes = header.encode("latin-1") - value_bytes = value.encode("latin-1") + value_bytes = str(value).encode("latin-1") if not _is_legal_header_name(header_bytes): raise ValueError('Invalid header name %r' % (header_bytes,)) if _is_illegal_header_value(value_bytes): diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 5b1d6e0aa52079..bd8cdfe55c6b4d 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2766,6 +2766,23 @@ def _create_connection(address, timeout=None, source_address=None): self.assertIsNotNone(exc) self.assertTrue(sock.file_closed) + def test_tunnel_nonstr_headers(self): + class CustomObject: + def __str__(self): + return 'custom-str' + + self.conn.set_tunnel('destination.com', headers={ + 'None-Value': None, + 'Integer-Value': 9, + 'Custom-Value': CustomObject(), + }) + + self.conn.request('HEAD', '/', '') + + self.assertIn(b'None-Value: None\r\n', self.conn.sock.data) + self.assertIn(b'Integer-Value: 9\r\n', self.conn.sock.data) + self.assertIn(b'Custom-Value: custom-str\r\n', self.conn.sock.data) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst b/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst new file mode 100644 index 00000000000000..ef51dc8eca1066 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst @@ -0,0 +1,4 @@ +Restore the previous behaviour of :meth:`http.client.HTTPConnection.set_tunnel` +where header values that are not strings (such as :const:`None`, :class:`int` +or other objects) are converted with :class:`str` instead of raising +:exc:`AttributeError` when the request is sent.