Merge pull request #1729 from mhils/mitmweb-fixes

minor fixes
This commit is contained in:
Maximilian Hils 2016-11-16 22:26:17 +01:00 committed by GitHub
commit 83fe8b5302
9 changed files with 76 additions and 36 deletions

2
dev.sh
View File

@ -7,7 +7,7 @@ VENV="venv$PYVERSION"
echo "Creating dev environment in $VENV using Python $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" . "$VENV/bin/activate"
pip$PYVERSION install -U pip setuptools pip$PYVERSION install -U pip setuptools
pip$PYVERSION install -r requirements.txt pip$PYVERSION install -r requirements.txt

View File

@ -83,7 +83,7 @@ class Options(optmanager.OptManager):
ssl_verify_upstream_trusted_cadir: Optional[str] = None, ssl_verify_upstream_trusted_cadir: Optional[str] = None,
ssl_verify_upstream_trusted_ca: Optional[str] = None, ssl_verify_upstream_trusted_ca: Optional[str] = None,
tcp_hosts: Sequence[str] = () tcp_hosts: Sequence[str] = ()
): ) -> None:
# We could replace all assignments with clever metaprogramming, # We could replace all assignments with clever metaprogramming,
# but type hints are a much more valueable asset. # but type hints are a much more valueable asset.

View File

@ -0,0 +1,5 @@
from mitmproxy.tools import web
from mitmproxy.tools import console
from mitmproxy.tools import dump
__all__ = ["web", "console", "dump"]

View File

@ -26,8 +26,7 @@ class Column(col_bytes.Column):
# This is the same for both edit and display. # This is the same for both edit and display.
class EncodingMixin: class EncodingMixin:
def __init__(self, data, encoding_args): def __init__(self, data: str, encoding_args) -> "TDisplay":
# type: (str) -> TDisplay
self.encoding_args = encoding_args self.encoding_args = encoding_args
data = data.encode(*self.encoding_args) data = data.encode(*self.encoding_args)
super().__init__(data) super().__init__(data)

View File

@ -1,5 +1,4 @@
import typing from typing import Optional, IO
from typing import Optional
from mitmproxy import controller from mitmproxy import controller
from mitmproxy import exceptions from mitmproxy import exceptions
@ -22,9 +21,9 @@ class Options(options.Options):
keepserving: bool = False, keepserving: bool = False,
filtstr: Optional[str] = None, filtstr: Optional[str] = None,
flow_detail: int = 1, flow_detail: int = 1,
tfile: Optional[typing.io.TextIO] = None, tfile: Optional[IO[str]] = None,
**kwargs **kwargs
): ) -> None:
self.filtstr = filtstr self.filtstr = filtstr
self.flow_detail = flow_detail self.flow_detail = flow_detail
self.keepserving = keepserving self.keepserving = keepserving

View File

