From e4460567b78539e83c20073fe6e570a708656af0 Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sat, 28 May 2022 20:28:56 +0200 Subject: [PATCH 1/8] omit scope from IPv6 when used as Host header --- Lib/http/client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lib/http/client.py b/Lib/http/client.py index 3d98e4eb54bb45..661393fe7f48dd 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1195,6 +1195,12 @@ def putrequest(self, method, url, skip_host=False, netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc_enc = netloc.encode("idna") + + # remove interface scope from IPv6 address + # when used as Host header + if netloc.find('%') >= 0: + netloc_enc = netloc_enc[:netloc.find('%')] + b']' + self.putheader('Host', netloc_enc) else: if self._tunnel_host: @@ -1213,6 +1219,10 @@ def putrequest(self, method, url, skip_host=False, # when used as Host header if host.find(':') >= 0: + # remove interface scope from IPv6 address + # when used as Host header + if host.find('%') >= 0: + host_enc = host_enc[:host.find('%')] + b']' host_enc = b'[' + host_enc + b']' if port == self.default_port: From 62470b84c5f366ee33635c63bc765f35ceba4976 Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sat, 28 May 2022 20:51:27 +0200 Subject: [PATCH 2/8] add test --- Lib/test/test_httplib.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 8955d45fa93dd4..4cc601a5c1b0d3 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -281,6 +281,14 @@ 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)) + 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" From 56058ab6f4370b4fde962b6ae951fbb1fa1b244b Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sat, 28 May 2022 20:55:26 +0200 Subject: [PATCH 3/8] add news --- .../next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst 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..e376691fcfb78c --- /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 IPv6 address when used as Host header. From 5a495f8cfc3cd55b70247a5e9a2effdb1d662c01 Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sat, 28 May 2022 22:37:50 +0200 Subject: [PATCH 4/8] fix --- Lib/http/client.py | 2 +- Lib/test/test_httplib.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 661393fe7f48dd..2f9abf3a4a9fd2 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1222,7 +1222,7 @@ def putrequest(self, method, url, skip_host=False, # remove interface scope from IPv6 address # when used as Host header if host.find('%') >= 0: - host_enc = host_enc[:host.find('%')] + b']' + host_enc = host_enc[:host.find('%')] host_enc = b'[' + host_enc + b']' if port == self.default_port: diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 4cc601a5c1b0d3..db3cdf38a3c6af 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -289,6 +289,14 @@ 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::]: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" From a69e99a6e1ef631e45d4f3dfe62c364df39c700f Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sat, 8 Jul 2023 13:51:37 +0200 Subject: [PATCH 5/8] use the "in" operator in if statements --- Lib/http/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index 2f9abf3a4a9fd2..e453a7b67ddcdd 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -1198,7 +1198,7 @@ def putrequest(self, method, url, skip_host=False, # remove interface scope from IPv6 address # when used as Host header - if netloc.find('%') >= 0: + if "%" in netloc: netloc_enc = netloc_enc[:netloc.find('%')] + b']' self.putheader('Host', netloc_enc) @@ -1218,10 +1218,10 @@ 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: # remove interface scope from IPv6 address # when used as Host header - if host.find('%') >= 0: + if "%" in host: host_enc = host_enc[:host.find('%')] host_enc = b'[' + host_enc + b']' From 43fc859e84fa2530508117573e4e2b1eb1fc0968 Mon Sep 17 00:00:00 2001 From: mib1185 Date: Sun, 19 Nov 2023 19:25:18 +0100 Subject: [PATCH 6/8] use partition method to split bytes --- Lib/http/client.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Lib/http/client.py b/Lib/http/client.py index e453a7b67ddcdd..f6190598ab39ee 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -172,6 +172,12 @@ 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: + enc_name += b']' + return enc_name class HTTPMessage(email.message.Message): # XXX The only usage of this method is in @@ -1195,13 +1201,7 @@ def putrequest(self, method, url, skip_host=False, netloc_enc = netloc.encode("ascii") except UnicodeEncodeError: netloc_enc = netloc.encode("idna") - - # remove interface scope from IPv6 address - # when used as Host header - if "%" in netloc: - netloc_enc = netloc_enc[:netloc.find('%')] + b']' - - self.putheader('Host', netloc_enc) + self.putheader('Host', _strip_ipv6_iface(netloc_enc)) else: if self._tunnel_host: host = self._tunnel_host @@ -1219,11 +1219,8 @@ def putrequest(self, method, url, skip_host=False, # when used as Host header if ":" in host: - # remove interface scope from IPv6 address - # when used as Host header - if "%" in host: - host_enc = host_enc[:host.find('%')] host_enc = b'[' + host_enc + b']' + host_enc = _strip_ipv6_iface(host_enc) if port == self.default_port: self.putheader('Host', host_enc) From cc309f77bc66d8795c304b6ff32d0f1cf853d0fe Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 19 Nov 2023 14:20:51 -0800 Subject: [PATCH 7/8] ReSTify the NEWS entry, mention http.client --- .../next/Library/2022-05-28-20-55-07.gh-issue-73561.YRmAvy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e376691fcfb78c..5e00b7d20b8ca8 100644 --- 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 @@ -1 +1 @@ -Omit the interface scope from IPv6 address when used as Host header. +Omit the interface scope from an IPv6 address when used as Host header by :mod:`http.client`. From 2303e79484c214d1d7951470ba34d751cb10af83 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 19 Nov 2023 14:22:09 -0800 Subject: [PATCH 8/8] Add an assertion that enc_name starts with [ before appending ] --- Lib/http/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/http/client.py b/Lib/http/client.py index 01d46833a7bead..7bb5d824bb6da4 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -176,6 +176,7 @@ 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