def read_body(self, event: events.Event) -> layer.CommandGenerator[None]: assert self.stream_id while True: try: if isinstance(event, events.DataReceived): h11_event = self.body_reader(self.buf) elif isinstance(event, events.ConnectionClosed): h11_event = self.body_reader.read_eof() else: raise AssertionError(f"Unexpected event: {event}") except h11.ProtocolError as e: yield commands.CloseConnection(self.conn) yield ReceiveHttp( self.ReceiveProtocolError(self.stream_id, f"HTTP/1 protocol error: {e}")) return if h11_event is None: return elif isinstance(h11_event, h11.Data): data: bytes = bytes(h11_event.data) if data: yield ReceiveHttp(self.ReceiveData(self.stream_id, data)) elif isinstance(h11_event, h11.EndOfMessage): assert self.request if h11_event.headers: raise NotImplementedError( f"HTTP trailers are not implemented yet.") if self.request.data.method.upper() != b"CONNECT": yield ReceiveHttp(self.ReceiveEndOfMessage(self.stream_id)) is_request = isinstance(self, Http1Server) yield from self.mark_done(request=is_request, response=not is_request) return
def read_headers( self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived): if not self.request: # we just received some data for an unknown request. yield commands.Log( f"Unexpected data from server: {bytes(self.buf)!r}") yield commands.CloseConnection(self.conn) return assert self.stream_id response_head = self.buf.maybe_extract_lines() if response_head: response_head = [ bytes(x) for x in response_head ] # TODO: Make url.parse compatible with bytearrays try: self.response = http1.read_response_head(response_head) if self.context.options.validate_inbound_headers: http1.validate_headers(self.response.headers) expected_size = http1.expected_http_body_size( self.request, self.response) except ValueError as e: yield commands.CloseConnection(self.conn) yield ReceiveHttp( ResponseProtocolError( self.stream_id, f"Cannot parse HTTP response: {e}")) return yield ReceiveHttp( ResponseHeaders(self.stream_id, self.response, expected_size == 0)) self.body_reader = make_body_reader(expected_size) self.state = self.read_body yield from self.state(event) else: pass # FIXME: protect against header size DoS elif isinstance(event, events.ConnectionClosed): if self.conn.state & ConnectionState.CAN_WRITE: yield commands.CloseConnection(self.conn) if self.stream_id: if self.buf: yield ReceiveHttp( ResponseProtocolError( self.stream_id, f"unexpected server response: {bytes(self.buf)!r}") ) else: # The server has closed the connection to prevent us from continuing. # We need to signal that to the stream. # https://tools.ietf.org/html/rfc7231#section-6.5.11 yield ReceiveHttp( ResponseProtocolError(self.stream_id, "server closed connection")) else: return else: raise AssertionError(f"Unexpected event: {event}")
def passthrough(self, event: events.Event) -> layer.CommandGenerator[None]: assert self.stream_id if isinstance(event, events.DataReceived): yield ReceiveHttp(self.ReceiveData(self.stream_id, event.data)) elif isinstance(event, events.ConnectionClosed): if isinstance(self, Http1Server): yield ReceiveHttp(RequestEndOfMessage(self.stream_id)) else: yield ReceiveHttp(ResponseEndOfMessage(self.stream_id))
def read_headers( self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived): request_head = self.buf.maybe_extract_lines() if request_head: request_head = [ bytes(x) for x in request_head ] # TODO: Make url.parse compatible with bytearrays try: self.request = http1.read_request_head(request_head) if self.context.options.validate_inbound_headers: http1.validate_headers(self.request.headers) expected_body_size = http1.expected_http_body_size( self.request) except ValueError as e: yield commands.SendData(self.conn, make_error_response(400, str(e))) yield commands.CloseConnection(self.conn) if self.request: # we have headers that we can show in the ui yield ReceiveHttp( RequestHeaders(self.stream_id, self.request, False)) yield ReceiveHttp( RequestProtocolError(self.stream_id, str(e), 400)) else: yield commands.Log( f"{human.format_address(self.conn.peername)}: {e}") self.state = self.done return yield ReceiveHttp( RequestHeaders(self.stream_id, self.request, expected_body_size == 0)) self.body_reader = make_body_reader(expected_body_size) self.state = self.read_body yield from self.state(event) else: pass # FIXME: protect against header size DoS elif isinstance(event, events.ConnectionClosed): buf = bytes(self.buf) if buf.strip(): yield commands.Log( f"Client closed connection before completing request headers: {buf!r}" ) yield commands.CloseConnection(self.conn) else: raise AssertionError(f"Unexpected event: {event}")
def read_headers(self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived): request_head = self.buf.maybe_extract_lines() if request_head: request_head = [bytes(x) for x in request_head] # TODO: Make url.parse compatible with bytearrays try: self.request = http1.read_request_head(request_head) expected_body_size = http1.expected_http_body_size(self.request, expect_continue_as_0=False) except ValueError as e: yield commands.Log(f"{human.format_address(self.conn.peername)}: {e}") yield commands.CloseConnection(self.conn) self.state = self.done return yield ReceiveHttp(RequestHeaders(self.stream_id, self.request, expected_body_size == 0)) self.body_reader = make_body_reader(expected_body_size) self.state = self.read_body yield from self.state(event) else: pass # FIXME: protect against header size DoS elif isinstance(event, events.ConnectionClosed): buf = bytes(self.buf) if buf.strip(): yield commands.Log(f"Client closed connection before completing request headers: {buf!r}") yield commands.CloseConnection(self.conn) else: raise AssertionError(f"Unexpected event: {event}")
def wait(self, event: events.Event) -> layer.CommandGenerator[None]: """ We wait for the current flow to be finished before parsing the next message, as we may want to upgrade to WebSocket or plain TCP before that. """ assert self.stream_id if isinstance(event, events.DataReceived): return elif isinstance(event, events.ConnectionClosed): # for practical purposes, we assume that a peer which sent at least a FIN # is not interested in any more data from us, see # see https://github.com/httpwg/http-core/issues/22 if event.connection.state is not ConnectionState.CLOSED: yield commands.CloseConnection(event.connection) yield ReceiveHttp(self.ReceiveProtocolError(self.stream_id, f"Client disconnected.", code=status_codes.CLIENT_CLOSED_REQUEST)) else: # pragma: no cover raise AssertionError(f"Unexpected event: {event}")