@ -1,3 +1,4 @@
import base64 import base64
import hashlib import hashlib
import json import json
@ -10,14 +11,15 @@ import tornado.web
import tornado.websocket import tornado.websocket
import tornado.escape import tornado.escape
from mitmproxy import contentviews from mitmproxy import contentviews
from mitmproxy import flow
from mitmproxy import flowfilter from mitmproxy import flowfilter
from mitmproxy import http from mitmproxy import http
from mitmproxy import io from mitmproxy import io
from mitmproxy import version 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. 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 '): if auth_header is None or not auth_header.startswith('Basic '):
self.set_auth_headers() self.set_auth_headers()
else: else:
self.auth_decoded = base64.decodestring(auth_header[6:]) auth_decoded = base64.decodebytes(auth_header[6:])
self.username, self.password = self.auth_decoded.split(':', 2) username, password = auth_decoded.split(':', 2)
if not wauthenticator.test(self.username, self.password): if not wauthenticator.test(username, password):
self.set_auth_headers() self.set_auth_headers()
raise APIError(401, "Invalid username or password.") raise APIError(401, "Invalid username or password.")
@ -123,7 +125,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler):
return json.loads(self.request.body.decode()) return json.loads(self.request.body.decode())
@property @property
def view(self): def view(self) -> mitmproxy.addons.view.View:
return self.application.master.view return self.application.master.view
@property @property
@ -131,7 +133,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler):
return self.application.master return self.application.master
@property @property
def flow(self): def flow(self) -> mitmproxy.flow.Flow:
flow_id = str(self.path_kwargs["flow_id"]) flow_id = str(self.path_kwargs["flow_id"])
# FIXME: Add a facility to addon.view to safely access the store # FIXME: Add a facility to addon.view to safely access the store
flow = self.view._store.get(flow_id) flow = self.view._store.get(flow_id)
@ -140,7 +142,7 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler):
else: else:
raise APIError(400, "Flow not found.") 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): if "exc_info" in kwargs and isinstance(kwargs["exc_info"][1], APIError):
self.finish(kwargs["exc_info"][1].log_message) self.finish(kwargs["exc_info"][1].log_message)
else: else:
@ -165,7 +167,7 @@ class FilterHelp(RequestHandler):
class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler): class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler):
# raise an error if inherited class doesn't specify its own instance. # raise an error if inherited class doesn't specify its own instance.
connections = None connections = None # type: set
def open(self): def open(self):
self.connections.add(self) self.connections.add(self)
@ -180,12 +182,12 @@ class WebSocketEventBroadcaster(BasicAuth, tornado.websocket.WebSocketHandler):
for conn in cls.connections: for conn in cls.connections:
try: try:
conn.write_message(message) conn.write_message(message)
except: except Exception:
logging.error("Error sending message", exc_info=True) logging.error("Error sending message", exc_info=True)
class ClientConnection(WebSocketEventBroadcaster): class ClientConnection(WebSocketEventBroadcaster):
connections = set() connections = set() # type: set
class Flows(RequestHandler): class Flows(RequestHandler):
@ -212,7 +214,7 @@ class DumpFlows(RequestHandler):
content = self.request.files.values()[0][0].body content = self.request.files.values()[0][0].body
bio = BytesIO(content) bio = BytesIO(content)
self.view.load_flows(io.FlowReader(bio).stream()) self.master.load_flows(io.FlowReader(bio).stream())
bio.close() bio.close()
@ -225,7 +227,7 @@ class ClearAll(RequestHandler):
class AcceptFlows(RequestHandler): class AcceptFlows(RequestHandler):
def post(self): def post(self):
self.view.flows.accept_all(self.master) self.master.accept_all(self.master)
class AcceptFlow(RequestHandler): class AcceptFlow(RequestHandler):
@ -239,13 +241,13 @@ class FlowHandler(RequestHandler):
def delete(self, flow_id): def delete(self, flow_id):
if self.flow.killable: if self.flow.killable:
self.flow.kill(self.master) self.flow.kill(self.master)
self.view.delete_flow(self.flow) self.view.remove(self.flow)
def put(self, flow_id): def put(self, flow_id):
flow = self.flow flow = self.flow
flow.backup() flow.backup()
for a, b in self.json.items(): for a, b in self.json.items():
if a == "request": if a == "request" and hasattr(flow, "request"):
request = flow.request request = flow.request
for k, v in b.items(): for k, v in b.items():
if k in ["method", "scheme", "host", "path", "http_version"]: if k in ["method", "scheme", "host", "path", "http_version"]:
@ -261,7 +263,7 @@ class FlowHandler(RequestHandler):
else: else:
print("Warning: Unknown update {}.{}: {}".format(a, k, v)) print("Warning: Unknown update {}.{}: {}".format(a, k, v))
elif a == "response": elif a == "response" and hasattr(flow, "response"):
response = flow.response response = flow.response
for k, v in b.items(): for k, v in b.items():
if k == "msg": if k == "msg":
@ -280,7 +282,7 @@ class FlowHandler(RequestHandler):
print("Warning: Unknown update {}.{}: {}".format(a, k, v)) print("Warning: Unknown update {}.{}: {}".format(a, k, v))
else: else:
print("Warning: Unknown update {}: {}".format(a, b)) print("Warning: Unknown update {}: {}".format(a, b))
self.view.update_flow(flow) self.view.update(flow)
class DuplicateFlow(RequestHandler): class DuplicateFlow(RequestHandler):
@ -300,7 +302,7 @@ class ReplayFlow(RequestHandler):
def post(self, flow_id): def post(self, flow_id):
self.flow.backup() self.flow.backup()
self.flow.response = None self.flow.response = None
self.view.update_flow(self.flow) self.view.update(self.flow)
r = self.master.replay_request(self.flow) r = self.master.replay_request(self.flow)
if r: if r:
@ -313,7 +315,7 @@ class FlowContent(RequestHandler):
self.flow.backup() self.flow.backup()
message = getattr(self.flow, message) message = getattr(self.flow, message)
message.content = self.request.files.values()[0][0].body 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): def get(self, flow_id, message):
message = getattr(self.flow, message) message = getattr(self.flow, message)
@ -329,13 +331,13 @@ class FlowContent(RequestHandler):
original_cd = message.headers.get("Content-Disposition", None) original_cd = message.headers.get("Content-Disposition", None)
filename = None filename = None
if original_cd: if original_cd:
filename = re.search("filename=([\w\" \.\-\(\)]+)", original_cd) filename = re.search('filename=([-\w" .()]+)', original_cd)
if filename: if filename:
filename = filename.group(1) filename = filename.group(1)
if not filename: if not filename:
filename = self.flow.request.path.split("?")[0].split("/")[-1] 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) cd = "attachment; filename={}".format(filename)
self.set_header("Content-Disposition", cd) self.set_header("Content-Disposition", cd)
self.set_header("Content-Type", "application/text") self.set_header("Content-Type", "application/text")

