mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-01-30 23:09:44 +00:00
update docs, mostly revert 2f44b26b4c
This commit is contained in:
parent
f2570c773a
commit
2a6337343a
@ -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.
|
||||
|
||||
@ -96,22 +95,10 @@ The main classes you will deal with in writing mitmproxy scripts are:
|
||||
<th>libmproxy.proxy.connection.ServerConnection</th>
|
||||
<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>
|
||||
@ -120,10 +107,22 @@ The main classes you will deal with in writing mitmproxy scripts are:
|
||||
<th>libmproxy.protocol.http.HTTPRequest</th>
|
||||
<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>
|
||||
@ -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
|
||||
|
@ -1,2 +1,2 @@
|
||||
def response(context, flow):
|
||||
flow.response.headers["newheader"] = ["foo"]
|
||||
def response(ctx, flow):
|
||||
flow.response.headers["newheader"] = ["foo"]
|
@ -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)
|
@ -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()
|
||||
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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",
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1 +1,2 @@
|
||||
from .primitives import *
|
||||
from .primitives import *
|
||||
from .config import ProxyConfig
|
||||
|
@ -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)
|
||||
|
@ -1,5 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
|
||||
import socket
|
||||
from OpenSSL import SSL
|
||||
|
@ -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.set_query(q)
|
||||
assert r.get_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_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", {})
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user