Merge branch 'http-models'

This commit is contained in:
Maximilian Hils 2015-09-28 13:54:53 +02:00
commit c11ab3676d
18 changed files with 136 additions and 206 deletions

View File

@ -192,4 +192,4 @@ pseudoxml:
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
livehtml: livehtml:
sphinx-autobuild -b html -z '../libmproxy' -r '___jb_(old|bak)___$$' $(ALLSPHINXOPTS) $(BUILDDIR)/html sphinx-autobuild -b html -z '../libmproxy' -z '../../netlib/netlib' -r '___jb_(old|bak)___$$' $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@ -40,6 +40,9 @@ extensions = [
'sphinxcontrib.documentedlist' 'sphinxcontrib.documentedlist'
] ]
# https://github.com/sphinx-doc/sphinx/pull/2053
napoleon_include_special_with_doc = False
autodoc_member_order = "bysource" autodoc_member_order = "bysource"
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.

View File

@ -3,23 +3,57 @@
Models Models
====== ======
.. warning:: .. automodule:: netlib.http
The documentation for models has not been converted to rst yet and **many attributes/features
are missing**.
Please read the source code instead.
.. automodule:: libmproxy.models .. autoclass:: Request
:show-inheritance:
:members:
:undoc-members:
.. rubric:: Data
.. autoattribute:: first_line_format
.. autoattribute:: method
.. autoattribute:: scheme
.. autoattribute:: host
.. autoattribute:: port
.. autoattribute:: path
.. autoattribute:: http_version
.. autoattribute:: headers
.. autoattribute:: content
.. autoattribute:: timestamp_start
.. autoattribute:: timestamp_end
.. rubric:: Computed Properties and Convenience Methods
.. autoattribute:: text
.. autoattribute:: url
.. autoattribute:: pretty_host
.. autoattribute:: pretty_url
.. autoattribute:: query
.. autoattribute:: cookies
.. autoattribute:: path_components
.. automethod:: anticache
.. automethod:: anticomp
.. automethod:: constrain_encoding
.. autoattribute:: urlencoded_form
.. autoattribute:: multipart_form
.. automodule:: netlib.http.semantics .. autoclass:: Response
:members: Request, Response
:undoc-members: .. rubric:: Data
.. autoattribute:: http_version
.. autoattribute:: status_code
.. autoattribute:: reason
.. autoattribute:: headers
.. autoattribute:: content
.. autoattribute:: timestamp_start
.. autoattribute:: timestamp_end
.. rubric:: Computed Properties and Convenience Methods
.. autoattribute:: text
.. autoattribute:: cookies
.. autoclass:: Headers .. autoclass:: Headers
:show-inheritance:
:members: :members:
:special-members: :special-members:
:no-undoc-members: :no-undoc-members:
.. autoclass:: decoded
.. automodule:: libmproxy.models
:show-inheritance:
:members: HTTPFlow, Error, ClientConnection, ServerConnection

View File

@ -39,7 +39,7 @@ def request(context, flow):
sni = None sni = None
port = 80 port = 80
host_header = flow.request.pretty_host(hostheader=True) host_header = flow.request.pretty_host
m = parse_host_header.match(host_header) m = parse_host_header.match(host_header)
if m: if m:
host_header = m.group("host").strip("[]") host_header = m.group("host").strip("[]")

View File

