diff options
-rw-r--r-- | doc-src/scripting/inlinescripts.html | 2 | ||||
-rw-r--r-- | examples/stub.py | 3 | ||||
-rw-r--r-- | libmproxy/cmdline.py | 9 | ||||
-rw-r--r-- | libmproxy/console/__init__.py | 2 | ||||
-rw-r--r-- | libmproxy/dump.py | 11 | ||||
-rw-r--r-- | libmproxy/flow.py | 53 | ||||
-rw-r--r-- | libmproxy/proxy.py | 5 | ||||
-rw-r--r-- | libmproxy/script.py | 20 | ||||
-rw-r--r-- | test/scripts/a.py | 8 | ||||
-rw-r--r-- | test/scripts/starterr.py | 2 | ||||
-rw-r--r-- | test/test_cmdline.py | 13 | ||||
-rw-r--r-- | test/test_console.py | 4 | ||||
-rw-r--r-- | test/test_console_common.py | 5 | ||||
-rw-r--r-- | test/test_console_contentview.py | 5 | ||||
-rw-r--r-- | test/test_console_help.py | 5 | ||||
-rw-r--r-- | test/test_dump.py | 7 | ||||
-rw-r--r-- | test/test_flow.py | 35 | ||||
-rw-r--r-- | test/test_proxy.py | 1 | ||||
-rw-r--r-- | test/test_script.py | 21 | ||||
-rw-r--r-- | test/test_server.py | 3 | ||||
-rw-r--r-- | test/tutils.py | 10 |
21 files changed, 148 insertions, 76 deletions
diff --git a/doc-src/scripting/inlinescripts.html b/doc-src/scripting/inlinescripts.html index 2d53df80..c9e188fc 100644 --- a/doc-src/scripting/inlinescripts.html +++ b/doc-src/scripting/inlinescripts.html @@ -25,7 +25,7 @@ The new header will be added to all responses passing through the proxy. ## Events -### start(ScriptContext) +### start(ScriptContext, argv) Called once on startup, before any other events. diff --git a/examples/stub.py b/examples/stub.py index 31411145..42b2935a 100644 --- a/examples/stub.py +++ b/examples/stub.py @@ -1,8 +1,7 @@ """ This is a script stub, with definitions for all events. """ - -def start(ctx): +def start(ctx, argv): """ Called once on script startup, before any other events. """ diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index a394d7f3..53926028 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -1,6 +1,8 @@ import proxy import re, filt import argparse +import shlex +import os APP_DOMAIN = "mitm" APP_IP = "1.1.1.1" @@ -143,7 +145,7 @@ def get_common_options(options): replacements = reps, setheaders = setheaders, server_replay = options.server_replay, - script = options.script, + scripts = options.scripts, stickycookie = stickycookie, stickyauth = stickyauth, showhost = options.showhost, @@ -201,8 +203,9 @@ def common_options(parser): ) parser.add_argument( "-s", - action="store", dest="script", default=None, - help="Run a script." + action="append", type=lambda x: shlex.split(x,posix=(os.name != "nt")), dest="scripts", default=[], + metavar='"script.py --bar"', + help="Run a script. Surround with quotes to pass script arguments. Can be passed multiple times." ) parser.add_argument( "-t", diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 37f5d646..04e240d3 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -459,7 +459,7 @@ class ConsoleMaster(flow.FlowMaster): self._run_script_method("response", s, f) if f.error: self._run_script_method("error", s, f) - s.run("done") + s.unload() self.refresh_flow(f) self.state.last_script = path diff --git a/libmproxy/dump.py b/libmproxy/dump.py index 7a49cee0..8b9c9813 100644 --- a/libmproxy/dump.py +++ b/libmproxy/dump.py @@ -24,7 +24,7 @@ class Options(object): "rheaders", "setheaders", "server_replay", - "script", + "scripts", "showhost", "stickycookie", "stickyauth", @@ -109,8 +109,9 @@ class DumpMaster(flow.FlowMaster): not options.keepserving ) - if options.script: - err = self.load_script(options.script) + scripts = options.scripts or [] + for script_argv in scripts: + err = self.load_script(script_argv) if err: raise DumpError(err) @@ -221,8 +222,8 @@ class DumpMaster(flow.FlowMaster): def run(self): # pragma: no cover if self.o.rfile and not self.o.keepserving: - if self.script: - self.load_script(None) + for script in self.scripts: + self.unload_script(script) return try: return flow.FlowMaster.run(self) diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 84836d11..da946b0b 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -1349,7 +1349,7 @@ class FlowMaster(controller.Master): self.server_playback = None self.client_playback = None self.kill_nonreplay = False - self.script = None + self.scripts = [] self.pause_scripts = False self.stickycookie_state = False @@ -1385,37 +1385,43 @@ class FlowMaster(controller.Master): """ pass - def get_script(self, path): + def get_script(self, script_argv): """ Returns an (error, script) tuple. """ - s = script.Script(path, ScriptContext(self)) + s = script.Script(script_argv, ScriptContext(self)) try: s.load() except script.ScriptError, v: return (v.args[0], None) - ret = s.run("start") - if not ret[0] and ret[1]: - return ("Error in script start:\n\n" + ret[1][1], None) return (None, s) - def load_script(self, path): + def unload_script(self,script): + script.unload() + self.scripts.remove(script) + + def load_script(self, script_argv): """ Loads a script. Returns an error description if something went - wrong. If path is None, the current script is terminated. + wrong. """ - if path is None: - self.run_script_hook("done") - self.script = None + r = self.get_script(script_argv) + if r[0]: + return r[0] else: - r = self.get_script(path) - if r[0]: - return r[0] - else: - if self.script: - self.run_script_hook("done") - self.script = r[1] + self.scripts.append(r[1]) + + def run_single_script_hook(self, script, name, *args, **kwargs): + if script and not self.pause_scripts: + ret = script.run(name, *args, **kwargs) + if not ret[0] and ret[1]: + e = "Script error:\n" + ret[1][1] + self.add_event(e, "error") + def run_script_hook(self, name, *args, **kwargs): + for script in self.scripts: + self.run_single_script_hook(script, name, *args, **kwargs) + def set_stickycookie(self, txt): if txt: flt = filt.parse(txt) @@ -1565,13 +1571,6 @@ class FlowMaster(controller.Master): if block: rt.join() - def run_script_hook(self, name, *args, **kwargs): - if self.script and not self.pause_scripts: - ret = self.script.run(name, *args, **kwargs) - if not ret[0] and ret[1]: - e = "Script error:\n" + ret[1][1] - self.add_event(e, "error") - def handle_clientconnect(self, cc): self.run_script_hook("clientconnect", cc) cc.reply() @@ -1613,8 +1612,8 @@ class FlowMaster(controller.Master): return f def shutdown(self): - if self.script: - self.load_script(None) + for script in self.scripts: + self.unload_script(script) controller.Master.shutdown(self) if self.stream: for i in self.state._flow_list: diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index b0c15463..5dcf6dc1 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -467,8 +467,9 @@ class ProxyHandler(tcp.BaseHandler): self.wfile.write("Server: %s\r\n"%self.server_version) self.wfile.write("Content-type: text/html\r\n") self.wfile.write("Content-Length: %d\r\n"%len(html_content)) - for key, value in headers.items(): - self.wfile.write("%s: %s\r\n"%(key, value)) + if headers: + for key, value in headers.items(): + self.wfile.write("%s: %s\r\n"%(key, value)) self.wfile.write("Connection: close\r\n") self.wfile.write("\r\n") self.wfile.write(html_content) diff --git a/libmproxy/script.py b/libmproxy/script.py index 95fd5be2..623f2b92 100644 --- a/libmproxy/script.py +++ b/libmproxy/script.py @@ -8,12 +8,12 @@ class Script: """ The instantiator should do something along this vein: - s = Script(path, master) + s = Script(argv, master) s.load() - s.run("start") """ - def __init__(self, path, ctx): - self.path, self.ctx = path, ctx + def __init__(self, argv, ctx): + self.argv = argv + self.ctx = ctx self.ns = None def load(self): @@ -23,17 +23,23 @@ class Script: Raises ScriptError on failure, with argument equal to an error message that may be a formatted traceback. """ - path = os.path.expanduser(self.path) + path = os.path.expanduser(self.argv[0]) if not os.path.exists(path): - raise ScriptError("No such file: %s"%self.path) + raise ScriptError("No such file: %s" % path) if not os.path.isfile(path): - raise ScriptError("Not a file: %s"%self.path) + raise ScriptError("Not a file: %s" % path) ns = {} try: execfile(path, ns, ns) except Exception, v: raise ScriptError(traceback.format_exc(v)) self.ns = ns + r = self.run("start", self.argv) + if not r[0] and r[1]: + raise ScriptError(r[1][1]) + + def unload(self): + return self.run("done") def run(self, name, *args, **kwargs): """ diff --git a/test/scripts/a.py b/test/scripts/a.py index 0a21b619..1d5717b0 100644 --- a/test/scripts/a.py +++ b/test/scripts/a.py @@ -1,5 +1,13 @@ +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--var', type=int) var = 0 +def start(ctx, argv): + global var + var = parser.parse_args(argv[1:]).var + def here(ctx): global var var += 1 diff --git a/test/scripts/starterr.py b/test/scripts/starterr.py index 456fecc0..b217bdfe 100644 --- a/test/scripts/starterr.py +++ b/test/scripts/starterr.py @@ -1,3 +1,3 @@ -def start(ctx): +def start(ctx, argv): raise ValueError diff --git a/test/test_cmdline.py b/test/test_cmdline.py index e1c5c88d..92e2adbd 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -1,6 +1,7 @@ import argparse from libmproxy import cmdline import tutils +import os.path def test_parse_replace_hook(): @@ -39,6 +40,18 @@ def test_parse_setheaders(): x = cmdline.parse_setheader("/foo/bar/voing") assert x == ("foo", "bar", "voing") +def test_shlex(): + """ + shlex.split assumes posix=True by default, we do manual detection for windows. + Test whether script paths are parsed correctly + """ + absfilepath = os.path.normcase(os.path.abspath(__file__)) + + parser = argparse.ArgumentParser() + cmdline.common_options(parser) + opts = parser.parse_args(args=["-s",absfilepath]) + + assert os.path.isfile(opts.scripts[0][0]) def test_common(): parser = argparse.ArgumentParser() diff --git a/test/test_console.py b/test/test_console.py index 498a93bd..4fd9bb9f 100644 --- a/test/test_console.py +++ b/test/test_console.py @@ -1,4 +1,8 @@ import os +from nose.plugins.skip import SkipTest +if os.name == "nt": + raise SkipTest("Skipped on Windows.") + from libmproxy import console from libmproxy.console import common import tutils diff --git a/test/test_console_common.py b/test/test_console_common.py index 29bf7b84..d798e4dc 100644 --- a/test/test_console_common.py +++ b/test/test_console_common.py @@ -1,3 +1,8 @@ +import os +from nose.plugins.skip import SkipTest +if os.name == "nt": + raise SkipTest("Skipped on Windows.") + import libmproxy.console.common as common from libmproxy import utils, flow, encoding import tutils diff --git a/test/test_console_contentview.py b/test/test_console_contentview.py index f6b45f67..ef44f834 100644 --- a/test/test_console_contentview.py +++ b/test/test_console_contentview.py @@ -1,3 +1,8 @@ +import os +from nose.plugins.skip import SkipTest +if os.name == "nt": + raise SkipTest("Skipped on Windows.") + import sys import libmproxy.console.contentview as cv from libmproxy import utils, flow, encoding diff --git a/test/test_console_help.py b/test/test_console_help.py index 98e202ad..6e1f9fad 100644 --- a/test/test_console_help.py +++ b/test/test_console_help.py @@ -1,3 +1,8 @@ +import os +from nose.plugins.skip import SkipTest +if os.name == "nt": + raise SkipTest("Skipped on Windows.") + import libmproxy.console.help as help class DummyMaster: diff --git a/test/test_dump.py b/test/test_dump.py index b195d5f0..255651a9 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -43,6 +43,7 @@ class TestDumpMaster: m = dump.DumpMaster(None, o, filt, outfile=cs) for i in range(n): self._cycle(m, content) + m.shutdown() return cs.getvalue() def _flowfile(self, path): @@ -149,7 +150,7 @@ class TestDumpMaster: def test_script(self): ret = self._dummy_cycle( 1, None, "", - script=tutils.test_data.path("scripts/all.py"), verbosity=0, eventlog=True + scripts=[[tutils.test_data.path("scripts/all.py")]], verbosity=0, eventlog=True ) assert "XCLIENTCONNECT" in ret assert "XREQUEST" in ret @@ -157,11 +158,11 @@ class TestDumpMaster: assert "XCLIENTDISCONNECT" in ret tutils.raises( dump.DumpError, - self._dummy_cycle, 1, None, "", script="nonexistent" + self._dummy_cycle, 1, None, "", scripts=[["nonexistent"]] ) tutils.raises( dump.DumpError, - self._dummy_cycle, 1, None, "", script="starterr.py" + self._dummy_cycle, 1, None, "", scripts=[["starterr.py"]] ) def test_stickycookie(self): diff --git a/test/test_flow.py b/test/test_flow.py index 32cfb0ca..9844e0fd 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -542,11 +542,13 @@ class TestFlowMaster: def test_load_script(self): s = flow.State() fm = flow.FlowMaster(None, s) - assert not fm.load_script(tutils.test_data.path("scripts/a.py")) - assert not fm.load_script(tutils.test_data.path("scripts/a.py")) - assert not fm.load_script(None) - assert fm.load_script("nonexistent") - assert "ValueError" in fm.load_script(tutils.test_data.path("scripts/starterr.py")) + assert not fm.load_script([tutils.test_data.path("scripts/a.py")]) + assert not fm.load_script([tutils.test_data.path("scripts/a.py")]) + assert not fm.unload_script(fm.scripts[0]) + assert not fm.unload_script(fm.scripts[0]) + assert fm.load_script(["nonexistent"]) + assert "ValueError" in fm.load_script([tutils.test_data.path("scripts/starterr.py")]) + assert len(fm.scripts) == 0 def test_replay(self): s = flow.State() @@ -561,7 +563,7 @@ class TestFlowMaster: def test_script_reqerr(self): s = flow.State() fm = flow.FlowMaster(None, s) - assert not fm.load_script(tutils.test_data.path("scripts/reqerr.py")) + assert not fm.load_script([tutils.test_data.path("scripts/reqerr.py")]) req = tutils.treq() fm.handle_clientconnect(req.client_conn) assert fm.handle_request(req) @@ -569,23 +571,30 @@ class TestFlowMaster: def test_script(self): s = flow.State() fm = flow.FlowMaster(None, s) - assert not fm.load_script(tutils.test_data.path("scripts/all.py")) + assert not fm.load_script([tutils.test_data.path("scripts/all.py")]) req = tutils.treq() fm.handle_clientconnect(req.client_conn) - assert fm.script.ns["log"][-1] == "clientconnect" + assert fm.scripts[0].ns["log"][-1] == "clientconnect" f = fm.handle_request(req) - assert fm.script.ns["log"][-1] == "request" + assert fm.scripts[0].ns["log"][-1] == "request" resp = tutils.tresp(req) fm.handle_response(resp) - assert fm.script.ns["log"][-1] == "response" + assert fm.scripts[0].ns["log"][-1] == "response" + #load second script + assert not fm.load_script([tutils.test_data.path("scripts/all.py")]) + assert len(fm.scripts) == 2 dc = flow.ClientDisconnect(req.client_conn) dc.reply = controller.DummyReply() fm.handle_clientdisconnect(dc) - assert fm.script.ns["log"][-1] == "clientdisconnect" + assert fm.scripts[0].ns["log"][-1] == "clientdisconnect" + assert fm.scripts[1].ns["log"][-1] == "clientdisconnect" + #unload first script + fm.unload_script(fm.scripts[0]) + assert len(fm.scripts) == 1 err = flow.Error(f.request, "msg") err.reply = controller.DummyReply() fm.handle_error(err) - assert fm.script.ns["log"][-1] == "error" + assert fm.scripts[0].ns["log"][-1] == "error" def test_duplicate_flow(self): s = flow.State() @@ -625,7 +634,7 @@ class TestFlowMaster: err.reply = controller.DummyReply() fm.handle_error(err) - fm.load_script(tutils.test_data.path("scripts/a.py")) + fm.load_script([tutils.test_data.path("scripts/a.py")]) fm.shutdown() def test_client_playback(self): diff --git a/test/test_proxy.py b/test/test_proxy.py index 85be82cb..dfccaaf7 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -130,6 +130,7 @@ class TestProcessProxyOptions: class TestProxyServer: + @tutils.SkipWindows # binding to 0.0.0.0:1 works without special permissions on Windows def test_err(self): parser = argparse.ArgumentParser() cmdline.common_options(parser) diff --git a/test/test_script.py b/test/test_script.py index 407c9b4b..9033c4fc 100644 --- a/test/test_script.py +++ b/test/test_script.py @@ -1,5 +1,7 @@ from libmproxy import script, flow import tutils +import shlex +import os class TestScript: def test_simple(self): @@ -7,11 +9,12 @@ class TestScript: fm = flow.FlowMaster(None, s) ctx = flow.ScriptContext(fm) - p = script.Script(tutils.test_data.path("scripts/a.py"), ctx) + p = script.Script(shlex.split(tutils.test_data.path("scripts/a.py")+" --var 40", posix=(os.name != "nt")), ctx) p.load() + assert "here" in p.ns - assert p.run("here") == (True, 1) - assert p.run("here") == (True, 2) + assert p.run("here") == (True, 41) + assert p.run("here") == (True, 42) ret = p.run("errargs") assert not ret[0] @@ -19,12 +22,12 @@ class TestScript: # Check reload p.load() - assert p.run("here") == (True, 1) + assert p.run("here") == (True, 41) def test_duplicate_flow(self): s = flow.State() fm = flow.FlowMaster(None, s) - fm.load_script(tutils.test_data.path("scripts/duplicate_flow.py")) + fm.load_script([tutils.test_data.path("scripts/duplicate_flow.py")]) r = tutils.treq() fm.handle_request(r) assert fm.state.flow_count() == 2 @@ -37,25 +40,25 @@ class TestScript: ctx = flow.ScriptContext(fm) - s = script.Script("nonexistent", ctx) + s = script.Script(["nonexistent"], ctx) tutils.raises( "no such file", s.load ) - s = script.Script(tutils.test_data.path("scripts"), ctx) + s = script.Script([tutils.test_data.path("scripts")], ctx) tutils.raises( "not a file", s.load ) - s = script.Script(tutils.test_data.path("scripts/syntaxerr.py"), ctx) + s = script.Script([tutils.test_data.path("scripts/syntaxerr.py")], ctx) tutils.raises( script.ScriptError, s.load ) - s = script.Script(tutils.test_data.path("scripts/loaderr.py"), ctx) + s = script.Script([tutils.test_data.path("scripts/loaderr.py")], ctx) tutils.raises( script.ScriptError, s.load diff --git a/test/test_server.py b/test/test_server.py index 079ed8ce..85c20737 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -256,13 +256,14 @@ class TestProxy(tservers.HTTPProxTest): # call pathod server, wait a second to complete the request connection.send("GET http://localhost:%d/p/304:b@1k HTTP/1.1\r\n"%self.server.port) + time.sleep(1) connection.send("\r\n"); connection.recv(50000) connection.close() request, response = self.master.state.view[0].request, self.master.state.view[0].response assert response.code == 304 # sanity test for our low level request - assert request.timestamp_end - request.timestamp_start > 0 + assert 0.95 < (request.timestamp_end - request.timestamp_start) < 1.2 #time.sleep might be a little bit shorter than a second def test_request_timestamps_not_affected_by_client_time(self): # test that don't include user wait time in request's timestamps diff --git a/test/tutils.py b/test/tutils.py index fbce615a..e42256ed 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -2,7 +2,15 @@ import os, shutil, tempfile from contextlib import contextmanager from libmproxy import flow, utils, controller from netlib import certutils -import mock +from nose.plugins.skip import SkipTest + +def _SkipWindows(): + raise SkipTest("Skipped on Windows.") +def SkipWindows(fn): + if os.name == "nt": + return _SkipWindows + else: + return fn def treq(conn=None): if not conn: |