Skip to content

Commit b215a11

Browse files
authored
Merge pull request #452 from python-hyper/issue-451
Resolve issue with plaintext upgrade on the server side
2 parents 52aa2f2 + 0b13b0d commit b215a11

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

HISTORY.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Bugfixes
2525
- Resolved issue where the ``HTTP2-Settings`` header value for plaintext
2626
upgrade that was emitted by ``initiate_upgrade_connection`` included the
2727
*entire* ``SETTINGS`` frame, instead of just the payload.
28+
- Resolved issue where the ``HTTP2-Settings`` header value sent by a client for
29+
plaintext upgrade would be ignored by ``initiate_upgrade_connection``, rather
30+
than have those settings applied appropriately.
2831

2932
2.5.0 (2016-10-25)
3033
------------------

h2/connection.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,19 +588,29 @@ def initiate_upgrade_connection(self, settings_header=None):
588588
"""
589589
frame_data = None
590590

591+
# Begin by getting the preamble in place.
592+
self.initiate_connection()
593+
591594
if self.config.client_side:
592595
f = SettingsFrame(0)
593596
for setting, value in self.local_settings.items():
594597
f.settings[setting] = value
595598

596599
frame_data = f.serialize_body()
597600
frame_data = base64.urlsafe_b64encode(frame_data)
601+
elif settings_header:
602+
# We have a settings header from the client. This needs to be
603+
# applied, but we want to throw away the ACK. We do this by
604+
# inserting the data into a Settings frame and then passing it to
605+
# the state machine, but ignoring the return value.
606+
settings_header = base64.urlsafe_b64decode(settings_header)
607+
f = SettingsFrame(0)
608+
f.parse_body(settings_header)
609+
self._receive_settings_frame(f)
598610

599611
# Set up appropriate state. Stream 1 in a half-closed state:
600612
# half-closed(local) for clients, half-closed(remote) for servers.
601613
# Additionally, we need to set up the Connection state machine.
602-
self.initiate_connection()
603-
604614
connection_input = (
605615
ConnectionInputs.SEND_HEADERS if self.config.client_side
606616
else ConnectionInputs.RECV_HEADERS

test/test_h2_upgrade.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def test_returns_nothing(self, frame_factory):
160160
Calling initiate_upgrade_connection returns nothing.
161161
"""
162162
conn = h2.connection.H2Connection(client_side=False)
163-
curl_header = "AAMAAABkAAQAAP__"
163+
curl_header = b"AAMAAABkAAQAAP__"
164164
data = conn.initiate_upgrade_connection(curl_header)
165165
assert data is None
166166

@@ -263,3 +263,42 @@ def test_cannot_receive_data_stream_1(self, frame_factory):
263263
error_code=h2.errors.ErrorCodes.STREAM_CLOSED,
264264
)
265265
assert c.data_to_send() == expected_frame.serialize()
266+
267+
def test_client_settings_are_applied(self, frame_factory):
268+
"""
269+
The settings provided by the client are applied and immediately
270+
ACK'ed.
271+
"""
272+
server = h2.connection.H2Connection(client_side=False)
273+
client = h2.connection.H2Connection(client_side=True)
274+
275+
# As a precaution, let's confirm that the server and client, at the
276+
# start of the connection, do not agree on their initial settings
277+
# state.
278+
assert (
279+
client.local_settings._settings != server.remote_settings._settings
280+
)
281+
282+
# Get the client header data and pass it to the server.
283+
header_data = client.initiate_upgrade_connection()
284+
server.initiate_upgrade_connection(header_data)
285+
286+
# This gets complex, but here we go.
287+
# RFC 7540 § 3.2.1 says that "explicit acknowledgement" of the settings
288+
# in the header is "not necessary". That's annoyingly vague, but we
289+
# interpret that to mean "should not be sent". So to test that this
290+
# worked we need to test that the server has only sent the preamble,
291+
# and has not sent a SETTINGS ack, and also that the server has the
292+
# correct settings.
293+
expected_frame = frame_factory.build_settings_frame(
294+
server.local_settings
295+
)
296+
assert server.data_to_send() == expected_frame.serialize()
297+
298+
# We violate abstraction layers here, but I don't think defining __eq__
299+
# for this is worth it. In this case, both the client and server should
300+
# agree that these settings have been ACK'd, so their underlying
301+
# dictionaries should be identical.
302+
assert (
303+
client.local_settings._settings == server.remote_settings._settings
304+
)

0 commit comments

Comments
 (0)