First pass of script hooks for mitmdump.
Also stub out docs, improve mitmdump error handling.
This commit is contained in:
parent
7769e5a898
commit
cd4eea3934
|
@ -2,7 +2,7 @@ body {
|
|||
-x-system-font:none;
|
||||
font-family: Helvetica,Arial,Tahoma,Verdana,Sans-Serif;
|
||||
color: #555555;
|
||||
font-size: 1.3em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import countershape
|
||||
from countershape import Page, Directory, PythonModule, markup
|
||||
import countershape.grok
|
||||
|
||||
import countershape.grok, countershape.template
|
||||
|
||||
this.layout = countershape.Layout("_layout.html")
|
||||
ns.docTitle = "mitmproxy"
|
||||
|
@ -16,12 +16,25 @@ ns.sidebar = countershape.widgets.SiblingPageIndex(
|
|||
)
|
||||
|
||||
ns.license = file("../LICENSE").read()
|
||||
ns.index_contents = file("../README").read()
|
||||
ns.example = file("../examples/stickycookies.py").read()
|
||||
ns.index_contents = file("../README.mkd").read()
|
||||
|
||||
|
||||
top = os.path.abspath(os.getcwd())
|
||||
def example(s):
|
||||
d = file(os.path.join(top, s)).read()
|
||||
return countershape.template.pySyntax(d)
|
||||
|
||||
|
||||
ns.example = example
|
||||
|
||||
|
||||
|
||||
pages = [
|
||||
Page("index.html", "introduction"),
|
||||
Page("library.html", "library"),
|
||||
Page("mitmproxy.html", "mitmproxy"),
|
||||
Page("mitmdump.html", "mitmdump"),
|
||||
Page("scripts.html", "scripts"),
|
||||
Page("library.html", "libmproxy"),
|
||||
Page("faq.html", "faq"),
|
||||
Page("admin.html", "administrivia")
|
||||
]
|
||||
|
|
|
@ -8,8 +8,5 @@ this lets you log in to a site using your browser, and then make subsequent
|
|||
requests using a tool like __curl__, which will then seem to be part of the
|
||||
authenticated session.
|
||||
|
||||
|
||||
<!--(block |pySyntax)-->
|
||||
$!example!$
|
||||
<!--(end)-->
|
||||
$!example("../examples/stickycookies.py")!$
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
Both __mitmproxy__ and __mitmdump__ allow you to modify requests and responses
|
||||
with external scripts. The script interface is simple - scripts simply read,
|
||||
modify and return a single __libmproxy.flow.Flow__ object, using the methods
|
||||
defined in the __libmproxy.script__ module. Scripts must be executable.
|
||||
|
||||
$!example("../examples/simple_script")!$
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
from libmproxy import script
|
||||
|
||||
f = script.load_flow()
|
||||
f.request.headers["newheader"] = ["foo"]
|
||||
script.return_flow(f)
|
|
@ -549,7 +549,7 @@ class ConnectionView(WWrap):
|
|||
path = os.path.expanduser(path)
|
||||
self.state.last_script = path
|
||||
try:
|
||||
newflow, serr = self.flow.run_script(path)
|
||||
serr = self.flow.run_script(path)
|
||||
except flow.RunException, e:
|
||||
if e.errout:
|
||||
serr = "Script error code: %s\n\n"%e.returncode + e.errout
|
||||
|
@ -559,7 +559,6 @@ class ConnectionView(WWrap):
|
|||
if serr:
|
||||
serr = "Script output:\n\n" + serr
|
||||
self.master.spawn_external_viewer(serr, None)
|
||||
self.flow.load_state(newflow.get_state())
|
||||
self.master.refresh_connection(self.flow)
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ class Options(object):
|
|||
__slots__ = [
|
||||
"verbosity",
|
||||
"wfile",
|
||||
"request_script",
|
||||
"response_script",
|
||||
]
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
|
@ -44,8 +46,24 @@ class DumpMaster(flow.FlowMaster):
|
|||
flow.FlowMaster.handle_error(self, r)
|
||||
r.ack()
|
||||
|
||||
def _runscript(self, f, script):
|
||||
try:
|
||||
ret = f.run_script(script)
|
||||
if self.o.verbosity > 0:
|
||||
print >> self.outfile, ret
|
||||
except flow.RunException, e:
|
||||
if e.errout:
|
||||
eout = "Script output:\n" + self.indent(4, e.errout) + "\n"
|
||||
else:
|
||||
eout = ""
|
||||
raise DumpError(
|
||||
"%s: %s\n%s"%(script, e.args[0], eout)
|
||||
)
|
||||
|
||||
def handle_request(self, r):
|
||||
flow.FlowMaster.handle_request(self, r)
|
||||
f = flow.FlowMaster.handle_request(self, r)
|
||||
if self.o.request_script:
|
||||
self._runscript(f, self.o.request_script)
|
||||
r.ack()
|
||||
|
||||
def indent(self, n, t):
|
||||
|
@ -55,6 +73,8 @@ class DumpMaster(flow.FlowMaster):
|
|||
def handle_response(self, msg):
|
||||
f = flow.FlowMaster.handle_response(self, msg)
|
||||
if f:
|
||||
if self.o.response_script:
|
||||
self._runscript(f, self.o.response_script)
|
||||
msg.ack()
|
||||
if self.filt and not f.match(self.filt):
|
||||
return
|
||||
|
@ -96,8 +116,6 @@ class DumpMaster(flow.FlowMaster):
|
|||
def run(self):
|
||||
try:
|
||||
return flow.FlowMaster.run(self)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception, v:
|
||||
traceback.print_exc()
|
||||
self.shutdown()
|
||||
except BaseException, v:
|
||||
self.shutdown()
|
||||
raise
|
||||
|
|
|
@ -82,7 +82,8 @@ class Flow:
|
|||
p.returncode,
|
||||
se
|
||||
)
|
||||
return f, se
|
||||
self.load_state(f.get_state())
|
||||
return se
|
||||
|
||||
def get_state(self, nobackup=False):
|
||||
d = dict(
|
||||
|
|
24
mitmdump
24
mitmdump
|
@ -41,8 +41,16 @@ if __name__ == '__main__':
|
|||
parser.add_option("-w", "--writefile",
|
||||
action="store", dest="wfile", default=None,
|
||||
help="Write flows to file.")
|
||||
parser.add_option("", "--reqscript",
|
||||
action="store", dest="request_script", default=None,
|
||||
help="Script to run when a request is recieved.")
|
||||
parser.add_option("", "--respscript",
|
||||
action="store", dest="response_script", default=None,
|
||||
help="Script to run when a response is recieved.")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
|
||||
if options.quiet:
|
||||
options.verbose = 0
|
||||
|
||||
|
@ -58,15 +66,23 @@ if __name__ == '__main__':
|
|||
|
||||
dumpopts = dump.Options(
|
||||
verbosity = options.verbose,
|
||||
wfile = options.wfile
|
||||
wfile = options.wfile,
|
||||
request_script = options.request_script,
|
||||
response_script = options.response_script,
|
||||
)
|
||||
if args:
|
||||
filt = " ".join(args)
|
||||
else:
|
||||
filt = None
|
||||
|
||||
m = dump.DumpMaster(server, dumpopts, filt)
|
||||
|
||||
if options.verbose > 0:
|
||||
print >> sys.stderr, "Running on port %s"%options.port
|
||||
m.run()
|
||||
|
||||
try:
|
||||
m = dump.DumpMaster(server, dumpopts, filt)
|
||||
m.run()
|
||||
except dump.DumpError, e:
|
||||
print >> sys.stderr, "mitmdump:", e
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
|
|
@ -6,7 +6,7 @@ import utils
|
|||
|
||||
|
||||
class uDumpMaster(libpry.AutoTree):
|
||||
def _dummy_cycle(self, m):
|
||||
def _cycle(self, m):
|
||||
req = utils.treq()
|
||||
cc = req.client_conn
|
||||
resp = utils.tresp(req)
|
||||
|
@ -14,50 +14,64 @@ class uDumpMaster(libpry.AutoTree):
|
|||
m.handle_request(req)
|
||||
m.handle_response(resp)
|
||||
|
||||
def _dummy_cycle(self, filt, **options):
|
||||
cs = StringIO()
|
||||
o = dump.Options(**options)
|
||||
m = dump.DumpMaster(None, o, filt, outfile=cs)
|
||||
self._cycle(m)
|
||||
return cs.getvalue()
|
||||
|
||||
def test_options(self):
|
||||
o = dump.Options(verbosity = 2)
|
||||
assert o.verbosity == 2
|
||||
libpry.raises(AttributeError, dump.Options, nonexistent = 2)
|
||||
|
||||
def test_filter(self):
|
||||
cs = StringIO()
|
||||
o = dump.Options(
|
||||
verbosity = 1
|
||||
)
|
||||
m = dump.DumpMaster(None, o, "~u foo", outfile=cs)
|
||||
self._dummy_cycle(m)
|
||||
assert not "GET" in cs.getvalue()
|
||||
assert not "GET" in self._dummy_cycle("~u foo", verbosity=1)
|
||||
|
||||
def test_basic(self):
|
||||
for i in (1, 2, 3):
|
||||
cs = StringIO()
|
||||
o = dump.Options(
|
||||
verbosity = i
|
||||
)
|
||||
m = dump.DumpMaster(None, o, "~s", outfile=cs)
|
||||
self._dummy_cycle(m)
|
||||
assert "GET" in cs.getvalue()
|
||||
assert "GET" in self._dummy_cycle("~s", verbosity=i)
|
||||
|
||||
def test_write(self):
|
||||
d = self.tmpdir()
|
||||
p = os.path.join(d, "a")
|
||||
o = dump.Options(
|
||||
wfile = p,
|
||||
verbosity = 0
|
||||
)
|
||||
cs = StringIO()
|
||||
m = dump.DumpMaster(None, o, None, outfile=cs)
|
||||
self._dummy_cycle(m)
|
||||
del m
|
||||
self._dummy_cycle(None, wfile=p, verbosity=0)
|
||||
assert len(list(flow.FlowReader(open(p)).stream())) == 1
|
||||
|
||||
def test_write_err(self):
|
||||
o = dump.Options(
|
||||
wfile = "nonexistentdir/foo",
|
||||
verbosity = 0
|
||||
libpry.raises(
|
||||
dump.DumpError,
|
||||
self._dummy_cycle,
|
||||
None,
|
||||
wfile = "nonexistentdir/foo"
|
||||
)
|
||||
|
||||
def test_request_script(self):
|
||||
ret = self._dummy_cycle(None, request_script="scripts/a", verbosity=1)
|
||||
assert "TESTOK" in ret
|
||||
assert "DEBUG" in ret
|
||||
libpry.raises(
|
||||
dump.DumpError,
|
||||
self._dummy_cycle, None, request_script="nonexistent"
|
||||
)
|
||||
libpry.raises(
|
||||
dump.DumpError,
|
||||
self._dummy_cycle, None, request_script="scripts/err_data"
|
||||
)
|
||||
|
||||
def test_response_script(self):
|
||||
ret = self._dummy_cycle(None, response_script="scripts/a", verbosity=1)
|
||||
assert "TESTOK" in ret
|
||||
assert "DEBUG" in ret
|
||||
libpry.raises(
|
||||
dump.DumpError,
|
||||
self._dummy_cycle, None, response_script="nonexistent"
|
||||
)
|
||||
libpry.raises(
|
||||
dump.DumpError,
|
||||
self._dummy_cycle, None, response_script="scripts/err_data"
|
||||
)
|
||||
cs = StringIO()
|
||||
libpry.raises(dump.DumpError, dump.DumpMaster, None, o, None)
|
||||
|
||||
|
||||
|
||||
|
@ -67,5 +81,3 @@ class uDumpMaster(libpry.AutoTree):
|
|||
tests = [
|
||||
uDumpMaster()
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class uFlow(libpry.AutoTree):
|
|||
f = utils.tflow()
|
||||
f.response = utils.tresp()
|
||||
f.request = f.response.request
|
||||
f, se = f.run_script("scripts/a")
|
||||
se = f.run_script("scripts/a")
|
||||
assert "DEBUG" == se.strip()
|
||||
assert f.request.host == "TESTOK"
|
||||
|
||||
|
|
Loading…
Reference in New Issue