@ -5,12 +5,12 @@ from libmproxy.models import HTTPResponse
from netlib.http import Headers from netlib.http import Headers
def request(context, flow): def request(context, flow):
# pretty_host(hostheader=True) takes the Host: header of the request into account, # pretty_host takes the "Host" header of the request into account,
# which is useful in transparent mode where we usually only have the IP # which is useful in transparent mode where we usually only have the IP
# otherwise. # otherwise.
# Method 1: Answer with a locally generated response # Method 1: Answer with a locally generated response
if flow.request.pretty_host(hostheader=True).endswith("example.com"): if flow.request.pretty_host.endswith("example.com"):
resp = HTTPResponse( resp = HTTPResponse(
[1, 1], 200, "OK", [1, 1], 200, "OK",
Headers(Content_Type="text/html"), Headers(Content_Type="text/html"),
@ -18,6 +18,5 @@ def request(context, flow):
flow.reply(resp) flow.reply(resp)
# Method 2: Redirect the request to a different server # Method 2: Redirect the request to a different server
if flow.request.pretty_host(hostheader=True).endswith("example.org"): if flow.request.pretty_host.endswith("example.org"):
flow.request.host = "mitmproxy.org" flow.request.host = "mitmproxy.org"
flow.request.update_host_header()

View File

@ -390,7 +390,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2,
req_timestamp = f.request.timestamp_start, req_timestamp = f.request.timestamp_start,
req_is_replay = f.request.is_replay, req_is_replay = f.request.is_replay,
req_method = f.request.method, req_method = f.request.method,
req_url = f.request.pretty_url(hostheader=hostheader), req_url = f.request.pretty_url if hostheader else f.request.url,
err_msg = f.error.msg if f.error else None, err_msg = f.error.msg if f.error else None,
resp_code = f.response.status_code if f.response else None, resp_code = f.response.status_code if f.response else None,

View File

@ -168,7 +168,7 @@ class FlowView(tabs.Tabs):
self.show() self.show()
def content_view(self, viewmode, message): def content_view(self, viewmode, message):
if message.body == CONTENT_MISSING: if message.content == CONTENT_MISSING:
msg, body = "", [urwid.Text([("error", "[content missing]")])] msg, body = "", [urwid.Text([("error", "[content missing]")])]
return msg, body return msg, body
else: else:
@ -193,13 +193,13 @@ class FlowView(tabs.Tabs):
try: try:
description, lines = contentviews.get_content_view( description, lines = contentviews.get_content_view(
viewmode, message.body, headers=message.headers viewmode, message.content, headers=message.headers
) )
except ContentViewException: except ContentViewException:
s = "Content viewer failed: \n" + traceback.format_exc() s = "Content viewer failed: \n" + traceback.format_exc()
signals.add_event(s, "error") signals.add_event(s, "error")
description, lines = contentviews.get_content_view( description, lines = contentviews.get_content_view(
contentviews.get("Raw"), message.body, headers=message.headers contentviews.get("Raw"), message.content, headers=message.headers
) )
description = description.replace("Raw", "Couldn't parse: falling back to Raw") description = description.replace("Raw", "Couldn't parse: falling back to Raw")

View File

@ -174,15 +174,15 @@ class DumpMaster(flow.FlowMaster):
) )
self.echo(headers, indent=4) self.echo(headers, indent=4)
if self.o.flow_detail >= 3: if self.o.flow_detail >= 3:
if message.body == CONTENT_MISSING: if message.content == CONTENT_MISSING:
self.echo("(content missing)", indent=4) self.echo("(content missing)", indent=4)
elif message.body: elif message.content:
self.echo("") self.echo("")
try: try:
type, lines = contentviews.get_content_view( type, lines = contentviews.get_content_view(
contentviews.get("Auto"), contentviews.get("Auto"),
message.body, message.content,
headers=message.headers headers=message.headers
) )
except ContentViewException: except ContentViewException:
@ -190,7 +190,7 @@ class DumpMaster(flow.FlowMaster):
self.add_event(s, "debug") self.add_event(s, "debug")
type, lines = contentviews.get_content_view( type, lines = contentviews.get_content_view(
contentviews.get("Raw"), contentviews.get("Raw"),
message.body, message.content,
headers=message.headers headers=message.headers
) )
@ -241,7 +241,11 @@ class DumpMaster(flow.FlowMaster):
DELETE="red" DELETE="red"
).get(method.upper(), "magenta") ).get(method.upper(), "magenta")
method = click.style(method, fg=method_color, bold=True) method = click.style(method, fg=method_color, bold=True)
url = click.style(flow.request.pretty_url(self.showhost), bold=True) if self.showhost:
url = flow.request.pretty_url
else:
url = flow.request.url
url = click.style(url, bold=True)
line = "{stickycookie}{client} {method} {url}".format( line = "{stickycookie}{client} {method} {url}".format(
stickycookie=stickycookie, stickycookie=stickycookie,
@ -266,7 +270,7 @@ class DumpMaster(flow.FlowMaster):
elif 400 <= code < 600: elif 400 <= code < 600:
code_color = "red" code_color = "red"
code = click.style(str(code), fg=code_color, bold=True, blink=(code == 418)) code = click.style(str(code), fg=code_color, bold=True, blink=(code == 418))
msg = click.style(flow.response.msg, fg=code_color, bold=True) reason = click.style(flow.response.reason, fg=code_color, bold=True)
if flow.response.content == CONTENT_MISSING: if flow.response.content == CONTENT_MISSING:
size = "(content missing)" size = "(content missing)"
@ -276,11 +280,11 @@ class DumpMaster(flow.FlowMaster):
arrows = click.style("<<", bold=True) arrows = click.style("<<", bold=True)
line = "{replay} {arrows} {code} {msg} {size}".format( line = "{replay} {arrows} {code} {reason} {size}".format(
replay=replay, replay=replay,
arrows=arrows, arrows=arrows,
code=code, code=code,
msg=msg, reason=reason,
size=size size=size
) )
self.echo(line) self.echo(line)

View File

@ -252,7 +252,7 @@ class ServerPlaybackState:
] ]
if not self.ignore_content: if not self.ignore_content:
form_contents = r.get_form() form_contents = r.urlencoded_form or r.multipart_form
if self.ignore_payload_params and form_contents: if self.ignore_payload_params and form_contents:
key.extend( key.extend(
p for p in form_contents p for p in form_contents

View File

@ -6,9 +6,8 @@ import time
from libmproxy import utils from libmproxy import utils
from netlib import encoding from netlib import encoding
from netlib.http import status_codes, Headers, Request, Response, CONTENT_MISSING from netlib.http import status_codes, Headers, Request, Response, CONTENT_MISSING, decoded
from netlib.tcp import Address from netlib.tcp import Address
from netlib.utils import native
from .. import version, stateobject from .. import version, stateobject
from .flow import Flow from .flow import Flow
@ -17,7 +16,6 @@ class MessageMixin(stateobject.StateObject):
_stateobject_attributes = dict( _stateobject_attributes = dict(
http_version=bytes, http_version=bytes,
headers=Headers, headers=Headers,
body=bytes,
timestamp_start=float, timestamp_start=float,
timestamp_end=float timestamp_end=float
) )
@ -26,9 +24,9 @@ class MessageMixin(stateobject.StateObject):
def get_state(self, short=False): def get_state(self, short=False):
ret = super(MessageMixin, self).get_state(short) ret = super(MessageMixin, self).get_state(short)
if short: if short:
if self.body: if self.content:
ret["contentLength"] = len(self.body) ret["contentLength"] = len(self.content)
elif self.body == CONTENT_MISSING: elif self.content == CONTENT_MISSING:
ret["contentLength"] = None ret["contentLength"] = None
else: else:
ret["contentLength"] = 0 ret["contentLength"] = 0
@ -41,9 +39,9 @@ class MessageMixin(stateobject.StateObject):
Doesn't change the message iteself or its headers. Doesn't change the message iteself or its headers.
""" """
ce = self.headers.get("content-encoding") ce = self.headers.get("content-encoding")
if not self.body or ce not in encoding.ENCODINGS: if not self.content or ce not in encoding.ENCODINGS:
return self.body return self.content
return encoding.decode(ce, self.body) return encoding.decode(ce, self.content)
def decode(self): def decode(self):
""" """
@ -54,12 +52,12 @@ class MessageMixin(stateobject.StateObject):
Returns True if decoding succeeded, False otherwise. Returns True if decoding succeeded, False otherwise.
""" """
ce = self.headers.get("content-encoding") ce = self.headers.get("content-encoding")
if not self.body or ce not in encoding.ENCODINGS: if not self.content or ce not in encoding.ENCODINGS:
return False return False
data = encoding.decode(ce, self.body) data = encoding.decode(ce, self.content)
if data is None: if data is None:
return False return False
self.body = data self.content = data
self.headers.pop("content-encoding", None) self.headers.pop("content-encoding", None)
return True return True
@ -69,11 +67,14 @@ class MessageMixin(stateobject.StateObject):
or "identity". or "identity".
""" """
# FIXME: Error if there's an existing encoding header? # FIXME: Error if there's an existing encoding header?
self.body = encoding.encode(e, self.body) self.content = encoding.encode(e, self.content)
self.headers["content-encoding"] = e self.headers["content-encoding"] = e
def copy(self): def copy(self):
c = copy.copy(self) c = copy.copy(self)
if hasattr(self, "data"): # FIXME remove condition
c.data = copy.copy(self.data)
c.headers = self.headers.copy() c.headers = self.headers.copy()
return c return c
@ -86,8 +87,8 @@ class MessageMixin(stateobject.StateObject):
Returns the number of replacements made. Returns the number of replacements made.
""" """
with decoded(self): with decoded(self):
self.body, count = utils.safe_subn( self.content, count = utils.safe_subn(
pattern, repl, self.body, *args, **kwargs pattern, repl, self.content, *args, **kwargs
) )
fields = [] fields = []
for name, value in self.headers.fields: for name, value in self.headers.fields:
@ -147,7 +148,7 @@ class HTTPRequest(MessageMixin, Request):
def __init__( def __init__(
self, self,
form_in, first_line_format,
method, method,
scheme, scheme,
host, host,
@ -155,14 +156,14 @@ class HTTPRequest(MessageMixin, Request):
path, path,
http_version, http_version,
headers, headers,
body, content,
timestamp_start=None, timestamp_start=None,
timestamp_end=None, timestamp_end=None,
form_out=None, form_out=None,
): ):
Request.__init__( Request.__init__(
self, self,
form_in, first_line_format,
method, method,
scheme, scheme,
host, host,
@ -170,11 +171,11 @@ class HTTPRequest(MessageMixin, Request):
path, path,
http_version, http_version,
headers, headers,
body, content,
timestamp_start, timestamp_start,
timestamp_end, timestamp_end,
) )
self.form_out = form_out or form_in self.form_out = form_out or first_line_format # FIXME remove
# Have this request's cookies been modified by sticky cookies or auth? # Have this request's cookies been modified by sticky cookies or auth?
self.stickycookie = False self.stickycookie = False
@ -185,7 +186,8 @@ class HTTPRequest(MessageMixin, Request):
_stateobject_attributes = MessageMixin._stateobject_attributes.copy() _stateobject_attributes = MessageMixin._stateobject_attributes.copy()
_stateobject_attributes.update( _stateobject_attributes.update(
form_in=str, content=bytes,
first_line_format=str,
method=bytes, method=bytes,
scheme=bytes, scheme=bytes,
host=bytes, host=bytes,
@ -225,7 +227,7 @@ class HTTPRequest(MessageMixin, Request):
@classmethod @classmethod
def wrap(self, request): def wrap(self, request):
req = HTTPRequest( req = HTTPRequest(
form_in=request.form_in, first_line_format=request.form_in,
method=request.method, method=request.method,
scheme=request.scheme, scheme=request.scheme,
host=request.host, host=request.host,
@ -233,7 +235,7 @@ class HTTPRequest(MessageMixin, Request):
path=request.path, path=request.path,
http_version=request.http_version, http_version=request.http_version,
headers=request.headers, headers=request.headers,
body=request.body, content=request.content,
timestamp_start=request.timestamp_start, timestamp_start=request.timestamp_start,
timestamp_end=request.timestamp_end, timestamp_end=request.timestamp_end,
form_out=(request.form_out if hasattr(request, 'form_out') else None), form_out=(request.form_out if hasattr(request, 'form_out') else None),
@ -288,9 +290,9 @@ class HTTPResponse(MessageMixin, Response):
self, self,
http_version, http_version,
status_code, status_code,
msg, reason,
headers, headers,
body, content,
timestamp_start=None, timestamp_start=None,
timestamp_end=None, timestamp_end=None,
): ):
@ -298,9 +300,9 @@ class HTTPResponse(MessageMixin, Response):
self, self,
http_version, http_version,
status_code, status_code,
msg, reason,
headers, headers,
body, content,
timestamp_start=timestamp_start, timestamp_start=timestamp_start,
timestamp_end=timestamp_end, timestamp_end=timestamp_end,
) )
@ -311,6 +313,7 @@ class HTTPResponse(MessageMixin, Response):
_stateobject_attributes = MessageMixin._stateobject_attributes.copy() _stateobject_attributes = MessageMixin._stateobject_attributes.copy()
_stateobject_attributes.update( _stateobject_attributes.update(
body=bytes,
status_code=int, status_code=int,
msg=bytes msg=bytes
) )
@ -336,9 +339,9 @@ class HTTPResponse(MessageMixin, Response):
resp = HTTPResponse( resp = HTTPResponse(
http_version=response.http_version, http_version=response.http_version,
status_code=response.status_code, status_code=response.status_code,
msg=response.msg, reason=response.reason,
headers=response.headers, headers=response.headers,
body=response.body, content=response.content,
timestamp_start=response.timestamp_start, timestamp_start=response.timestamp_start,
timestamp_end=response.timestamp_end, timestamp_end=response.timestamp_end,
) )
@ -400,22 +403,20 @@ class HTTPResponse(MessageMixin, Response):
class HTTPFlow(Flow): class HTTPFlow(Flow):
""" """
A HTTPFlow is a collection of objects representing a single HTTP A HTTPFlow is a collection of objects representing a single HTTP
transaction. The main attributes are: transaction.
Attributes:
request: HTTPRequest object request: HTTPRequest object
response: HTTPResponse object response: HTTPResponse object
error: Error object error: Error object
server_conn: ServerConnection object server_conn: ServerConnection object
client_conn: ClientConnection object client_conn: ClientConnection object
intercepted: Is this flow currently being intercepted?
live: Does this flow have a live client connection?
Note that it's possible for a Flow to have both a response and an error Note that it's possible for a Flow to have both a response and an error
object. This might happen, for instance, when a response was received object. This might happen, for instance, when a response was received
from the server, but there was an error sending it back to the client. from the server, but there was an error sending it back to the client.
The following additional attributes are exposed:
intercepted: Is this flow currently being intercepted?
live: Does this flow have a live client connection?
""" """
def __init__(self, client_conn, server_conn, live=None): def __init__(self, client_conn, server_conn, live=None):
@ -485,36 +486,6 @@ class HTTPFlow(Flow):
return c return c
class decoded(object):
"""
A context manager that decodes a request or response, and then
re-encodes it with the same encoding after execution of the block.
Example:
with decoded(request):
request.content = request.content.replace("foo", "bar")
"""
def __init__(self, o):
self.o = o
ce = o.headers.get("content-encoding")
if ce:
ce = native(ce, "ascii", "ignore")
if ce in encoding.ENCODINGS:
self.ce = ce
else:
self.ce = None
def __enter__(self):
if self.ce:
if not self.o.decode():
self.ce = None
def __exit__(self, type, value, tb):
if self.ce:
self.o.encode(self.ce)
def make_error_response(status_code, message, headers=None): def make_error_response(status_code, message, headers=None):
response = status_codes.RESPONSES.get(status_code, "Unknown") response = status_codes.RESPONSES.get(status_code, "Unknown")
body = """ body = """

View File

@ -54,7 +54,7 @@ class _StreamingHttpLayer(_HttpLayer):
def read_response(self, request): def read_response(self, request):
response = self.read_response_headers() response = self.read_response_headers()
response.body = b"".join( response.content = b"".join(
self.read_response_body(request, response) self.read_response_body(request, response)
) )
return response return response
@ -66,10 +66,10 @@ class _StreamingHttpLayer(_HttpLayer):
raise NotImplementedError() raise NotImplementedError()
def send_response(self, response): def send_response(self, response):
if response.body == CONTENT_MISSING: if response.content == CONTENT_MISSING:
raise HttpException("Cannot assemble flow with CONTENT_MISSING") raise HttpException("Cannot assemble flow with CONTENT_MISSING")
self.send_response_headers(response) self.send_response_headers(response)
self.send_response_body(response, [response.body]) self.send_response_body(response, [response.content])
class Http1Layer(_StreamingHttpLayer): class Http1Layer(_StreamingHttpLayer):

View File

@ -7,7 +7,6 @@ from construct import ConstructError
import six import six
from netlib.exceptions import InvalidCertificateException, TcpException, TlsException from netlib.exceptions import InvalidCertificateException, TcpException, TlsException
from netlib.http import ALPN_PROTO_HTTP1
from ..contrib.tls._constructs import ClientHello from ..contrib.tls._constructs import ClientHello
from ..exceptions import ProtocolException, TlsProtocolException, ClientHandshakeException from ..exceptions import ProtocolException, TlsProtocolException, ClientHandshakeException
from .base import Layer from .base import Layer
@ -367,8 +366,8 @@ class TlsLayer(Layer):
""" """
# This gets triggered if we haven't established an upstream connection yet. # This gets triggered if we haven't established an upstream connection yet.
default_alpn = ALPN_PROTO_HTTP1 default_alpn = b'http/1.1'
# alpn_preference = ALPN_PROTO_H2 # alpn_preference = b'h2'
if self.alpn_for_client_connection in options: if self.alpn_for_client_connection in options:
choice = bytes(self.alpn_for_client_connection) choice = bytes(self.alpn_for_client_connection)

View File

@ -6,7 +6,6 @@ import six
from libmproxy.exceptions import ProtocolException from libmproxy.exceptions import ProtocolException
from netlib.exceptions import TcpException from netlib.exceptions import TcpException
from netlib.http import ALPN_PROTO_H2, ALPN_PROTO_HTTP1
from ..protocol import ( from ..protocol import (
RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin, RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin,
UpstreamConnectLayer UpstreamConnectLayer
@ -85,9 +84,9 @@ class RootContext(object):
# 5. Check for TLS ALPN (HTTP1/HTTP2) # 5. Check for TLS ALPN (HTTP1/HTTP2)
if isinstance(top_layer, TlsLayer): if isinstance(top_layer, TlsLayer):
alpn = top_layer.client_conn.get_alpn_proto_negotiated() alpn = top_layer.client_conn.get_alpn_proto_negotiated()
if alpn == ALPN_PROTO_H2: if alpn == b'h2':
return Http2Layer(top_layer, 'transparent') return Http2Layer(top_layer, 'transparent')
if alpn == ALPN_PROTO_HTTP1: if alpn == b'http/1.1':
return Http1Layer(top_layer, 'transparent') return Http1Layer(top_layer, 'transparent')
# 6. Check for raw tcp mode # 6. Check for raw tcp mode

View File

@ -29,7 +29,7 @@ class RequestHandler(tornado.web.RequestHandler):
def json(self): def json(self):
if not self.request.headers.get("Content-Type").startswith("application/json"): if not self.request.headers.get("Content-Type").startswith("application/json"):
return None return None
return json.loads(self.request.body) return json.loads(self.request.content)
@property @property
def state(self): def state(self):

View File

@ -38,13 +38,13 @@ def test_strfuncs():
flow.request.stickycookie = True flow.request.stickycookie = True
flow.client_conn = mock.MagicMock() flow.client_conn = mock.MagicMock()
flow.client_conn.address.host = "foo" flow.client_conn.address.host = "foo"
flow.response = netlib.tutils.tresp(body=CONTENT_MISSING) flow.response = netlib.tutils.tresp(content=CONTENT_MISSING)
flow.response.is_replay = True flow.response.is_replay = True
flow.response.status_code = 300 flow.response.status_code = 300
m.echo_flow(flow) m.echo_flow(flow)
flow = tutils.tflow(resp=netlib.tutils.tresp(body="{")) flow = tutils.tflow(resp=netlib.tutils.tresp(content="{"))
flow.response.headers["content-type"] = "application/json" flow.response.headers["content-type"] = "application/json"
flow.response.status_code = 400 flow.response.status_code = 400
m.echo_flow(flow) m.echo_flow(flow)
@ -62,14 +62,14 @@ def test_contentview(get_content_view):
class TestDumpMaster: class TestDumpMaster:
def _cycle(self, m, content): def _cycle(self, m, content):
f = tutils.tflow(req=netlib.tutils.treq(body=content)) f = tutils.tflow(req=netlib.tutils.treq(content=content))
l = Log("connect") l = Log("connect")
l.reply = mock.MagicMock() l.reply = mock.MagicMock()
m.handle_log(l) m.handle_log(l)
m.handle_clientconnect(f.client_conn) m.handle_clientconnect(f.client_conn)
m.handle_serverconnect(f.server_conn) m.handle_serverconnect(f.server_conn)
m.handle_request(f) m.handle_request(f)
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=content)) f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=content))
f = m.handle_response(f) f = m.handle_response(f)
m.handle_clientdisconnect(f.client_conn) m.handle_clientdisconnect(f.client_conn)
return f return f

