aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/protocol/http.py
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2014-10-26 18:32:45 +1300
committerAldo Cortesi <aldo@nullcube.com>2014-10-26 18:32:45 +1300
commit340d0570bfe7ceae68d7d592e3b7283480c351b0 (patch)
treeee86adfca0ed58021c8ca1ad61f8a9e756c6583c /libmproxy/protocol/http.py
parent16654ad6a4ba4f12287d5707dafe3794b6e33fb8 (diff)
downloadmitmproxy-340d0570bfe7ceae68d7d592e3b7283480c351b0.tar.gz
mitmproxy-340d0570bfe7ceae68d7d592e3b7283480c351b0.tar.bz2
mitmproxy-340d0570bfe7ceae68d7d592e3b7283480c351b0.zip
Legibility
Diffstat (limited to 'libmproxy/protocol/http.py')
-rw-r--r--libmproxy/protocol/http.py172
1 files changed, 124 insertions, 48 deletions
diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py
index e81c7640..3560f0bd 100644
--- a/libmproxy/protocol/http.py
+++ b/libmproxy/protocol/http.py
@@ -27,7 +27,8 @@ def get_line(fp):
Get a line, possibly preceded by a blank.
"""
line = fp.readline()
- if line == "\r\n" or line == "\n": # Possible leftover from previous message
+ if line == "\r\n" or line == "\n":
+ # Possible leftover from previous message
line = fp.readline()
if line == "":
raise tcp.NetLibDisconnect()
@@ -241,25 +242,47 @@ class HTTPRequest(HTTPMessage):
is content associated, but not present. CONTENT_MISSING evaluates
to False to make checking for the presence of content natural.
- form_in: The request form which mitmproxy has received. The following values are possible:
- - relative (GET /index.html, OPTIONS *) (covers origin form and asterisk form)
- - absolute (GET http://example.com:80/index.html)
- - authority-form (CONNECT example.com:443)
- Details: http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-25#section-5.3
+ form_in: The request form which mitmproxy has received. The following
+ values are possible:
- form_out: The request form which mitmproxy has send out to the destination
+ - relative (GET /index.html, OPTIONS *) (covers origin form and
+ asterisk form)
+ - absolute (GET http://example.com:80/index.html)
+ - authority-form (CONNECT example.com:443)
+ Details: http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-25#section-5.3
+
+ form_out: The request form which mitmproxy has send out to the
+ destination
timestamp_start: Timestamp indicating when request transmission started
timestamp_end: Timestamp indicating when request transmission ended
"""
- def __init__(self, form_in, method, scheme, host, port, path, httpversion, headers,
- content, timestamp_start=None, timestamp_end=None, form_out=None):
+ def __init__(
+ self,
+ form_in,
+ method,
+ scheme,
+ host,
+ port,
+ path,
+ httpversion,
+ headers,
+ content,
+ timestamp_start=None,
+ timestamp_end=None,
+ form_out=None
+ ):
assert isinstance(headers, ODictCaseless) or not headers
- HTTPMessage.__init__(self, httpversion, headers, content, timestamp_start,
- timestamp_end)
-
+ HTTPMessage.__init__(
+ self,
+ httpversion,
+ headers,
+ content,
+ timestamp_start,
+ timestamp_end
+ )
self.form_in = form_in
self.method = method
self.scheme = scheme
@@ -312,7 +335,8 @@ class HTTPRequest(HTTPMessage):
request_line = get_line(rfile)
- if hasattr(rfile, "first_byte_timestamp"): # more accurate timestamp_start
+ if hasattr(rfile, "first_byte_timestamp"):
+ # more accurate timestamp_start
timestamp_start = rfile.first_byte_timestamp
request_line_parts = http.parse_init(request_line)
@@ -683,7 +707,9 @@ class HTTPResponse(HTTPMessage):
return "<HTTPResponse: {code} {msg} ({contenttype}, {size})>".format(
code=self.code,
msg=self.msg,
- contenttype=self.headers.get_first("content-type", "unknown content type"),
+ contenttype=self.headers.get_first(
+ "content-type", "unknown content type"
+ ),
size=size
)
@@ -704,7 +730,8 @@ class HTTPResponse(HTTPMessage):
body_size_limit,
include_body=include_body)
- if hasattr(rfile, "first_byte_timestamp"): # more accurate timestamp_start
+ if hasattr(rfile, "first_byte_timestamp"):
+ # more accurate timestamp_start
timestamp_start = rfile.first_byte_timestamp
if include_body:
@@ -745,7 +772,11 @@ class HTTPResponse(HTTPMessage):
def _assemble_head(self, preserve_transfer_encoding=False):
return '%s\r\n%s\r\n' % (
- self._assemble_first_line(), self._assemble_headers(preserve_transfer_encoding=preserve_transfer_encoding))
+ self._assemble_first_line(),
+ self._assemble_headers(
+ preserve_transfer_encoding=preserve_transfer_encoding
+ )
+ )
def assemble(self):
"""
@@ -755,7 +786,10 @@ class HTTPResponse(HTTPMessage):
Raises an Exception if the request cannot be assembled.
"""
if self.content == CONTENT_MISSING:
- raise proxy.ProxyError(502, "Cannot assemble flow with CONTENT_MISSING")
+ raise proxy.ProxyError(
+ 502,
+ "Cannot assemble flow with CONTENT_MISSING"
+ )
head = self._assemble_head()
if self.content:
return head + self.content
@@ -822,8 +856,9 @@ class HTTPResponse(HTTPMessage):
pairs = [pair.partition("=") for pair in header.split(';')]
cookie_name = pairs[0][0] # the key of the first key/value pairs
cookie_value = pairs[0][2] # the value of the first key/value pairs
- cookie_parameters = {key.strip().lower(): value.strip() for key, sep, value in
- pairs[1:]}
+ cookie_parameters = {
+ key.strip().lower(): value.strip() for key, sep, value in pairs[1:]
+ }
cookies.append((cookie_name, (cookie_value, cookie_parameters)))
return dict(cookies)
@@ -856,7 +891,8 @@ class HTTPFlow(Flow):
self.response = None
"""@type: HTTPResponse"""
- self.intercepting = False # FIXME: Should that rather be an attribute of Flow?
+ # FIXME: Should that rather be an attribute of Flow?
+ self.intercepting = False
_stateobject_attributes = Flow._stateobject_attributes.copy()
_stateobject_attributes.update(
@@ -944,7 +980,9 @@ class HTTPFlow(Flow):
class HttpAuthenticationError(Exception):
def __init__(self, auth_headers=None):
- super(HttpAuthenticationError, self).__init__("Proxy Authentication Required")
+ super(HttpAuthenticationError, self).__init__(
+ "Proxy Authentication Required"
+ )
self.headers = auth_headers
self.code = 407
@@ -1114,7 +1152,12 @@ class HTTPHandler(ProtocolHandler):
def handle_server_reconnect(self, state):
if state["state"] == "connect":
- send_connect_request(self.c.server_conn, state["host"], state["port"], update_state=False)
+ send_connect_request(
+ self.c.server_conn,
+ state["host"],
+ state["port"],
+ update_state=False
+ )
else: # pragma: nocover
raise RuntimeError("Unknown State: %s" % state["state"])
@@ -1138,11 +1181,11 @@ class HTTPHandler(ProtocolHandler):
self.c.log(message_debug, level="debug")
if flow:
- # TODO: no flows without request or with both request and response at the moment.
+ # TODO: no flows without request or with both request and response
+ # at the moment.
if flow.request and not flow.response:
flow.error = Error(message or message_debug)
self.c.channel.ask("error", flow)
-
try:
code = getattr(error, "code", 502)
headers = getattr(error, "headers", None)
@@ -1156,12 +1199,22 @@ class HTTPHandler(ProtocolHandler):
def send_error(self, code, message, headers):
response = http_status.RESPONSES.get(code, "Unknown")
- html_content = '<html><head>\n<title>%d %s</title>\n</head>\n<body>\n%s\n</body>\n</html>' % \
- (code, response, message)
+ html_content = """
+ <html>
+ <head>
+ <title>%d %s</title>
+ </head>
+ <body %s</body>
+ </html>
+ """ % (code, response, message)
self.c.client_conn.wfile.write("HTTP/1.1 %s %s\r\n" % (code, response))
- self.c.client_conn.wfile.write("Server: %s\r\n" % self.c.config.server_version)
+ self.c.client_conn.wfile.write(
+ "Server: %s\r\n" % self.c.config.server_version
+ )
self.c.client_conn.wfile.write("Content-type: text/html\r\n")
- self.c.client_conn.wfile.write("Content-Length: %d\r\n" % len(html_content))
+ self.c.client_conn.wfile.write(
+ "Content-Length: %d\r\n" % len(html_content)
+ )
if headers:
for key, value in headers.items():
self.c.client_conn.wfile.write("%s: %s\r\n" % (key, value))
@@ -1201,11 +1254,15 @@ class HTTPHandler(ProtocolHandler):
# Now we can process the request.
if request.form_in == "authority":
if self.c.client_conn.ssl_established:
- raise http.HttpError(400, "Must not CONNECT on already encrypted connection")
+ raise http.HttpError(
+ 400,
+ "Must not CONNECT on already encrypted connection"
+ )
if self.c.config.mode == "regular":
self.c.set_server_address((request.host, request.port))
- flow.server_conn = self.c.server_conn # Update server_conn attribute on the flow
+ # Update server_conn attribute on the flow
+ flow.server_conn = self.c.server_conn
self.c.establish_server_connection()
self.c.client_conn.send(
'HTTP/1.1 200 Connection established\r\n' +
@@ -1217,7 +1274,9 @@ class HTTPHandler(ProtocolHandler):
elif self.c.config.mode == "upstream":
return None
else:
- pass # CONNECT should never occur if we don't expect absolute-form requests
+ # CONNECT should never occur if we don't expect absolute-form
+ # requests
+ pass
elif request.form_in == self.expected_form_in:
@@ -1225,61 +1284,77 @@ class HTTPHandler(ProtocolHandler):
if request.form_in == "absolute":
if request.scheme != "http":
- raise http.HttpError(400, "Invalid request scheme: %s" % request.scheme)
+ raise http.HttpError(
+ 400,
+ "Invalid request scheme: %s" % request.scheme
+ )
if self.c.config.mode == "regular":
- # Update info so that an inline script sees the correct value at flow.server_conn
+ # Update info so that an inline script sees the correct
+ # value at flow.server_conn
self.c.set_server_address((request.host, request.port))
flow.server_conn = self.c.server_conn
return None
-
- raise http.HttpError(400, "Invalid HTTP request form (expected: %s, got: %s)" %
- (self.expected_form_in, request.form_in))
+ raise http.HttpError(
+ 400, "Invalid HTTP request form (expected: %s, got: %s)" % (
+ self.expected_form_in, request.form_in
+ )
+ )
def process_server_address(self, flow):
# Depending on the proxy mode, server handling is entirely different
- # We provide a mostly unified API to the user, which needs to be unfiddled here
+ # We provide a mostly unified API to the user, which needs to be
+ # unfiddled here
# ( See also: https://github.com/mitmproxy/mitmproxy/issues/337 )
address = netlib.tcp.Address((flow.request.host, flow.request.port))
ssl = (flow.request.scheme == "https")
if self.c.config.mode == "upstream":
-
- # The connection to the upstream proxy may have a state we may need to take into account.
+ # The connection to the upstream proxy may have a state we may need
+ # to take into account.
connected_to = None
for s in flow.server_conn.state:
if s[0] == "http" and s[1]["state"] == "connect":
connected_to = tcp.Address((s[1]["host"], s[1]["port"]))
- # We need to reconnect if the current flow either requires a (possibly impossible)
- # change to the connection state, e.g. the host has changed but we already CONNECTed somewhere else.
+ # We need to reconnect if the current flow either requires a
+ # (possibly impossible) change to the connection state, e.g. the
+ # host has changed but we already CONNECTed somewhere else.
needs_server_change = (
ssl != self.c.server_conn.ssl_established
or
- (connected_to and address != connected_to) # HTTP proxying is "stateless", CONNECT isn't.
+ # HTTP proxying is "stateless", CONNECT isn't.
+ (connected_to and address != connected_to)
)
if needs_server_change:
# force create new connection to the proxy server to reset state
self.live.change_server(self.c.server_conn.address, force=True)
if ssl:
- send_connect_request(self.c.server_conn, address.host, address.port)
+ send_connect_request(
+ self.c.server_conn,
+ address.host,
+ address.port
+ )
self.c.establish_ssl(server=True)
else:
- # If we're not in upstream mode, we just want to update the host and possibly establish TLS.
- self.live.change_server(address, ssl=ssl) # this is a no op if the addresses match.
+ # If we're not in upstream mode, we just want to update the host and
+ # possibly establish TLS. This is a no op if the addresses match.
+ self.live.change_server(address, ssl=ssl)
flow.server_conn = self.c.server_conn
def send_response_to_client(self, flow):
if not flow.response.stream:
# no streaming:
- # we already received the full response from the server and can send it to the client straight away.
+ # we already received the full response from the server and can send
+ # it to the client straight away.
self.c.client_conn.send(flow.response.assemble())
else:
# streaming:
- # First send the headers and then transfer the response incrementally:
+ # First send the headers and then transfer the response
+ # incrementally:
h = flow.response._assemble_head(preserve_transfer_encoding=True)
self.c.client_conn.send(h)
for chunk in http.read_http_body_chunked(self.c.server_conn.rfile,
@@ -1293,7 +1368,8 @@ class HTTPHandler(ProtocolHandler):
def check_close_connection(self, flow):
"""
- Checks if the connection should be closed depending on the HTTP semantics. Returns True, if so.
+ Checks if the connection should be closed depending on the HTTP
+ semantics. Returns True, if so.
"""
close_connection = (
http.connection_close(flow.request.httpversion, flow.request.headers) or