From 677789a6171fb2428eca072e85e28732b83c032e Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 10 Nov 2016 08:50:21 +0100 Subject: minor fixes --- dev.sh | 2 +- mitmproxy/options.py | 2 +- mitmproxy/tools/__init__.py | 5 +++ mitmproxy/tools/console/grideditor/col_text.py | 3 +- mitmproxy/tools/dump.py | 7 ++-- mitmproxy/tools/web/app.py | 44 ++++++++++++++------------ mitmproxy/tools/web/master.py | 2 +- tox.ini | 6 +++- 8 files changed, 40 insertions(+), 31 deletions(-) diff --git a/dev.sh b/dev.sh index 3a68a9fe..4a2b766a 100755 --- a/dev.sh +++ b/dev.sh @@ -7,7 +7,7 @@ VENV="venv$PYVERSION" echo "Creating dev environment in $VENV using Python $PYVERSION" -python$PYVERSION -m virtualenv "$VENV" --always-copy +python$PYVERSION -m venv "$VENV" . "$VENV/bin/activate" pip$PYVERSION install -U pip setuptools pip$PYVERSION install -r requirements.txt diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 45f18372..6f41bf85 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -83,7 +83,7 @@ class Options(optmanager.OptManager): ssl_verify_upstream_trusted_cadir: Optional[str] = None, ssl_verify_upstream_trusted_ca: Optional[str] = None, tcp_hosts: Sequence[str] = () - ): + ) -> None: # We could replace all assignments with clever metaprogramming, # but type hints are a much more valueable asset. diff --git a/mitmproxy/tools/__init__.py b/mitmproxy/tools/__init__.py index e69de29b..5a8593df 100644 --- a/mitmproxy/tools/__init__.py +++ b/mitmproxy/tools/__init__.py @@ -0,0 +1,5 @@ +from mitmproxy.tools import web +from mitmproxy.tools import console +from mitmproxy.tools import dump + +__all__ = ["web", "console", "dump"] diff --git a/mitmproxy/tools/console/grideditor/col_text.py b/mitmproxy/tools/console/grideditor/col_text.py index 2d5192ae..430ad037 100644 --- a/mitmproxy/tools/console/grideditor/col_text.py +++ b/mitmproxy/tools/console/grideditor/col_text.py @@ -26,8 +26,7 @@ class Column(col_bytes.Column): # This is the same for both edit and display. class EncodingMixin: - def __init__(self, data, encoding_args): - # type: (str) -> TDisplay + def __init__(self, data: str, encoding_args) -> "TDisplay": self.encoding_args = encoding_args data = data.encode(*self.encoding_args) super().__init__(data) diff --git a/mitmproxy/tools/dump.py b/mitmproxy/tools/dump.py index 837959bc..3cd94c30 100644 --- a/mitmproxy/tools/dump.py +++ b/mitmproxy/tools/dump.py @@ -1,5 +1,4 @@ -import typing -from typing import Optional +from typing import Optional, IO from mitmproxy import controller from mitmproxy import exceptions @@ -22,9 +21,9 @@ class Options(options.Options): keepserving: bool = False, filtstr: Optional[str] = None, flow_detail: int = 1, - tfile: Optional[typing.io.TextIO] = None, + tfile: Optional[IO[str]] = None, **kwargs - ): + ) -> None: self.filtstr = filtstr self.flow_detail = flow_detail self.keepserving = keepserving diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py index 4449a13c..25a46169 100644 --- a/mitmproxy/tools/web/app.py +++ b/mitmproxy/tools/web/app.py @@ -1,3 +1,4 @@ + import base64 import hashlib import json @@ -10,14 +11,15 @@ import tornado.web import tornado.websocket import tornado.escape from mitmproxy import contentviews -from mitmproxy import flow from mitmproxy import flowfilter from mitmproxy import http from mitmproxy import io from mitmproxy import version +import mitmproxy.addons.view +import mitmproxy.flow -def convert_flow_to_json_dict(flow: flow.Flow) -> dict: +def convert_flow_to_json_dict(flow: mitmproxy.flow.Flow) -> dict: """ Remove flow message content and cert to save transmission space. @@ -86,9 +88,9 @@ class BasicAuth: if auth_header is None or not auth_header.startswith('Basic '): self.set_auth_headers() else: - self.auth_decoded = base64.decodestring(auth_header[6:]) - self.username, self.password = self.auth_decoded.split(':', 2) - if not wauthenticator.test(self.username, self.password): + 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.") @@ -123,7 +125,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler): return json.loads(self.request.body.decode()) @property - def view(self): + def view(self) -> mitmproxy.addons.view.View: return self.application.master.view @property @@ -131,7 +133,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler): return self.application.master @property - def flow(self): + def flow(self) -> mitmproxy.flow.Flow: flow_id = str(self.path_kwargs["flow_id"]) # FIXME: Add a facility to addon.view to safely access the store flow = self.view._store.get(flow_id) @@ -140,7 +142,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler): else: raise APIError(400, "Flow not found.") - def write_error(self, status_code, **kwargs): + def write_error(self, status_code: int, **kwargs): if "exc_info" in kwargs and isinstance(kwargs["exc_info"][1], APIError): self.finish(kwargs["exc_info"][1].log_message) else: @@ -165,7 +167,7 @@ class FilterHelp(RequestHandler): class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler): # raise an error if inherited class doesn't specify its own instance. - connections = None + connections = None # type: set def open(self): self.connections.add(self) @@ -180,12 +182,12 @@ class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler): for conn in cls.connections: try: conn.write_message(message) - except: + except Exception: logging.error("Error sending message", exc_info=True) class ClientConnection(WebSocketEventBroadcaster): - connections = set() + connections = set() # type: set class Flows(RequestHandler): @@ -212,7 +214,7 @@ class DumpFlows(RequestHandler): content = self.request.files.values()[0][0].body bio = BytesIO(content) - self.view.load_flows(io.FlowReader(bio).stream()) + self.master.load_flows(io.FlowReader(bio).stream()) bio.close() @@ -225,7 +227,7 @@ class ClearAll(RequestHandler): class AcceptFlows(RequestHandler): def post(self): - self.view.flows.accept_all(self.master) + self.master.accept_all(self.master) class AcceptFlow(RequestHandler): @@ -239,13 +241,13 @@ class FlowHandler(RequestHandler): def delete(self, flow_id): if self.flow.killable: self.flow.kill(self.master) - self.view.delete_flow(self.flow) + self.view.remove(self.flow) def put(self, flow_id): flow = self.flow flow.backup() for a, b in self.json.items(): - if a == "request": + if a == "request" and hasattr(flow, "request"): request = flow.request for k, v in b.items(): if k in ["method", "scheme", "host", "path", "http_version"]: @@ -261,7 +263,7 @@ class FlowHandler(RequestHandler): else: print("Warning: Unknown update {}.{}: {}".format(a, k, v)) - elif a == "response": + elif a == "response" and hasattr(flow, "response"): response = flow.response for k, v in b.items(): if k == "msg": @@ -280,7 +282,7 @@ class FlowHandler(RequestHandler): print("Warning: Unknown update {}.{}: {}".format(a, k, v)) else: print("Warning: Unknown update {}: {}".format(a, b)) - self.view.update_flow(flow) + self.view.update(flow) class DuplicateFlow(RequestHandler): @@ -300,7 +302,7 @@ class ReplayFlow(RequestHandler): def post(self, flow_id): self.flow.backup() self.flow.response = None - self.view.update_flow(self.flow) + self.view.update(self.flow) r = self.master.replay_request(self.flow) if r: @@ -313,7 +315,7 @@ class FlowContent(RequestHandler): self.flow.backup() message = getattr(self.flow, message) message.content = self.request.files.values()[0][0].body - self.view.update_flow(self.flow) + self.view.update(self.flow) def get(self, flow_id, message): message = getattr(self.flow, message) @@ -329,13 +331,13 @@ class FlowContent(RequestHandler): original_cd = message.headers.get("Content-Disposition", None) filename = None if original_cd: - filename = re.search("filename=([\w\" \.\-\(\)]+)", original_cd) + filename = re.search('filename=([-\w" .()]+)', original_cd) if filename: filename = filename.group(1) if not filename: filename = self.flow.request.path.split("?")[0].split("/")[-1] - filename = re.sub(r"[^\w\" \.\-\(\)]", "", filename) + filename = re.sub(r'[^-\w" .()]', "", filename) cd = "attachment; filename={}".format(filename) self.set_header("Content-Disposition", cd) self.set_header("Content-Type", "application/text") diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py index 26cdc750..83926686 100644 --- a/mitmproxy/tools/web/master.py +++ b/mitmproxy/tools/web/master.py @@ -55,7 +55,7 @@ class Options(options.Options): wsingleuser: Optional[str] = None, whtpasswd: Optional[str] = None, **kwargs - ): + ) -> None: self.wdebug = wdebug self.wport = wport self.wiface = wiface diff --git a/tox.ini b/tox.ini index e2921464..80e22389 100644 --- a/tox.ini +++ b/tox.ini @@ -23,4 +23,8 @@ commands = mitmdump --sysinfo flake8 --jobs 8 --count mitmproxy pathod examples test rstcheck README.rst - mypy --silent-imports mitmproxy/addons mitmproxy/addonmanager.py mitmproxy/proxy/protocol/ + mypy --silent-imports \ + mitmproxy/addons \ + mitmproxy/addonmanager.py \ + mitmproxy/proxy/protocol/ \ + mitmproxy/tools/dump.py mitmproxy/tools/web -- cgit v1.2.3 From de6bf175e212e4c386ebb45d5744dbf9210af690 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 16 Nov 2016 15:09:34 +0100 Subject: mitmweb: start browser on startup --- mitmproxy/tools/web/master.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py index 83926686..d2203f10 100644 --- a/mitmproxy/tools/web/master.py +++ b/mitmproxy/tools/web/master.py @@ -1,4 +1,5 @@ import sys +import webbrowser import tornado.httpserver import tornado.ioloop @@ -143,13 +144,16 @@ class WebMaster(master.Master): iol = tornado.ioloop.IOLoop.instance() http_server = tornado.httpserver.HTTPServer(self.app) - http_server.listen(self.options.wport) + http_server.listen(self.options.wport, self.options.wiface) iol.add_callback(self.start) tornado.ioloop.PeriodicCallback(lambda: self.tick(timeout=0), 5).start() try: - print("Server listening at http://{}:{}".format( - self.options.wiface, self.options.wport), file=sys.stderr) + url = "http://{}:{}/".format(self.options.wiface, self.options.wport) + print("Server listening at {}".format(url), file=sys.stderr) + if not open_browser(url): + print("No webbrowser found. Please open a browser and point it to {}".format(url)) + iol.start() except (Stop, KeyboardInterrupt): self.shutdown() @@ -157,3 +161,30 @@ class WebMaster(master.Master): # 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: + """ + Open a URL in a browser window. + In contrast to webbrowser.open, we limit the list of suitable browsers. + This gracefully degrades to a no-op on headless servers, where webbrowser.open + would otherwise open lynx. + + Returns: + True, if a browser has been opened + False, if no suitable browser has been found. + """ + browsers = ( + "windows-default", "macosx", + "google-chrome", "chrome", "chromium", "chromium-browser", + "firefox", "opera", "safari", + ) + for browser in browsers: + try: + b = webbrowser.get(browser) + except webbrowser.Error: + pass + else: + b.open(url) + return True + return False -- cgit v1.2.3 From 2ee8bc2f1a368681c533b6c5b957446d2744a7d8 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 16 Nov 2016 17:19:38 +0100 Subject: fix type checks --- mitmproxy/utils/typecheck.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mitmproxy/utils/typecheck.py b/mitmproxy/utils/typecheck.py index ce57cff1..c68357b7 100644 --- a/mitmproxy/utils/typecheck.py +++ b/mitmproxy/utils/typecheck.py @@ -10,7 +10,7 @@ def check_type(attr_name: str, value: typing.Any, typeinfo: type) -> None: - Union - Tuple - - TextIO + - IO """ # If we realize that we need to extend this list substantially, it may make sense # to use typeguard for this, but right now it's not worth the hassle for 16 lines of code. @@ -37,7 +37,7 @@ def check_type(attr_name: str, value: typing.Any, typeinfo: type) -> None: for i, (x, T) in enumerate(zip(value, typeinfo.__tuple_params__)): check_type("{}[{}]".format(attr_name, i), x, T) return - if typeinfo == typing.TextIO: + if issubclass(typeinfo, typing.IO): if hasattr(value, "read"): return -- cgit v1.2.3