diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2016-10-19 15:25:39 +1300 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2016-10-19 20:26:05 +1300 |
commit | 24cf8da27eb56a65bf3e4ceb78bbeacdb1864597 (patch) | |
tree | fee98428fbf36897aa874fd91fe5c9738bf2626f /mitmproxy/http.py | |
parent | 5a68d21e8c87e05f2ad0c18e6c7c505f5e9fc93d (diff) | |
download | mitmproxy-24cf8da27eb56a65bf3e4ceb78bbeacdb1864597.tar.gz mitmproxy-24cf8da27eb56a65bf3e4ceb78bbeacdb1864597.tar.bz2 mitmproxy-24cf8da27eb56a65bf3e4ceb78bbeacdb1864597.zip |
Move all tools into mitmproxy.tools, move models/* to top level
The primary motivation here (and for all the other moving around) is to present
a clean "front of house" to library users, and to migrate primary objects to
the top of the module hierarchy.
Diffstat (limited to 'mitmproxy/http.py')
-rw-r--r-- | mitmproxy/http.py | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/mitmproxy/http.py b/mitmproxy/http.py new file mode 100644 index 00000000..7fe70f9b --- /dev/null +++ b/mitmproxy/http.py @@ -0,0 +1,262 @@ +import cgi + +from mitmproxy import flow +from netlib import http +from netlib import version +from netlib import tcp + + +class HTTPRequest(http.Request): + + """ + A mitmproxy HTTP request. + """ + + # This is a very thin wrapper on top of :py:class:`netlib.http.Request` and + # may be removed in the future. + + def __init__( + self, + first_line_format, + method, + scheme, + host, + port, + path, + http_version, + headers, + content, + timestamp_start=None, + timestamp_end=None, + is_replay=False, + stickycookie=False, + stickyauth=False, + ): + http.Request.__init__( + self, + first_line_format, + method, + scheme, + host, + port, + path, + http_version, + headers, + content, + timestamp_start, + timestamp_end, + ) + + # Have this request's cookies been modified by sticky cookies or auth? + self.stickycookie = stickycookie + self.stickyauth = stickyauth + + # Is this request replayed? + self.is_replay = is_replay + self.stream = None + + def get_state(self): + state = super().get_state() + state.update( + stickycookie=self.stickycookie, + stickyauth=self.stickyauth, + is_replay=self.is_replay, + ) + return state + + def set_state(self, state): + self.stickycookie = state.pop("stickycookie") + self.stickyauth = state.pop("stickyauth") + self.is_replay = state.pop("is_replay") + super().set_state(state) + + @classmethod + def wrap(self, request): + """ + Wraps an existing :py:class:`netlib.http.Request`. + """ + req = HTTPRequest( + first_line_format=request.data.first_line_format, + method=request.data.method, + scheme=request.data.scheme, + host=request.data.host, + port=request.data.port, + path=request.data.path, + http_version=request.data.http_version, + headers=request.data.headers, + content=request.data.content, + timestamp_start=request.data.timestamp_start, + timestamp_end=request.data.timestamp_end, + ) + return req + + def __hash__(self): + return id(self) + + +class HTTPResponse(http.Response): + + """ + A mitmproxy HTTP response. + """ + # This is a very thin wrapper on top of :py:class:`netlib.http.Response` and + # may be removed in the future. + + def __init__( + self, + http_version, + status_code, + reason, + headers, + content, + timestamp_start=None, + timestamp_end=None, + is_replay=False + ): + http.Response.__init__( + self, + http_version, + status_code, + reason, + headers, + content, + timestamp_start=timestamp_start, + timestamp_end=timestamp_end, + ) + + # Is this request replayed? + self.is_replay = is_replay + self.stream = None + + @classmethod + def wrap(self, response): + """ + Wraps an existing :py:class:`netlib.http.Response`. + """ + resp = HTTPResponse( + http_version=response.data.http_version, + status_code=response.data.status_code, + reason=response.data.reason, + headers=response.data.headers, + content=response.data.content, + timestamp_start=response.data.timestamp_start, + timestamp_end=response.data.timestamp_end, + ) + return resp + + +class HTTPFlow(flow.Flow): + + """ + An HTTPFlow is a collection of objects representing a single HTTP + transaction. + """ + + def __init__(self, client_conn, server_conn, live=None): + super().__init__("http", client_conn, server_conn, live) + + self.request = None + """ :py:class:`HTTPRequest` object """ + self.response = None + """ :py:class:`HTTPResponse` object """ + self.error = None + """ :py:class:`Error` object + + Note that it's possible for a Flow to have both a response and an error + object. This might happen, for instance, when a response was received + from the server, but there was an error sending it back to the client. + """ + self.server_conn = server_conn + """ :py:class:`ServerConnection` object """ + self.client_conn = client_conn + """:py:class:`ClientConnection` object """ + self.intercepted = False + """ Is this flow currently being intercepted? """ + + _stateobject_attributes = flow.Flow._stateobject_attributes.copy() + _stateobject_attributes.update( + request=HTTPRequest, + response=HTTPResponse + ) + + def __repr__(self): + s = "<HTTPFlow" + for a in ("request", "response", "error", "client_conn", "server_conn"): + if getattr(self, a, False): + s += "\r\n %s = {flow.%s}" % (a, a) + s += ">" + return s.format(flow=self) + + def copy(self): + f = super().copy() + if self.request: + f.request = self.request.copy() + if self.response: + f.response = self.response.copy() + return f + + def replace(self, pattern, repl, *args, **kwargs): + """ + Replaces a regular expression pattern with repl in both request and + response of the flow. Encoded content will be decoded before + replacement, and re-encoded afterwards. + + Returns the number of replacements made. + """ + c = self.request.replace(pattern, repl, *args, **kwargs) + if self.response: + c += self.response.replace(pattern, repl, *args, **kwargs) + return c + + +def make_error_response(status_code, message, headers=None): + response = http.status_codes.RESPONSES.get(status_code, "Unknown") + body = """ + <html> + <head> + <title>%d %s</title> + </head> + <body>%s</body> + </html> + """.strip() % (status_code, response, cgi.escape(message)) + body = body.encode("utf8", "replace") + + if not headers: + headers = http.Headers( + Server=version.MITMPROXY, + Connection="close", + Content_Length=str(len(body)), + Content_Type="text/html" + ) + + return HTTPResponse( + b"HTTP/1.1", + status_code, + response, + headers, + body, + ) + + +def make_connect_request(address): + address = tcp.Address.wrap(address) + return HTTPRequest( + "authority", b"CONNECT", None, address.host, address.port, None, b"HTTP/1.1", + http.Headers(), b"" + ) + + +def make_connect_response(http_version): + # Do not send any response headers as it breaks proxying non-80 ports on + # Android emulators using the -http-proxy option. + return HTTPResponse( + http_version, + 200, + b"Connection established", + http.Headers(), + b"", + ) + +expect_continue_response = HTTPResponse( + b"HTTP/1.1", 100, b"Continue", http.Headers(), b"" +) |