def twebsocketflow(messages=True, err=None, close_code=None, close_reason='') -> http.HTTPFlow: flow = http.HTTPFlow(tclient_conn(), tserver_conn()) flow.request = http.Request( "example.com", 80, b"GET", b"http", b"example.com", b"/ws", b"HTTP/1.1", headers=http.Headers( connection="upgrade", upgrade="websocket", sec_websocket_version="13", sec_websocket_key="1234", ), content=b'', trailers=None, timestamp_start=946681200, timestamp_end=946681201, ) flow.response = http.Response( b"HTTP/1.1", 101, reason=b"Switching Protocols", headers=http.Headers( connection='upgrade', upgrade='websocket', sec_websocket_accept=b'', ), content=b'', trailers=None, timestamp_start=946681202, timestamp_end=946681203, ) flow.websocket = twebsocket() flow.websocket.close_reason = close_reason if close_code is not None: flow.websocket.close_code = close_code else: if err is True: # ABNORMAL_CLOSURE flow.websocket.close_code = 1006 else: # NORMAL_CLOSURE flow.websocket.close_code = 1000 flow.reply = controller.DummyReply() return flow
def twebsocketflow(messages=True, err=None) -> http.HTTPFlow: flow = http.HTTPFlow(tclient_conn(), tserver_conn()) flow.request = http.Request( "example.com", 80, b"GET", b"http", b"example.com", b"/ws", b"HTTP/1.1", headers=http.Headers( connection="upgrade", upgrade="websocket", sec_websocket_version="13", sec_websocket_key="1234", ), content=b'', trailers=None, timestamp_start=946681200, timestamp_end=946681201, ) flow.response = http.Response( b"HTTP/1.1", 101, reason=b"Switching Protocols", headers=http.Headers( connection='upgrade', upgrade='websocket', sec_websocket_accept=b'', ), content=b'', trailers=None, timestamp_start=946681202, timestamp_end=946681203, ) flow.websocket = websocket.WebSocketData() if messages is True: flow.websocket.messages = [ websocket.WebSocketMessage(Opcode.BINARY, True, b"hello binary", 946681203), websocket.WebSocketMessage(Opcode.TEXT, True, b"hello text", 946681204), websocket.WebSocketMessage(Opcode.TEXT, False, b"it's me", 946681205), ] if err is True: flow.error = terr() flow.reply = controller.DummyReply() return flow
def handle_connect_finish(self): if not self.flow.response: # Do not send any response headers as it breaks proxying non-80 ports on # Android emulators using the -http-proxy option. self.flow.response = http.Response( self.flow.request.data.http_version, 200, b"Connection established", http.Headers(), b"", None, time.time(), time.time(), ) if 200 <= self.flow.response.status_code < 300: self.child_layer = self.child_layer or layer.NextLayer( self.context) yield from self.child_layer.handle_event(events.Start()) self._handle_event = self.passthrough yield SendHttp( ResponseHeaders(self.stream_id, self.flow.response, True), self.context.client) yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client) else: yield from self.send_response()
def make_error_response( status_code: int, message: str = "", ) -> http.Response: body: bytes = """ <html> <head> <title>{status_code} {reason}</title> </head> <body> <h1>{status_code} {reason}</h1> <p>{message}</p> </body> </html> """.strip().format( status_code=status_code, reason=http.status_codes.RESPONSES.get(status_code, "Unknown"), message=html.escape(message), ).encode("utf8", "replace") return http.Response.make( status_code, body, http.Headers( Server=version.MITMPROXY, Connection="close", Content_Type="text/html", ))
def start_handshake(self) -> layer.CommandGenerator[None]: if self.tunnel_connection.tls: # "Secure Web Proxy": We may have negotiated an ALPN when connecting to the upstream proxy. # The semantics are not really clear here, but we make sure that if we negotiated h2, # we act as an h2 client. self.conn.alpn = self.tunnel_connection.alpn if not self.send_connect: return (yield from super().start_handshake()) assert self.conn.address req = http.Request( host=self.conn.address[0], port=self.conn.address[1], method=b"CONNECT", scheme=b"", authority=f"{self.conn.address[0]}:{self.conn.address[1]}".encode( ), path=b"", http_version=b"HTTP/1.1", headers=http.Headers(), content=b"", trailers=None, timestamp_start=time.time(), timestamp_end=time.time(), ) raw = http1.assemble_request(req) yield commands.SendData(self.tunnel_connection, raw)
def make_error_response( status_code: int, message: str = "", ) -> http.Response: return http.Response.make( status_code, format_error(status_code, message), http.Headers( Server=version.MITMPROXY, Connection="close", Content_Type="text/html", ))
def split_pseudo_headers(h2_headers: Sequence[Tuple[bytes, bytes]]) -> Tuple[Dict[bytes, bytes], http.Headers]: pseudo_headers: Dict[bytes, bytes] = {} i = 0 for (header, value) in h2_headers: if header.startswith(b":"): if header in pseudo_headers: raise ValueError(f"Duplicate HTTP/2 pseudo header: {header!r}") pseudo_headers[header] = value i += 1 else: # Pseudo-headers must be at the start, we are done here. break headers = http.Headers(h2_headers[i:]) return pseudo_headers, headers
def send(self, event: HttpEvent) -> layer.CommandGenerator[None]: assert event.stream_id == self.stream_id if isinstance(event, ResponseHeaders): self.response = response = event.response if response.is_http2: response = response.copy() # Convert to an HTTP/1 response. response.http_version = "HTTP/1.1" # not everyone supports empty reason phrases, so we better make up one. response.reason = status_codes.RESPONSES.get( response.status_code, "") # Shall we set a Content-Length header here if there is none? # For now, let's try to modify as little as possible. raw = http1.assemble_response_head(response) yield commands.SendData(self.conn, raw) elif isinstance(event, ResponseData): assert self.response if "chunked" in self.response.headers.get("transfer-encoding", "").lower(): raw = b"%x\r\n%s\r\n" % (len(event.data), event.data) else: raw = event.data if raw: yield commands.SendData(self.conn, raw) elif isinstance(event, ResponseEndOfMessage): assert self.response if "chunked" in self.response.headers.get("transfer-encoding", "").lower(): yield commands.SendData(self.conn, b"0\r\n\r\n") yield from self.mark_done(response=True) elif isinstance(event, ResponseProtocolError): if not self.response and event.code != status_codes.NO_RESPONSE: resp = http.Response.make( event.code, format_error(event.code, event.message), http.Headers( Server=version.MITMPROXY, Connection="close", Content_Type="text/html", )) raw = http1.assemble_response(resp) yield commands.SendData(self.conn, raw) if self.conn.state & ConnectionState.CAN_WRITE: yield commands.CloseConnection(self.conn) else: raise AssertionError(f"Unexpected event: {event}")
def start_handshake(self) -> layer.CommandGenerator[None]: if not self.send_connect: return (yield from super().start_handshake()) assert self.conn.address flow = http.HTTPFlow(self.context.client, self.tunnel_connection) flow.request = http.Request( host=self.conn.address[0], port=self.conn.address[1], method=b"CONNECT", scheme=b"", authority=f"{self.conn.address[0]}:{self.conn.address[1]}".encode( ), path=b"", http_version=b"HTTP/1.1", headers=http.Headers(), content=b"", trailers=None, timestamp_start=time.time(), timestamp_end=time.time(), ) yield HttpConnectUpstreamHook(flow) raw = http1.assemble_request(flow.request) yield commands.SendData(self.tunnel_connection, raw)
def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]: """returns true if further processing should be stopped.""" if isinstance(event, h2.events.DataReceived): state = self.streams.get(event.stream_id, None) if state is StreamState.HEADERS_RECEIVED: yield ReceiveHttp(self.ReceiveData(event.stream_id, event.data)) elif state is StreamState.EXPECTING_HEADERS: yield from self.protocol_error( f"Received HTTP/2 data frame, expected headers.") return True self.h2_conn.acknowledge_received_data( event.flow_controlled_length, event.stream_id) elif isinstance(event, h2.events.TrailersReceived): trailers = http.Headers(event.headers) yield ReceiveHttp(self.ReceiveTrailers(event.stream_id, trailers)) elif isinstance(event, h2.events.StreamEnded): state = self.streams.get(event.stream_id, None) if state is StreamState.HEADERS_RECEIVED: yield ReceiveHttp(self.ReceiveEndOfMessage(event.stream_id)) elif state is StreamState.EXPECTING_HEADERS: raise AssertionError("unreachable") if self.is_closed(event.stream_id): self.streams.pop(event.stream_id, None) elif isinstance(event, h2.events.StreamReset): if event.stream_id in self.streams: try: err_str = h2.errors.ErrorCodes(event.error_code).name except ValueError: err_str = str(event.error_code) err_code = { h2.errors.ErrorCodes.CANCEL: status_codes.CLIENT_CLOSED_REQUEST, }.get(event.error_code, self.ReceiveProtocolError.code) yield ReceiveHttp( self.ReceiveProtocolError( event.stream_id, f"stream reset by client ({err_str})", code=err_code)) self.streams.pop(event.stream_id) else: pass # We don't track priority frames which could be followed by a stream reset here. elif isinstance(event, h2.exceptions.ProtocolError): yield from self.protocol_error(f"HTTP/2 protocol error: {event}") return True elif isinstance(event, h2.events.ConnectionTerminated): yield from self.close_connection( f"HTTP/2 connection closed: {event!r}") return True # The implementation above isn't really ideal, we should probably only terminate streams > last_stream_id? # We currently lack a mechanism to signal that connections are still active but cannot be reused. # for stream_id in self.streams: # if stream_id > event.last_stream_id: # yield ReceiveHttp(self.ReceiveProtocolError(stream_id, f"HTTP/2 connection closed: {event!r}")) # self.streams.pop(stream_id) elif isinstance(event, h2.events.RemoteSettingsChanged): pass elif isinstance(event, h2.events.SettingsAcknowledged): pass elif isinstance(event, h2.events.PriorityUpdated): pass elif isinstance(event, h2.events.PingReceived): pass elif isinstance(event, h2.events.PingAckReceived): pass elif isinstance(event, h2.events.PushedStreamReceived): yield Log( "Received HTTP/2 push promise, even though we signalled no support.", "error") elif isinstance(event, h2.events.UnknownFrameReceived): # https://http2.github.io/http2-spec/#rfc.section.4.1 # Implementations MUST ignore and discard any frame that has a type that is unknown. yield Log( f"Ignoring unknown HTTP/2 frame type: {event.frame.type}") else: raise AssertionError(f"Unexpected event: {event!r}") return False
def twebsocketflow(client_conn=True, server_conn=True, messages=True, err=None, handshake_flow=True): if client_conn is True: client_conn = tclient_conn() if server_conn is True: server_conn = tserver_conn() if handshake_flow is True: req = http.Request( "example.com", 80, b"GET", b"http", b"example.com", b"/ws", b"HTTP/1.1", headers=http.Headers( connection="upgrade", upgrade="websocket", sec_websocket_version="13", sec_websocket_key="1234", ), content=b'', trailers=None, timestamp_start=946681200, timestamp_end=946681201, ) resp = http.Response( b"HTTP/1.1", 101, reason=status_codes.RESPONSES.get(101), headers=http.Headers( connection='upgrade', upgrade='websocket', sec_websocket_accept=b'', ), content=b'', trailers=None, timestamp_start=946681202, timestamp_end=946681203, ) handshake_flow = http.HTTPFlow(client_conn, server_conn) handshake_flow.request = req handshake_flow.response = resp f = websocket.WebSocketFlow(client_conn, server_conn, handshake_flow) f.metadata['websocket_handshake'] = handshake_flow.id handshake_flow.metadata['websocket_flow'] = f.id handshake_flow.metadata['websocket'] = True if messages is True: messages = [ websocket.WebSocketMessage(Opcode.BINARY, True, b"hello binary"), websocket.WebSocketMessage(Opcode.TEXT, True, b"hello text"), websocket.WebSocketMessage(Opcode.TEXT, False, b"it's me"), ] if err is True: err = terr() f.messages = messages f.error = err f.reply = controller.DummyReply() return f