View File

@ -1,4 +1,5 @@
import sys import sys
import webbrowser
import tornado.httpserver import tornado.httpserver
import tornado.ioloop import tornado.ioloop
@ -55,7 +56,7 @@ class Options(options.Options):
wsingleuser: Optional[str] = None, wsingleuser: Optional[str] = None,
whtpasswd: Optional[str] = None, whtpasswd: Optional[str] = None,
**kwargs **kwargs
): ) -> None:
self.wdebug = wdebug self.wdebug = wdebug
self.wport = wport self.wport = wport
self.wiface = wiface self.wiface = wiface
@ -143,13 +144,16 @@ class WebMaster(master.Master):
iol = tornado.ioloop.IOLoop.instance() iol = tornado.ioloop.IOLoop.instance()
http_server = tornado.httpserver.HTTPServer(self.app) 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) iol.add_callback(self.start)
tornado.ioloop.PeriodicCallback(lambda: self.tick(timeout=0), 5).start() tornado.ioloop.PeriodicCallback(lambda: self.tick(timeout=0), 5).start()
try: try:
print("Server listening at http://{}:{}".format( url = "http://{}:{}/".format(self.options.wiface, self.options.wport)
self.options.wiface, self.options.wport), file=sys.stderr) 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() iol.start()
except (Stop, KeyboardInterrupt): except (Stop, KeyboardInterrupt):
self.shutdown() self.shutdown()
@ -157,3 +161,30 @@ class WebMaster(master.Master):
# def add_log(self, e, level="info"): # def add_log(self, e, level="info"):
# super().add_log(e, level) # super().add_log(e, level)
# return self.state.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

View File

@ -10,7 +10,7 @@ def check_type(attr_name: str, value: typing.Any, typeinfo: type) -> None:
- Union - Union
- Tuple - Tuple
- TextIO - IO
""" """
# If we realize that we need to extend this list substantially, it may make sense # 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. # 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__)): for i, (x, T) in enumerate(zip(value, typeinfo.__tuple_params__)):
check_type("{}[{}]".format(attr_name, i), x, T) check_type("{}[{}]".format(attr_name, i), x, T)
return return
if typeinfo == typing.TextIO: if issubclass(typeinfo, typing.IO):
if hasattr(value, "read"): if hasattr(value, "read"):
return return

View File

@ -23,4 +23,8 @@ commands =
mitmdump --sysinfo mitmdump --sysinfo
flake8 --jobs 8 --count mitmproxy pathod examples test flake8 --jobs 8 --count mitmproxy pathod examples test
rstcheck README.rst 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