Skip to content

Commit 5f129a0

Browse files
committed
refactor headers bytes encoding
1 parent 7fa6a5d commit 5f129a0

File tree

8 files changed

+35
-28
lines changed

8 files changed

+35
-28
lines changed

src/h2/connection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from .frame_buffer import FrameBuffer
3434
from .settings import Settings, SettingCodes
3535
from .stream import H2Stream, StreamClosedBy
36-
from .utilities import SizeLimitDict, guard_increment_window, utf8_encode_headers
36+
from .utilities import SizeLimitDict, guard_increment_window
3737
from .windows import WindowManager
3838

3939

@@ -975,7 +975,6 @@ def push_stream(self, stream_id, promised_stream_id, request_headers):
975975
)
976976
self.streams[promised_stream_id] = new_stream
977977

978-
request_headers = utf8_encode_headers(request_headers)
979978
frames = stream.push_stream_in_band(
980979
promised_stream_id, request_headers, self.encoder
981980
)

src/h2/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,8 @@ def __init__(self):
591591
def __repr__(self):
592592
return (
593593
"<AlternativeServiceAvailable origin:%s, field_value:%s>" % (
594-
self.origin.decode('utf-8', 'ignore'),
595-
self.field_value.decode('utf-8', 'ignore'),
594+
(self.origin or b'').decode('utf-8', 'ignore'),
595+
(self.field_value or b'').decode('utf-8', 'ignore'),
596596
)
597597
)
598598

src/h2/stream.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ def send_headers(self, headers, encoder, end_stream=False):
854854
input_ = StreamInputs.SEND_HEADERS
855855

856856
headers = utf8_encode_headers(headers)
857+
857858
if ((not self.state_machine.client) and
858859
is_informational_response(headers)):
859860
if end_stream:
@@ -1243,6 +1244,9 @@ def _build_headers_frames(self,
12431244
"""
12441245
Helper method to build headers or push promise frames.
12451246
"""
1247+
1248+
headers = utf8_encode_headers(headers)
1249+
12461250
# We need to lowercase the header names, and to ensure that secure
12471251
# header fields are kept out of compression contexts.
12481252
if self.config.normalize_outbound_headers:

src/h2/utilities.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,27 +105,26 @@ def extract_method_header(headers):
105105

106106
def is_informational_response(headers):
107107
"""
108-
Searches a header block for a :status header to confirm that a given
108+
Searches headers list for a :status header to confirm that a given
109109
collection of headers are an informational response. Assumes the header
110-
block is well formed: that is, that the HTTP/2 special headers are first
111-
in the block, and so that it can stop looking when it finds the first
112-
header field whose name does not begin with a colon.
110+
are well formed and encoded as bytes: that is, that the HTTP/2 special
111+
headers are first in the block, and so that it can stop looking when it
112+
finds the first header field whose name does not begin with a colon.
113113
114-
:param headers: The HTTP/2 header block.
114+
:param headers: The HTTP/2 headers.
115115
:returns: A boolean indicating if this is an informational response.
116116
"""
117117
for n, v in headers:
118-
# If we find a non-special header, we're done here: stop looping.
118+
if not isinstance(n, bytes) or not isinstance(v, bytes):
119+
raise ProtocolError(f"header not bytes: {n=:r}, {v=:r}") # pragma: no cover
119120

120-
if n and n[0] != SIGIL:
121+
if not n.startswith(b':'):
121122
return False
122-
123-
# This isn't the status header, bail.
124123
if n != b':status':
124+
# If we find a non-special header, we're done here: stop looping.
125125
continue
126-
127126
# If the first digit is a 1, we've got informational headers.
128-
return v[0] == INFORMATIONAL_START
127+
return v.startswith(b'1')
129128

130129

131130
def guard_increment_window(current, increment):
@@ -515,14 +514,14 @@ def utf8_encode_headers(headers):
515514
tuples that preserve the original type of the header tuple for tuple and
516515
any ``HeaderTuple``.
517516
"""
518-
return [
519-
(
520-
header.__class__(_to_bytes(header[0]), _to_bytes(header[1]))
521-
if isinstance(header, HeaderTuple)
522-
else (_to_bytes(header[0]), _to_bytes(header[1]))
523-
)
524-
for header in headers
525-
]
517+
encoded_headers = []
518+
for header in headers:
519+
h = (_to_bytes(header[0]), _to_bytes(header[1]))
520+
if isinstance(header, HeaderTuple):
521+
encoded_headers.append(header.__class__(h[0], h[1]))
522+
else:
523+
encoded_headers.append(h)
524+
return encoded_headers
526525

527526

528527
def _lowercase_header_names(headers, hdr_validation_flags):

test/test_h2_upgrade.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
(b':method', b'GET'),
3333
]
3434

35+
3536
class TestClientUpgrade(object):
3637
"""
3738
Tests of the client-side of the HTTP/2 upgrade dance.

test/test_head_request.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
(':method', 'HEAD'),
2222
]
2323

24+
2425
class TestHeadRequest(object):
2526
example_response_headers = [
2627
(b':status', b'200'),

test/test_invalid_headers.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,9 @@ def test_headers_event_skipping_validation(self, frame_factory, headers):
297297
c.send_headers(1, headers)
298298

299299
# Ensure headers are still normalized.
300+
headers = h2.utilities.utf8_encode_headers(headers)
300301
norm_headers = h2.utilities.normalize_outbound_headers(
301-
h2.utilities.utf8_encode_headers(headers), None, False
302+
headers, None, False
302303
)
303304
f = frame_factory.build_headers_frame(norm_headers)
304305
assert c.data_to_send() == f.serialize()
@@ -323,10 +324,12 @@ def test_push_promise_skipping_validation(self, frame_factory, headers):
323324
)
324325
c.receive_data(header_frame.serialize())
325326

326-
# Create push promise frame with normalized headers.
327327
frame_factory.refresh_encoder()
328+
329+
# Create push promise frame with normalized headers.
330+
headers = h2.utilities.utf8_encode_headers(headers)
328331
norm_headers = h2.utilities.normalize_outbound_headers(
329-
h2.utilities.utf8_encode_headers(headers), None, False
332+
headers, None, False
330333
)
331334
pp_frame = frame_factory.build_push_promise_frame(
332335
stream_id=1, promised_stream_id=2, headers=norm_headers

tox.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py39, py310, py311, py312, py13, pypy3, lint, docs, packaging
2+
envlist = py39, py310, py311, py312, py313, pypy3, lint, docs, packaging
33

44
[gh-actions]
55
python =
@@ -27,7 +27,7 @@ commands = pytest {posargs}
2727

2828
[testenv:lint]
2929
deps =
30-
flake8>=3.9.1,<4
30+
flake8>=7.1.1,<8
3131
commands = flake8 src/ test/
3232

3333
[testenv:docs]

0 commit comments

Comments
 (0)