Ejemplo n.º 1
0
    def send_error(self, status_code, message, headers):
        response = http.status_codes.RESPONSES.get(status_code, "Unknown")
        body = """
            <html>
                <head>
                    <title>%d %s</title>
                </head>
                <body>%s</body>
            </html>
        """ % (status_code, response, message)

        if not headers:
            headers = odict.ODictCaseless()
        assert isinstance(headers, odict.ODictCaseless)

        headers["Server"] = [self.c.config.server_version]
        headers["Connection"] = ["close"]
        headers["Content-Length"] = [len(body)]
        headers["Content-Type"] = ["text/html"]

        resp = HTTPResponse(
            (1, 1),  # if HTTP/2 is used, this value is ignored anyway
            status_code,
            response,
            headers,
            body,
        )

        # if no protocol is assigned yet - just assume HTTP/1
        # TODO: maybe check ALPN and use HTTP/2 if required?
        protocol = self.c.client_conn.protocol or http1.HTTP1Protocol(self.c.client_conn)
        self.c.client_conn.send(protocol.assemble(resp))
Ejemplo n.º 2
0
def send_connect_request(conn, host, port, update_state=True):
    upstream_request = HTTPRequest(
        "authority",
        "CONNECT",
        None,
        host,
        port,
        None,
        (1, 1),
        odict.ODictCaseless(),
        ""
    )

    # we currently only support HTTP/1 CONNECT requests
    protocol = http1.HTTP1Protocol(conn)

    conn.send(protocol.assemble(upstream_request))
    resp = HTTPResponse.from_protocol(protocol, upstream_request.method)
    if resp.status_code != 200:
        raise proxy.ProxyError(resp.status_code,
                               "Cannot establish SSL " +
                               "connection with upstream proxy: \r\n" +
                               repr(resp))
    if update_state:
        conn.state.append(("http", {
            "state": "connect",
            "host": host,
            "port": port}
        ))
    return resp
Ejemplo n.º 3
0
def mock_protocol(data=''):
    rfile = cStringIO.StringIO(data)
    wfile = cStringIO.StringIO()
    return http1.HTTP1Protocol(rfile=rfile, wfile=wfile)
Ejemplo n.º 4
0
    def run(self):
        r = self.flow.request
        form_out_backup = r.form_out
        try:
            self.flow.response = None

            # If we have a channel, run script hooks.
            if self.channel:
                request_reply = self.channel.ask("request", self.flow)
                if request_reply is None or request_reply == KILL:
                    raise KillSignal()
                elif isinstance(request_reply, HTTPResponse):
                    self.flow.response = request_reply

            if not self.flow.response:
                # In all modes, we directly connect to the server displayed
                if self.config.mode == "upstream":
                    server_address = self.config.mode.get_upstream_server(
                        self.flow.client_conn
                    )[2:]
                    server = ServerConnection(server_address)
                    server.connect()
                    if r.scheme == "https":
                        send_connect_request(server, r.host, r.port)
                        server.establish_ssl(
                            self.config.clientcerts,
                            sni=self.flow.server_conn.sni
                        )
                        r.form_out = "relative"
                    else:
                        r.form_out = "absolute"
                else:
                    server_address = (r.host, r.port)
                    server = ServerConnection(server_address)
                    server.connect()
                    if r.scheme == "https":
                        server.establish_ssl(
                            self.config.clientcerts,
                            sni=self.flow.server_conn.sni
                        )
                    r.form_out = "relative"

                server.send(self.flow.server_conn.protocol.assemble(r))
                self.flow.server_conn = server
                self.flow.server_conn.protocol = http1.HTTP1Protocol(self.flow.server_conn)
                self.flow.response = HTTPResponse.from_protocol(
                    self.flow.server_conn.protocol,
                    r.method,
                    body_size_limit=self.config.body_size_limit,
                )
            if self.channel:
                response_reply = self.channel.ask("response", self.flow)
                if response_reply is None or response_reply == KILL:
                    raise KillSignal()
        except (proxy.ProxyError, http.HttpError, tcp.NetLibError) as v:
            self.flow.error = Error(repr(v))
            if self.channel:
                self.channel.ask("error", self.flow)
        except KillSignal:
            # KillSignal should only be raised if there's a channel in the
            # first place.
            self.channel.tell("log", proxy.Log("Connection killed", "info"))
        finally:
            r.form_out = form_out_backup
