diff options
Diffstat (limited to 'mitmproxy/libmproxy/protocol/http_replay.py')
-rw-r--r-- | mitmproxy/libmproxy/protocol/http_replay.py | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/mitmproxy/libmproxy/protocol/http_replay.py b/mitmproxy/libmproxy/protocol/http_replay.py new file mode 100644 index 00000000..63870dfb --- /dev/null +++ b/mitmproxy/libmproxy/protocol/http_replay.py @@ -0,0 +1,105 @@ +from __future__ import (absolute_import, print_function, division) +import threading +import traceback +from libmproxy.exceptions import ReplayException +from netlib.exceptions import HttpException, TcpException +from netlib.http import http1 + +from ..controller import Channel +from ..models import Error, HTTPResponse, ServerConnection, make_connect_request +from .base import Kill + + +# TODO: Doesn't really belong into libmproxy.protocol... + + +class RequestReplayThread(threading.Thread): + name = "RequestReplayThread" + + def __init__(self, config, flow, masterq, should_exit): + """ + masterqueue can be a queue or None, if no scripthooks should be + processed. + """ + self.config, self.flow = config, flow + if masterq: + self.channel = Channel(masterq, should_exit) + else: + self.channel = None + super(RequestReplayThread, self).__init__() + + 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 == Kill: + raise Kill() + 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.upstream_server.address + server = ServerConnection(server_address, (self.config.host, 0)) + server.connect() + if r.scheme == "https": + connect_request = make_connect_request((r.host, r.port)) + server.wfile.write(http1.assemble_request(connect_request)) + server.wfile.flush() + resp = http1.read_response( + server.rfile, + connect_request, + body_size_limit=self.config.body_size_limit + ) + if resp.status_code != 200: + raise ReplayException("Upstream server refuses CONNECT request") + 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, (self.config.host, 0)) + server.connect() + if r.scheme == "https": + server.establish_ssl( + self.config.clientcerts, + sni=self.flow.server_conn.sni + ) + r.form_out = "relative" + + server.wfile.write(http1.assemble_request(r)) + server.wfile.flush() + self.flow.server_conn = server + self.flow.response = HTTPResponse.wrap(http1.read_response( + server.rfile, + r, + body_size_limit=self.config.body_size_limit + )) + if self.channel: + response_reply = self.channel.ask("response", self.flow) + if response_reply == Kill: + raise Kill() + except (ReplayException, HttpException, TcpException) as e: + self.flow.error = Error(str(e)) + if self.channel: + self.channel.ask("error", self.flow) + except Kill: + # Kill should only be raised if there's a channel in the + # first place. + from ..proxy.root_context import Log + self.channel.tell("log", Log("Connection killed", "info")) + except Exception: + from ..proxy.root_context import Log + self.channel.tell("log", Log(traceback.format_exc(), "error")) + finally: + r.form_out = form_out_backup |