def handle_regular_connect(self, f): self.connect_request = True try: self.set_server((f.request.host, f.request.port)) if f.response: resp = f.response else: resp = http.make_connect_response(f.request.data.http_version) self.send_response(resp) if is_ok(resp.status_code): layer = self.ctx.next_layer(self) layer() except (exceptions.ProtocolException, exceptions.NetlibException) as e: # HTTPS tasting means that ordinary errors like resolution # and connection errors can happen here. self.send_error_response(502, repr(e)) f.error = flow.Error(str(e)) self.channel.ask("error", f) return False return False
def __call__(self): self.connect() if not self.ignore: f = tcp.TCPFlow(self.client_conn, self.server_conn, self) self.channel.ask("tcp_start", f) buf = memoryview(bytearray(self.chunk_size)) client = self.client_conn.connection server = self.server_conn.connection conns = [client, server] # https://github.com/openssl/openssl/issues/6234 for conn in conns: if isinstance(conn, SSL.Connection) and hasattr( SSL._lib, "SSL_clear_mode"): SSL._lib.SSL_clear_mode(conn._ssl, SSL._lib.SSL_MODE_AUTO_RETRY) try: while not self.channel.should_exit.is_set(): r = seleniumwire.thirdparty.mitmproxy.net.tcp.ssl_read_select( conns, 10) for conn in r: dst = server if conn == client else client try: size = conn.recv_into(buf, self.chunk_size) except (SSL.WantReadError, SSL.WantWriteError): continue if not size: conns.remove(conn) # Shutdown connection to the other peer if isinstance(conn, SSL.Connection): # We can't half-close a connection, so we just close everything here. # Sockets will be cleaned up on a higher level. return else: dst.shutdown(socket.SHUT_WR) if len(conns) == 0: return continue tcp_message = tcp.TCPMessage(dst == server, buf[:size].tobytes()) if not self.ignore: f.messages.append(tcp_message) self.channel.ask("tcp_message", f) dst.sendall(tcp_message.content) except (socket.error, exceptions.TcpException, SSL.Error) as e: if not self.ignore: f.error = flow.Error( "TCP connection closed unexpectedly: {}".format(repr(e))) self.channel.tell("tcp_error", f) finally: if not self.ignore: self.channel.tell("tcp_end", f)
def __call__(self): self.flow = WebSocketFlow(self.client_conn, self.server_conn, self.handshake_flow) self.flow.metadata['websocket_handshake'] = self.handshake_flow.id self.handshake_flow.metadata['websocket_flow'] = self.flow.id self.channel.ask("websocket_start", self.flow) conns = [c.connection for c in self.connections.keys()] close_received = False try: while not self.channel.should_exit.is_set(): self._inject_messages(self.client_conn, self.flow._inject_messages_client) self._inject_messages(self.server_conn, self.flow._inject_messages_server) r = tcp.ssl_read_select(conns, 0.1) for conn in r: source_conn = self.client_conn if conn == self.client_conn.connection else self.server_conn other_conn = self.server_conn if conn == self.client_conn.connection else self.client_conn is_server = (source_conn == self.server_conn) frame = websockets.Frame.from_file(source_conn.rfile) data = self.connections[source_conn].receive_data( bytes(frame)) source_conn.send(data) if close_received: return for event in self.connections[source_conn].events(): if not self._handle_event(event, source_conn, other_conn, is_server): if not close_received: close_received = True except (socket.error, exceptions.TcpException, SSL.Error) as e: s = 'server' if is_server else 'client' self.flow.error = flow.Error( "WebSocket connection closed unexpectedly by {}: {}".format( s, repr(e))) self.channel.tell("websocket_error", self.flow) finally: self.flow.ended = True self.channel.tell("websocket_end", self.flow)
def _process_flow(self, f): try: try: request: http.HTTPRequest = self.read_request_headers(f) except exceptions.HttpReadDisconnect: # don't throw an error for disconnects that happen # before/between requests. return False f.request = request if request.first_line_format == "authority": # The standards are silent on what we should do with a CONNECT # request body, so although it's not common, it's allowed. f.request.data.content = b"".join( self.read_request_body(f.request)) f.request.data.trailers = self.read_request_trailers(f.request) f.request.timestamp_end = time.time() self.channel.ask("http_connect", f) if self.mode is HTTPMode.regular: return self.handle_regular_connect(f) elif self.mode is HTTPMode.upstream: return self.handle_upstream_connect(f) else: msg = "Unexpected CONNECT request." self.send_error_response(400, msg) return False if not self.config.options.relax_http_form_validation: validate_request_form(self.mode, request) self.channel.ask("requestheaders", f) # Re-validate request form in case the user has changed something. if not self.config.options.relax_http_form_validation: validate_request_form(self.mode, request) if request.headers.get("expect", "").lower() == "100-continue": # TODO: We may have to use send_response_headers for HTTP2 # here. self.send_response(http.make_expect_continue_response()) request.headers.pop("expect") if f.request.stream: f.request.data.content = None else: f.request.data.content = b"".join( self.read_request_body(request)) f.request.data.trailers = self.read_request_trailers(f.request) request.timestamp_end = time.time() except exceptions.HttpException as e: # We optimistically guess there might be an HTTP client on the # other end self.send_error_response(400, repr(e)) # Request may be malformed at this point, so we unset it. f.request = None f.error = flow.Error(str(e)) self.channel.ask("error", f) self.log("request", "warn", ["HTTP protocol error in client request: {}".format(e)]) return False self.log("request", "debug", [repr(request)]) # set first line format to relative in regular mode, # see https://github.com/mitmproxy/mitmproxy/issues/1759 if self.mode is HTTPMode.regular and request.first_line_format == "absolute": request.authority = "" # update host header in reverse mitmproxy mode if self.config.options.mode.startswith( "reverse:") and not self.config.options.keep_host_header: f.request.host_header = self.config.upstream_server.address[0] # Determine .scheme, .host and .port attributes for inline scripts. For # absolute-form requests, they are directly given in the request. For # authority-form requests, we only need to determine the request # scheme. For relative-form requests, we need to determine host and # port as well. if self.mode is HTTPMode.transparent: # Setting request.host also updates the host header, which we want # to preserve f.request.data.host = self.__initial_server_address[0] f.request.data.port = self.__initial_server_address[1] f.request.data.scheme = b"https" if self.__initial_server_tls else b"http" self.channel.ask("request", f) try: if websockets.check_handshake( request.headers) and websockets.check_client_version( request.headers): f.metadata['websocket'] = True # We only support RFC6455 with WebSocket version 13 # allow inline scripts to manipulate the client handshake self.channel.ask("websocket_handshake", f) if not f.response: self.establish_server_connection(f.request.host, f.request.port, f.request.scheme) def get_response(): self.send_request_headers(f.request) if f.request.stream: chunks = self.read_request_body(f.request) if callable(f.request.stream): chunks = f.request.stream(chunks) self.send_request_body(f.request, chunks) else: self.send_request_body(f.request, [f.request.data.content]) self.send_request_trailers(f.request) f.response = self.read_response_headers() try: get_response() except exceptions.NetlibException as e: self.log("server communication error: %s" % repr(e), level="debug") # In any case, we try to reconnect at least once. This is # necessary because it might be possible that we already # initiated an upstream connection after clientconnect that # has already been expired, e.g consider the following event # log: # > clientconnect (transparent mode destination known) # > serverconnect (required for client tls handshake) # > read n% of large request # > server detects timeout, disconnects # > read (100-n)% of large request # > send large request upstream if isinstance(e, exceptions.Http2ProtocolException): # do not try to reconnect for HTTP2 raise exceptions.ProtocolException( "First and only attempt to get response via HTTP2 failed." ) elif f.request.stream: # We may have already consumed some request chunks already, # so all we can do is signal downstream that upstream closed the connection. self.send_error_response(408, "Request Timeout") f.error = flow.Error(repr(e)) self.channel.ask("error", f) return False self.disconnect() self.connect() get_response() # call the appropriate script hook - this is an opportunity for # an inline script to set f.stream = True self.channel.ask("responseheaders", f) if f.response.stream: f.response.data.content = None else: f.response.data.content = b"".join( self.read_response_body(f.request, f.response)) f.response.timestamp_end = time.time() # no further manipulation of self.server_conn beyond this point # we can safely set it as the final attribute value here. f.server_conn = self.server_conn else: # response was set by an inline script. # we now need to emulate the responseheaders hook. self.channel.ask("responseheaders", f) f.response.data.trailers = self.read_response_trailers( f.request, f.response) self.log("response", "debug", [repr(f.response)]) self.channel.ask("response", f) if not f.response.stream: # no streaming: # we already received the full response from the server and can # send it to the client straight away. self.send_response(f.response) else: # streaming: # First send the headers and then transfer the response incrementally self.send_response_headers(f.response) chunks = self.read_response_body(f.request, f.response) if callable(f.response.stream): chunks = f.response.stream(chunks) self.send_response_body(f.response, chunks) f.response.timestamp_end = time.time() if self.check_close_connection(f): return False # Handle 101 Switching Protocols if f.response.status_code == 101: # Handle a successful HTTP 101 Switching Protocols Response, # received after e.g. a WebSocket upgrade request. # Check for WebSocket handshake is_websocket = (websockets.check_handshake(f.request.headers) and websockets.check_handshake( f.response.headers)) if is_websocket and not self.config.options.websocket: self.log( "Client requested WebSocket connection, but the protocol is disabled.", "info") if is_websocket and self.config.options.websocket: layer = WebSocketLayer(self, f) else: layer = self.ctx.next_layer(self) layer() return False # should never be reached except (exceptions.ProtocolException, exceptions.NetlibException) as e: if not f.response: self.send_error_response(502, repr(e)) f.error = flow.Error(str(e)) self.channel.ask("error", f) return False else: raise exceptions.ProtocolException( "Error in HTTP connection: %s" % repr(e)) finally: if f: f.live = False return True