aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/addons/eventstore.py19
-rw-r--r--mitmproxy/tools/main.py1
-rw-r--r--mitmproxy/tools/web/app.py88
-rw-r--r--mitmproxy/tools/web/master.py116
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:
"""