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.
### 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

View File

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

View File

@ -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)

View File

@ -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()

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"]:
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)

View File

@ -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)

View File

@ -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)

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"):
resp = HTTPResponse(
[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
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()

View File

@ -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.
"""

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

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
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)

View File

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

View File

@ -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", {})

View File

@ -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()

View File

@ -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()