Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
    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()
Exemple #4
0
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",
        ))
Exemple #5
0
 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)
Exemple #6
0
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",
        ))
Exemple #7
0
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
Exemple #8
0
    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}")
Exemple #9
0
 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)
Exemple #10
0
 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
Exemple #11
0
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