1212from ..packages .hyperframe .frame import (
1313 FRAMES , DataFrame , HeadersFrame , PushPromiseFrame , RstStreamFrame ,
1414 SettingsFrame , Frame , WindowUpdateFrame , GoAwayFrame , PingFrame ,
15- BlockedFrame
15+ BlockedFrame , FRAME_MAX_LEN , FRAME_MAX_ALLOWED_LEN
1616)
1717from ..packages .hpack .hpack_compat import Encoder , Decoder
1818from .stream import Stream
@@ -123,6 +123,7 @@ def __init_state(self):
123123 # Values for the settings used on an HTTP/2 connection.
124124 self ._settings = {
125125 SettingsFrame .INITIAL_WINDOW_SIZE : 65535 ,
126+ SettingsFrame .SETTINGS_MAX_FRAME_SIZE : FRAME_MAX_LEN ,
126127 }
127128
128129 # The socket used to send data.
@@ -247,16 +248,17 @@ def _send_preamble(self):
247248 # The server will also send an initial settings frame, so get it.
248249 self ._recv_cb ()
249250
250- def close (self ):
251+ def close (self , error_code = None ):
251252 """
252253 Close the connection to the server.
253254
255+ :param error_code: (optional) The error code to reset all streams with.
254256 :returns: Nothing.
255257 """
256258 # Close all streams
257259 for stream in list (self .streams .values ()):
258260 log .debug ("Close stream %d" % stream .stream_id )
259- stream .close ()
261+ stream .close (error_code )
260262
261263 # Send GoAway frame to the server
262264 try :
@@ -388,10 +390,14 @@ def receive_frame(self, frame):
388390 if 'ACK' not in frame .flags :
389391 self ._update_settings (frame )
390392
391- # Need to return an ack.
392- f = SettingsFrame (0 )
393- f .flags .add ('ACK' )
394- self ._send_cb (f )
393+ # When the setting containing the max frame size value is out
394+ # of range, the spec dictates to tear down the connection.
395+ # Therefore we make sure the socket is still alive before
396+ # returning the ack.
397+ if self ._sock is not None :
398+ f = SettingsFrame (0 )
399+ f .flags .add ('ACK' )
400+ self ._send_cb (f )
395401 elif frame .type == GoAwayFrame .type :
396402 # If we get GoAway with error code zero, we are doing a graceful
397403 # shutdown and all is well. Otherwise, throw an exception.
@@ -449,6 +455,20 @@ def _update_settings(self, frame):
449455
450456 self ._settings [SettingsFrame .INITIAL_WINDOW_SIZE ] = newsize
451457
458+ if SettingsFrame .SETTINGS_MAX_FRAME_SIZE in frame .settings :
459+ new_size = frame .settings [SettingsFrame .SETTINGS_MAX_FRAME_SIZE ]
460+ if FRAME_MAX_LEN <= new_size <= FRAME_MAX_ALLOWED_LEN :
461+ self ._settings [SettingsFrame .SETTINGS_MAX_FRAME_SIZE ] = new_size
462+ else :
463+ log .warning (
464+ "Frame size %d is outside of allowed range" ,
465+ new_size )
466+ # Tear the connection down with error code PROTOCOL_ERROR
467+ self .close (1 )
468+ error_string = ("Advertised frame size %d is outside of range" %
469+ (new_size ))
470+ raise ConnectionError (error_string )
471+
452472 def _new_stream (self , stream_id = None , local_closed = False ):
453473 """
454474 Returns a new stream object for this connection.
@@ -469,12 +489,7 @@ def _close_stream(self, stream_id, error_code=None):
469489 """
470490 Called by a stream when it would like to be 'closed'.
471491 """
472- if error_code is not None :
473- f = RstStreamFrame (stream_id )
474- f .error_code = error_code
475- self ._send_cb (f )
476-
477- del self .streams [stream_id ]
492+ self ._send_rst_frame (stream_id , error_code )
478493
479494 def _send_cb (self , frame , tolerate_peer_gone = False ):
480495 """
@@ -495,6 +510,13 @@ def _send_cb(self, frame, tolerate_peer_gone=False):
495510
496511 data = frame .serialize ()
497512
513+ if frame .body_len > self ._settings [SettingsFrame .SETTINGS_MAX_FRAME_SIZE ]:
514+ raise ValueError (
515+ "Frame size %d exceeds maximum frame size setting %d" %
516+ (frame .body_len ,
517+ self ._settings [SettingsFrame .SETTINGS_MAX_FRAME_SIZE ])
518+ )
519+
498520 log .info (
499521 "Sending frame %s on stream %d" ,
500522 frame .__class__ .__name__ ,
@@ -535,6 +557,15 @@ def _consume_single_frame(self):
535557 # Parse the header. We can use the returned memoryview directly here.
536558 frame , length = Frame .parse_frame_header (header )
537559
560+ if (length > FRAME_MAX_LEN ):
561+ log .warning (
562+ "Frame size exceeded on stream %d (received: %d, max: %d)" ,
563+ frame .stream_id ,
564+ length ,
565+ FRAME_MAX_LEN
566+ )
567+ self ._send_rst_frame (frame .stream_id , 6 ) # 6 = FRAME_SIZE_ERROR
568+
538569 # Read the remaining data from the socket.
539570 data = self ._recv_payload (length )
540571 self ._consume_frame_payload (frame , data )
@@ -595,9 +626,7 @@ def _consume_frame_payload(self, frame, data):
595626 # the ENABLE_PUSH setting is 0, but the spec leaves the client
596627 # action undefined when they do it anyway. So we just refuse
597628 # the stream and go about our business.
598- f = RstStreamFrame (frame .promised_stream_id )
599- f .error_code = 7 # REFUSED_STREAM
600- self ._send_cb (f )
629+ self ._send_rst_frame (frame .promised_stream_id , 7 )
601630
602631 # Work out to whom this frame should go.
603632 if frame .stream_id != 0 :
@@ -606,9 +635,7 @@ def _consume_frame_payload(self, frame, data):
606635 except KeyError :
607636 # If we receive an unexpected stream identifier then we
608637 # cancel the stream with an error of type PROTOCOL_ERROR
609- f = RstStreamFrame (frame .stream_id )
610- f .error_code = 1 # PROTOCOL_ERROR
611- self ._send_cb (f )
638+ self ._send_rst_frame (frame .stream_id , 1 )
612639 log .warning (
613640 "Unexpected stream identifier %d" % (frame .stream_id )
614641 )
@@ -637,6 +664,20 @@ def _recv_cb(self):
637664 except ConnectionResetError :
638665 break
639666
667+ def _send_rst_frame (self , stream_id , error_code ):
668+ """
669+ Send reset stream frame with error code and remove stream from map.
670+ """
671+ f = RstStreamFrame (stream_id )
672+ f .error_code = error_code
673+ self ._send_cb (f )
674+
675+ try :
676+ del self .streams [stream_id ]
677+ except KeyError as e : # pragma: no cover
678+ log .warn (
679+ "Stream with id %d does not exist: %s" ,
680+ stream_id , e )
640681
641682 # The following two methods are the implementation of the context manager
642683 # protocol.
0 commit comments