Commit 95492e9f authored by Luke Zielinski's avatar Luke Zielinski Committed by Chromium LUCI CQ

Roll wpt tooling.

This rolls up to SHA 0fb69997a8b5154bf39ba3ffa586c2aa25442da8

Change-Id: I04ceba8cf7238ff660f3aa3aad70041656ce26be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2585624
Commit-Queue: Robert Ma <robertma@chromium.org>
Auto-Submit: Luke Z <lpz@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835820}
parent 314cbf4c
...@@ -22,7 +22,7 @@ Local Modifications: None ...@@ -22,7 +22,7 @@ Local Modifications: None
Name: web-platform-tests - Test Suites for Web Platform specifications Name: web-platform-tests - Test Suites for Web Platform specifications
Short Name: wpt Short Name: wpt
URL: https://github.com/web-platform-tests/wpt/ URL: https://github.com/web-platform-tests/wpt/
Version: da46f68d5b82371132b4f877e05b9b457de89af9 Version: 0fb69997a8b5154bf39ba3ffa586c2aa25442da8
License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
License File: wpt/wpt/LICENSE.md License File: wpt/wpt/LICENSE.md
Security Critical: no Security Critical: no
......
...@@ -9,7 +9,7 @@ cd $DIR ...@@ -9,7 +9,7 @@ cd $DIR
TARGET_DIR=$DIR/wpt TARGET_DIR=$DIR/wpt
REMOTE_REPO="https://github.com/web-platform-tests/wpt.git" REMOTE_REPO="https://github.com/web-platform-tests/wpt.git"
WPT_HEAD=da46f68d5b82371132b4f877e05b9b457de89af9 WPT_HEAD=0fb69997a8b5154bf39ba3ffa586c2aa25442da8
function clone { function clone {
# Remove existing repo if already exists. # Remove existing repo if already exists.
......
...@@ -4,7 +4,7 @@ import sys as _sys ...@@ -4,7 +4,7 @@ import sys as _sys
__all__ = ['Enum', 'IntEnum', 'unique'] __all__ = ['Enum', 'IntEnum', 'unique']
version = 1, 1, 6 version = 1, 1, 10
pyver = float('%s.%s' % _sys.version_info[:2]) pyver = float('%s.%s' % _sys.version_info[:2])
...@@ -183,7 +183,8 @@ class EnumMeta(type): ...@@ -183,7 +183,8 @@ class EnumMeta(type):
else: else:
del classdict['_order_'] del classdict['_order_']
if pyver < 3.0: if pyver < 3.0:
_order_ = _order_.replace(',', ' ').split() if isinstance(_order_, basestring):
_order_ = _order_.replace(',', ' ').split()
aliases = [name for name in members if name not in _order_] aliases = [name for name in members if name not in _order_]
_order_ += aliases _order_ += aliases
...@@ -463,7 +464,7 @@ class EnumMeta(type): ...@@ -463,7 +464,7 @@ class EnumMeta(type):
_order_.append(member_name) _order_.append(member_name)
# only set _order_ in classdict if name/value was not from a mapping # only set _order_ in classdict if name/value was not from a mapping
if not isinstance(item, basestring): if not isinstance(item, basestring):
classdict['_order_'] = ' '.join(_order_) classdict['_order_'] = _order_
enum_class = metacls.__new__(metacls, class_name, bases, classdict) enum_class = metacls.__new__(metacls, class_name, bases, classdict)
# TODO: replace the frame hack if a blessed way to know the calling # TODO: replace the frame hack if a blessed way to know the calling
......
...@@ -5,4 +5,4 @@ h2 ...@@ -5,4 +5,4 @@ h2
A HTTP/2 implementation. A HTTP/2 implementation.
""" """
__version__ = '3.0.1' __version__ = '3.2.0'
...@@ -29,7 +29,7 @@ class DummyLogger(object): ...@@ -29,7 +29,7 @@ class DummyLogger(object):
""" """
An Logger object that does not actual logging, hence a DummyLogger. An Logger object that does not actual logging, hence a DummyLogger.
For the class the log operation is merely a no-op. The intent is to avoid For the class the log operation is merely a no-op. The intent is to avoid
conditionals being sprinkled throughout the hyper-h2 code for calls to conditionals being sprinkled throughout the hyper-h2 code for calls to
logging functions when no logger is passed into the corresponding object. logging functions when no logger is passed into the corresponding object.
""" """
...@@ -42,6 +42,12 @@ class DummyLogger(object): ...@@ -42,6 +42,12 @@ class DummyLogger(object):
""" """
pass pass
def trace(self, *vargs, **kwargs):
"""
No-op logging. Only level needed for now.
"""
pass
class H2Configuration(object): class H2Configuration(object):
""" """
......
...@@ -21,7 +21,7 @@ from hpack.exceptions import HPACKError, OversizedHeaderListError ...@@ -21,7 +21,7 @@ from hpack.exceptions import HPACKError, OversizedHeaderListError
from .config import H2Configuration from .config import H2Configuration
from .errors import ErrorCodes, _error_code_from_int from .errors import ErrorCodes, _error_code_from_int
from .events import ( from .events import (
WindowUpdated, RemoteSettingsChanged, PingAcknowledged, WindowUpdated, RemoteSettingsChanged, PingReceived, PingAckReceived,
SettingsAcknowledged, ConnectionTerminated, PriorityUpdated, SettingsAcknowledged, ConnectionTerminated, PriorityUpdated,
AlternativeServiceAvailable, UnknownFrameReceived AlternativeServiceAvailable, UnknownFrameReceived
) )
...@@ -33,7 +33,7 @@ from .exceptions import ( ...@@ -33,7 +33,7 @@ from .exceptions import (
from .frame_buffer import FrameBuffer from .frame_buffer import FrameBuffer
from .settings import Settings, SettingCodes from .settings import Settings, SettingCodes
from .stream import H2Stream, StreamClosedBy from .stream import H2Stream, StreamClosedBy
from .utilities import guard_increment_window from .utilities import SizeLimitDict, guard_increment_window
from .windows import WindowManager from .windows import WindowManager
...@@ -281,6 +281,9 @@ class H2Connection(object): ...@@ -281,6 +281,9 @@ class H2Connection(object):
# The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE. # The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE.
DEFAULT_MAX_HEADER_LIST_SIZE = 2**16 DEFAULT_MAX_HEADER_LIST_SIZE = 2**16
# Keep in memory limited amount of results for streams closes
MAX_CLOSED_STREAMS = 2**16
def __init__(self, config=None): def __init__(self, config=None):
self.state_machine = H2ConnectionStateMachine() self.state_machine = H2ConnectionStateMachine()
self.streams = {} self.streams = {}
...@@ -325,7 +328,7 @@ class H2Connection(object): ...@@ -325,7 +328,7 @@ class H2Connection(object):
) )
self.remote_settings = Settings(client=not self.config.client_side) self.remote_settings = Settings(client=not self.config.client_side)
# The curent value of the connection flow control windows on the # The current value of the connection flow control windows on the
# connection. # connection.
self.outbound_flow_control_window = ( self.outbound_flow_control_window = (
self.remote_settings.initial_window_size self.remote_settings.initial_window_size
...@@ -347,7 +350,7 @@ class H2Connection(object): ...@@ -347,7 +350,7 @@ class H2Connection(object):
self._header_frames = [] self._header_frames = []
# Data that needs to be sent. # Data that needs to be sent.
self._data_to_send = b'' self._data_to_send = bytearray()
# Keeps track of how streams are closed. # Keeps track of how streams are closed.
# Used to ensure that we don't blow up in the face of frames that were # Used to ensure that we don't blow up in the face of frames that were
...@@ -355,7 +358,9 @@ class H2Connection(object): ...@@ -355,7 +358,9 @@ class H2Connection(object):
# Also used to determine whether we should consider a frame received # Also used to determine whether we should consider a frame received
# while a stream is closed as either a stream error or a connection # while a stream is closed as either a stream error or a connection
# error. # error.
self._closed_streams = {} self._closed_streams = SizeLimitDict(
size_limit=self.MAX_CLOSED_STREAMS
)
# The flow control window manager for the connection. # The flow control window manager for the connection.
self._inbound_flow_control_window_manager = WindowManager( self._inbound_flow_control_window_manager = WindowManager(
...@@ -909,16 +914,21 @@ class H2Connection(object): ...@@ -909,16 +914,21 @@ class H2Connection(object):
frames = stream.increase_flow_control_window( frames = stream.increase_flow_control_window(
increment increment
) )
self.config.logger.debug(
"Increase stream ID %d flow control window by %d",
stream_id, increment
)
else: else:
self._inbound_flow_control_window_manager.window_opened(increment) self._inbound_flow_control_window_manager.window_opened(increment)
f = WindowUpdateFrame(0) f = WindowUpdateFrame(0)
f.window_increment = increment f.window_increment = increment
frames = [f] frames = [f]
self.config.logger.debug( self.config.logger.debug(
"Increase stream ID %d flow control window by %d", "Increase connection flow control window by %d", increment
stream_id, increment )
)
self._prepare_for_sending(frames) self._prepare_for_sending(frames)
def push_stream(self, stream_id, promised_stream_id, request_headers): def push_stream(self, stream_id, promised_stream_id, request_headers):
...@@ -1327,7 +1337,7 @@ class H2Connection(object): ...@@ -1327,7 +1337,7 @@ class H2Connection(object):
self._prepare_for_sending(frames) self._prepare_for_sending(frames)
def data_to_send(self, amt=None): def data_to_send(self, amount=None):
""" """
Returns some data for sending out of the internal data buffer. Returns some data for sending out of the internal data buffer.
...@@ -1336,19 +1346,19 @@ class H2Connection(object): ...@@ -1336,19 +1346,19 @@ class H2Connection(object):
or less if that much data is not available. It does not perform any or less if that much data is not available. It does not perform any
I/O, and so uses a different name. I/O, and so uses a different name.
:param amt: (optional) The maximum amount of data to return. If not :param amount: (optional) The maximum amount of data to return. If not
set, or set to ``None``, will return as much data as possible. set, or set to ``None``, will return as much data as possible.
:type amt: ``int`` :type amount: ``int``
:returns: A bytestring containing the data to send on the wire. :returns: A bytestring containing the data to send on the wire.
:rtype: ``bytes`` :rtype: ``bytes``
""" """
if amt is None: if amount is None:
data = self._data_to_send data = bytes(self._data_to_send)
self._data_to_send = b'' self._data_to_send = bytearray()
return data return data
else: else:
data = self._data_to_send[:amt] data = bytes(self._data_to_send[:amount])
self._data_to_send = self._data_to_send[amt:] self._data_to_send = self._data_to_send[amount:]
return data return data
def clear_outbound_data_buffer(self): def clear_outbound_data_buffer(self):
...@@ -1361,7 +1371,7 @@ class H2Connection(object): ...@@ -1361,7 +1371,7 @@ class H2Connection(object):
This method should not normally be used, but is made available to avoid This method should not normally be used, but is made available to avoid
exposing implementation details. exposing implementation details.
""" """
self._data_to_send = b'' self._data_to_send = bytearray()
def _acknowledge_settings(self): def _acknowledge_settings(self):
""" """
...@@ -1440,7 +1450,7 @@ class H2Connection(object): ...@@ -1440,7 +1450,7 @@ class H2Connection(object):
:returns: A list of events that the remote peer triggered by sending :returns: A list of events that the remote peer triggered by sending
this data. this data.
""" """
self.config.logger.debug( self.config.logger.trace(
"Process received data on connection. Received data: %r", data "Process received data on connection. Received data: %r", data
) )
...@@ -1624,6 +1634,35 @@ class H2Connection(object): ...@@ -1624,6 +1634,35 @@ class H2Connection(object):
return frames, events + stream_events return frames, events + stream_events
def _handle_data_on_closed_stream(self, events, exc, frame):
# This stream is already closed - and yet we received a DATA frame.
# The received DATA frame counts towards the connection flow window.
# We need to manually to acknowledge the DATA frame to update the flow
# window of the connection. Otherwise the whole connection stalls due
# the inbound flow window being 0.
frames = []
conn_manager = self._inbound_flow_control_window_manager
conn_increment = conn_manager.process_bytes(
frame.flow_controlled_length
)
if conn_increment:
f = WindowUpdateFrame(0)
f.window_increment = conn_increment
frames.append(f)
self.config.logger.debug(
"Received DATA frame on closed stream %d - "
"auto-emitted a WINDOW_UPDATE by %d",
frame.stream_id, conn_increment
)
f = RstStreamFrame(exc.stream_id)
f.error_code = exc.error_code
frames.append(f)
self.config.logger.debug(
"Stream %d already CLOSED or cleaned up - "
"auto-emitted a RST_FRAME" % frame.stream_id
)
return frames, events + exc._events
def _receive_data_frame(self, frame): def _receive_data_frame(self, frame):
""" """
Receive a data frame on the connection. Receive a data frame on the connection.
...@@ -1636,12 +1675,19 @@ class H2Connection(object): ...@@ -1636,12 +1675,19 @@ class H2Connection(object):
self._inbound_flow_control_window_manager.window_consumed( self._inbound_flow_control_window_manager.window_consumed(
flow_controlled_length flow_controlled_length
) )
stream = self._get_stream_by_id(frame.stream_id)
frames, stream_events = stream.receive_data( try:
frame.data, stream = self._get_stream_by_id(frame.stream_id)
'END_STREAM' in frame.flags, frames, stream_events = stream.receive_data(
flow_controlled_length frame.data,
) 'END_STREAM' in frame.flags,
flow_controlled_length
)
except StreamClosedError as e:
# This stream is either marked as CLOSED or already gone from our
# internal state.
return self._handle_data_on_closed_stream(events, e, frame)
return frames, events + stream_events return frames, events + stream_events
def _receive_settings_frame(self, frame): def _receive_settings_frame(self, frame):
...@@ -1717,15 +1763,19 @@ class H2Connection(object): ...@@ -1717,15 +1763,19 @@ class H2Connection(object):
flags = [] flags = []
if 'ACK' in frame.flags: if 'ACK' in frame.flags:
evt = PingAcknowledged() evt = PingAckReceived()
evt.ping_data = frame.opaque_data
events.append(evt)
else: else:
evt = PingReceived()
# automatically ACK the PING with the same 'opaque data'
f = PingFrame(0) f = PingFrame(0)
f.flags = {'ACK'} f.flags = {'ACK'}
f.opaque_data = frame.opaque_data f.opaque_data = frame.opaque_data
flags.append(f) flags.append(f)
evt.ping_data = frame.opaque_data
events.append(evt)
return flags, events return flags, events
def _receive_rst_stream_frame(self, frame): def _receive_rst_stream_frame(self, frame):
......
...@@ -356,22 +356,51 @@ class RemoteSettingsChanged(Event): ...@@ -356,22 +356,51 @@ class RemoteSettingsChanged(Event):
) )
class PingReceived(Event):
"""
The PingReceived event is fired whenever a PING is received. It contains
the 'opaque data' of the PING frame. A ping acknowledgment with the same
'opaque data' is automatically emitted after receiving a ping.
.. versionadded:: 3.1.0
"""
def __init__(self):
#: The data included on the ping.
self.ping_data = None
def __repr__(self):
return "<PingReceived ping_data:%s>" % (
_bytes_representation(self.ping_data),
)
class PingAcknowledged(Event): class PingAcknowledged(Event):
""" """
The PingAcknowledged event is fired whenever a user-emitted PING is Same as PingAckReceived.
acknowledged. This contains the data in the ACK'ed PING, allowing the
user to correlate PINGs and calculate RTT. .. deprecated:: 3.1.0
""" """
def __init__(self): def __init__(self):
#: The data included on the ping. #: The data included on the ping.
self.ping_data = None self.ping_data = None
def __repr__(self): def __repr__(self):
return "<PingAcknowledged ping_data:%s>" % ( return "<PingAckReceived ping_data:%s>" % (
_bytes_representation(self.ping_data), _bytes_representation(self.ping_data),
) )
class PingAckReceived(PingAcknowledged):
"""
The PingAckReceived event is fired whenever a PING acknowledgment is
received. It contains the 'opaque data' of the PING+ACK frame, allowing the
user to correlate PINGs and calculate RTT.
.. versionadded:: 3.1.0
"""
pass
class StreamEnded(Event): class StreamEnded(Event):
""" """
The StreamEnded event is fired whenever a stream is ended by a remote The StreamEnded event is fired whenever a stream is ended by a remote
......
...@@ -15,6 +15,12 @@ from hyperframe.frame import SettingsFrame ...@@ -15,6 +15,12 @@ from hyperframe.frame import SettingsFrame
from h2.errors import ErrorCodes from h2.errors import ErrorCodes
from h2.exceptions import InvalidSettingsValueError from h2.exceptions import InvalidSettingsValueError
try:
from collections.abc import MutableMapping
except ImportError: # pragma: no cover
# Python 2.7 compatibility
from collections import MutableMapping
class SettingCodes(enum.IntEnum): class SettingCodes(enum.IntEnum):
""" """
...@@ -49,6 +55,10 @@ class SettingCodes(enum.IntEnum): ...@@ -49,6 +55,10 @@ class SettingCodes(enum.IntEnum):
#: and value in octets plus an overhead of 32 octets for each header field. #: and value in octets plus an overhead of 32 octets for each header field.
MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE
#: This setting can be used to enable the connect protocol. To enable on a
#: client set this to 1.
ENABLE_CONNECT_PROTOCOL = SettingsFrame.ENABLE_CONNECT_PROTOCOL
def _setting_code_from_int(code): def _setting_code_from_int(code):
""" """
...@@ -88,7 +98,7 @@ class ChangedSetting: ...@@ -88,7 +98,7 @@ class ChangedSetting:
) )
class Settings(collections.MutableMapping): class Settings(MutableMapping):
""" """
An object that encapsulates HTTP/2 settings state. An object that encapsulates HTTP/2 settings state.
...@@ -135,6 +145,7 @@ class Settings(collections.MutableMapping): ...@@ -135,6 +145,7 @@ class Settings(collections.MutableMapping):
SettingCodes.ENABLE_PUSH: collections.deque([int(client)]), SettingCodes.ENABLE_PUSH: collections.deque([int(client)]),
SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]), SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]),
SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]), SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]),
SettingCodes.ENABLE_CONNECT_PROTOCOL: collections.deque([0]),
} }
if initial_values is not None: if initial_values is not None:
for key, value in initial_values.items(): for key, value in initial_values.items():
...@@ -243,6 +254,18 @@ class Settings(collections.MutableMapping): ...@@ -243,6 +254,18 @@ class Settings(collections.MutableMapping):
def max_header_list_size(self, value): def max_header_list_size(self, value):
self[SettingCodes.MAX_HEADER_LIST_SIZE] = value self[SettingCodes.MAX_HEADER_LIST_SIZE] = value
@property
def enable_connect_protocol(self):
"""
The current value of the :data:`ENABLE_CONNECT_PROTOCOL
<h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL>` setting.
"""
return self[SettingCodes.ENABLE_CONNECT_PROTOCOL]
@enable_connect_protocol.setter
def enable_connect_protocol(self, value):
self[SettingCodes.ENABLE_CONNECT_PROTOCOL] = value
# Implement the MutableMapping API. # Implement the MutableMapping API.
def __getitem__(self, key): def __getitem__(self, key):
val = self._settings[key][0] val = self._settings[key][0]
...@@ -292,7 +315,7 @@ class Settings(collections.MutableMapping): ...@@ -292,7 +315,7 @@ class Settings(collections.MutableMapping):
return NotImplemented return NotImplemented
def _validate_setting(setting, value): def _validate_setting(setting, value): # noqa: C901
""" """
Confirms that a specific setting has a well-formed value. If the setting is Confirms that a specific setting has a well-formed value. If the setting is
invalid, returns an error code. Otherwise, returns 0 (NO_ERROR). invalid, returns an error code. Otherwise, returns 0 (NO_ERROR).
...@@ -309,5 +332,8 @@ def _validate_setting(setting, value): ...@@ -309,5 +332,8 @@ def _validate_setting(setting, value):
elif setting == SettingCodes.MAX_HEADER_LIST_SIZE: elif setting == SettingCodes.MAX_HEADER_LIST_SIZE:
if value < 0: if value < 0:
return ErrorCodes.PROTOCOL_ERROR return ErrorCodes.PROTOCOL_ERROR
elif setting == SettingCodes.ENABLE_CONNECT_PROTOCOL:
if value not in (0, 1):
return ErrorCodes.PROTOCOL_ERROR
return 0 return 0
...@@ -378,41 +378,6 @@ class H2StreamStateMachine(object): ...@@ -378,41 +378,6 @@ class H2StreamStateMachine(object):
""" """
raise ProtocolError("Attempted to push on closed stream.") raise ProtocolError("Attempted to push on closed stream.")
def window_on_closed_stream(self, previous_state):
"""
Called when a WINDOW_UPDATE frame is received on an already-closed
stream.
If we sent an END_STREAM frame, we just ignore the frame, as instructed
in RFC 7540 Section 5.1. Technically we should eventually consider
WINDOW_UPDATE in this state an error, but we don't have access to a
clock so we just always allow it. If we closed the stream for any other
reason, we behave as we do for receiving any other frame on a closed
stream.
"""
assert self.stream_closed_by is not None
if self.stream_closed_by == StreamClosedBy.SEND_END_STREAM:
return []
return self.recv_on_closed_stream(previous_state)
def reset_on_closed_stream(self, previous_state):
"""
Called when a RST_STREAM frame is received on an already-closed stream.
If we sent an END_STREAM frame, we just ignore the frame, as instructed
in RFC 7540 Section 5.1. Technically we should eventually consider
RST_STREAM in this state an error, but we don't have access to a clock
so we just always allow it. If we closed the stream for any other
reason, we behave as we do for receiving any other frame on a closed
stream.
"""
assert self.stream_closed_by is not None
if self.stream_closed_by is StreamClosedBy.SEND_END_STREAM:
return []
return self.recv_on_closed_stream(previous_state)
def send_informational_response(self, previous_state): def send_informational_response(self, previous_state):
""" """
Called when an informational header block is sent (that is, a block Called when an informational header block is sent (that is, a block
...@@ -740,11 +705,12 @@ _transitions = { ...@@ -740,11 +705,12 @@ _transitions = {
# > WINDOW_UPDATE or RST_STREAM frames can be received in this state # > WINDOW_UPDATE or RST_STREAM frames can be received in this state
# > for a short period after a DATA or HEADERS frame containing a # > for a short period after a DATA or HEADERS frame containing a
# > END_STREAM flag is sent. # > END_STREAM flag is sent, as instructed in RFC 7540 Section 5.1. But we
# > don't have access to a clock so we just always allow it.
(StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE): (StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE):
(H2StreamStateMachine.window_on_closed_stream, StreamState.CLOSED), (None, StreamState.CLOSED),
(StreamState.CLOSED, StreamInputs.RECV_RST_STREAM): (StreamState.CLOSED, StreamInputs.RECV_RST_STREAM):
(H2StreamStateMachine.reset_on_closed_stream, StreamState.CLOSED), (None, StreamState.CLOSED),
# > A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is # > A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is
# > neither "open" nor "half-closed (local)" as a connection error of type # > neither "open" nor "half-closed (local)" as a connection error of type
...@@ -788,7 +754,7 @@ class H2Stream(object): ...@@ -788,7 +754,7 @@ class H2Stream(object):
self.max_outbound_frame_size = None self.max_outbound_frame_size = None
self.request_method = None self.request_method = None
# The curent value of the outbound stream flow control window # The current value of the outbound stream flow control window
self.outbound_flow_control_window = outbound_window_size self.outbound_flow_control_window = outbound_window_size
# The flow control manager. # The flow control manager.
......
...@@ -33,6 +33,7 @@ _ALLOWED_PSEUDO_HEADER_FIELDS = frozenset([ ...@@ -33,6 +33,7 @@ _ALLOWED_PSEUDO_HEADER_FIELDS = frozenset([
b':authority', u':authority', b':authority', u':authority',
b':path', u':path', b':path', u':path',
b':status', u':status', b':status', u':status',
b':protocol', u':protocol',
]) ])
...@@ -47,13 +48,19 @@ _REQUEST_ONLY_HEADERS = frozenset([ ...@@ -47,13 +48,19 @@ _REQUEST_ONLY_HEADERS = frozenset([
b':scheme', u':scheme', b':scheme', u':scheme',
b':path', u':path', b':path', u':path',
b':authority', u':authority', b':authority', u':authority',
b':method', u':method' b':method', u':method',
b':protocol', u':protocol',
]) ])
_RESPONSE_ONLY_HEADERS = frozenset([b':status', u':status']) _RESPONSE_ONLY_HEADERS = frozenset([b':status', u':status'])
# A Set of pseudo headers that are only valid if the method is
# CONNECT, see RFC 8441 § 5
_CONNECT_REQUEST_ONLY_HEADERS = frozenset([b':protocol', u':protocol'])
if sys.version_info[0] == 2: # Python 2.X if sys.version_info[0] == 2: # Python 2.X
_WHITESPACE = frozenset(whitespace) _WHITESPACE = frozenset(whitespace)
else: # Python 3.3+ else: # Python 3.3+
...@@ -323,6 +330,7 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags): ...@@ -323,6 +330,7 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
""" """
seen_pseudo_header_fields = set() seen_pseudo_header_fields = set()
seen_regular_header = False seen_regular_header = False
method = None
for header in headers: for header in headers:
if _custom_startswith(header[0], b':', u':'): if _custom_startswith(header[0], b':', u':'):
...@@ -344,6 +352,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags): ...@@ -344,6 +352,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
"Received custom pseudo-header field %s" % header[0] "Received custom pseudo-header field %s" % header[0]
) )
if header[0] in (b':method', u':method'):
if not isinstance(header[1], bytes):
method = header[1].encode('utf-8')
else:
method = header[1]
else: else:
seen_regular_header = True seen_regular_header = True
...@@ -351,11 +365,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags): ...@@ -351,11 +365,12 @@ def _reject_pseudo_header_fields(headers, hdr_validation_flags):
# Check the pseudo-headers we got to confirm they're acceptable. # Check the pseudo-headers we got to confirm they're acceptable.
_check_pseudo_header_field_acceptability( _check_pseudo_header_field_acceptability(
seen_pseudo_header_fields, hdr_validation_flags seen_pseudo_header_fields, method, hdr_validation_flags
) )
def _check_pseudo_header_field_acceptability(pseudo_headers, def _check_pseudo_header_field_acceptability(pseudo_headers,
method,
hdr_validation_flags): hdr_validation_flags):
""" """
Given the set of pseudo-headers present in a header block and the Given the set of pseudo-headers present in a header block and the
...@@ -394,6 +409,13 @@ def _check_pseudo_header_field_acceptability(pseudo_headers, ...@@ -394,6 +409,13 @@ def _check_pseudo_header_field_acceptability(pseudo_headers,
"Encountered response-only headers %s" % "Encountered response-only headers %s" %
invalid_request_headers invalid_request_headers
) )
if method != b'CONNECT':
invalid_headers = pseudo_headers & _CONNECT_REQUEST_ONLY_HEADERS
if invalid_headers:
raise ProtocolError(
"Encountered connect-request-only headers %s" %
invalid_headers
)
def _validate_host_authority_header(headers): def _validate_host_authority_header(headers):
...@@ -617,3 +639,22 @@ def validate_outbound_headers(headers, hdr_validation_flags): ...@@ -617,3 +639,22 @@ def validate_outbound_headers(headers, hdr_validation_flags):
headers = _check_path_header(headers, hdr_validation_flags) headers = _check_path_header(headers, hdr_validation_flags)
return headers return headers
class SizeLimitDict(collections.OrderedDict):
def __init__(self, *args, **kwargs):
self._size_limit = kwargs.pop("size_limit", None)
super(SizeLimitDict, self).__init__(*args, **kwargs)
self._check_size_limit()
def __setitem__(self, key, value):
super(SizeLimitDict, self).__setitem__(key, value)
self._check_size_limit()
def _check_size_limit(self):
if self._size_limit is not None:
while len(self) > self._size_limit:
self.popitem(last=False)
...@@ -5,4 +5,4 @@ hyperframe ...@@ -5,4 +5,4 @@ hyperframe
A module for providing a pure-Python HTTP/2 framing layer. A module for providing a pure-Python HTTP/2 framing layer.
""" """
__version__ = '5.1.0' __version__ = '5.2.0'
...@@ -7,11 +7,16 @@ Defines basic Flag and Flags data structures. ...@@ -7,11 +7,16 @@ Defines basic Flag and Flags data structures.
""" """
import collections import collections
try:
from collections.abc import MutableSet
except ImportError: # pragma: no cover
# Python 2.7 compatibility
from collections import MutableSet
Flag = collections.namedtuple("Flag", ["name", "bit"]) Flag = collections.namedtuple("Flag", ["name", "bit"])
class Flags(collections.MutableSet): class Flags(MutableSet):
""" """
A simple MutableSet implementation that will only accept known flags as A simple MutableSet implementation that will only accept known flags as
elements. elements.
......
...@@ -395,6 +395,8 @@ class SettingsFrame(Frame): ...@@ -395,6 +395,8 @@ class SettingsFrame(Frame):
MAX_FRAME_SIZE = 0x05 MAX_FRAME_SIZE = 0x05
#: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting. #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.
MAX_HEADER_LIST_SIZE = 0x06 MAX_HEADER_LIST_SIZE = 0x06
#: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting.
ENABLE_CONNECT_PROTOCOL = 0x08
def __init__(self, stream_id=0, settings=None, **kwargs): def __init__(self, stream_id=0, settings=None, **kwargs):
super(SettingsFrame, self).__init__(stream_id, **kwargs) super(SettingsFrame, self).__init__(stream_id, **kwargs)
......
...@@ -133,6 +133,8 @@ def is_bad_port(port): ...@@ -133,6 +133,8 @@ def is_bad_port(port):
2049, # nfs 2049, # nfs
3659, # apple-sasl 3659, # apple-sasl
4045, # lockd 4045, # lockd
5060, # sip
5061, # sips
6000, # x11 6000, # x11
6665, # irc (alternate) 6665, # irc (alternate)
6666, # irc (alternate) 6666, # irc (alternate)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment