diff options
author | Maximilian Hils <git@maximilianhils.com> | 2014-09-05 15:16:20 +0200 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2014-09-05 15:16:20 +0200 |
commit | 2a6337343a14f7f72c28d8bf5f24220f6d9ca6d0 (patch) | |
tree | 755d8cf7f6578c9346e9f3c68604615177cf9332 | |
parent | f2570c773aa18e4ac236b1cf7f43acfb4ca080dd (diff) | |
download | mitmproxy-2a6337343a14f7f72c28d8bf5f24220f6d9ca6d0.tar.gz mitmproxy-2a6337343a14f7f72c28d8bf5f24220f6d9ca6d0.tar.bz2 mitmproxy-2a6337343a14f7f72c28d8bf5f24220f6d9ca6d0.zip |
update docs, mostly revert 2f44b26b4cd014e03dd62a125d79af9b81663a93
-rw-r--r-- | doc-src/scripting/inlinescripts.html | 53 | ||||
-rw-r--r-- | examples/add_header.py | 4 | ||||
-rw-r--r-- | examples/dup_and_replay.py | 6 | ||||
-rwxr-xr-x | examples/flowbasic | 9 | ||||
-rw-r--r-- | examples/modify_form.py | 10 | ||||
-rw-r--r-- | examples/modify_querystring.py | 7 | ||||
-rw-r--r-- | examples/nonblocking.py | 3 | ||||
-rw-r--r-- | examples/redirect_requests.py | 4 | ||||
-rwxr-xr-x | examples/stickycookies | 8 | ||||
-rw-r--r-- | examples/stub.py | 6 | ||||
-rw-r--r-- | examples/upsidedownternet.py | 2 | ||||
-rw-r--r-- | libmproxy/console/flowview.py | 14 | ||||
-rw-r--r-- | libmproxy/protocol/http.py | 89 | ||||
-rw-r--r-- | libmproxy/protocol/primitives.py | 21 | ||||
-rw-r--r-- | libmproxy/proxy/__init__.py | 3 | ||||
-rw-r--r-- | libmproxy/proxy/config.py | 6 | ||||
-rw-r--r-- | libmproxy/proxy/server.py | 1 | ||||
-rw-r--r-- | test/test_flow.py | 86 | ||||
-rw-r--r-- | test/test_protocol_http.py | 8 | ||||
-rw-r--r-- | test/test_proxy.py | 2 |
20 files changed, 176 insertions, 166 deletions
diff --git a/doc-src/scripting/inlinescripts.html b/doc-src/scripting/inlinescripts.html index 738f8dc3..eef4e440 100644 --- a/doc-src/scripting/inlinescripts.html +++ b/doc-src/scripting/inlinescripts.html @@ -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. -### clientconnect(ScriptContext, ClientConnect) +### clientconnect(ScriptContext, ConnectionHandler) Called when a client initiates a connection to the proxy. Note that a connection can correspond to multiple HTTP requests. - -### serverconnect(ScriptContext, ServerConnection) +### serverconnect(ScriptContext, ConnectionHandler) Called when the proxy initiates a connection to the target server. Note that 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. -### responseheaders(ScriptContext, Flow) +### responseheaders(ScriptContext, HTTPFlow) Called when the headers of a server response have been received. This will always be called before the response hook. -The __Flow__ object is guaranteed to have non-None __request__ and -__response__ attributes. __response.content__ will not be valid, +The __HTTPFlow__ object is guaranteed to have non-None __request__ and +__response__ attributes. __response.content__ will be None, 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. Note that if response streaming is enabled for this response, __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 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. -### clientdisconnect(ScriptContext, ClientDisconnect) +### clientdisconnect(ScriptContext, ConnectionHandler) Called when a client disconnects from the proxy. @@ -97,22 +96,10 @@ The main classes you will deal with in writing mitmproxy scripts are: <td>Describes a server connection.</td> </tr> <tr> - <th>libmproxy.protocol.primitives.Error</th> - <td>A communications error.</td> - </tr> - <tr> <th>libmproxy.protocol.http.HTTPFlow</th> <td>A collection of objects representing a single HTTP transaction.</td> </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> <th>libmproxy.protocol.http.HTTPResponse</th> <td>An HTTP response.</td> </tr> @@ -121,10 +108,22 @@ The main classes you will deal with in writing mitmproxy scripts are: <td>An HTTP request.</td> </tr> <tr> + <th>libmproxy.protocol.primitives.Error</th> + <td>A communications error.</td> + </tr> + <tr> <th>libmproxy.script.ScriptContext</th> <td> A handle for interacting with mitmproxy's from within scripts.</td> </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> <th>libmproxy.certutils.SSLCert</th> <td>Exposes information SSL certificates.</td> </tr> @@ -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. 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 -matching event will be skipped. +matching events will be skipped. ## 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 diff --git a/examples/add_header.py b/examples/add_header.py index 0c0593d1..b9c8c1c6 100644 --- a/examples/add_header.py +++ b/examples/add_header.py @@ -1,2 +1,2 @@ -def response(context, flow): - flow.response.headers["newheader"] = ["foo"] +def response(ctx, flow): + flow.response.headers["newheader"] = ["foo"]
\ No newline at end of file diff --git a/examples/dup_and_replay.py b/examples/dup_and_replay.py index 9c58d3a4..b38c2b7e 100644 --- a/examples/dup_and_replay.py +++ b/examples/dup_and_replay.py @@ -1,4 +1,4 @@ def request(ctx, flow): - f = ctx.duplicate_flow(flow) - f.request.path = "/changed" - ctx.replay_request(f) + f = ctx.duplicate_flow(flow) + f.request.path = "/changed" + ctx.replay_request(f)
\ No newline at end of file diff --git a/examples/flowbasic b/examples/flowbasic index 8dbe2f28..2b44be3f 100755 --- a/examples/flowbasic +++ b/examples/flowbasic @@ -3,11 +3,14 @@ This example shows how to build a proxy based on mitmproxy's Flow 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, so we need to implement handlers to do this. """ import os -from libmproxy import proxy, flow +from libmproxy import flow, proxy +from libmproxy.proxy.server import ProxyServer class MyMaster(flow.FlowMaster): def run(self): @@ -31,9 +34,9 @@ class MyMaster(flow.FlowMaster): config = proxy.ProxyConfig( - cacert = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem") + ca_file = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem") ) state = flow.State() -server = proxy.ProxyServer(config, 8080) +server = ProxyServer(config, 8080) m = MyMaster(server, state) m.run() diff --git a/examples/modify_form.py b/examples/modify_form.py index cb12ee0f..6d651b19 100644 --- a/examples/modify_form.py +++ b/examples/modify_form.py @@ -1,8 +1,6 @@ -def request(context, flow): +def request(ctx, flow): if "application/x-www-form-urlencoded" in flow.request.headers["content-type"]: - frm = flow.request.form_urlencoded - frm["mitmproxy"] = ["rocks"] - flow.request.form_urlencoded = frm - - + form = flow.request.get_form_urlencoded() + form["mitmproxy"] = ["rocks"] + flow.request.set_form_urlencoded(form)
\ No newline at end of file diff --git a/examples/modify_querystring.py b/examples/modify_querystring.py index 7e3a068a..56fbbb32 100644 --- a/examples/modify_querystring.py +++ b/examples/modify_querystring.py @@ -1,7 +1,6 @@ -def request(context, flow): - q = flow.request.query +def request(ctx, flow): + q = flow.request.get_query() if q: q["mitmproxy"] = ["rocks"] - flow.request.query = q - + flow.request.set_query(q)
\ No newline at end of file diff --git a/examples/nonblocking.py b/examples/nonblocking.py index 9a131b32..1396742a 100644 --- a/examples/nonblocking.py +++ b/examples/nonblocking.py @@ -1,8 +1,9 @@ import time from libmproxy.script import concurrent + @concurrent def request(context, flow): print "handle request: %s%s" % (flow.request.host, flow.request.path) time.sleep(5) - print "start request: %s%s" % (flow.request.host, flow.request.path) + print "start request: %s%s" % (flow.request.host, flow.request.path)
\ No newline at end of file diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py index cc642039..c5561839 100644 --- a/examples/redirect_requests.py +++ b/examples/redirect_requests.py @@ -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"): resp = HTTPResponse( [1, 1], 200, "OK", diff --git a/examples/stickycookies b/examples/stickycookies index 17cd6019..2aab31d6 100755 --- a/examples/stickycookies +++ b/examples/stickycookies @@ -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 request and response objects. """ -from libmproxy import controller, proxy import os +from libmproxy import controller, proxy +from libmproxy.proxy.server import ProxyServer + class StickyMaster(controller.Master): def __init__(self, server): @@ -35,8 +37,8 @@ class StickyMaster(controller.Master): 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.run() diff --git a/examples/stub.py b/examples/stub.py index 0cf67db7..5976dd76 100644 --- a/examples/stub.py +++ b/examples/stub.py @@ -7,14 +7,14 @@ def start(ctx, argv): """ 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 connection can correspond to multiple HTTP requests """ 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 connection can correspond to multiple HTTP requests @@ -50,7 +50,7 @@ def error(ctx, flow): """ ctx.log("error") -def clientdisconnect(ctx, client_disconnect): +def clientdisconnect(ctx, conn_handler): """ Called when a client disconnects from the proxy. """ diff --git a/examples/upsidedownternet.py b/examples/upsidedownternet.py index 181a40c2..a52b6d30 100644 --- a/examples/upsidedownternet.py +++ b/examples/upsidedownternet.py @@ -1,7 +1,7 @@ import cStringIO from PIL import Image -def response(context, flow): +def response(ctx, flow): if flow.response.headers["content-type"] == ["image/png"]: s = cStringIO.StringIO(flow.response.content) img = Image.open(s).rotate(180) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 9063c3e1..014d44c0 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -554,17 +554,17 @@ class FlowView(common.WWrap): conn.headers = flow.ODictCaseless(lst) def set_query(self, lst, conn): - conn.query = flow.ODict(lst) + conn.set_query(flow.ODict(lst)) 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): - conn.form_urlencoded = flow.ODict(lst) + conn.set_form_urlencoded(flow.ODict(lst)) def edit_form(self, conn): 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): @@ -589,7 +589,7 @@ class FlowView(common.WWrap): c = self.master.spawn_editor(conn.content or "") conn.content = c.rstrip("\n") # what? elif part == "f": - if not conn.form_urlencoded and conn.content: + if not conn.get_form_urlencoded() and conn.content: self.master.prompt_onekey( "Existing body is not a URL-encoded form. Clear and edit?", [ @@ -604,11 +604,11 @@ class FlowView(common.WWrap): elif part == "h": self.master.view_grideditor(grideditor.HeaderEditor(self.master, conn.headers.lst, self.set_headers, conn)) elif part == "p": - p = conn.path_components + p = conn.get_path_components() p = [[i] for i in p] self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, conn)) 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: self.master.prompt_edit("URL", conn.url, self.set_url) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 90ee127c..9593c3cb 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -29,13 +29,13 @@ def get_line(fp): def send_connect_request(conn, host, port, update_state=True): upstream_request = HTTPRequest("authority", "CONNECT", None, host, port, None, (1, 1), ODictCaseless(), "") - conn.send(upstream_request._assemble()) + conn.send(upstream_request.assemble()) resp = HTTPResponse.from_stream(conn.rfile, upstream_request.method) if resp.code != 200: raise proxy.ProxyError(resp.code, "Cannot establish SSL " + "connection with upstream proxy: \r\n" + - str(resp._assemble())) + str(resp.assemble())) if update_state: conn.state.append(("http", { "state": "connect", @@ -73,6 +73,9 @@ class decoded(object): class HTTPMessage(stateobject.SimpleStateObject): + """ + Base class for HTTPRequest and HTTPResponse + """ def __init__(self, httpversion, headers, content, timestamp_start=None, timestamp_end=None): self.httpversion = httpversion @@ -162,31 +165,31 @@ class HTTPMessage(stateobject.SimpleStateObject): """ Parse an HTTP message from a file stream """ - raise NotImplementedError # pragma: nocover + raise NotImplementedError() # pragma: nocover def _assemble_first_line(self): """ Returns the assembled request/response line """ - raise NotImplementedError # pragma: nocover + raise NotImplementedError() # pragma: nocover def _assemble_headers(self): """ Returns the assembled headers """ - raise NotImplementedError # pragma: nocover + raise NotImplementedError() # pragma: nocover def _assemble_head(self): """ 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 """ - raise NotImplementedError # pragma: nocover + raise NotImplementedError() # pragma: nocover class HTTPRequest(HTTPMessage): @@ -195,7 +198,17 @@ class HTTPRequest(HTTPMessage): 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 @@ -211,18 +224,6 @@ class HTTPRequest(HTTPMessage): 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_end: Timestamp indicating when request transmission ended @@ -364,7 +365,7 @@ class HTTPRequest(HTTPMessage): def _assemble_head(self, form=None): 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 modifications to make sure interception works properly. @@ -417,8 +418,7 @@ class HTTPRequest(HTTPMessage): """ self.headers["Host"] = [self.host] - @property - def form_urlencoded(self): + def get_form_urlencoded(self): """ Retrieves the URL-encoded form data, returning an ODict object. 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([]) - @form_urlencoded.setter - def form_urlencoded(self, odict): + def set_form_urlencoded(self, odict): """ Sets the body to the URL-encoded form data, and adds 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.content = utils.urlencode(odict.lst) - @property - def path_components(self): + def get_path_components(self): """ Returns the path components of the URL as a list of strings. @@ -450,8 +448,7 @@ class HTTPRequest(HTTPMessage): _, _, path, _, _, _ = urlparse.urlparse(self.url) return [urllib.unquote(i) for i in path.split("/") if i] - @path_components.setter - def path_components(self, lst): + def set_path_components(self, lst): """ 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) self.url = urlparse.urlunparse([scheme, netloc, path, params, query, fragment]) - @property - def query(self): + def get_query(self): """ Gets the request query string. Returns an ODict object. """ @@ -472,8 +468,7 @@ class HTTPRequest(HTTPMessage): return ODict(utils.urldecode(query)) return ODict([]) - @query.setter - def query(self, odict): + def set_query(self, odict): """ Takes an ODict object, and sets the request query string. """ @@ -528,8 +523,7 @@ class HTTPRequest(HTTPMessage): raise ValueError("Invalid URL: %s" % url) self.scheme, self.host, self.port, self.path = parts - @property - def cookies(self): + def get_cookies(self): cookie_headers = self.headers.get("cookie") if not cookie_headers: return None @@ -560,7 +554,7 @@ class HTTPResponse(HTTPMessage): Exposes the following attributes: - flow: Flow object the request belongs to + httpversion: HTTP version tuple, e.g. (1,1) code: HTTP response code @@ -572,8 +566,6 @@ class HTTPResponse(HTTPMessage): is content associated, but not present. CONTENT_MISSING evaluates to False to make checking for the presence of content natural. - httpversion: HTTP version tuple - timestamp_start: Timestamp indicating when request transmission started timestamp_end: Timestamp indicating when request transmission ended @@ -661,7 +653,7 @@ class HTTPResponse(HTTPMessage): return '%s\r\n%s\r\n' % ( 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 modifications to make sure interception works properly. @@ -726,8 +718,7 @@ class HTTPResponse(HTTPMessage): if c: self.headers["set-cookie"] = c - @property - def cookies(self): + def get_cookies(self): cookie_headers = self.headers.get("set-cookie") if not cookie_headers: return None @@ -745,12 +736,14 @@ class HTTPResponse(HTTPMessage): 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: request: HTTPRequest object response: HTTPResponse 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 object. This might happen, for instance, when a response was received @@ -866,6 +859,10 @@ class HttpAuthenticationError(Exception): class HTTPHandler(ProtocolHandler): + """ + HTTPHandler implements mitmproxys understanding of the HTTP protocol. + + """ def __init__(self, c): super(HTTPHandler, self).__init__(c) 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): self.c.establish_server_connection() - request_raw = request._assemble() + request_raw = request.assemble() for i in range(2): try: @@ -957,7 +954,7 @@ class HTTPHandler(ProtocolHandler): 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. - self.c.client_conn.send(flow.response._assemble()) + self.c.client_conn.send(flow.response.assemble()) else: # streaming: # 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) r.form_out = "relative" - server.send(r._assemble()) + server.send(r.assemble()) self.flow.response = HTTPResponse.from_stream(server.rfile, r.method, body_size_limit=self.config.body_size_limit) self.channel.ask("response", self.flow) diff --git a/libmproxy/protocol/primitives.py b/libmproxy/protocol/primitives.py index ee1199fc..ecad9d9e 100644 --- a/libmproxy/protocol/primitives.py +++ b/libmproxy/protocol/primitives.py @@ -12,9 +12,9 @@ class Error(stateobject.SimpleStateObject): """ An Error. - This is distinct from an HTTP error response (say, a code 500), which - is represented by a normal Response object. This class is responsible - for indicating errors that fall outside of normal HTTP communications, + This is distinct from an protocol error response (say, a HTTP code 500), which + is represented by a normal HTTPResponse object. This class is responsible + for indicating errors that fall outside of normal protocol communications, like interrupted connections, timeouts, protocol errors. Exposes the following attributes: @@ -52,6 +52,10 @@ class Error(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): self.conntype = conntype self.client_conn = client_conn @@ -117,6 +121,10 @@ class Flow(stateobject.SimpleStateObject): class ProtocolHandler(object): + """ + A ProtocolHandler implements an application-layer protocol, e.g. HTTP. + See: libmproxy.protocol.http.HTTPHandler + """ def __init__(self, c): self.c = c """@type: libmproxy.proxy.server.ConnectionHandler""" @@ -148,13 +156,14 @@ class ProtocolHandler(object): class LiveConnection(object): """ - This facade allows protocol handlers to interface with a live connection, - without requiring the expose the ConnectionHandler. + This facade allows interested parties (FlowMaster, inline scripts) to interface with a live connection, + without requiring to expose the internals of the ConnectionHandler. """ def __init__(self, c): self.c = c - self._backup_server_conn = None """@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): address = netlib.tcp.Address.wrap(address) diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py index f5d6a2d0..e4c20030 100644 --- a/libmproxy/proxy/__init__.py +++ b/libmproxy/proxy/__init__.py @@ -1 +1,2 @@ -from .primitives import *
\ No newline at end of file +from .primitives import * +from .config import ProxyConfig diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 6d4c078b..ea815c69 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -1,8 +1,8 @@ from __future__ import absolute_import import os -from .. import utils, platform import re from netlib import http_auth, certutils +from .. import utils, platform from .primitives import ConstUpstreamServerResolver, TransparentUpstreamServerResolver TRANSPARENT_SSL_PORTS = [443, 8443] @@ -11,7 +11,7 @@ CONF_DIR = "~/.mitmproxy" 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, mode=None, upstream_server=None, http_form_in=None, http_form_out=None, authenticator=None, ignore=[], @@ -44,7 +44,7 @@ class ProxyConfig: self.ignore = [re.compile(i, re.IGNORECASE) for i in ignore] self.authenticator = authenticator 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) for spec, cert in certs: self.certstore.add_cert_file(spec, cert) diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 58e386ab..aacc908e 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -1,5 +1,4 @@ from __future__ import absolute_import -import re import socket from OpenSSL import SSL diff --git a/test/test_flow.py b/test/test_flow.py index 4bc2391e..914138c9 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -481,7 +481,7 @@ class TestSerialize: f2 = l[0] 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): r = self._treader() @@ -757,18 +757,18 @@ class TestRequest: r.url = u tutils.raises(ValueError, setattr, r, "url", "") assert r.url == u - assert r._assemble() - assert r.size() == len(r._assemble()) + assert r.assemble() + assert r.size() == len(r.assemble()) r2 = r.copy() assert r == r2 r.content = None - assert r._assemble() - assert r.size() == len(r._assemble()) + assert r.assemble() + assert r.size() == len(r.assemble()) 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): r = tutils.treq() @@ -794,58 +794,58 @@ class TestRequest: def test_path_components(self): r = tutils.treq() r.path = "/" - assert r.path_components == [] + assert r.get_path_components() == [] r.path = "/foo/bar" - assert r.path_components == ["foo", "bar"] + assert r.get_path_components() == ["foo", "bar"] q = flow.ODict() q["test"] = ["123"] - r.query = q - assert r.path_components == ["foo", "bar"] - - r.path_components = [] - assert r.path_components == [] - r.path_components = ["foo"] - assert r.path_components == ["foo"] - r.path_components = ["/oo"] - assert r.path_components == ["/oo"] + r.set_query(q) + assert r.get_path_components() == ["foo", "bar"] + + r.set_path_components([]) + assert r.get_path_components() == [] + r.set_path_components(["foo"]) + assert r.get_path_components() == ["foo"] + r.set_path_components(["/oo"]) + assert r.get_path_components() == ["/oo"] assert "%2F" in r.path def test_getset_form_urlencoded(self): d = flow.ODict([("one", "two"), ("three", "four")]) r = tutils.treq(content=utils.urlencode(d.lst)) 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")]) - r.form_urlencoded = d - assert r.form_urlencoded == d + r.set_form_urlencoded(d) + assert r.get_form_urlencoded() == d r.headers["content-type"] = ["foo"] - assert not r.form_urlencoded + assert not r.get_form_urlencoded() def test_getset_query(self): h = flow.ODictCaseless() r = tutils.treq() r.path = "/foo?x=y&a=b" - q = r.query + q = r.get_query() assert q.lst == [("x", "y"), ("a", "b")] r.path = "/" - q = r.query + q = r.get_query() assert not q r.path = "/?adsfa" - q = r.query + q = r.get_query() assert q.lst == [("adsfa", "")] r.path = "/foo?x=y&a=b" - assert r.query - r.query = flow.ODict([]) - assert not r.query + assert r.get_query() + r.set_query(flow.ODict([])) + assert not r.get_query() qv = flow.ODict([("a", "b"), ("c", "d")]) - r.query = qv - assert r.query == qv + r.set_query(qv) + assert r.get_query() == qv def test_anticache(self): h = flow.ODictCaseless() @@ -916,14 +916,14 @@ class TestRequest: h = flow.ODictCaseless() r = tutils.treq() r.headers = h - assert r.cookies is None + assert r.get_cookies() is None def test_get_cookies_single(self): h = flow.ODictCaseless() h["Cookie"] = ["cookiename=cookievalue"] r = tutils.treq() r.headers = h - result = r.cookies + result = r.get_cookies() assert len(result)==1 assert result['cookiename']==('cookievalue',{}) @@ -932,7 +932,7 @@ class TestRequest: h["Cookie"] = ["cookiename=cookievalue;othercookiename=othercookievalue"] r = tutils.treq() r.headers = h - result = r.cookies + result = r.get_cookies() assert len(result)==2 assert result['cookiename']==('cookievalue',{}) assert result['othercookiename']==('othercookievalue',{}) @@ -942,7 +942,7 @@ class TestRequest: h["Cookie"] = ["cookiename=coo=kievalue;othercookiename=othercookievalue"] r = tutils.treq() r.headers = h - result = r.cookies + result = r.get_cookies() assert len(result)==2 assert result['cookiename']==('coo=kievalue',{}) assert result['othercookiename']==('othercookievalue',{}) @@ -966,18 +966,18 @@ class TestResponse: def test_simple(self): f = tutils.tflow(resp=True) resp = f.response - assert resp._assemble() - assert resp.size() == len(resp._assemble()) + assert resp.assemble() + assert resp.size() == len(resp.assemble()) resp2 = resp.copy() assert resp2 == resp resp.content = None - assert resp._assemble() - assert resp.size() == len(resp._assemble()) + assert resp.assemble() + assert resp.size() == len(resp.assemble()) 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): r = tutils.tresp() @@ -1052,14 +1052,14 @@ class TestResponse: h = flow.ODictCaseless() resp = tutils.tresp() resp.headers = h - assert not resp.cookies + assert not resp.get_cookies() def test_get_cookies_simple(self): h = flow.ODictCaseless() h["Set-Cookie"] = ["cookiename=cookievalue"] resp = tutils.tresp() resp.headers = h - result = resp.cookies + result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result 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"] resp = tutils.tresp() resp.headers = h - result = resp.cookies + result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result 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=/"] resp = tutils.tresp() resp.headers = h - result = resp.cookies + result = resp.get_cookies() assert len(result)==1 assert "cookiename" in result assert result["cookiename"][0] == "" @@ -1095,7 +1095,7 @@ class TestResponse: h["Set-Cookie"] = ["cookiename=cookievalue","othercookie=othervalue"] resp = tutils.tresp() resp.headers = h - result = resp.cookies + result = resp.get_cookies() assert len(result)==2 assert "cookiename" in result assert result["cookiename"] == ("cookievalue", {}) diff --git a/test/test_protocol_http.py b/test/test_protocol_http.py index 41019672..ea6cf3fd 100644 --- a/test/test_protocol_http.py +++ b/test/test_protocol_http.py @@ -31,7 +31,7 @@ class TestHTTPRequest: f.request.host = f.server_conn.address.host f.request.port = f.server_conn.address.port 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): s = StringIO("GET /foo\xff HTTP/1.1") @@ -59,7 +59,7 @@ class TestHTTPRequest: s = StringIO("CONNECT address:22 HTTP/1.1") r = HTTPRequest.from_stream(s) 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" def test_absolute_form(self): @@ -67,11 +67,11 @@ class TestHTTPRequest: tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s) s = StringIO("GET http://address:22/ HTTP/1.1") 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): r = tutils.treq() - tutils.raises("Invalid request form", r._assemble, "antiauthority") + tutils.raises("Invalid request form", r.assemble, "antiauthority") def test_set_url(self): r = tutils.treq_absolute() diff --git a/test/test_proxy.py b/test/test_proxy.py index e65841f4..073e76b5 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -28,7 +28,7 @@ class TestServerConnection: f = tutils.tflow() f.server_conn = sc 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 self.d.last_log() |