update docs, mostly revert 2f44b26b4c

This commit is contained in:
Maximilian Hils 2014-09-05 15:16:20 +02:00
parent f2570c773a
commit 2a6337343a
20 changed files with 175 additions and 165 deletions

View File

@ -29,46 +29,45 @@ The new header will be added to all responses passing through the proxy.
Called once on startup, before any other events. Called once on startup, before any other events.
### clientconnect(ScriptContext, ClientConnect) ### clientconnect(ScriptContext, ConnectionHandler)
Called when a client initiates a connection to the proxy. Note that Called when a client initiates a connection to the proxy. Note that
a connection can correspond to multiple HTTP requests. a connection can correspond to multiple HTTP requests.
### serverconnect(ScriptContext, ConnectionHandler)
### serverconnect(ScriptContext, ServerConnection)
Called when the proxy initiates a connection to the target server. Note that Called when the proxy initiates a connection to the target server. Note that
a connection can correspond to multiple HTTP requests. a connection can correspond to multiple HTTP requests.
### request(ScriptContext, Flow) ### request(ScriptContext, HTTPFlow)
Called when a client request has been received. The __Flow__ object is Called when a client request has been received. The __HTTPFlow__ object is
guaranteed to have a non-None __request__ attribute. guaranteed to have a non-None __request__ attribute.
### responseheaders(ScriptContext, Flow) ### responseheaders(ScriptContext, HTTPFlow)
Called when the headers of a server response have been received. Called when the headers of a server response have been received.
This will always be called before the response hook. This will always be called before the response hook.
The __Flow__ object is guaranteed to have non-None __request__ and The __HTTPFlow__ object is guaranteed to have non-None __request__ and
__response__ attributes. __response.content__ will not be valid, __response__ attributes. __response.content__ will be None,
as the response body has not been read yet. as the response body has not been read yet.
### response(ScriptContext, Flow) ### response(ScriptContext, HTTPFlow)
Called when a server response has been received. The __Flow__ object is Called when a server response has been received. The __HTTPFlow__ object is
guaranteed to have non-None __request__ and __response__ attributes. guaranteed to have non-None __request__ and __response__ attributes.
Note that if response streaming is enabled for this response, Note that if response streaming is enabled for this response,
__response.content__ will not contain the response body. __response.content__ will not contain the response body.
### error(ScriptContext, Flow) ### error(ScriptContext, HTTPFlow)
Called when a flow error has occurred, e.g. invalid server responses, or Called when a flow error has occurred, e.g. invalid server responses, or
interrupted connections. This is distinct from a valid server HTTP error interrupted connections. This is distinct from a valid server HTTP error
response, which is simply a response with an HTTP error code. The __Flow__ response, which is simply a response with an HTTP error code. The __HTTPFlow__
object is guaranteed to have non-None __request__ and __error__ attributes. object is guaranteed to have non-None __request__ and __error__ attributes.
### clientdisconnect(ScriptContext, ClientDisconnect) ### clientdisconnect(ScriptContext, ConnectionHandler)
Called when a client disconnects from the proxy. Called when a client disconnects from the proxy.
@ -96,22 +95,10 @@ The main classes you will deal with in writing mitmproxy scripts are:
<th>libmproxy.proxy.connection.ServerConnection</th> <th>libmproxy.proxy.connection.ServerConnection</th>
<td>Describes a server connection.</td> <td>Describes a server connection.</td>
</tr> </tr>
<tr>
<th>libmproxy.protocol.primitives.Error</th>
<td>A communications error.</td>
</tr>
<tr> <tr>
<th>libmproxy.protocol.http.HTTPFlow</th> <th>libmproxy.protocol.http.HTTPFlow</th>
<td>A collection of objects representing a single HTTP transaction.</td> <td>A collection of objects representing a single HTTP transaction.</td>
</tr> </tr>
<tr>
<th>libmproxy.flow.ODict</th>
<td>A dictionary-like object for managing sets of key/value data. There
is also a variant called CaselessODict that ignores key case for some
calls (used mainly for headers).
</td>
</tr>
<tr> <tr>
<th>libmproxy.protocol.http.HTTPResponse</th> <th>libmproxy.protocol.http.HTTPResponse</th>
<td>An HTTP response.</td> <td>An HTTP response.</td>
@ -120,10 +107,22 @@ The main classes you will deal with in writing mitmproxy scripts are:
<th>libmproxy.protocol.http.HTTPRequest</th> <th>libmproxy.protocol.http.HTTPRequest</th>
<td>An HTTP request.</td> <td>An HTTP request.</td>
</tr> </tr>
<tr>
<th>libmproxy.protocol.primitives.Error</th>
<td>A communications error.</td>
</tr>
<tr> <tr>
<th>libmproxy.script.ScriptContext</th> <th>libmproxy.script.ScriptContext</th>
<td> A handle for interacting with mitmproxy's from within scripts.</td> <td> A handle for interacting with mitmproxy's from within scripts.</td>
</tr> </tr>
<tr>
<th>libmproxy.flow.ODict</th>
<td>A dictionary-like object for managing sets of key/value data. There
is also a variant called CaselessODict that ignores key case for some
calls (used mainly for headers).
</td>
</tr>
<tr> <tr>
<th>libmproxy.certutils.SSLCert</th> <th>libmproxy.certutils.SSLCert</th>
<td>Exposes information SSL certificates.</td> <td>Exposes information SSL certificates.</td>
@ -161,9 +160,9 @@ flows from a file (see the "scripted data transformation" example on the
one-shot script on a single flow through the _|_ (pipe) shortcut in mitmproxy. one-shot script on a single flow through the _|_ (pipe) shortcut in mitmproxy.
In this case, there are no client connections, and the events are run in the In this case, there are no client connections, and the events are run in the
following order: __start__, __request__, __response__, __error__, __done__. If following order: __start__, __request__, __responseheaders__, __response__, __error__, __done__. If
the flow doesn't have a __response__ or __error__ associated with it, the the flow doesn't have a __response__ or __error__ associated with it, the
matching event will be skipped. matching events will be skipped.
## Spaces in the script path ## Spaces in the script path
By default, spaces are interpreted as separator between the inline script and its arguments (e.g. <code>-s "foo.py By default, spaces are interpreted as separator between the inline script and its arguments (e.g. <code>-s "foo.py

View File

@ -1,2 +1,2 @@
def response(context, flow): def response(ctx, flow):
flow.response.headers["newheader"] = ["foo"] flow.response.headers["newheader"] = ["foo"]

View File

@ -1,4 +1,4 @@
def request(ctx, flow): def request(ctx, flow):
f = ctx.duplicate_flow(flow) f = ctx.duplicate_flow(flow)
f.request.path = "/changed" f.request.path = "/changed"
ctx.replay_request(f) ctx.replay_request(f)

View File

@ -3,11 +3,14 @@
This example shows how to build a proxy based on mitmproxy's Flow This example shows how to build a proxy based on mitmproxy's Flow
primitives. primitives.
Heads Up: In the majority of cases, you want to use inline scripts.
Note that request and response messages are not automatically replied to, Note that request and response messages are not automatically replied to,
so we need to implement handlers to do this. so we need to implement handlers to do this.
""" """
import os import os
from libmproxy import proxy, flow from libmproxy import flow, proxy
from libmproxy.proxy.server import ProxyServer
class MyMaster(flow.FlowMaster): class MyMaster(flow.FlowMaster):
def run(self): def run(self):
@ -31,9 +34,9 @@ class MyMaster(flow.FlowMaster):
config = proxy.ProxyConfig( config = proxy.ProxyConfig(
cacert = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem") ca_file = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem")
) )
state = flow.State() state = flow.State()
server = proxy.ProxyServer(config, 8080) server = ProxyServer(config, 8080)
m = MyMaster(server, state) m = MyMaster(server, state)
m.run() m.run()

View File

@ -1,8 +1,6 @@
def request(context, flow): def request(ctx, flow):
if "application/x-www-form-urlencoded" in flow.request.headers["content-type"]: if "application/x-www-form-urlencoded" in flow.request.headers["content-type"]:
frm = flow.request.form_urlencoded form = flow.request.get_form_urlencoded()
frm["mitmproxy"] = ["rocks"] form["mitmproxy"] = ["rocks"]
flow.request.form_urlencoded = frm flow.request.set_form_urlencoded(form)

View File

@ -1,7 +1,6 @@
def request(context, flow): def request(ctx, flow):
q = flow.request.query q = flow.request.get_query()
if q: if q:
q["mitmproxy"] = ["rocks"] q["mitmproxy"] = ["rocks"]
flow.request.query = q flow.request.set_query(q)

View File

@ -1,6 +1,7 @@
import time import time
from libmproxy.script import concurrent from libmproxy.script import concurrent
@concurrent @concurrent
def request(context, flow): def request(context, flow):
print "handle request: %s%s" % (flow.request.host, flow.request.path) print "handle request: %s%s" % (flow.request.host, flow.request.path)

View File

@ -6,7 +6,9 @@ This example shows two ways to redirect flows to other destinations.
""" """
def request(context, flow): def request(ctx, flow):
# pretty_host(hostheader=True) takes the Host: header of the request into account,
# which is useful in transparent mode where we usually only have the IP otherwise.
if flow.request.pretty_host(hostheader=True).endswith("example.com"): if flow.request.pretty_host(hostheader=True).endswith("example.com"):
resp = HTTPResponse( resp = HTTPResponse(
[1, 1], 200, "OK", [1, 1], 200, "OK",

View File

@ -5,8 +5,10 @@ implement functionality similar to the "sticky cookies" option. This is at
a lower level than the Flow mechanism, so we're dealing directly with a lower level than the Flow mechanism, so we're dealing directly with
request and response objects. request and response objects.
""" """
from libmproxy import controller, proxy
import os import os
from libmproxy import controller, proxy
from libmproxy.proxy.server import ProxyServer
class StickyMaster(controller.Master): class StickyMaster(controller.Master):
def __init__(self, server): def __init__(self, server):
@ -35,8 +37,8 @@ class StickyMaster(controller.Master):
config = proxy.ProxyConfig( config = proxy.ProxyConfig(
cacert = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem") ca_file = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem")
) )
server = proxy.ProxyServer(config, 8080) server = ProxyServer(config, 8080)
m = StickyMaster(server) m = StickyMaster(server)
m.run() m.run()

View File

@ -7,14 +7,14 @@ def start(ctx, argv):
""" """
ctx.log("start") ctx.log("start")
def clientconnect(ctx, client_connect): def clientconnect(ctx, conn_handler):
""" """
Called when a client initiates a connection to the proxy. Note that a Called when a client initiates a connection to the proxy. Note that a
connection can correspond to multiple HTTP requests connection can correspond to multiple HTTP requests
""" """
ctx.log("clientconnect") ctx.log("clientconnect")
def serverconnect(ctx, server_connection): def serverconnect(ctx, conn_handler):
""" """
Called when the proxy initiates a connection to the target server. Note that a Called when the proxy initiates a connection to the target server. Note that a
connection can correspond to multiple HTTP requests connection can correspond to multiple HTTP requests
@ -50,7 +50,7 @@ def error(ctx, flow):
""" """
ctx.log("error") ctx.log("error")
def clientdisconnect(ctx, client_disconnect): def clientdisconnect(ctx, conn_handler):
""" """
Called when a client disconnects from the proxy. Called when a client disconnects from the proxy.
""" """

View File

@ -1,7 +1,7 @@
import cStringIO import cStringIO
from PIL import Image from PIL import Image
def response(context, flow): def response(ctx, flow):
if flow.response.headers["content-type"] == ["image/png"]: if flow.response.headers["content-type"] == ["image/png"]:
s = cStringIO.StringIO(flow.response.content) s = cStringIO.StringIO(flow.response.content)
img = Image.open(s).rotate(180) img = Image.open(s).rotate(180)

View File

@ -554,17 +554,17 @@ class FlowView(common.WWrap):
conn.headers = flow.ODictCaseless(lst) conn.headers = flow.ODictCaseless(lst)
def set_query(self, lst, conn): def set_query(self, lst, conn):
conn.query = flow.ODict(lst) conn.set_query(flow.ODict(lst))
def set_path_components(self, lst, conn): def set_path_components(self, lst, conn):
conn.path_components = [i[0] for i in lst] conn.set_path_components([i[0] for i in lst])
def set_form(self, lst, conn): def set_form(self, lst, conn):
conn.form_urlencoded = flow.ODict(lst) conn.set_form_urlencoded(flow.ODict(lst))
def edit_form(self, conn): def edit_form(self, conn):
self.master.view_grideditor( self.master.view_grideditor(
grideditor.URLEncodedFormEditor(self.master, conn.form_urlencoded.lst, self.set_form, conn) grideditor.URLEncodedFormEditor(self.master, conn.get_form_urlencoded().lst, self.set_form, conn)
) )
def edit_form_confirm(self, key, conn): def edit_form_confirm(self, key, conn):
@ -589,7 +589,7 @@ class FlowView(common.WWrap):
c = self.master.spawn_editor(conn.content or "") c = self.master.spawn_editor(conn.content or "")
conn.content = c.rstrip("\n") # what? conn.content = c.rstrip("\n") # what?
elif part == "f": elif part == "f":
if not conn.form_urlencoded and conn.content: if not conn.get_form_urlencoded() and conn.content:
self.master.prompt_onekey( self.master.prompt_onekey(
"Existing body is not a URL-encoded form. Clear and edit?", "Existing body is not a URL-encoded form. Clear and edit?",
[ [
@ -604,11 +604,11 @@ class FlowView(common.WWrap):
elif part == "h": elif part == "h":
self.master.view_grideditor(grideditor.HeaderEditor(self.master, conn.headers.lst, self.set_headers, conn)) self.master.view_grideditor(grideditor.HeaderEditor(self.master, conn.headers.lst, self.set_headers, conn))
elif part == "p": elif part == "p":
p = conn.path_components p = conn.get_path_components()
p = [[i] for i in p] p = [[i] for i in p]
self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, conn)) self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, conn))
elif part == "q": elif part == "q":
self.master.view_grideditor(grideditor.QueryEditor(self.master, conn.query.lst, self.set_query, conn)) self.master.view_grideditor(grideditor.QueryEditor(self.master, conn.get_query().lst, self.set_query, conn))
elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
self.master.prompt_edit("URL", conn.url, self.set_url) self.master.prompt_edit("URL", conn.url, self.set_url)
elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:

View File

@ -29,13 +29,13 @@ def get_line(fp):
def send_connect_request(conn, host, port, update_state=True): def send_connect_request(conn, host, port, update_state=True):
upstream_request = HTTPRequest("authority", "CONNECT", None, host, port, None, upstream_request = HTTPRequest("authority", "CONNECT", None, host, port, None,
(1, 1), ODictCaseless(), "") (1, 1), ODictCaseless(), "")
conn.send(upstream_request._assemble()) conn.send(upstream_request.assemble())
resp = HTTPResponse.from_stream(conn.rfile, upstream_request.method) resp = HTTPResponse.from_stream(conn.rfile, upstream_request.method)
if resp.code != 200: if resp.code != 200:
raise proxy.ProxyError(resp.code, raise proxy.ProxyError(resp.code,
"Cannot establish SSL " + "Cannot establish SSL " +
"connection with upstream proxy: \r\n" + "connection with upstream proxy: \r\n" +
str(resp._assemble())) str(resp.assemble()))
if update_state: if update_state:
conn.state.append(("http", { conn.state.append(("http", {
"state": "connect", "state": "connect",
@ -73,6 +73,9 @@ class decoded(object):
class HTTPMessage(stateobject.SimpleStateObject): class HTTPMessage(stateobject.SimpleStateObject):
"""
Base class for HTTPRequest and HTTPResponse
"""
def __init__(self, httpversion, headers, content, timestamp_start=None, def __init__(self, httpversion, headers, content, timestamp_start=None,
timestamp_end=None): timestamp_end=None):
self.httpversion = httpversion self.httpversion = httpversion
@ -162,31 +165,31 @@ class HTTPMessage(stateobject.SimpleStateObject):
""" """
Parse an HTTP message from a file stream Parse an HTTP message from a file stream
""" """
raise NotImplementedError # pragma: nocover raise NotImplementedError() # pragma: nocover
def _assemble_first_line(self): def _assemble_first_line(self):
""" """
Returns the assembled request/response line Returns the assembled request/response line
""" """
raise NotImplementedError # pragma: nocover raise NotImplementedError() # pragma: nocover
def _assemble_headers(self): def _assemble_headers(self):
""" """
Returns the assembled headers Returns the assembled headers
""" """
raise NotImplementedError # pragma: nocover raise NotImplementedError() # pragma: nocover
def _assemble_head(self): def _assemble_head(self):
""" """
Returns the assembled request/response line plus headers Returns the assembled request/response line plus headers
""" """
raise NotImplementedError # pragma: nocover raise NotImplementedError() # pragma: nocover
def _assemble(self): def assemble(self):
""" """
Returns the assembled request/response Returns the assembled request/response
""" """
raise NotImplementedError # pragma: nocover raise NotImplementedError() # pragma: nocover
class HTTPRequest(HTTPMessage): class HTTPRequest(HTTPMessage):
@ -195,7 +198,17 @@ class HTTPRequest(HTTPMessage):
Exposes the following attributes: Exposes the following attributes:
flow: Flow object the request belongs to method: HTTP method
scheme: URL scheme (http/https) (absolute-form only)
host: Host portion of the URL (absolute-form and authority-form only)
port: Destination port (absolute-form and authority-form only)
path: Path portion of the URL (not present in authority-form)
httpversion: HTTP version tuple, e.g. (1,1)
headers: ODictCaseless object headers: ODictCaseless object
@ -211,18 +224,6 @@ class HTTPRequest(HTTPMessage):
form_out: The request form which mitmproxy has send out to the destination form_out: The request form which mitmproxy has send out to the destination
method: HTTP method
scheme: URL scheme (http/https) (absolute-form only)
host: Host portion of the URL (absolute-form and authority-form only)
port: Destination port (absolute-form and authority-form only)
path: Path portion of the URL (not present in authority-form)
httpversion: HTTP version tuple
timestamp_start: Timestamp indicating when request transmission started timestamp_start: Timestamp indicating when request transmission started
timestamp_end: Timestamp indicating when request transmission ended timestamp_end: Timestamp indicating when request transmission ended
@ -364,7 +365,7 @@ class HTTPRequest(HTTPMessage):
def _assemble_head(self, form=None): def _assemble_head(self, form=None):
return "%s\r\n%s\r\n" % (self._assemble_first_line(form), self._assemble_headers()) return "%s\r\n%s\r\n" % (self._assemble_first_line(form), self._assemble_headers())
def _assemble(self, form=None): def assemble(self, form=None):
""" """
Assembles the request for transmission to the server. We make some Assembles the request for transmission to the server. We make some
modifications to make sure interception works properly. modifications to make sure interception works properly.
@ -417,8 +418,7 @@ class HTTPRequest(HTTPMessage):
""" """
self.headers["Host"] = [self.host] self.headers["Host"] = [self.host]
@property def get_form_urlencoded(self):
def form_urlencoded(self):
""" """
Retrieves the URL-encoded form data, returning an ODict object. Retrieves the URL-encoded form data, returning an ODict object.
Returns an empty ODict if there is no data or the content-type Returns an empty ODict if there is no data or the content-type
@ -428,8 +428,7 @@ class HTTPRequest(HTTPMessage):
return ODict(utils.urldecode(self.content)) return ODict(utils.urldecode(self.content))
return ODict([]) return ODict([])
@form_urlencoded.setter def set_form_urlencoded(self, odict):
def form_urlencoded(self, odict):
""" """
Sets the body to the URL-encoded form data, and adds the Sets the body to the URL-encoded form data, and adds the
appropriate content-type header. Note that this will destory the appropriate content-type header. Note that this will destory the
@ -440,8 +439,7 @@ class HTTPRequest(HTTPMessage):
self.headers["Content-Type"] = [HDR_FORM_URLENCODED] self.headers["Content-Type"] = [HDR_FORM_URLENCODED]
self.content = utils.urlencode(odict.lst) self.content = utils.urlencode(odict.lst)
@property def get_path_components(self):
def path_components(self):
""" """
Returns the path components of the URL as a list of strings. Returns the path components of the URL as a list of strings.
@ -450,8 +448,7 @@ class HTTPRequest(HTTPMessage):
_, _, path, _, _, _ = urlparse.urlparse(self.url) _, _, path, _, _, _ = urlparse.urlparse(self.url)
return [urllib.unquote(i) for i in path.split("/") if i] return [urllib.unquote(i) for i in path.split("/") if i]
@path_components.setter def set_path_components(self, lst):
def path_components(self, lst):
""" """
Takes a list of strings, and sets the path component of the URL. Takes a list of strings, and sets the path component of the URL.
@ -462,8 +459,7 @@ class HTTPRequest(HTTPMessage):
scheme, netloc, _, params, query, fragment = urlparse.urlparse(self.url) scheme, netloc, _, params, query, fragment = urlparse.urlparse(self.url)
self.url = urlparse.urlunparse([scheme, netloc, path, params, query, fragment]) self.url = urlparse.urlunparse([scheme, netloc, path, params, query, fragment])
@property def get_query(self):
def query(self):
""" """
Gets the request query string. Returns an ODict object. Gets the request query string. Returns an ODict object.
""" """
@ -472,8 +468,7 @@ class HTTPRequest(HTTPMessage):
return ODict(utils.urldecode(query)) return ODict(utils.urldecode(query))
return ODict([]) return ODict([])
@query.setter def set_query(self, odict):
def query(self, odict):
""" """
Takes an ODict object, and sets the request query string. Takes an ODict object, and sets the request query string.
""" """
@ -528,8 +523,7 @@ class HTTPRequest(HTTPMessage):
raise ValueError("Invalid URL: %s" % url) raise ValueError("Invalid URL: %s" % url)
self.scheme, self.host, self.port, self.path = parts self.scheme, self.host, self.port, self.path = parts
@property def get_cookies(self):
def cookies(self):
cookie_headers = self.headers.get("cookie") cookie_headers = self.headers.get("cookie")
if not cookie_headers: if not cookie_headers:
return None return None
@ -560,7 +554,7 @@ class HTTPResponse(HTTPMessage):
Exposes the following attributes: Exposes the following attributes:
flow: Flow object the request belongs to httpversion: HTTP version tuple, e.g. (1,1)
code: HTTP response code code: HTTP response code
@ -572,8 +566,6 @@ class HTTPResponse(HTTPMessage):
is content associated, but not present. CONTENT_MISSING evaluates is content associated, but not present. CONTENT_MISSING evaluates
to False to make checking for the presence of content natural. to False to make checking for the presence of content natural.
httpversion: HTTP version tuple
timestamp_start: Timestamp indicating when request transmission started timestamp_start: Timestamp indicating when request transmission started
timestamp_end: Timestamp indicating when request transmission ended timestamp_end: Timestamp indicating when request transmission ended
@ -661,7 +653,7 @@ class HTTPResponse(HTTPMessage):
return '%s\r\n%s\r\n' % ( 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): def assemble(self):
""" """
Assembles the response for transmission to the client. We make some Assembles the response for transmission to the client. We make some
modifications to make sure interception works properly. modifications to make sure interception works properly.
@ -726,8 +718,7 @@ class HTTPResponse(HTTPMessage):
if c: if c:
self.headers["set-cookie"] = c self.headers["set-cookie"] = c
@property def get_cookies(self):
def cookies(self):
cookie_headers = self.headers.get("set-cookie") cookie_headers = self.headers.get("set-cookie")
if not cookie_headers: if not cookie_headers:
return None return None
@ -745,12 +736,14 @@ class HTTPResponse(HTTPMessage):
class HTTPFlow(Flow): class HTTPFlow(Flow):
""" """
A Flow is a collection of objects representing a single HTTP A HTTPFlow is a collection of objects representing a single HTTP
transaction. The main attributes are: transaction. The main attributes are:
request: HTTPRequest object request: HTTPRequest object
response: HTTPResponse object response: HTTPResponse object
error: Error object error: Error object
server_conn: ServerConnection object
client_conn: ClientConnection object
Note that it's possible for a Flow to have both a response and an error 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 object. This might happen, for instance, when a response was received
@ -866,6 +859,10 @@ class HttpAuthenticationError(Exception):
class HTTPHandler(ProtocolHandler): class HTTPHandler(ProtocolHandler):
"""
HTTPHandler implements mitmproxys understanding of the HTTP protocol.
"""
def __init__(self, c): def __init__(self, c):
super(HTTPHandler, self).__init__(c) super(HTTPHandler, self).__init__(c)
self.expected_form_in = c.config.http_form_in self.expected_form_in = c.config.http_form_in
@ -878,7 +875,7 @@ class HTTPHandler(ProtocolHandler):
def get_response_from_server(self, request, include_body=True): def get_response_from_server(self, request, include_body=True):
self.c.establish_server_connection() self.c.establish_server_connection()
request_raw = request._assemble() request_raw = request.assemble()
for i in range(2): for i in range(2):
try: try:
@ -957,7 +954,7 @@ class HTTPHandler(ProtocolHandler):
if not flow.response.stream: if not flow.response.stream:
# no streaming: # 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()) self.c.client_conn.send(flow.response.assemble())
else: else:
# streaming: # streaming:
# First send the body and then transfer the response incrementally: # First send the body and then transfer the response incrementally:
@ -1225,7 +1222,7 @@ class RequestReplayThread(threading.Thread):
server.establish_ssl(self.config.clientcerts, sni=r.host) server.establish_ssl(self.config.clientcerts, sni=r.host)
r.form_out = "relative" r.form_out = "relative"
server.send(r._assemble()) server.send(r.assemble())
self.flow.response = HTTPResponse.from_stream(server.rfile, r.method, self.flow.response = HTTPResponse.from_stream(server.rfile, r.method,
body_size_limit=self.config.body_size_limit) body_size_limit=self.config.body_size_limit)
self.channel.ask("response", self.flow) self.channel.ask("response", self.flow)

View File

@ -12,9 +12,9 @@ class Error(stateobject.SimpleStateObject):
""" """
An Error. An Error.
This is distinct from an HTTP error response (say, a code 500), which This is distinct from an protocol error response (say, a HTTP code 500), which
is represented by a normal Response object. This class is responsible is represented by a normal HTTPResponse object. This class is responsible
for indicating errors that fall outside of normal HTTP communications, for indicating errors that fall outside of normal protocol communications,
like interrupted connections, timeouts, protocol errors. like interrupted connections, timeouts, protocol errors.
Exposes the following attributes: Exposes the following attributes:
@ -52,6 +52,10 @@ class Error(stateobject.SimpleStateObject):
class Flow(stateobject.SimpleStateObject): class Flow(stateobject.SimpleStateObject):
"""
A Flow is a collection of objects representing a single transaction.
This class is usually subclassed for each protocol, e.g. HTTPFlow.
"""
def __init__(self, conntype, client_conn, server_conn, live=None): def __init__(self, conntype, client_conn, server_conn, live=None):
self.conntype = conntype self.conntype = conntype
self.client_conn = client_conn self.client_conn = client_conn
@ -117,6 +121,10 @@ class Flow(stateobject.SimpleStateObject):
class ProtocolHandler(object): class ProtocolHandler(object):
"""
A ProtocolHandler implements an application-layer protocol, e.g. HTTP.
See: libmproxy.protocol.http.HTTPHandler
"""
def __init__(self, c): def __init__(self, c):
self.c = c self.c = c
"""@type: libmproxy.proxy.server.ConnectionHandler""" """@type: libmproxy.proxy.server.ConnectionHandler"""
@ -148,13 +156,14 @@ class ProtocolHandler(object):
class LiveConnection(object): class LiveConnection(object):
""" """
This facade allows protocol handlers to interface with a live connection, This facade allows interested parties (FlowMaster, inline scripts) to interface with a live connection,
without requiring the expose the ConnectionHandler. without requiring to expose the internals of the ConnectionHandler.
""" """
def __init__(self, c): def __init__(self, c):
self.c = c self.c = c
self._backup_server_conn = None
"""@type: libmproxy.proxy.server.ConnectionHandler""" """@type: libmproxy.proxy.server.ConnectionHandler"""
self._backup_server_conn = None
"""@type: libmproxy.proxy.connection.ServerConnection"""
def change_server(self, address, ssl=False, force=False, persistent_change=False): def change_server(self, address, ssl=False, force=False, persistent_change=False):
address = netlib.tcp.Address.wrap(address) address = netlib.tcp.Address.wrap(address)

View File

@ -1 +1,2 @@
from .primitives import * from .primitives import *
from .config import ProxyConfig

View File

@ -1,8 +1,8 @@
from __future__ import absolute_import from __future__ import absolute_import
import os import os
from .. import utils, platform
import re import re
from netlib import http_auth, certutils from netlib import http_auth, certutils
from .. import utils, platform
from .primitives import ConstUpstreamServerResolver, TransparentUpstreamServerResolver from .primitives import ConstUpstreamServerResolver, TransparentUpstreamServerResolver
TRANSPARENT_SSL_PORTS = [443, 8443] TRANSPARENT_SSL_PORTS = [443, 8443]
@ -11,7 +11,7 @@ CONF_DIR = "~/.mitmproxy"
class ProxyConfig: class ProxyConfig:
def __init__(self, confdir=CONF_DIR, clientcerts=None, def __init__(self, confdir=CONF_DIR, ca_file=None, clientcerts=None,
no_upstream_cert=False, body_size_limit=None, no_upstream_cert=False, body_size_limit=None,
mode=None, upstream_server=None, http_form_in=None, http_form_out=None, mode=None, upstream_server=None, http_form_in=None, http_form_out=None,
authenticator=None, ignore=[], authenticator=None, ignore=[],
@ -44,7 +44,7 @@ class ProxyConfig:
self.ignore = [re.compile(i, re.IGNORECASE) for i in ignore] self.ignore = [re.compile(i, re.IGNORECASE) for i in ignore]
self.authenticator = authenticator self.authenticator = authenticator
self.confdir = os.path.expanduser(confdir) self.confdir = os.path.expanduser(confdir)
self.ca_file = os.path.join(self.confdir, CONF_BASENAME + "-ca.pem") self.ca_file = ca_file or os.path.join(self.confdir, CONF_BASENAME + "-ca.pem")
self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME) self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME)
for spec, cert in certs: for spec, cert in certs:
self.certstore.add_cert_file(spec, cert) self.certstore.add_cert_file(spec, cert)

View File

@ -1,5 +1,4 @@
from __future__ import absolute_import from __future__ import absolute_import
import re
import socket import socket
from OpenSSL import SSL from OpenSSL import SSL

View File

@ -481,7 +481,7 @@ class TestSerialize:
f2 = l[0] f2 = l[0]
assert f2._get_state() == f._get_state() assert f2._get_state() == f._get_state()
assert f2.request._assemble() == f.request._assemble() assert f2.request.assemble() == f.request.assemble()
def test_load_flows(self): def test_load_flows(self):
r = self._treader() r = self._treader()
@ -757,18 +757,18 @@ class TestRequest:
r.url = u r.url = u
tutils.raises(ValueError, setattr, r, "url", "") tutils.raises(ValueError, setattr, r, "url", "")
assert r.url == u assert r.url == u
assert r._assemble() assert r.assemble()
assert r.size() == len(r._assemble()) assert r.size() == len(r.assemble())
r2 = r.copy() r2 = r.copy()
assert r == r2 assert r == r2
r.content = None r.content = None
assert r._assemble() assert r.assemble()
assert r.size() == len(r._assemble()) assert r.size() == len(r.assemble())
r.content = CONTENT_MISSING r.content = CONTENT_MISSING
tutils.raises("Cannot assemble flow with CONTENT_MISSING", r._assemble) tutils.raises("Cannot assemble flow with CONTENT_MISSING", r.assemble)
def test_get_url(self): def test_get_url(self):
r = tutils.treq() r = tutils.treq()
@ -794,58 +794,58 @@ class TestRequest:
def test_path_components(self): def test_path_components(self):
r = tutils.treq() r = tutils.treq()
r.path = "/" r.path = "/"
assert r.path_components == [] assert r.get_path_components() == []
r.path = "/foo/bar" r.path = "/foo/bar"
assert r.path_components == ["foo", "bar"] assert r.get_path_components() == ["foo", "bar"]
q = flow.ODict() q = flow.ODict()
q["test"] = ["123"] q["test"] = ["123"]
r.query = q r.set_query(q)
assert r.path_components == ["foo", "bar"] assert r.get_path_components() == ["foo", "bar"]
r.path_components = [] r.set_path_components([])
assert r.path_components == [] assert r.get_path_components() == []
r.path_components = ["foo"] r.set_path_components(["foo"])
assert r.path_components == ["foo"] assert r.get_path_components() == ["foo"]
r.path_components = ["/oo"] r.set_path_components(["/oo"])
assert r.path_components == ["/oo"] assert r.get_path_components() == ["/oo"]
assert "%2F" in r.path assert "%2F" in r.path
def test_getset_form_urlencoded(self): def test_getset_form_urlencoded(self):
d = flow.ODict([("one", "two"), ("three", "four")]) d = flow.ODict([("one", "two"), ("three", "four")])
r = tutils.treq(content=utils.urlencode(d.lst)) r = tutils.treq(content=utils.urlencode(d.lst))
r.headers["content-type"] = [protocol.http.HDR_FORM_URLENCODED] r.headers["content-type"] = [protocol.http.HDR_FORM_URLENCODED]
assert r.form_urlencoded == d assert r.get_form_urlencoded() == d
d = flow.ODict([("x", "y")]) d = flow.ODict([("x", "y")])
r.form_urlencoded = d r.set_form_urlencoded(d)
assert r.form_urlencoded == d assert r.get_form_urlencoded() == d
r.headers["content-type"] = ["foo"] r.headers["content-type"] = ["foo"]
assert not r.form_urlencoded assert not r.get_form_urlencoded()
def test_getset_query(self): def test_getset_query(self):
h = flow.ODictCaseless() h = flow.ODictCaseless()
r = tutils.treq() r = tutils.treq()
r.path = "/foo?x=y&a=b" r.path = "/foo?x=y&a=b"
q = r.query q = r.get_query()
assert q.lst == [("x", "y"), ("a", "b")] assert q.lst == [("x", "y"), ("a", "b")]
r.path = "/" r.path = "/"
q = r.query q = r.get_query()
assert not q assert not q
r.path = "/?adsfa" r.path = "/?adsfa"
q = r.query q = r.get_query()
assert q.lst == [("adsfa", "")] assert q.lst == [("adsfa", "")]
r.path = "/foo?x=y&a=b" r.path = "/foo?x=y&a=b"
assert r.query assert r.get_query()
r.query = flow.ODict([]) r.set_query(flow.ODict([]))
assert not r.query assert not r.get_query()
qv = flow.ODict([("a", "b"), ("c", "d")]) qv = flow.ODict([("a", "b"), ("c", "d")])
r.query = qv r.set_query(qv)
assert r.query == qv assert r.get_query() == qv
def test_anticache(self): def test_anticache(self):
h = flow.ODictCaseless() h = flow.ODictCaseless()
@ -916,14 +916,14 @@ class TestRequest:
h = flow.ODictCaseless() h = flow.ODictCaseless()
r = tutils.treq() r = tutils.treq()
r.headers = h r.headers = h
assert r.cookies is None assert r.get_cookies() is None
def test_get_cookies_single(self): def test_get_cookies_single(self):
h = flow.ODictCaseless() h = flow.ODictCaseless()
h["Cookie"] = ["cookiename=cookievalue"] h["Cookie"] = ["cookiename=cookievalue"]
r = tutils.treq() r = tutils.treq()
r.headers = h r.headers = h
result = r.cookies result = r.get_cookies()
assert len(result)==1 assert len(result)==1
assert result['cookiename']==('cookievalue',{}) assert result['cookiename']==('cookievalue',{})
@ -932,7 +932,7 @@ class TestRequest:
h["Cookie"] = ["cookiename=cookievalue;othercookiename=othercookievalue"] h["Cookie"] = ["cookiename=cookievalue;othercookiename=othercookievalue"]
r = tutils.treq() r = tutils.treq()
r.headers = h r.headers = h
result = r.cookies result = r.get_cookies()
assert len(result)==2 assert len(result)==2
assert result['cookiename']==('cookievalue',{}) assert result['cookiename']==('cookievalue',{})
assert result['othercookiename']==('othercookievalue',{}) assert result['othercookiename']==('othercookievalue',{})
@ -942,7 +942,7 @@ class TestRequest:
h["Cookie"] = ["cookiename=coo=kievalue;othercookiename=othercookievalue"] h["Cookie"] = ["cookiename=coo=kievalue;othercookiename=othercookievalue"]
r = tutils.treq() r = tutils.treq()
r.headers = h r.headers = h
result = r.cookies result = r.get_cookies()
assert len(result)==2 assert len(result)==2
assert result['cookiename']==('coo=kievalue',{}) assert result['cookiename']==('coo=kievalue',{})
assert result['othercookiename']==('othercookievalue',{}) assert result['othercookiename']==('othercookievalue',{})
@ -966,18 +966,18 @@ class TestResponse:
def test_simple(self): def test_simple(self):
f = tutils.tflow(resp=True) f = tutils.tflow(resp=True)
resp = f.response resp = f.response
assert resp._assemble() assert resp.assemble()
assert resp.size() == len(resp._assemble()) assert resp.size() == len(resp.assemble())
resp2 = resp.copy() resp2 = resp.copy()
assert resp2 == resp assert resp2 == resp
resp.content = None resp.content = None
assert resp._assemble() assert resp.assemble()
assert resp.size() == len(resp._assemble()) assert resp.size() == len(resp.assemble())
resp.content = CONTENT_MISSING resp.content = CONTENT_MISSING
tutils.raises("Cannot assemble flow with CONTENT_MISSING", resp._assemble) tutils.raises("Cannot assemble flow with CONTENT_MISSING", resp.assemble)
def test_refresh(self): def test_refresh(self):
r = tutils.tresp() r = tutils.tresp()
@ -1052,14 +1052,14 @@ class TestResponse:
h = flow.ODictCaseless() h = flow.ODictCaseless()
resp = tutils.tresp() resp = tutils.tresp()
resp.headers = h resp.headers = h
assert not resp.cookies assert not resp.get_cookies()
def test_get_cookies_simple(self): def test_get_cookies_simple(self):
h = flow.ODictCaseless() h = flow.ODictCaseless()
h["Set-Cookie"] = ["cookiename=cookievalue"] h["Set-Cookie"] = ["cookiename=cookievalue"]
resp = tutils.tresp() resp = tutils.tresp()
resp.headers = h resp.headers = h
result = resp.cookies result = resp.get_cookies()
assert len(result)==1 assert len(result)==1
assert "cookiename" in result assert "cookiename" in result
assert result["cookiename"] == ("cookievalue", {}) assert result["cookiename"] == ("cookievalue", {})
@ -1069,7 +1069,7 @@ class TestResponse:
h["Set-Cookie"] = ["cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly"] h["Set-Cookie"] = ["cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly"]
resp = tutils.tresp() resp = tutils.tresp()
resp.headers = h resp.headers = h
result = resp.cookies result = resp.get_cookies()
assert len(result)==1 assert len(result)==1
assert "cookiename" in result assert "cookiename" in result
assert result["cookiename"][0] == "cookievalue" assert result["cookiename"][0] == "cookievalue"
@ -1084,7 +1084,7 @@ class TestResponse:
h["Set-Cookie"] = ["cookiename=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/"] h["Set-Cookie"] = ["cookiename=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/"]
resp = tutils.tresp() resp = tutils.tresp()
resp.headers = h resp.headers = h
result = resp.cookies result = resp.get_cookies()
assert len(result)==1 assert len(result)==1
assert "cookiename" in result assert "cookiename" in result
assert result["cookiename"][0] == "" assert result["cookiename"][0] == ""
@ -1095,7 +1095,7 @@ class TestResponse:
h["Set-Cookie"] = ["cookiename=cookievalue","othercookie=othervalue"] h["Set-Cookie"] = ["cookiename=cookievalue","othercookie=othervalue"]
resp = tutils.tresp() resp = tutils.tresp()
resp.headers = h resp.headers = h
result = resp.cookies result = resp.get_cookies()
assert len(result)==2 assert len(result)==2
assert "cookiename" in result assert "cookiename" in result
assert result["cookiename"] == ("cookievalue", {}) assert result["cookiename"] == ("cookievalue", {})

View File

@ -31,7 +31,7 @@ class TestHTTPRequest:
f.request.host = f.server_conn.address.host f.request.host = f.server_conn.address.host
f.request.port = f.server_conn.address.port f.request.port = f.server_conn.address.port
f.request.scheme = "http" f.request.scheme = "http"
assert f.request._assemble() == "OPTIONS * HTTP/1.1\r\nHost: address:22\r\n\r\n" assert f.request.assemble() == "OPTIONS * HTTP/1.1\r\nHost: address:22\r\n\r\n"
def test_origin_form(self): def test_origin_form(self):
s = StringIO("GET /foo\xff HTTP/1.1") s = StringIO("GET /foo\xff HTTP/1.1")
@ -59,7 +59,7 @@ class TestHTTPRequest:
s = StringIO("CONNECT address:22 HTTP/1.1") s = StringIO("CONNECT address:22 HTTP/1.1")
r = HTTPRequest.from_stream(s) r = HTTPRequest.from_stream(s)
r.scheme, r.host, r.port = "http", "address", 22 r.scheme, r.host, r.port = "http", "address", 22
assert r._assemble() == "CONNECT address:22 HTTP/1.1\r\nHost: address:22\r\n\r\n" assert r.assemble() == "CONNECT address:22 HTTP/1.1\r\nHost: address:22\r\n\r\n"
assert r.pretty_url(False) == "address:22" assert r.pretty_url(False) == "address:22"
def test_absolute_form(self): def test_absolute_form(self):
@ -67,11 +67,11 @@ class TestHTTPRequest:
tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s) tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s)
s = StringIO("GET http://address:22/ HTTP/1.1") s = StringIO("GET http://address:22/ HTTP/1.1")
r = HTTPRequest.from_stream(s) r = HTTPRequest.from_stream(s)
assert r._assemble() == "GET http://address:22/ HTTP/1.1\r\nHost: address:22\r\n\r\n" assert r.assemble() == "GET http://address:22/ HTTP/1.1\r\nHost: address:22\r\n\r\n"
def test_assemble_unknown_form(self): def test_assemble_unknown_form(self):
r = tutils.treq() r = tutils.treq()
tutils.raises("Invalid request form", r._assemble, "antiauthority") tutils.raises("Invalid request form", r.assemble, "antiauthority")
def test_set_url(self): def test_set_url(self):
r = tutils.treq_absolute() r = tutils.treq_absolute()

View File

@ -28,7 +28,7 @@ class TestServerConnection:
f = tutils.tflow() f = tutils.tflow()
f.server_conn = sc f.server_conn = sc
f.request.path = "/p/200:da" f.request.path = "/p/200:da"
sc.send(f.request._assemble()) sc.send(f.request.assemble())
assert http.read_response(sc.rfile, f.request.method, 1000) assert http.read_response(sc.rfile, f.request.method, 1000)
assert self.d.last_log() assert self.d.last_log()