diff options
-rw-r--r-- | mitmproxy/addons/eventstore.py | 19 | ||||
-rw-r--r-- | mitmproxy/tools/main.py | 1 | ||||
-rw-r--r-- | mitmproxy/tools/web/app.py | 88 | ||||
-rw-r--r-- | mitmproxy/tools/web/master.py | 116 |
4 files changed, 87 insertions, 137 deletions
diff --git a/mitmproxy/addons/eventstore.py b/mitmproxy/addons/eventstore.py new file mode 100644 index 00000000..0afd018a --- /dev/null +++ b/mitmproxy/addons/eventstore.py @@ -0,0 +1,19 @@ +from typing import List # noqa + +import blinker +from mitmproxy import log + + +class EventStore: + def __init__(self): + self.data = [] # type: List[log.LogEntry] + self.sig_add = blinker.Signal() + self.sig_refresh = blinker.Signal() + + def log(self, entry: log.LogEntry): + self.data.append(entry) + self.sig_add.send(self, entry=entry) + + def clear(self): + self.data.clear() + self.sig_refresh.send(self) diff --git a/mitmproxy/tools/main.py b/mitmproxy/tools/main.py index 69dd3791..478690eb 100644 --- a/mitmproxy/tools/main.py +++ b/mitmproxy/tools/main.py @@ -135,7 +135,6 @@ def mitmweb(args=None): # pragma: no cover web_options.wdebug = args.wdebug web_options.wiface = args.wiface web_options.wport = args.wport - web_options.process_web_options(parser) server = process_options(parser, web_options, args) m = web.master.WebMaster(web_options, server) diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index 25a46169..8f71d8da 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -1,5 +1,3 @@ - -import base64 import hashlib import json import logging @@ -7,19 +5,20 @@ import os.path import re from io import BytesIO +import mitmproxy.addons.view +import mitmproxy.flow +import tornado.escape import tornado.web import tornado.websocket -import tornado.escape from mitmproxy import contentviews from mitmproxy import flowfilter from mitmproxy import http from mitmproxy import io +from mitmproxy import log from mitmproxy import version -import mitmproxy.addons.view -import mitmproxy.flow -def convert_flow_to_json_dict(flow: mitmproxy.flow.Flow) -> dict: +def flow_to_json(flow: mitmproxy.flow.Flow) -> dict: """ Remove flow message content and cert to save transmission space. @@ -46,8 +45,10 @@ def convert_flow_to_json_dict(flow: mitmproxy.flow.Flow) -> dict: "path": flow.request.path, "http_version": flow.request.http_version, "headers": tuple(flow.request.headers.items(True)), - "contentLength": len(flow.request.raw_content) if flow.request.raw_content is not None else None, - "contentHash": hashlib.sha256(flow.request.raw_content).hexdigest() if flow.request.raw_content is not None else None, + "contentLength": len( + flow.request.raw_content) if flow.request.raw_content is not None else None, + "contentHash": hashlib.sha256( + flow.request.raw_content).hexdigest() if flow.request.raw_content is not None else None, "timestamp_start": flow.request.timestamp_start, "timestamp_end": flow.request.timestamp_end, "is_replay": flow.request.is_replay, @@ -58,8 +59,10 @@ def convert_flow_to_json_dict(flow: mitmproxy.flow.Flow) -> dict: "status_code": flow.response.status_code, "reason": flow.response.reason, "headers": tuple(flow.response.headers.items(True)), - "contentLength": len(flow.response.raw_content) if flow.response.raw_content is not None else None, - "contentHash": hashlib.sha256(flow.response.raw_content).hexdigest() if flow.response.raw_content is not None else None, + "contentLength": len( + flow.response.raw_content) if flow.response.raw_content is not None else None, + "contentHash": hashlib.sha256( + flow.response.raw_content).hexdigest() if flow.response.raw_content is not None else None, "timestamp_start": flow.response.timestamp_start, "timestamp_end": flow.response.timestamp_end, "is_replay": flow.response.is_replay, @@ -69,34 +72,18 @@ def convert_flow_to_json_dict(flow: mitmproxy.flow.Flow) -> dict: return f -class APIError(tornado.web.HTTPError): - pass - - -class BasicAuth: - - def set_auth_headers(self): - self.set_status(401) - self.set_header('WWW-Authenticate', 'Basic realm=MITMWeb') - self._transforms = [] - self.finish() +def logentry_to_json(e: log.LogEntry): + return { + "message": e.msg, + "level": e.level + } - def prepare(self): - wauthenticator = self.application.settings['wauthenticator'] - if wauthenticator: - auth_header = self.request.headers.get('Authorization') - if auth_header is None or not auth_header.startswith('Basic '): - self.set_auth_headers() - else: - auth_decoded = base64.decodebytes(auth_header[6:]) - username, password = auth_decoded.split(':', 2) - if not wauthenticator.test(username, password): - self.set_auth_headers() - raise APIError(401, "Invalid username or password.") +class APIError(tornado.web.HTTPError): + pass -class RequestHandler(BasicAuth, tornado.web.RequestHandler): +class RequestHandler(tornado.web.RequestHandler): def write(self, chunk): # Writing arrays on the top level is ok nowadays. # http://flask.pocoo.org/docs/0.11/security/#json-security @@ -150,7 +137,6 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler): class IndexHandler(RequestHandler): - def get(self): token = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645 assert token @@ -158,14 +144,13 @@ class IndexHandler(RequestHandler): class FilterHelp(RequestHandler): - def get(self): self.write(dict( commands=flowfilter.help )) -class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler): +class WebSocketEventBroadcaster(tornado.websocket.WebSocketHandler): # raise an error if inherited class doesn't specify its own instance. connections = None # type: set @@ -191,9 +176,8 @@ class ClientConnection(WebSocketEventBroadcaster): class Flows(RequestHandler): - def get(self): - self.write([convert_flow_to_json_dict(f) for f in self.view]) + self.write([flow_to_json(f) for f in self.view]) class DumpFlows(RequestHandler): @@ -219,25 +203,22 @@ class DumpFlows(RequestHandler): class ClearAll(RequestHandler): - def post(self): self.view.clear() + self.master.events.clear() class AcceptFlows(RequestHandler): - def post(self): self.master.accept_all(self.master) class AcceptFlow(RequestHandler): - def post(self, flow_id): self.flow.resume(self.master) class FlowHandler(RequestHandler): - def delete(self, flow_id): if self.flow.killable: self.flow.kill(self.master) @@ -286,19 +267,16 @@ class FlowHandler(RequestHandler): class DuplicateFlow(RequestHandler): - def post(self, flow_id): self.master.view.duplicate_flow(self.flow) class RevertFlow(RequestHandler): - def post(self, flow_id): self.flow.revert() class ReplayFlow(RequestHandler): - def post(self, flow_id): self.flow.backup() self.flow.response = None @@ -310,7 +288,6 @@ class ReplayFlow(RequestHandler): class FlowContent(RequestHandler): - def post(self, flow_id, message): self.flow.backup() message = getattr(self.flow, message) @@ -347,15 +324,14 @@ class FlowContent(RequestHandler): class FlowContentView(RequestHandler): - def get(self, flow_id, message, content_view): message = getattr(self.flow, message) description, lines, error = contentviews.get_message_content_view( content_view.replace('_', ' '), message ) -# if error: -# add event log + # if error: + # add event log self.write(dict( lines=list(lines), @@ -364,13 +340,11 @@ class FlowContentView(RequestHandler): class Events(RequestHandler): - def get(self): - self.write([]) # FIXME + self.write([logentry_to_json(e) for e in self.master.events.data]) class Settings(RequestHandler): - def get(self): self.write(dict( version=version.VERSION, @@ -432,8 +406,7 @@ class Settings(RequestHandler): class Application(tornado.web.Application): - - def __init__(self, master, debug, wauthenticator): + def __init__(self, master, debug): self.master = master handlers = [ (r"/", IndexHandler), @@ -449,7 +422,9 @@ class Application(tornado.web.Application): (r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow), (r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow), (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content", FlowContent), - (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)", FlowContentView), + ( + r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)", + FlowContentView), (r"/settings", Settings), (r"/clear", ClearAll), ] @@ -460,6 +435,5 @@ class Application(tornado.web.Application): cookie_secret=os.urandom(256), debug=debug, autoreload=False, - wauthenticator=wauthenticator, ) super().__init__(handlers, **settings) diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py index d2203f10..453455f0 100644 --- a/mitmproxy/tools/web/master.py +++ b/mitmproxy/tools/web/master.py @@ -1,49 +1,20 @@ import sys import webbrowser +from typing import Optional import tornado.httpserver import tornado.ioloop - -from typing import Optional - from mitmproxy import addons from mitmproxy import exceptions -from mitmproxy.addons import view -from mitmproxy.addons import intercept -from mitmproxy import options +from mitmproxy import log from mitmproxy import master +from mitmproxy import options +from mitmproxy.addons import eventstore +from mitmproxy.addons import intercept +from mitmproxy.addons import view from mitmproxy.tools.web import app -class Stop(Exception): - pass - - -class _WebState(): - def add_log(self, e, level): - # server-side log ids are odd - self._last_event_id += 2 - entry = { - "id": self._last_event_id, - "message": e, - "level": level - } - self.events.append(entry) - app.ClientConnection.broadcast( - resource="events", - cmd="add", - data=entry - ) - - def clear(self): - super().clear() - self.events.clear() - app.ClientConnection.broadcast( - resource="events", - cmd="reset" - ) - - class Options(options.Options): def __init__( self, @@ -52,54 +23,32 @@ class Options(options.Options): wdebug: bool = False, wport: int = 8081, wiface: str = "127.0.0.1", - # wauthenticator: Optional[authentication.PassMan] = None, - wsingleuser: Optional[str] = None, - whtpasswd: Optional[str] = None, **kwargs ) -> None: + self.intercept = intercept self.wdebug = wdebug self.wport = wport self.wiface = wiface - # self.wauthenticator = wauthenticator - # self.wsingleuser = wsingleuser - # self.whtpasswd = whtpasswd - self.intercept = intercept super().__init__(**kwargs) - # TODO: This doesn't belong here. - def process_web_options(self, parser): - # if self.wsingleuser or self.whtpasswd: - # if self.wsingleuser: - # if len(self.wsingleuser.split(':')) != 2: - # return parser.error( - # "Invalid single-user specification. Please use the format username:password" - # ) - # username, password = self.wsingleuser.split(':') - # # self.wauthenticator = authentication.PassManSingleUser(username, password) - # elif self.whtpasswd: - # try: - # self.wauthenticator = authentication.PassManHtpasswd(self.whtpasswd) - # except ValueError as v: - # return parser.error(v.message) - # else: - # self.wauthenticator = None - pass - class WebMaster(master.Master): - def __init__(self, options, server): super().__init__(options, server) self.view = view.View() - self.view.sig_view_add.connect(self._sig_add) - self.view.sig_view_remove.connect(self._sig_remove) - self.view.sig_view_update.connect(self._sig_update) - self.view.sig_view_refresh.connect(self._sig_refresh) + self.view.sig_view_add.connect(self._sig_view_add) + self.view.sig_view_remove.connect(self._sig_view_remove) + self.view.sig_view_update.connect(self._sig_view_update) + self.view.sig_view_refresh.connect(self._sig_view_refresh) + + self.events = eventstore.EventStore() + self.events.sig_add.connect(self._sig_events_add) + self.events.sig_refresh.connect(self._sig_events_refresh) self.addons.add(*addons.default_addons()) - self.addons.add(self.view, intercept.Intercept()) + self.addons.add(self.view, self.events, intercept.Intercept()) self.app = app.Application( - self, self.options.wdebug, False + self, self.options.wdebug ) # This line is just for type hinting self.options = self.options # type: Options @@ -112,33 +61,46 @@ class WebMaster(master.Master): "error" ) - def _sig_add(self, view, flow): + def _sig_view_add(self, view, flow): app.ClientConnection.broadcast( resource="flows", cmd="add", - data=app.convert_flow_to_json_dict(flow) + data=app.flow_to_json(flow) ) - def _sig_update(self, view, flow): + def _sig_view_update(self, view, flow): app.ClientConnection.broadcast( resource="flows", cmd="update", - data=app.convert_flow_to_json_dict(flow) + data=app.flow_to_json(flow) ) - def _sig_remove(self, view, flow): + def _sig_view_remove(self, view, flow): app.ClientConnection.broadcast( resource="flows", cmd="remove", data=dict(id=flow.id) ) - def _sig_refresh(self, view): + def _sig_view_refresh(self, view): app.ClientConnection.broadcast( resource="flows", cmd="reset" ) + def _sig_events_add(self, event_store, entry: log.LogEntry): + app.ClientConnection.broadcast( + resource="events", + cmd="add", + data=app.logentry_to_json(entry) + ) + + def _sig_events_refresh(self, event_store): + app.ClientConnection.broadcast( + resource="events", + cmd="reset" + ) + def run(self): # pragma: no cover iol = tornado.ioloop.IOLoop.instance() @@ -155,13 +117,9 @@ class WebMaster(master.Master): print("No webbrowser found. Please open a browser and point it to {}".format(url)) iol.start() - except (Stop, KeyboardInterrupt): + except (KeyboardInterrupt): self.shutdown() - # def add_log(self, e, level="info"): - # super().add_log(e, level) - # return self.state.add_log(e, level) - def open_browser(url: str) -> bool: """ |