View File

@ -8,8 +8,8 @@ import mock
import netlib.utils import netlib.utils
from netlib import odict from netlib import odict
from netlib.http import CONTENT_MISSING, HDR_FORM_URLENCODED, Headers from netlib.http import CONTENT_MISSING, Headers
from libmproxy import filt, protocol, controller, tnetstring, flow from libmproxy import filt, controller, tnetstring, flow
from libmproxy.models import Error, Flow, HTTPRequest, HTTPResponse, HTTPFlow, decoded from libmproxy.models import Error, Flow, HTTPRequest, HTTPResponse, HTTPFlow, decoded
from libmproxy.proxy.config import HostMatcher from libmproxy.proxy.config import HostMatcher
from libmproxy.proxy import ProxyConfig from libmproxy.proxy import ProxyConfig
@ -338,7 +338,7 @@ class TestServerPlaybackState:
assert s._hash(r) == s._hash(r2) assert s._hash(r) == s._hash(r2)
class TestFlow: class TestFlow(object):
def test_copy(self): def test_copy(self):
f = tutils.tflow(resp=True) f = tutils.tflow(resp=True)
a0 = f.get_state() a0 = f.get_state()
@ -849,7 +849,7 @@ class TestFlowMaster:
s = flow.State() s = flow.State()
f = tutils.tflow() f = tutils.tflow()
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=f.request)) f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=f.request))
pb = [f] pb = [f]
fm = flow.FlowMaster(None, s) fm = flow.FlowMaster(None, s)
@ -903,7 +903,7 @@ class TestFlowMaster:
def test_server_playback_kill(self): def test_server_playback_kill(self):
s = flow.State() s = flow.State()
f = tutils.tflow() f = tutils.tflow()
f.response = HTTPResponse.wrap(netlib.tutils.tresp(body=f.request)) f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=f.request))
pb = [f] pb = [f]
fm = flow.FlowMaster(None, s) fm = flow.FlowMaster(None, s)
fm.refresh_server_playback = True fm.refresh_server_playback = True
@ -1017,10 +1017,10 @@ class TestRequest:
r.port = 22 r.port = 22
assert r.url == "https://address:22/path" assert r.url == "https://address:22/path"
assert r.pretty_url(True) == "https://address:22/path" assert r.pretty_url == "https://address:22/path"
r.headers["Host"] = "foo.com" r.headers["Host"] = "foo.com"
assert r.pretty_url(False) == "https://address:22/path" assert r.url == "https://address:22/path"
assert r.pretty_url(True) == "https://foo.com:22/path" assert r.pretty_url == "https://foo.com:22/path"
def test_path_components(self): def test_path_components(self):
r = HTTPRequest.wrap(netlib.tutils.treq()) r = HTTPRequest.wrap(netlib.tutils.treq())
@ -1043,8 +1043,8 @@ class TestRequest:
def test_getset_form_urlencoded(self): def test_getset_form_urlencoded(self):
d = odict.ODict([("one", "two"), ("three", "four")]) d = odict.ODict([("one", "two"), ("three", "four")])
r = HTTPRequest.wrap(netlib.tutils.treq(body=netlib.utils.urlencode(d.lst))) r = HTTPRequest.wrap(netlib.tutils.treq(content=netlib.utils.urlencode(d.lst)))
r.headers["content-type"] = HDR_FORM_URLENCODED r.headers["content-type"] = "application/x-www-form-urlencoded"
assert r.get_form_urlencoded() == d assert r.get_form_urlencoded() == d
d = odict.ODict([("x", "y")]) d = odict.ODict([("x", "y")])
@ -1105,35 +1105,6 @@ class TestRequest:
r.constrain_encoding() r.constrain_encoding()
assert "oink" not in r.headers["accept-encoding"] assert "oink" not in r.headers["accept-encoding"]
def test_decodeencode(self):
r = HTTPRequest.wrap(netlib.tutils.treq())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
r.decode()
assert "content-encoding" not in r.headers
assert r.content == "falafel"
r = HTTPRequest.wrap(netlib.tutils.treq())
r.content = "falafel"
assert not r.decode()
r = HTTPRequest.wrap(netlib.tutils.treq())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
r.encode("identity")
assert r.headers["content-encoding"] == "identity"
assert r.content == "falafel"
r = HTTPRequest.wrap(netlib.tutils.treq())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
r.encode("gzip")
assert r.headers["content-encoding"] == "gzip"
assert r.content != "falafel"
r.decode()
assert "content-encoding" not in r.headers
assert r.content == "falafel"
def test_get_decoded_content(self): def test_get_decoded_content(self):
r = HTTPRequest.wrap(netlib.tutils.treq()) r = HTTPRequest.wrap(netlib.tutils.treq())
r.content = None r.content = None
@ -1192,35 +1163,6 @@ class TestResponse:
assert not "foo" in r.content assert not "foo" in r.content
assert r.headers["boo"] == "boo" assert r.headers["boo"] == "boo"
def test_decodeencode(self):
r = HTTPResponse.wrap(netlib.tutils.tresp())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
assert r.decode()
assert "content-encoding" not in r.headers
assert r.content == "falafel"
r = HTTPResponse.wrap(netlib.tutils.tresp())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
r.encode("identity")
assert r.headers["content-encoding"] == "identity"
assert r.content == "falafel"
r = HTTPResponse.wrap(netlib.tutils.tresp())
r.headers["content-encoding"] = "identity"
r.content = "falafel"
r.encode("gzip")
assert r.headers["content-encoding"] == "gzip"
assert r.content != "falafel"
assert r.decode()
assert "content-encoding" not in r.headers
assert r.content == "falafel"
r.headers["content-encoding"] = "gzip"
assert not r.decode()
assert r.content == "falafel"
def test_get_content_type(self): def test_get_content_type(self):
resp = HTTPResponse.wrap(netlib.tutils.tresp()) resp = HTTPResponse.wrap(netlib.tutils.tresp())
resp.headers = Headers(content_type="text/plain") resp.headers = Headers(content_type="text/plain")
@ -1265,27 +1207,6 @@ class TestClientConnection:
assert str(c) assert str(c)
def test_decoded():
r = HTTPRequest.wrap(netlib.tutils.treq())
assert r.content == "content"
assert "content-encoding" not in r.headers
r.encode("gzip")
assert r.headers["content-encoding"]
assert r.content != "content"
with decoded(r):
assert "content-encoding" not in r.headers
assert r.content == "content"
assert r.headers["content-encoding"]
assert r.content != "content"
with decoded(r):
r.content = "foo"
assert r.content != "foo"
r.decode()
assert r.content == "foo"
def test_replacehooks(): def test_replacehooks():
h = flow.ReplaceHooks() h = flow.ReplaceHooks()
h.add("~q", "foo", "bar") h.add("~q", "foo", "bar")

