From 2c139997d3d8de000292efda0a37a86ee5611c5b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 19 Nov 2023 23:37:13 +0100 Subject: [PATCH] gh-73561: Omit interface scope from IPv6 when used as Host header (GH-93324) Omit the `@interface_scope` from an IPv6 address when used as Host header by `http.client`. --------- (cherry picked from commit ce1096f974d3158a92e050f9226700775b8db398) Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> Co-authored-by: Gregory P. Smith [Google LLC] --- Lib/http/client.py | 12 ++++++++++-- Lib/test/test_httplib.py | 16 ++++++++++++++++ ...2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 1c7f4639db5888..87bca4d76aba92 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -172,6 +172,13 @@ def _encode(data, name='data'): "if you want to send it encoded in UTF-8." % (name.title(), data[err.start:err.end], name)) from None +def _strip_ipv6_iface(enc_name: bytes) -> bytes: + """Remove interface scope from IPv6 address.""" + enc_name, percent, _ = enc_name.partition(b"%") + if percent: + assert enc_name.startswith(b'['), enc_name + enc_name += b']' + return enc_name class HTTPMessage(email.message.Message): # XXX The only usage of this method is in @@ -1161,7 +1168,7 @@ def putrequest(self, method, url, skip_host=False, netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc_enc = netloc.encode("idna") - self.putheader('Host', netloc_enc) + self.putheader('Host', _strip_ipv6_iface(netloc_enc)) else: if self._tunnel_host: host = self._tunnel_host @@ -1178,8 +1185,9 @@ def putrequest(self, method, url, skip_host=False, # As per RFC 273, IPv6 address should be wrapped with [] # when used as Host header - if host.find(':') >= 0: + if ":" in host: host_enc = b'[' + host_enc + b']' + host_enc = _strip_ipv6_iface(host_enc) if port == self.default_port: self.putheader('Host', host_enc) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index f6a9c820b54d31..f9da4927f682e9 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -285,6 +285,22 @@ def test_ipv6host_header(self): conn.request('GET', '/foo') self.assertTrue(sock.data.startswith(expected)) + expected = b'GET /foo HTTP/1.1\r\nHost: [fe80::]\r\n' \ + b'Accept-Encoding: identity\r\n\r\n' + conn = client.HTTPConnection('[fe80::%2]') + sock = FakeSocket('') + conn.sock = sock + conn.request('GET', '/foo') + self.assertTrue(sock.data.startswith(expected)) + + expected = b'GET /foo HTTP/1.1\r\nHost: [fe80::]:81\r\n' \ + b'Accept-Encoding: identity\r\n\r\n' + conn = client.HTTPConnection('[fe80::%2]:81') + sock = FakeSocket('') + conn.sock = sock + conn.request('GET', '/foo') + self.assertTrue(sock.data.startswith(expected)) + def test_malformed_headers_coped_with(self): # Issue 19996 body = "HTTP/1.1 200 OK\r\nFirst: val\r\n: nval\r\nSecond: val\r\n\r\n" diff --git a/Misc/NEWS.d/next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst b/Misc/NEWS.d/next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst new file mode 100644 index 00000000000000..5e00b7d20b8ca8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst @@ -0,0 +1 @@ +Omit the interface scope from an IPv6 address when used as Host header by :mod:`http.client`.