From 79eff4a58e7756bdf1d01f64d1bd010163db6d3d Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 22:22:31 -0800 Subject: [PATCH 1/6] bug: fix Headers.update to correctly handle repeated headers --- httpx/_models.py | 4 +--- tests/client/test_headers.py | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index f6cd6db939..433724bb0b 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -963,9 +963,7 @@ def get_list(self, key: str, split_commas: bool = False) -> typing.List[str]: return split_values def update(self, headers: HeaderTypes = None) -> None: # type: ignore - headers = Headers(headers) - for key, value in headers.raw: - self[key.decode(headers.encoding)] = value.decode(headers.encoding) + self._list.extend(Headers(headers)._list) def copy(self) -> "Headers": return Headers(self, encoding=self.encoding) diff --git a/tests/client/test_headers.py b/tests/client/test_headers.py index ba862fd521..d0bbe1c4f5 100755 --- a/tests/client/test_headers.py +++ b/tests/client/test_headers.py @@ -10,6 +10,16 @@ def echo_headers(request: httpx.Request) -> httpx.Response: return httpx.Response(200, json=data) +def echo_repeated_headers_multi_items(request: httpx.Request) -> httpx.Response: + data = {"headers": list(request.headers.multi_items())} + return httpx.Response(200, json=data) + + +def echo_repeated_headers_items(request: httpx.Request) -> httpx.Response: + data = {"headers": list(request.headers.items())} + return httpx.Response(200, json=data) + + def test_client_header(): """ Set a header in the Client. @@ -110,6 +120,35 @@ def test_header_update(): } +def test_header_repeated_items(): + url = "http://example.org/echo_headers" + client = httpx.Client(transport=httpx.MockTransport(echo_repeated_headers_items)) + response = client.get(url, headers=[("x-header", "1"), ("x-header", "2")]) + + assert response.status_code == 200 + + echoed_headers = response.json()["headers"] + # as per RFC 7230, the whitespace after a comma is insignificant + # so we split and strip here so that we can do a safe comparison + assert ["x-header", ["1", "2"]] in [ + [k, [subv.lstrip() for subv in v.split(",")]] for k, v in echoed_headers + ] + + +def test_header_repeated_multi_items(): + url = "http://example.org/echo_headers" + client = httpx.Client( + transport=httpx.MockTransport(echo_repeated_headers_multi_items) + ) + response = client.get(url, headers=[("x-header", "1"), ("x-header", "2")]) + + assert response.status_code == 200 + + echoed_headers = response.json()["headers"] + assert ["x-header", "1"] in echoed_headers + assert ["x-header", "2"] in echoed_headers + + def test_remove_default_header(): """ Remove a default header from the Client. From 609e8c2fed8e174ea81354415488be03572bd782 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 22:25:19 -0800 Subject: [PATCH 2/6] add test case with comma seperated headers --- tests/client/test_headers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/client/test_headers.py b/tests/client/test_headers.py index d0bbe1c4f5..dc728b5be0 100755 --- a/tests/client/test_headers.py +++ b/tests/client/test_headers.py @@ -123,30 +123,31 @@ def test_header_update(): def test_header_repeated_items(): url = "http://example.org/echo_headers" client = httpx.Client(transport=httpx.MockTransport(echo_repeated_headers_items)) - response = client.get(url, headers=[("x-header", "1"), ("x-header", "2")]) + response = client.get(url, headers=[("x-header", "1"), ("x-header", "2,3")]) assert response.status_code == 200 echoed_headers = response.json()["headers"] # as per RFC 7230, the whitespace after a comma is insignificant # so we split and strip here so that we can do a safe comparison - assert ["x-header", ["1", "2"]] in [ + assert ["x-header", ["1", "2", "3"]] in [ [k, [subv.lstrip() for subv in v.split(",")]] for k, v in echoed_headers ] + def test_header_repeated_multi_items(): url = "http://example.org/echo_headers" client = httpx.Client( transport=httpx.MockTransport(echo_repeated_headers_multi_items) ) - response = client.get(url, headers=[("x-header", "1"), ("x-header", "2")]) + response = client.get(url, headers=[("x-header", "1"), ("x-header", "2,3")]) assert response.status_code == 200 echoed_headers = response.json()["headers"] assert ["x-header", "1"] in echoed_headers - assert ["x-header", "2"] in echoed_headers + assert ["x-header", "2,3"] in echoed_headers def test_remove_default_header(): From d1bbfb62885ff94c0b0485fc3519ed40ad4e59c4 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 22:26:36 -0800 Subject: [PATCH 3/6] lint --- tests/client/test_headers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/client/test_headers.py b/tests/client/test_headers.py index dc728b5be0..264ca0bd67 100755 --- a/tests/client/test_headers.py +++ b/tests/client/test_headers.py @@ -135,7 +135,6 @@ def test_header_repeated_items(): ] - def test_header_repeated_multi_items(): url = "http://example.org/echo_headers" client = httpx.Client( From 54c0d7ee66572d99ffaac7671a876c55de39f399 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 23:08:27 -0800 Subject: [PATCH 4/6] working implementation --- httpx/_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/httpx/_models.py b/httpx/_models.py index 433724bb0b..94ba19a0b2 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -1,4 +1,5 @@ import cgi +from collections import defaultdict import datetime import email.message import json as jsonlib @@ -7,6 +8,7 @@ from collections.abc import MutableMapping from http.cookiejar import Cookie, CookieJar from urllib.parse import parse_qs, quote, unquote, urlencode +from black import List import charset_normalizer import idna @@ -963,7 +965,11 @@ def get_list(self, key: str, split_commas: bool = False) -> typing.List[str]: return split_values def update(self, headers: HeaderTypes = None) -> None: # type: ignore - self._list.extend(Headers(headers)._list) + headers = Headers(headers) + for key in headers.keys(): + if key in self: + self.pop(key) + self._list.extend(headers._list) def copy(self) -> "Headers": return Headers(self, encoding=self.encoding) From 975d2bfe721962d24a733f746937c98e1cdeaa09 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 23:10:20 -0800 Subject: [PATCH 5/6] remove unused import --- httpx/_models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index 94ba19a0b2..cd49dd2de0 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -1,5 +1,4 @@ import cgi -from collections import defaultdict import datetime import email.message import json as jsonlib @@ -8,12 +7,12 @@ from collections.abc import MutableMapping from http.cookiejar import Cookie, CookieJar from urllib.parse import parse_qs, quote, unquote, urlencode -from black import List import charset_normalizer import idna import rfc3986 import rfc3986.exceptions +from black import List from ._content import ByteStream, UnattachedStream, encode_request, encode_response from ._decoders import ( From 6d553934de7de8aeaebb08ce1d554125db9d2b29 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 20 Jan 2022 23:12:34 -0800 Subject: [PATCH 6/6] remove unused import --- httpx/_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/httpx/_models.py b/httpx/_models.py index cd49dd2de0..3755c2548c 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -12,7 +12,6 @@ import idna import rfc3986 import rfc3986.exceptions -from black import List from ._content import ByteStream, UnattachedStream, encode_request, encode_response from ._decoders import (