From 2b94ba4490a7f08bb14aedc37bd1e45050834662 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 12:01:41 +0100 Subject: [PATCH 1/9] Headers becomes a type of Sequence, rather than a raw, mutable list. --- h11/_connection.py | 10 +++++----- h11/_headers.py | 24 ++++++++++++++++++++++-- h11/tests/test_headers.py | 4 ++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/h11/_connection.py b/h11/_connection.py index fc6289a..410c4e9 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -534,7 +534,7 @@ def send_failed(self): def _clean_up_response_headers_for_sending(self, response): assert type(response) is Response - headers = list(response.headers) + headers = response.headers need_close = False # HEAD requests need some special handling: they always act like they @@ -560,13 +560,13 @@ def _clean_up_response_headers_for_sending(self, response): # but the HTTP spec says that if our peer does this then we have # to fix it instead of erroring out, so we'll accord the user the # same respect). - set_comma_header(headers, b"content-length", []) + headers = set_comma_header(headers, b"content-length", []) if self.their_http_version is None or self.their_http_version < b"1.1": # Either we never got a valid request and are sending back an # error (their_http_version is None), so we assume the worst; # or else we did get a valid HTTP/1.0 request, so we know that # they don't understand chunked encoding. - set_comma_header(headers, b"transfer-encoding", []) + headers = set_comma_header(headers, b"transfer-encoding", []) # This is actually redundant ATM, since currently we # unconditionally disable keep-alive when talking to HTTP/1.0 # peers. But let's be defensive just in case we add @@ -574,13 +574,13 @@ def _clean_up_response_headers_for_sending(self, response): if self._request_method != b"HEAD": need_close = True else: - set_comma_header(headers, b"transfer-encoding", ["chunked"]) + headers = set_comma_header(headers, b"transfer-encoding", ["chunked"]) if not self._cstate.keep_alive or need_close: # Make sure Connection: close is set connection = set(get_comma_header(headers, b"connection")) connection.discard(b"keep-alive") connection.add(b"close") - set_comma_header(headers, b"connection", sorted(connection)) + headers = set_comma_header(headers, b"connection", sorted(connection)) response.headers = headers diff --git a/h11/_headers.py b/h11/_headers.py index 878f63c..2836ebe 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -1,3 +1,4 @@ +from collections.abc import Sequence import re from ._abnf import field_name, field_value @@ -62,6 +63,25 @@ _field_value_re = re.compile(field_value.encode("ascii")) +class Headers(Sequence): + def __init__(self, items): + self._items = items + + def __getitem__(self, item): + _, value = self._data[item] + return value + + def __len__(self): + return len(self._items) + + def __iter__(self): + for name, value in self._items: + yield name, value + + def __eq__(self, other): + return list(self) == other + + def normalize_and_validate(headers, _parsed=False): new_headers = [] saw_content_length = False @@ -100,7 +120,7 @@ def normalize_and_validate(headers, _parsed=False): ) saw_transfer_encoding = True new_headers.append((name, value)) - return new_headers + return Headers(new_headers) def get_comma_header(headers, name): @@ -158,7 +178,7 @@ def set_comma_header(headers, name, new_values): new_headers.append((found_name, found_raw_value)) for new_value in new_values: new_headers.append((name, new_value)) - headers[:] = normalize_and_validate(new_headers) + return normalize_and_validate(new_headers) def has_expect_100_continue(request): diff --git a/h11/tests/test_headers.py b/h11/tests/test_headers.py index 67bcd7b..5c16102 100644 --- a/h11/tests/test_headers.py +++ b/h11/tests/test_headers.py @@ -83,7 +83,7 @@ def test_get_set_comma_header(): assert get_comma_header(headers, b"connection") == [b"close", b"foo", b"bar"] - set_comma_header(headers, b"newthing", ["a", "b"]) + headers = set_comma_header(headers, b"newthing", ["a", "b"]) with pytest.raises(LocalProtocolError): set_comma_header(headers, b"newthing", [" a", "b"]) @@ -96,7 +96,7 @@ def test_get_set_comma_header(): (b"newthing", b"b"), ] - set_comma_header(headers, b"whatever", ["different thing"]) + headers = set_comma_header(headers, b"whatever", ["different thing"]) assert headers == [ (b"connection", b"close"), From ad75a8c3c007875166529e34d35b8ee0fa2f4e75 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 12:21:45 +0100 Subject: [PATCH 2/9] Write headers from headers.raw(), but continue to use lower-casing --- h11/_headers.py | 23 ++++++++++++++--------- h11/_writers.py | 6 ++++-- h11/tests/test_io.py | 2 +- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/h11/_headers.py b/h11/_headers.py index 2836ebe..a5402d1 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -68,19 +68,22 @@ def __init__(self, items): self._items = items def __getitem__(self, item): - _, value = self._data[item] + _, _, value = self._data[item] return value def __len__(self): return len(self._items) def __iter__(self): - for name, value in self._items: + for name, _, value in self._items: yield name, value def __eq__(self, other): return list(self) == other + def raw(self): + return list(self._items) + def normalize_and_validate(headers, _parsed=False): new_headers = [] @@ -95,6 +98,7 @@ def normalize_and_validate(headers, _parsed=False): value = bytesify(value) validate(_field_name_re, name, "Illegal header name {!r}", name) validate(_field_value_re, value, "Illegal header value {!r}", value) + raw_name = name name = name.lower() if name == b"content-length": if saw_content_length: @@ -119,7 +123,7 @@ def normalize_and_validate(headers, _parsed=False): error_status_hint=501, ) saw_transfer_encoding = True - new_headers.append((name, value)) + new_headers.append((name, raw_name, value)) return Headers(new_headers) @@ -127,8 +131,6 @@ def get_comma_header(headers, name): # Should only be used for headers whose value is a list of # comma-separated, case-insensitive values. # - # The header name `name` is expected to be lower-case bytes. - # # Connection: meets these criteria (including cast insensitivity). # # Content-Length: technically is just a single value (1*DIGIT), but the @@ -159,6 +161,7 @@ def get_comma_header(headers, name): # Expect: the only legal value is the literal string # "100-continue". Splitting on commas is harmless. Case insensitive. # + name = name.lower() out = [] for found_name, found_raw_value in headers: if found_name == name: @@ -171,13 +174,15 @@ def get_comma_header(headers, name): def set_comma_header(headers, name, new_values): - # The header name `name` is expected to be lower-case bytes. + raw_name = name + name = name.lower() + new_headers = [] - for found_name, found_raw_value in headers: + for found_name, found_raw_name, found_raw_value in headers.raw(): if found_name != name: - new_headers.append((found_name, found_raw_value)) + new_headers.append((found_raw_name, found_raw_value)) for new_value in new_values: - new_headers.append((name, new_value)) + new_headers.append((raw_name, new_value)) return normalize_and_validate(new_headers) diff --git a/h11/_writers.py b/h11/_writers.py index 6a41100..33937d5 100644 --- a/h11/_writers.py +++ b/h11/_writers.py @@ -38,10 +38,12 @@ def write_headers(headers, write): # "Since the Host field-value is critical information for handling a # request, a user agent SHOULD generate Host as the first header field # following the request-line." - RFC 7230 - for name, value in headers: + raw_headers = headers.raw() + + for name, _, value in raw_headers: if name == b"host": write(bytesmod(b"%s: %s\r\n", (name, value))) - for name, value in headers: + for name, _, value in raw_headers: if name != b"host": write(bytesmod(b"%s: %s\r\n", (name, value))) write(b"\r\n") diff --git a/h11/tests/test_io.py b/h11/tests/test_io.py index ef5e31b..1448a4e 100644 --- a/h11/tests/test_io.py +++ b/h11/tests/test_io.py @@ -121,7 +121,7 @@ def test_writers_unusual(): normalize_and_validate([("foo", "bar"), ("baz", "quux")]), b"foo: bar\r\nbaz: quux\r\n\r\n", ) - tw(write_headers, [], b"\r\n") + tw(write_headers, normalize_and_validate([]), b"\r\n") # We understand HTTP/1.0, but we don't speak it with pytest.raises(LocalProtocolError): From 3641ffd96a8c43bc484304fbf13e060e86f303bb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 12:26:31 +0100 Subject: [PATCH 3/9] Use title casing in get_comma_header/set_comma_header usages --- h11/_connection.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/h11/_connection.py b/h11/_connection.py index 410c4e9..976097e 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -45,7 +45,7 @@ # - If someone says Connection: close, we will close # - If someone uses HTTP/1.0, we will close. def _keep_alive(event): - connection = get_comma_header(event.headers, b"connection") + connection = get_comma_header(event.headers, b"Connection") if b"close" in connection: return False if getattr(event, "http_version", b"1.1") < b"1.1": @@ -85,13 +85,13 @@ def _body_framing(request_method, event): assert event.status_code >= 200 # Step 2: check for Transfer-Encoding (T-E beats C-L): - transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") + transfer_encodings = get_comma_header(event.headers, b"Transfer-Encoding") if transfer_encodings: assert transfer_encodings == [b"chunked"] return ("chunked", ()) # Step 3: check for Content-Length - content_lengths = get_comma_header(event.headers, b"content-length") + content_lengths = get_comma_header(event.headers, b"Content-Length") if content_lengths: return ("content-length", (int(content_lengths[0]),)) @@ -234,7 +234,7 @@ def _process_event(self, role, event): if role is CLIENT and type(event) is Request: if event.method == b"CONNECT": self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) - if get_comma_header(event.headers, b"upgrade"): + if get_comma_header(event.headers, b"Upgrade"): self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) server_switch_event = None if role is SERVER: @@ -560,13 +560,13 @@ def _clean_up_response_headers_for_sending(self, response): # but the HTTP spec says that if our peer does this then we have # to fix it instead of erroring out, so we'll accord the user the # same respect). - headers = set_comma_header(headers, b"content-length", []) + headers = set_comma_header(headers, b"Content-Length", []) if self.their_http_version is None or self.their_http_version < b"1.1": # Either we never got a valid request and are sending back an # error (their_http_version is None), so we assume the worst; # or else we did get a valid HTTP/1.0 request, so we know that # they don't understand chunked encoding. - headers = set_comma_header(headers, b"transfer-encoding", []) + headers = set_comma_header(headers, b"Transfer-Encoding", []) # This is actually redundant ATM, since currently we # unconditionally disable keep-alive when talking to HTTP/1.0 # peers. But let's be defensive just in case we add @@ -574,13 +574,13 @@ def _clean_up_response_headers_for_sending(self, response): if self._request_method != b"HEAD": need_close = True else: - headers = set_comma_header(headers, b"transfer-encoding", ["chunked"]) + headers = set_comma_header(headers, b"Transfer-Encoding", ["chunked"]) if not self._cstate.keep_alive or need_close: # Make sure Connection: close is set - connection = set(get_comma_header(headers, b"connection")) + connection = set(get_comma_header(headers, b"Connection")) connection.discard(b"keep-alive") connection.add(b"close") - headers = set_comma_header(headers, b"connection", sorted(connection)) + headers = set_comma_header(headers, b"Connection", sorted(connection)) response.headers = headers From 5cac0b47ffa70c9e792be91fb1a1901b217a1219 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 12:34:00 +0100 Subject: [PATCH 4/9] Preserve header casing in I/O bytes --- h11/_writers.py | 8 ++++---- h11/tests/test_connection.py | 16 ++++++++-------- h11/tests/test_io.py | 10 +++++----- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/h11/_writers.py b/h11/_writers.py index 33937d5..a820cca 100644 --- a/h11/_writers.py +++ b/h11/_writers.py @@ -40,12 +40,12 @@ def write_headers(headers, write): # following the request-line." - RFC 7230 raw_headers = headers.raw() - for name, _, value in raw_headers: + for name, raw_name, value in raw_headers: if name == b"host": - write(bytesmod(b"%s: %s\r\n", (name, value))) - for name, _, value in raw_headers: + write(bytesmod(b"%s: %s\r\n", (raw_name, value))) + for name, raw_name, value in raw_headers: if name != b"host": - write(bytesmod(b"%s: %s\r\n", (name, value))) + write(bytesmod(b"%s: %s\r\n", (raw_name, value))) write(b"\r\n") diff --git a/h11/tests/test_connection.py b/h11/tests/test_connection.py index 13e6e2d..a43113e 100644 --- a/h11/tests/test_connection.py +++ b/h11/tests/test_connection.py @@ -96,7 +96,7 @@ def test_Connection_basics_and_content_length(): ), ) assert data == ( - b"GET / HTTP/1.1\r\n" b"host: example.com\r\n" b"content-length: 10\r\n\r\n" + b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 10\r\n\r\n" ) for conn in p.conns: @@ -113,7 +113,7 @@ def test_Connection_basics_and_content_length(): assert data == b"HTTP/1.1 100 \r\n\r\n" data = p.send(SERVER, Response(status_code=200, headers=[("Content-Length", "11")])) - assert data == b"HTTP/1.1 200 \r\ncontent-length: 11\r\n\r\n" + assert data == b"HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\n" for conn in p.conns: assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY} @@ -243,7 +243,7 @@ def test_server_talking_to_http10_client(): # We automatically Connection: close back at them assert ( c.send(Response(status_code=200, headers=[])) - == b"HTTP/1.1 200 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" ) assert c.send(Data(data=b"12345")) == b"12345" @@ -303,7 +303,7 @@ def test_automatic_transfer_encoding_in_response(): receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") assert ( c.send(Response(status_code=200, headers=user_headers)) - == b"HTTP/1.1 200 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" ) assert c.send(Data(data=b"12345")) == b"12345" @@ -876,7 +876,7 @@ def test_errors(): if role is SERVER: assert ( c.send(Response(status_code=400, headers=[])) - == b"HTTP/1.1 400 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 400 \r\nConnection: close\r\n\r\n" ) # After an error sending, you can no longer send @@ -988,14 +988,14 @@ def setup(method, http_version): c = setup(method, b"1.1") assert ( c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" - b"transfer-encoding: chunked\r\n\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" ) # No Content-Length, HTTP/1.0 peer, frame with connection: close c = setup(method, b"1.0") assert ( c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" - b"connection: close\r\n\r\n" + b"Connection: close\r\n\r\n" ) # Content-Length + Transfer-Encoding, TE wins @@ -1011,7 +1011,7 @@ def setup(method, http_version): ) ) == b"HTTP/1.1 200 \r\n" - b"transfer-encoding: chunked\r\n\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" ) diff --git a/h11/tests/test_io.py b/h11/tests/test_io.py index 1448a4e..51e7162 100644 --- a/h11/tests/test_io.py +++ b/h11/tests/test_io.py @@ -31,12 +31,12 @@ target="/a", headers=[("Host", "foo"), ("Connection", "close")], ), - b"GET /a HTTP/1.1\r\nhost: foo\r\nconnection: close\r\n\r\n", + b"GET /a HTTP/1.1\r\nHost: foo\r\nConnection: close\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), Response(status_code=200, headers=[("Connection", "close")], reason=b"OK"), - b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\n", + b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), @@ -48,7 +48,7 @@ InformationalResponse( status_code=101, headers=[("Upgrade", "websocket")], reason=b"Upgrade" ), - b"HTTP/1.1 101 Upgrade\r\nupgrade: websocket\r\n\r\n", + b"HTTP/1.1 101 Upgrade\r\nUpgrade: websocket\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), @@ -435,7 +435,7 @@ def test_ChunkedWriter(): assert ( dowrite(w, EndOfMessage(headers=[("Etag", "asdf"), ("a", "b")])) - == b"0\r\netag: asdf\r\na: b\r\n\r\n" + == b"0\r\nEtag: asdf\r\na: b\r\n\r\n" ) @@ -503,5 +503,5 @@ def test_host_comes_first(): tw( write_headers, normalize_and_validate([("foo", "bar"), ("Host", "example.com")]), - b"host: example.com\r\nfoo: bar\r\n\r\n", + b"Host: example.com\r\nfoo: bar\r\n\r\n", ) From edf88cba82d7f7259da5535105de3d3cdf90ab03 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 13:09:32 +0100 Subject: [PATCH 5/9] Python 2 support --- h11/_headers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/h11/_headers.py b/h11/_headers.py index a5402d1..fe737b0 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -1,4 +1,9 @@ -from collections.abc import Sequence +try: + from collections.abc import Sequence +except ImportError: + # Python 2.7 support + from collections import Sequence + import re from ._abnf import field_name, field_value From 0356d32b935d01f162732b7a447e29c47efc3a62 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Aug 2020 14:08:10 +0100 Subject: [PATCH 6/9] Fix __getitem__ implementation --- h11/_headers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h11/_headers.py b/h11/_headers.py index fe737b0..bd87766 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -73,7 +73,7 @@ def __init__(self, items): self._items = items def __getitem__(self, item): - _, _, value = self._data[item] + _, _, value = self._items[item] return value def __len__(self): From 054f0cac9de8b010586ec42178d38198f2ede5e5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 8 Sep 2020 10:39:27 +0100 Subject: [PATCH 7/9] Preserve header casing using tuple-like Header instances --- h11/_connection.py | 8 +++--- h11/_headers.py | 53 ++++++++++++++------------------------- h11/_writers.py | 14 +++++------ h11/tests/test_headers.py | 4 +-- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/h11/_connection.py b/h11/_connection.py index 976097e..5ac82b2 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -560,13 +560,13 @@ def _clean_up_response_headers_for_sending(self, response): # but the HTTP spec says that if our peer does this then we have # to fix it instead of erroring out, so we'll accord the user the # same respect). - headers = set_comma_header(headers, b"Content-Length", []) + set_comma_header(headers, b"Content-Length", []) if self.their_http_version is None or self.their_http_version < b"1.1": # Either we never got a valid request and are sending back an # error (their_http_version is None), so we assume the worst; # or else we did get a valid HTTP/1.0 request, so we know that # they don't understand chunked encoding. - headers = set_comma_header(headers, b"Transfer-Encoding", []) + set_comma_header(headers, b"Transfer-Encoding", []) # This is actually redundant ATM, since currently we # unconditionally disable keep-alive when talking to HTTP/1.0 # peers. But let's be defensive just in case we add @@ -574,13 +574,13 @@ def _clean_up_response_headers_for_sending(self, response): if self._request_method != b"HEAD": need_close = True else: - headers = set_comma_header(headers, b"Transfer-Encoding", ["chunked"]) + set_comma_header(headers, b"Transfer-Encoding", ["chunked"]) if not self._cstate.keep_alive or need_close: # Make sure Connection: close is set connection = set(get_comma_header(headers, b"Connection")) connection.discard(b"keep-alive") connection.add(b"close") - headers = set_comma_header(headers, b"Connection", sorted(connection)) + set_comma_header(headers, b"Connection", sorted(connection)) response.headers = headers diff --git a/h11/_headers.py b/h11/_headers.py index bd87766..ba83008 100644 --- a/h11/_headers.py +++ b/h11/_headers.py @@ -1,9 +1,3 @@ -try: - from collections.abc import Sequence -except ImportError: - # Python 2.7 support - from collections import Sequence - import re from ._abnf import field_name, field_value @@ -68,26 +62,18 @@ _field_value_re = re.compile(field_value.encode("ascii")) -class Headers(Sequence): - def __init__(self, items): - self._items = items - - def __getitem__(self, item): - _, _, value = self._items[item] - return value - - def __len__(self): - return len(self._items) +class Header: + def __init__(self, name, value): + self.raw_name = name + self.name = name.lower() + self.value = value def __iter__(self): - for name, _, value in self._items: - yield name, value + yield self.name + yield self.value def __eq__(self, other): - return list(self) == other - - def raw(self): - return list(self._items) + return (self.name, self.value) == other def normalize_and_validate(headers, _parsed=False): @@ -103,14 +89,13 @@ def normalize_and_validate(headers, _parsed=False): value = bytesify(value) validate(_field_name_re, name, "Illegal header name {!r}", name) validate(_field_value_re, value, "Illegal header value {!r}", value) - raw_name = name - name = name.lower() - if name == b"content-length": + header = Header(name, value) + if header.name == b"content-length": if saw_content_length: raise LocalProtocolError("multiple Content-Length headers") validate(_content_length_re, value, "bad Content-Length") saw_content_length = True - if name == b"transfer-encoding": + if header.name == b"transfer-encoding": # "A server that receives a request message with a transfer coding # it does not understand SHOULD respond with 501 (Not # Implemented)." @@ -121,15 +106,15 @@ def normalize_and_validate(headers, _parsed=False): ) # "All transfer-coding names are case-insensitive" # -- https://tools.ietf.org/html/rfc7230#section-4 - value = value.lower() - if value != b"chunked": + header.value = header.value.lower() + if header.value != b"chunked": raise LocalProtocolError( "Only Transfer-Encoding: chunked is supported", error_status_hint=501, ) saw_transfer_encoding = True - new_headers.append((name, raw_name, value)) - return Headers(new_headers) + new_headers.append(header) + return new_headers def get_comma_header(headers, name): @@ -183,12 +168,12 @@ def set_comma_header(headers, name, new_values): name = name.lower() new_headers = [] - for found_name, found_raw_name, found_raw_value in headers.raw(): - if found_name != name: - new_headers.append((found_raw_name, found_raw_value)) + for header in headers: + if header.name != name: + new_headers.append((header.raw_name, header.value)) for new_value in new_values: new_headers.append((raw_name, new_value)) - return normalize_and_validate(new_headers) + headers[:] = normalize_and_validate(new_headers) def has_expect_100_continue(request): diff --git a/h11/_writers.py b/h11/_writers.py index a820cca..2c5b79b 100644 --- a/h11/_writers.py +++ b/h11/_writers.py @@ -38,14 +38,12 @@ def write_headers(headers, write): # "Since the Host field-value is critical information for handling a # request, a user agent SHOULD generate Host as the first header field # following the request-line." - RFC 7230 - raw_headers = headers.raw() - - for name, raw_name, value in raw_headers: - if name == b"host": - write(bytesmod(b"%s: %s\r\n", (raw_name, value))) - for name, raw_name, value in raw_headers: - if name != b"host": - write(bytesmod(b"%s: %s\r\n", (raw_name, value))) + for header in headers: + if header.name == b"host": + write(bytesmod(b"%s: %s\r\n", (header.raw_name, header.value))) + for header in headers: + if header.name != b"host": + write(bytesmod(b"%s: %s\r\n", (header.raw_name, header.value))) write(b"\r\n") diff --git a/h11/tests/test_headers.py b/h11/tests/test_headers.py index 5c16102..67bcd7b 100644 --- a/h11/tests/test_headers.py +++ b/h11/tests/test_headers.py @@ -83,7 +83,7 @@ def test_get_set_comma_header(): assert get_comma_header(headers, b"connection") == [b"close", b"foo", b"bar"] - headers = set_comma_header(headers, b"newthing", ["a", "b"]) + set_comma_header(headers, b"newthing", ["a", "b"]) with pytest.raises(LocalProtocolError): set_comma_header(headers, b"newthing", [" a", "b"]) @@ -96,7 +96,7 @@ def test_get_set_comma_header(): (b"newthing", b"b"), ] - headers = set_comma_header(headers, b"whatever", ["different thing"]) + set_comma_header(headers, b"whatever", ["different thing"]) assert headers == [ (b"connection", b"close"), From 8cafe02e849b16cb9b7787933d7e6bfeb5c0b64b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 8 Sep 2020 10:43:50 +0100 Subject: [PATCH 8/9] Minimize change footprint --- h11/tests/test_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h11/tests/test_io.py b/h11/tests/test_io.py index 51e7162..e72d0b2 100644 --- a/h11/tests/test_io.py +++ b/h11/tests/test_io.py @@ -121,7 +121,7 @@ def test_writers_unusual(): normalize_and_validate([("foo", "bar"), ("baz", "quux")]), b"foo: bar\r\nbaz: quux\r\n\r\n", ) - tw(write_headers, normalize_and_validate([]), b"\r\n") + tw(write_headers, [], b"\r\n") # We understand HTTP/1.0, but we don't speak it with pytest.raises(LocalProtocolError): From a0eaaf8ede43a7ccacb49b9856551353aa316062 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 8 Sep 2020 10:55:45 +0100 Subject: [PATCH 9/9] Minimize change footprint --- h11/_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h11/_connection.py b/h11/_connection.py index 5ac82b2..74eca39 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -534,7 +534,7 @@ def send_failed(self): def _clean_up_response_headers_for_sending(self, response): assert type(response) is Response - headers = response.headers + headers = list(response.headers) need_close = False # HEAD requests need some special handling: they always act like they