Ejemplo n.º 5
0
    def handle_flow(self):
        flow = HTTPFlow(self.c.client_conn, self.c.server_conn, self.live)

        try:
            try:
                if not flow.client_conn.protocol:
                    # instantiate new protocol if connection does not have one yet
                    # the first request might be a CONNECT - which is currently only supported with HTTP/1
                    flow.client_conn.protocol = http1.HTTP1Protocol(self.c.client_conn)

                req = HTTPRequest.from_protocol(
                    flow.client_conn.protocol,
                    body_size_limit=self.c.config.body_size_limit
                )
            except tcp.NetLibError:
                # don't throw an error for disconnects that happen
                # before/between requests.
                return False

            self.c.log(
                "request",
                "debug",
                [repr(req)]
            )
            ret = self.process_request(flow, req)
            if ret:
                # instantiate new protocol if connection does not have one yet
                # TODO: select correct protocol based on ALPN (?)
                flow.client_conn.protocol = http1.HTTP1Protocol(self.c.client_conn)
                # flow.client_conn.protocol = http2.HTTP2Protocol(self.c.client_conn, is_server=True)
            if ret is not None:
                return ret

            # Be careful NOT to assign the request to the flow before
            # process_request completes. This is because the call can raise an
            # exception. If the request object is already attached, this results
            # in an Error object that has an attached request that has not been
            # sent through to the Master.
            flow.request = req
            request_reply = self.c.channel.ask("request", flow)
            if request_reply is None or request_reply == KILL:
                raise KillSignal()

            # The inline script may have changed request.host
            self.process_server_address(flow)

            if isinstance(request_reply, HTTPResponse):
                flow.response = request_reply
            else:
                self.get_response_from_server(flow)

            # no further manipulation of self.c.server_conn beyond this point
            # we can safely set it as the final attribute value here.
            flow.server_conn = self.c.server_conn

            self.c.log(
                "response",
                "debug",
                [repr(flow.response)]
            )
            response_reply = self.c.channel.ask("response", flow)
            if response_reply is None or response_reply == KILL:
                raise KillSignal()

            self.send_response_to_client(flow)

            if self.check_close_connection(flow):
                return False

            # We sent a CONNECT request to an upstream proxy.
            if flow.request.form_in == "authority" and flow.response.code == 200:
                # TODO: Possibly add headers (memory consumption/usefulness
                # tradeoff) Make sure to add state info before the actual
                # processing of the CONNECT request happens. During an SSL
                # upgrade, we may receive an SNI indication from the client,
                # which resets the upstream connection. If this is the case, we
                # must already re-issue the CONNECT request at this point.
                self.c.server_conn.state.append(
                    (
                        "http", {
                            "state": "connect",
                            "host": flow.request.host,
                            "port": flow.request.port
                        }
                    )
                )
                if not self.process_connect_request(
                        (flow.request.host, flow.request.port)):
                    return False

            # If the user has changed the target server on this connection,
            # restore the original target server
            flow.live.restore_server()

            return True  # Next flow please.
        except (
                http.HttpAuthenticationError,
                http.HttpError,
                proxy.ProxyError,
                tcp.NetLibError,
        ) as e:
            self.handle_error(e, flow)
        except KillSignal:
            self.c.log("Connection killed", "info")
        finally:
            flow.live = None  # Connection is not live anymore.
        return False
Ejemplo n.º 6
0
    def get_response_from_server(self, flow):
        self.c.establish_server_connection()

        for attempt in (0, 1):
            try:
                if not self.c.server_conn.protocol:
                    # instantiate new protocol if connection does not have one yet
                    # TODO: select correct protocol based on ALPN (?)
                    self.c.server_conn.protocol = http1.HTTP1Protocol(self.c.server_conn)
                    # self.c.server_conn.protocol = http2.HTTP2Protocol(self.c.server_conn)
                    # self.c.server_conn.protocol.perform_connection_preface()

                self.c.server_conn.send(self.c.server_conn.protocol.assemble(flow.request))

                # Only get the headers at first...
                flow.response = HTTPResponse.from_protocol(
                    self.c.server_conn.protocol,
                    flow.request.method,
                    body_size_limit=self.c.config.body_size_limit,
                    include_body=False,
                )
                break
            except (tcp.NetLibError, http.HttpErrorConnClosed) as v:
                self.c.log(
                    "error in server communication: %s" % repr(v),
                    level="debug"
                )
                if attempt == 0:
                    # 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
                    # > read n% of large request
                    # > server detects timeout, disconnects
                    # > read (100-n)% of large request
                    # > send large request upstream
                    self.c.server_reconnect()
                else:
                    raise

        # call the appropriate script hook - this is an opportunity for an
        # inline script to set flow.stream = True
        flow = self.c.channel.ask("responseheaders", flow)
        if flow is None or flow == KILL:
            raise KillSignal()
        else:
            # now get the rest of the request body, if body still needs to be
            # read but not streaming this response
            if flow.response.stream:
                flow.response.content = CONTENT_MISSING
            else:
                if isinstance(self.c.server_conn.protocol, http1.HTTP1Protocol):
                    # streaming is only supported with HTTP/1 at the moment
                    flow.response.content = self.c.server_conn.protocol.read_http_body(
                        flow.response.headers,
                        self.c.config.body_size_limit,
                        flow.request.method,
                        flow.response.code,
                        False
                    )
        flow.response.timestamp_end = utils.timestamp()