View File

@ -48,14 +48,14 @@ class TestInvalidRequests(tservers.HTTPProxTest):
p = self.pathoc() p = self.pathoc()
r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port)) r = p.request("connect:'%s:%s'" % ("127.0.0.1", self.server2.port))
assert r.status_code == 400 assert r.status_code == 400
assert "Invalid HTTP request form" in r.body assert "Invalid HTTP request form" in r.content
def test_relative_request(self): def test_relative_request(self):
p = self.pathoc_raw() p = self.pathoc_raw()
p.connect() p.connect()
r = p.request("get:/p/200") r = p.request("get:/p/200")
assert r.status_code == 400 assert r.status_code == 400
assert "Invalid HTTP request form" in r.body assert "Invalid HTTP request form" in r.content
class TestExpectHeader(tservers.HTTPProxTest): class TestExpectHeader(tservers.HTTPProxTest):
@ -81,4 +81,4 @@ class TestExpectHeader(tservers.HTTPProxTest):
resp = http1.read_response(client.rfile, treq()) resp = http1.read_response(client.rfile, treq())
assert resp.status_code == 200 assert resp.status_code == 200
client.finish() client.finish()

View File

@ -235,7 +235,8 @@ class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin):
# There's a race here, which means we can get any of a number of errors. # There's a race here, which means we can get any of a number of errors.
# Rather than introduce yet another sleep into the test suite, we just # Rather than introduce yet another sleep into the test suite, we just
# relax the Exception specification. # relax the Exception specification.
tutils.raises(Exception, p.request, "get:'%s'" % response) with raises(Exception):
p.request("get:'%s'" % response)
def test_reconnect(self): def test_reconnect(self):
req = "get:'%s/p/200:b@1:da'" % self.server.urlbase req = "get:'%s/p/200:b@1:da'" % self.server.urlbase
@ -573,7 +574,6 @@ class TestProxy(tservers.HTTPProxTest):
recvd += len(connection.recv(5000)) recvd += len(connection.recv(5000))
connection.close() connection.close()
print(self.master.state.view._list)
first_flow = self.master.state.view[0] first_flow = self.master.state.view[0]
second_flow = self.master.state.view[1] second_flow = self.master.state.view[1]
assert first_flow.server_conn.timestamp_tcp_setup assert first_flow.server_conn.timestamp_tcp_setup
@ -616,7 +616,7 @@ class MasterRedirectRequest(tservers.TestMaster):
super(MasterRedirectRequest, self).handle_request(f) super(MasterRedirectRequest, self).handle_request(f)
def handle_response(self, f): def handle_response(self, f):
f.response.body = str(f.client_conn.address.port) f.response.content = str(f.client_conn.address.port)
f.response.headers["server-conn-id"] = str(f.server_conn.source_address.port) f.response.headers["server-conn-id"] = str(f.server_conn.source_address.port)
super(MasterRedirectRequest, self).handle_response(f) super(MasterRedirectRequest, self).handle_response(f)