From 6a53ae5fd37b516074f9bf46cffab015f6626b9e Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Tue, 4 Feb 2014 05:02:17 +0100 Subject: push failing tests down to 43 --- libmproxy/protocol/http.py | 17 +++--- libmproxy/protocol/primitives.py | 124 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 libmproxy/protocol/primitives.py (limited to 'libmproxy/protocol') diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 8c44461e..be60f374 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -6,7 +6,7 @@ from netlib.odict import ODict, ODictCaseless from . import ProtocolHandler, ConnectionTypeChange, KILL from .. import encoding, utils, version, filt, controller, stateobject from ..proxy import ProxyError, AddressPriority -from ..flow import Flow, Error +from .primitives import Flow, Error HDR_FORM_URLENCODED = "application/x-www-form-urlencoded" @@ -340,7 +340,7 @@ class HTTPRequest(HTTPMessage): Raises an Exception if the request cannot be assembled. """ if self.content == CONTENT_MISSING: - raise RuntimeError("Cannot assemble flow with CONTENT_MISSING") + raise ProxyError(502, "Cannot assemble flow with CONTENT_MISSING") head = self._assemble_head(form) if self.content: return head + self.content @@ -444,6 +444,8 @@ class HTTPRequest(HTTPMessage): If hostheader is True, we use the value specified in the request Host header to construct the URL. """ + raise NotImplementedError + # FIXME: Take server_conn into account. host = None if hostheader: host = self.headers.get_first("host") @@ -462,6 +464,8 @@ class HTTPRequest(HTTPMessage): Returns False if the URL was invalid, True if the request succeeded. """ + raise NotImplementedError + # FIXME: Needs to update server_conn as well. parts = http.parse_url(url) if not parts: return False @@ -595,7 +599,7 @@ class HTTPResponse(HTTPMessage): Raises an Exception if the request cannot be assembled. """ if self.content == CONTENT_MISSING: - raise RuntimeError("Cannot assemble flow with CONTENT_MISSING") + raise ProxyError(502, "Cannot assemble flow with CONTENT_MISSING") head = self._assemble_head() if self.content: return head + self.content @@ -711,7 +715,7 @@ class HTTPFlow(Flow): if self.request: f.request = self.request.copy() if self.response: - f.response = self.request.copy() + f.response = self.response.copy() return f def match(self, f): @@ -795,8 +799,7 @@ class HTTPHandler(ProtocolHandler): for i in range(2): try: - self.c.server_conn.wfile.write(request_raw) - self.c.server_conn.wfile.flush() + self.c.server_conn.send(request_raw) return HTTPResponse.from_stream(self.c.server_conn.rfile, request.method, body_size_limit=self.c.config.body_size_limit) except (tcp.NetLibDisconnect, http.HttpErrorConnClosed), v: @@ -821,6 +824,7 @@ class HTTPHandler(ProtocolHandler): flow.request = HTTPRequest.from_stream(self.c.client_conn.rfile, body_size_limit=self.c.config.body_size_limit) self.c.log("request", [flow.request._assemble_first_line(flow.request.form_in)]) + self.process_request(flow.request) request_reply = self.c.channel.ask("request" if LEGACY else "httprequest", flow.request if LEGACY else flow) @@ -830,7 +834,6 @@ class HTTPHandler(ProtocolHandler): if isinstance(request_reply, HTTPResponse): flow.response = request_reply else: - self.process_request(flow.request) self.c.establish_server_connection() flow.response = self.get_response_from_server(flow.request) diff --git a/libmproxy/protocol/primitives.py b/libmproxy/protocol/primitives.py new file mode 100644 index 00000000..f77e097b --- /dev/null +++ b/libmproxy/protocol/primitives.py @@ -0,0 +1,124 @@ +from .. import stateobject, utils, version +from ..proxy import ServerConnection, ClientConnection +import copy + + +class _BackreferenceMixin(object): + """ + If an attribute from the _backrefattr tuple is set, + this mixin sets a reference back on the attribute object. + Example: + e = Error() + f = Flow() + f.error = e + assert f is e.flow + """ + _backrefattr = tuple() + + def __setattr__(self, key, value): + super(_BackreferenceMixin, self).__setattr__(key, value) + if key in self._backrefattr and value is not None: + setattr(value, self._backrefname, self) + + +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, + like interrupted connections, timeouts, protocol errors. + + Exposes the following attributes: + + flow: Flow object + msg: Message describing the error + timestamp: Seconds since the epoch + """ + def __init__(self, msg, timestamp=None): + """ + @type msg: str + @type timestamp: float + """ + self.msg = msg + self.timestamp = timestamp or utils.timestamp() + + _stateobject_attributes = dict( + msg=str, + timestamp=float + ) + + @classmethod + def _from_state(cls, state): + f = cls(None) # the default implementation assumes an empty constructor. Override accordingly. + f._load_state(state) + return f + + def copy(self): + c = copy.copy(self) + return c + + +class Flow(stateobject.SimpleStateObject, _BackreferenceMixin): + def __init__(self, conntype, client_conn, server_conn): + self.conntype = conntype + self.client_conn = client_conn + self.server_conn = server_conn + self.error = None + + _backrefattr = ("error",) + _backrefname = "flow" + + _stateobject_attributes = dict( + error=Error, + client_conn=ClientConnection, + server_conn=ServerConnection, + conntype=str + ) + + def _get_state(self): + d = super(Flow, self)._get_state() + d.update(version=version.IVERSION) + return d + + @classmethod + def _from_state(cls, state): + f = cls(None, None, None) + f._load_state(state) + return f + + def copy(self): + f = copy.copy(self) + + f.client_conn = self.client_conn.copy() + f.server_conn = self.server_conn.copy() + + if self.error: + f.error = self.error.copy() + return f + + def modified(self): + """ + Has this Flow been modified? + """ + if self._backup: + return self._backup != self._get_state() + else: + return False + + def backup(self, force=False): + """ + Save a backup of this Flow, which can be reverted to using a + call to .revert(). + """ + if not self._backup: + self._backup = self._get_state() + + def revert(self): + """ + Revert to the last backed up state. + """ + if self._backup: + self._load_state(self._backup) + self._backup = None \ No newline at end of file -- cgit v1.2.3