Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/h2/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .frame_buffer import FrameBuffer
from .settings import Settings, SettingCodes
from .stream import H2Stream, StreamClosedBy
from .utilities import SizeLimitDict, guard_increment_window, utf8_encode_headers
from .utilities import SizeLimitDict, guard_increment_window
from .windows import WindowManager


Expand Down Expand Up @@ -975,7 +975,6 @@ def push_stream(self, stream_id, promised_stream_id, request_headers):
)
self.streams[promised_stream_id] = new_stream

request_headers = utf8_encode_headers(request_headers)
frames = stream.push_stream_in_band(
promised_stream_id, request_headers, self.encoder
)
Expand Down
4 changes: 2 additions & 2 deletions src/h2/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,8 @@ def __init__(self):
def __repr__(self):
return (
"<AlternativeServiceAvailable origin:%s, field_value:%s>" % (
self.origin.decode('utf-8', 'ignore'),
self.field_value.decode('utf-8', 'ignore'),
(self.origin or b'').decode('utf-8', 'ignore'),
(self.field_value or b'').decode('utf-8', 'ignore'),
)
)

Expand Down
4 changes: 4 additions & 0 deletions src/h2/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ def send_headers(self, headers, encoder, end_stream=False):
input_ = StreamInputs.SEND_HEADERS

headers = utf8_encode_headers(headers)

if ((not self.state_machine.client) and
is_informational_response(headers)):
if end_stream:
Expand Down Expand Up @@ -1243,6 +1244,9 @@ def _build_headers_frames(self,
"""
Helper method to build headers or push promise frames.
"""

headers = utf8_encode_headers(headers)

# We need to lowercase the header names, and to ensure that secure
# header fields are kept out of compression contexts.
if self.config.normalize_outbound_headers:
Expand Down
37 changes: 18 additions & 19 deletions src/h2/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,26 @@ def extract_method_header(headers):

def is_informational_response(headers):
"""
Searches a header block for a :status header to confirm that a given
Searches headers list for a :status header to confirm that a given
collection of headers are an informational response. Assumes the header
block is well formed: that is, that the HTTP/2 special headers are first
in the block, and so that it can stop looking when it finds the first
header field whose name does not begin with a colon.
are well formed and encoded as bytes: that is, that the HTTP/2 special
headers are first in the block, and so that it can stop looking when it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
headers are first in the block, and so that it can stop looking when it
headers are first in the list, and so that it can stop looking when it

finds the first header field whose name does not begin with a colon.

:param headers: The HTTP/2 header block.
:param headers: The HTTP/2 headers.
:returns: A boolean indicating if this is an informational response.
"""
for n, v in headers:
# If we find a non-special header, we're done here: stop looping.
if not isinstance(n, bytes) or not isinstance(v, bytes):
raise ProtocolError(f"header not bytes: {n=:r}, {v=:r}") # pragma: no cover

if n and n[0] != SIGIL:
if not n.startswith(b':'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? (And the other .startswith() below from my n and n[0] == SIGIL version?) I'm quite sure they are significantly slower as they involve a function call and are not optimized for 1-byte look up.

return False

# This isn't the status header, bail.
if n != b':status':
# If we find a non-special header, we're done here: stop looping.
continue

# If the first digit is a 1, we've got informational headers.
return v[0] == INFORMATIONAL_START
return v.startswith(b'1')


def guard_increment_window(current, increment):
Expand Down Expand Up @@ -515,14 +514,14 @@ def utf8_encode_headers(headers):
tuples that preserve the original type of the header tuple for tuple and
any ``HeaderTuple``.
"""
return [
(
header.__class__(_to_bytes(header[0]), _to_bytes(header[1]))
if isinstance(header, HeaderTuple)
else (_to_bytes(header[0]), _to_bytes(header[1]))
)
for header in headers
]
encoded_headers = []
for header in headers:
h = (_to_bytes(header[0]), _to_bytes(header[1]))
if isinstance(header, HeaderTuple):
encoded_headers.append(header.__class__(h[0], h[1]))
else:
encoded_headers.append(h)
return encoded_headers
Comment on lines +517 to +524
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is again, quite less efficient compared to using a list-comprehension directly.



def _lowercase_header_names(headers, hdr_validation_flags):
Expand Down
1 change: 1 addition & 0 deletions test/test_h2_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
(b':method', b'GET'),
]


class TestClientUpgrade(object):
"""
Tests of the client-side of the HTTP/2 upgrade dance.
Expand Down
1 change: 1 addition & 0 deletions test/test_head_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
(':method', 'HEAD'),
]


class TestHeadRequest(object):
example_response_headers = [
(b':status', b'200'),
Expand Down
9 changes: 6 additions & 3 deletions test/test_invalid_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,9 @@ def test_headers_event_skipping_validation(self, frame_factory, headers):
c.send_headers(1, headers)

# Ensure headers are still normalized.
headers = h2.utilities.utf8_encode_headers(headers)
norm_headers = h2.utilities.normalize_outbound_headers(
h2.utilities.utf8_encode_headers(headers), None, False
headers, None, False
)
f = frame_factory.build_headers_frame(norm_headers)
assert c.data_to_send() == f.serialize()
Expand All @@ -323,10 +324,12 @@ def test_push_promise_skipping_validation(self, frame_factory, headers):
)
c.receive_data(header_frame.serialize())

# Create push promise frame with normalized headers.
frame_factory.refresh_encoder()

# Create push promise frame with normalized headers.
headers = h2.utilities.utf8_encode_headers(headers)
norm_headers = h2.utilities.normalize_outbound_headers(
h2.utilities.utf8_encode_headers(headers), None, False
headers, None, False
)
pp_frame = frame_factory.build_push_promise_frame(
stream_id=1, promised_stream_id=2, headers=norm_headers
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py39, py310, py311, py312, py13, pypy3, lint, docs, packaging
envlist = py39, py310, py311, py312, py313, pypy3, lint, docs, packaging

[gh-actions]
python =
Expand Down Expand Up @@ -27,7 +27,7 @@ commands = pytest {posargs}

[testenv:lint]
deps =
flake8>=3.9.1,<4
flake8>=7.1.1,<8
commands = flake8 src/ test/

[testenv:docs]
Expand Down
Loading