From 231172386b7a34f436ddc9d55726b35129c539ac Mon Sep 17 00:00:00 2001 From: Som Samantray <92726151+SomSamantray@users.noreply.github.com> Date: Sun, 11 Oct 2026 14:09:29 +0530 Subject: [PATCH] gh-159044: Restore string conversion for tunnel headers --- Doc/library/http.client.rst | 2 ++ Lib/http/client.py | 4 ++-- Lib/test/test_httplib.py | 13 +++++++++++++ .../2026-10-11-14-06-06.gh-issue-159044.Q7mX2p.rst | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-11-14-06-06.gh-issue-159044.Q7mX2p.rst diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst index 4002fc697ceaa56..fda469ec1cfa05e 100644 --- a/Doc/library/http.client.rst +++ b/Doc/library/http.client.rst @@ -372,6 +372,8 @@ HTTPConnection Objects The *headers* argument should be a mapping of extra HTTP headers to send with the CONNECT request. + Header values in the mapping are converted to strings before they are sent. + As HTTP/1.1 is used for HTTP CONNECT tunnelling request, `as per the RFC `_, a HTTP ``Host:`` header must be provided, matching the authority-form of the request target diff --git a/Lib/http/client.py b/Lib/http/client.py index 4b6fc549a9b5179..12525a7c897a6c9 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1014,8 +1014,8 @@ def _tunnel(self): self._http_vsn_str.encode("ascii")) headers = [connect] for header, value in self._tunnel_headers.items(): - header_bytes = header.encode("latin-1") - value_bytes = value.encode("latin-1") + header_bytes = str(header).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 ad28e921978e98f..e94030a16262b78 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -398,6 +398,19 @@ def test_invalid_tunnel_headers(self): with self.assertRaisesRegex(ValueError, 'Invalid header'): conn._tunnel() # Called in .connect() + def test_tunnel_headers_are_converted_to_strings(self): + conn = client.HTTPConnection('example.com') + conn.set_tunnel('tunnel', headers={ + 'None-Value': None, + 'Integer-Value': 42, + }) + conn.sock = FakeSocket('HTTP/1.1 200 Connection established\r\n\r\n') + + conn._tunnel() # Called in .connect() + + self.assertIn(b'None-Value: None\r\n', conn.sock.data) + self.assertIn(b'Integer-Value: 42\r\n', conn.sock.data) + def test_invalid_tunnel_host(self): cases = ( 'invalid\r.host', diff --git a/Misc/NEWS.d/next/Library/2026-10-11-14-06-06.gh-issue-159044.Q7mX2p.rst b/Misc/NEWS.d/next/Library/2026-10-11-14-06-06.gh-issue-159044.Q7mX2p.rst new file mode 100644 index 000000000000000..4dabb936b71eb40 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-11-14-06-06.gh-issue-159044.Q7mX2p.rst @@ -0,0 +1 @@ +Restore string conversion of HTTP CONNECT tunnel header names and values.