aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libmproxy/__init__.py1
-rw-r--r--libmproxy/console.py199
-rw-r--r--libmproxy/flow.py207
-rwxr-xr-xmitmdump5
-rwxr-xr-xmitmproxy5
-rw-r--r--setup.py4
-rw-r--r--test/test_console.py39
7 files changed, 257 insertions, 203 deletions
diff --git a/libmproxy/__init__.py b/libmproxy/__init__.py
index e69de29b..5422a234 100644
--- a/libmproxy/__init__.py
+++ b/libmproxy/__init__.py
@@ -0,0 +1 @@
+VERSION = "0.2"
diff --git a/libmproxy/console.py b/libmproxy/console.py
index 7c12cc0c..ff5383e0 100644
--- a/libmproxy/console.py
+++ b/libmproxy/console.py
@@ -13,12 +13,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import Queue, mailcap, mimetypes, tempfile, os, subprocess, threading
+import Queue, mailcap, mimetypes, tempfile, os, subprocess
import os.path, sys
import cStringIO
import urwid.curses_display
import urwid
-import controller, utils, filt, proxy
+import controller, utils, filt, proxy, flow
class Stop(Exception): pass
@@ -63,21 +63,6 @@ class WWrap(urwid.WidgetWrap):
w = property(get_w, set_w)
-class ReplayThread(threading.Thread):
- def __init__(self, flow, masterq):
- self.flow, self.masterq = flow, masterq
- threading.Thread.__init__(self)
-
- def run(self):
- try:
- server = proxy.ServerConnection(self.flow.request)
- response = server.read_response()
- response.send(self.masterq)
- except proxy.ProxyError, v:
- err = proxy.Error(self.flow.connection, v.msg)
- err.send(self.masterq)
-
-
class ConnectionItem(WWrap):
def __init__(self, master, state, flow):
self.master, self.state, self.flow = master, state, flow
@@ -478,44 +463,10 @@ class StatusBar(WWrap):
#end nocover
-class ReplayConnection:
- pass
-
-
-class Flow:
+class ConsoleFlow(flow.Flow):
def __init__(self, connection):
- self.connection = connection
- self.request, self.response, self.error = None, None, None
- self.waiting = True
+ flow.Flow.__init__(self, connection)
self.focus = False
- self.intercepting = False
- self._backup = None
-
- def backup(self):
- if not self._backup:
- self._backup = [
- self.connection.copy() if self.connection else None,
- self.request.copy() if self.request else None,
- self.response.copy() if self.response else None,
- self.error.copy() if self.error else None,
- ]
-
- def revert(self):
- if self._backup:
- self.waiting = False
- restore = [i.copy() if i else None for i in self._backup]
- self.connection, self.request, self.response, self.error = restore
-
- def match(self, pattern):
- if pattern:
- if self.response:
- return pattern(self.response)
- elif self.request:
- return pattern(self.request)
- return False
-
- def is_replay(self):
- return isinstance(self.connection, ReplayConnection)
def get_text(self, nofocus=False, padding=3):
if not self.request and not self.response:
@@ -564,74 +515,35 @@ class Flow:
txt.insert(0, " "*padding)
return txt
- def kill(self):
- if self.intercepting:
- if not self.request.acked:
- self.request.kill = True
- self.request.ack()
- elif self.response and not self.response.acked:
- self.response.kill = True
- self.response.ack()
- self.intercepting = False
- def intercept(self):
- self.intercepting = True
-
- def accept_intercept(self):
- if self.request:
- if not self.request.acked:
- self.request.ack()
- elif self.response and not self.response.acked:
- self.response.ack()
- self.intercepting = False
-
-
-class State:
+class ConsoleState(flow.State):
def __init__(self):
- self.flow_map = {}
- self.flow_list = []
- self.focus = None
- # These are compiled filt expressions:
- self.limit = None
- self.intercept = None
+ flow.State.__init__(self)
+ self.focus = False
self.beep = None
def add_browserconnect(self, f):
- self.flow_list.insert(0, f)
- self.flow_map[f.connection] = f
+ flow.State.add_browserconnect(self, f)
if self.focus is None:
self.set_focus(0)
else:
self.set_focus(self.focus + 1)
def add_request(self, req):
- f = self.flow_map.get(req.connection)
- if not f:
- return False
- f.request = req
if self.focus is None:
self.set_focus(0)
- return f
+ return flow.State.add_request(self, req)
def add_response(self, resp):
- f = self.flow_map.get(resp.request.connection)
- if not f:
- return False
- f.response = resp
- f.waiting = False
- f.backup()
+ f = flow.State.add_response(self, resp)
if self.focus is None:
self.set_focus(0)
return f
- def add_error(self, err):
- f = self.flow_map.get(err.connection)
- if not f:
- return False
- f.error = err
- f.waiting = False
- f.backup()
- return f
+ def set_limit(self, limit):
+ ret = flow.State.set_limit(self, limit)
+ self.set_focus(self.focus)
+ return ret
@property
def view(self):
@@ -640,30 +552,6 @@ class State:
else:
return self.flow_list[:]
- def set_limit(self, limit):
- """
- Limit is a compiled filter expression, or None.
- """
- self.limit = limit
- self.set_focus(self.focus)
-
- def get_connection(self, itm):
- if isinstance(itm, (proxy.BrowserConnection, ReplayConnection)):
- return itm
- elif hasattr(itm, "connection"):
- return itm.connection
- elif hasattr(itm, "request"):
- return itm.request.connection
-
- def lookup(self, itm):
- """
- Checks for matching connection, using a Flow, Replay Connection,
- BrowserConnection, Request, Response or Error object. Returns None
- if not found.
- """
- connection = self.get_connection(itm)
- return self.flow_map.get(connection)
-
def get_focus(self):
if not self.view or self.focus is None:
return None, None
@@ -693,59 +581,10 @@ class State:
def delete_flow(self, f):
if not f.intercepting:
- c = self.get_connection(f)
self.view[self.focus].focus = False
- del self.flow_map[c]
- self.flow_list.remove(f)
- self.set_focus(self.focus)
- return True
- return False
-
- def clear(self):
- for i in self.flow_list[:]:
- self.delete_flow(i)
-
- def accept_all(self):
- for i in self.flow_list[:]:
- i.accept_intercept()
-
- def kill_flow(self, f):
- f.kill()
- self.delete_flow(f)
-
- def revert(self, f):
- """
- Replaces the matching connection object with a ReplayConnection object.
- """
- conn = self.get_connection(f)
- del self.flow_map[conn]
- f.revert()
- self.flow_map[f.connection] = f
-
- def replay(self, f, masterq):
- """
- Replaces the matching connection object with a ReplayConnection object.
-
- Returns None if successful, or error message if not.
- """
- #begin nocover
- if f.intercepting:
- return "Can't replay while intercepting..."
- if f.request:
- f.backup()
- conn = self.get_connection(f)
- del self.flow_map[conn]
- rp = ReplayConnection()
- f.connection = rp
- f.request.connection = rp
- if f.request.content:
- f.request.headers["content-length"] = [str(len(f.request.content))]
- f.response = None
- f.error = None
- self.flow_map[rp] = f
- rt = ReplayThread(f, masterq)
- rt.start()
- #end nocover
+ ret = flow.State.delete_flow(self, f)
+ self.set_focus(self.focus)
+ return ret
#begin nocover
@@ -764,7 +603,7 @@ class ConsoleMaster(controller.Master):
def __init__(self, server, options):
self.set_palette()
controller.Master.__init__(self, server)
- self.state = State()
+ self.state = ConsoleState()
r = self.set_limit(options.limit)
if r:
@@ -1132,7 +971,7 @@ class ConsoleMaster(controller.Master):
# Handlers
def handle_browserconnection(self, r):
- f = Flow(r)
+ f = ConsoleFlow(r)
self.state.add_browserconnect(f)
r.ack()
self.sync_list_view()
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
new file mode 100644
index 00000000..bdca5ebd
--- /dev/null
+++ b/libmproxy/flow.py
@@ -0,0 +1,207 @@
+"""
+ This module provides more sophisticated flow tracking. These match requests
+ with their responses, and provide filtering and interception facilities.
+"""
+import proxy, threading
+
+class ReplayConnection:
+ pass
+
+
+class ReplayThread(threading.Thread):
+ def __init__(self, flow, masterq):
+ self.flow, self.masterq = flow, masterq
+ threading.Thread.__init__(self)
+
+ def run(self):
+ try:
+ server = proxy.ServerConnection(self.flow.request)
+ response = server.read_response()
+ response.send(self.masterq)
+ except proxy.ProxyError, v:
+ err = proxy.Error(self.flow.connection, v.msg)
+ err.send(self.masterq)
+
+
+class Flow:
+ def __init__(self, connection):
+ self.connection = connection
+ self.request, self.response, self.error = None, None, None
+ self.waiting = True
+ self.intercepting = False
+ self._backup = None
+
+ def backup(self):
+ if not self._backup:
+ self._backup = [
+ self.connection.copy() if self.connection else None,
+ self.request.copy() if self.request else None,
+ self.response.copy() if self.response else None,
+ self.error.copy() if self.error else None,
+ ]
+
+ def revert(self):
+ if self._backup:
+ self.waiting = False
+ restore = [i.copy() if i else None for i in self._backup]
+ self.connection, self.request, self.response, self.error = restore
+
+ def match(self, pattern):
+ if pattern:
+ if self.response:
+ return pattern(self.response)
+ elif self.request:
+ return pattern(self.request)
+ return False
+
+ def is_replay(self):
+ return isinstance(self.connection, ReplayConnection)
+
+ def kill(self):
+ if self.intercepting:
+ if not self.request.acked:
+ self.request.kill = True
+ self.request.ack()
+ elif self.response and not self.response.acked:
+ self.response.kill = True
+ self.response.ack()
+ self.intercepting = False
+
+ def intercept(self):
+ self.intercepting = True
+
+ def accept_intercept(self):
+ if self.request:
+ if not self.request.acked:
+ self.request.ack()
+ elif self.response and not self.response.acked:
+ self.response.ack()
+ self.intercepting = False
+
+
+class State:
+ def __init__(self):
+ self.flow_map = {}
+ self.flow_list = []
+ # These are compiled filt expressions:
+ self.limit = None
+ self.intercept = None
+
+ def add_browserconnect(self, f):
+ """
+ Start a browser connection.
+ """
+ self.flow_list.insert(0, f)
+ self.flow_map[f.connection] = f
+
+ def add_request(self, req):
+ """
+ Add a request to the state. Returns the matching flow.
+ """
+ f = self.flow_map.get(req.connection)
+ if not f:
+ return False
+ f.request = req
+ return f
+
+ def add_response(self, resp):
+ """
+ Add a response to the state. Returns the matching flow.
+ """
+ f = self.flow_map.get(resp.request.connection)
+ if not f:
+ return False
+ f.response = resp
+ f.waiting = False
+ f.backup()
+ return f
+
+ def add_error(self, err):
+ """
+ Add an error response to the state. Returns the matching flow, or
+ None if there isn't one.
+ """
+ f = self.flow_map.get(err.connection)
+ if not f:
+ return None
+ f.error = err
+ f.waiting = False
+ f.backup()
+ return f
+
+ def set_limit(self, limit):
+ """
+ Limit is a compiled filter expression, or None.
+ """
+ self.limit = limit
+
+ def get_connection(self, itm):
+ if isinstance(itm, (proxy.BrowserConnection, ReplayConnection)):
+ return itm
+ elif hasattr(itm, "connection"):
+ return itm.connection
+ elif hasattr(itm, "request"):
+ return itm.request.connection
+
+ def lookup(self, itm):
+ """
+ Checks for matching connection, using a Flow, Replay Connection,
+ BrowserConnection, Request, Response or Error object. Returns None
+ if not found.
+ """
+ connection = self.get_connection(itm)
+ return self.flow_map.get(connection)
+
+ def delete_flow(self, f):
+ if not f.intercepting:
+ c = self.get_connection(f)
+ del self.flow_map[c]
+ self.flow_list.remove(f)
+ return True
+ return False
+
+ def clear(self):
+ for i in self.flow_list[:]:
+ self.delete_flow(i)
+
+ def accept_all(self):
+ for i in self.flow_list[:]:
+ i.accept_intercept()
+
+ def kill_flow(self, f):
+ f.kill()
+ self.delete_flow(f)
+
+ def revert(self, f):
+ """
+ Replaces the matching connection object with a ReplayConnection object.
+ """
+ conn = self.get_connection(f)
+ del self.flow_map[conn]
+ f.revert()
+ self.flow_map[f.connection] = f
+
+ def replay(self, f, masterq):
+ """
+ Replaces the matching connection object with a ReplayConnection object.
+
+ Returns None if successful, or error message if not.
+ """
+ #begin nocover
+ if f.intercepting:
+ return "Can't replay while intercepting..."
+ if f.request:
+ f.backup()
+ conn = self.get_connection(f)
+ del self.flow_map[conn]
+ rp = ReplayConnection()
+ f.connection = rp
+ f.request.connection = rp
+ if f.request.content:
+ f.request.headers["content-length"] = [str(len(f.request.content))]
+ f.response = None
+ f.error = None
+ self.flow_map[rp] = f
+ rt = ReplayThread(f, masterq)
+ rt.start()
+ #end nocover
diff --git a/mitmdump b/mitmdump
index c9c3c63a..f3e8ae0a 100755
--- a/mitmdump
+++ b/mitmdump
@@ -17,13 +17,14 @@
import sys, os.path
from libmproxy import proxy, dump, utils
+from libmproxy import VERSION
from optparse import OptionParser, OptionGroup
if __name__ == '__main__':
parser = OptionParser(
- usage = "%prog [options] output",
- version="%prog 0.1",
+ usage = "%prog [options]",
+ version="%%prog %s"%VERSION,
)
parser.add_option(
"-c", "--cert", action="store",
diff --git a/mitmproxy b/mitmproxy
index fbe325fa..5753fd09 100755
--- a/mitmproxy
+++ b/mitmproxy
@@ -17,13 +17,14 @@
import sys, os.path
from libmproxy import proxy, controller, console, utils
+from libmproxy import VERSION
from optparse import OptionParser, OptionGroup
if __name__ == '__main__':
parser = OptionParser(
- usage = "%prog [options] output",
- version="%prog 0.1",
+ usage = "%prog [options]",
+ version="%%prog %s"%VERSION,
)
parser.add_option(
diff --git a/setup.py b/setup.py
index b667405a..d1abd092 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,6 @@
from distutils.core import setup
import fnmatch, os.path
+from libmproxy import VERSION
def _fnmatch(name, patternList):
for i in patternList:
@@ -68,10 +69,9 @@ def findPackages(path, dataExclude=[]):
long_description = file("README").read()
packages, package_data = findPackages("libmproxy")
-version = "0.2"
setup(
name = "mitmproxy",
- version = version,
+ version = VERSION,
description = "An interactive SSL-capable intercepting HTTP proxy for penetration testers and software developers.",
long_description = long_description,
author = "Aldo Cortesi",
diff --git a/test/test_console.py b/test/test_console.py
index 50780aa5..cda61bd5 100644
--- a/test/test_console.py
+++ b/test/test_console.py
@@ -1,4 +1,4 @@
-from libmproxy import console, proxy, utils, filt
+from libmproxy import console, proxy, utils, filt, flow
import libpry
def treq(conn=None):
@@ -19,14 +19,14 @@ def tresp(req=None):
def tflow():
bc = proxy.BrowserConnection("address", 22)
- return console.Flow(bc)
+ return console.ConsoleFlow(bc)
class uState(libpry.AutoTree):
def test_backup(self):
bc = proxy.BrowserConnection("address", 22)
- c = console.State()
- f = console.Flow(bc)
+ c = console.ConsoleState()
+ f = console.ConsoleFlow(bc)
c.add_browserconnect(f)
f.backup()
@@ -39,8 +39,8 @@ class uState(libpry.AutoTree):
connect -> request -> response
"""
bc = proxy.BrowserConnection("address", 22)
- c = console.State()
- f = console.Flow(bc)
+ c = console.ConsoleState()
+ f = console.ConsoleFlow(bc)
c.add_browserconnect(f)
assert c.lookup(bc)
assert c.get_focus() == (f, 0)
@@ -66,8 +66,8 @@ class uState(libpry.AutoTree):
def test_err(self):
bc = proxy.BrowserConnection("address", 22)
- c = console.State()
- f = console.Flow(bc)
+ c = console.ConsoleState()
+ f = console.ConsoleFlow(bc)
c.add_browserconnect(f)
e = proxy.Error(bc, "message")
assert c.add_error(e)
@@ -76,7 +76,7 @@ class uState(libpry.AutoTree):
assert not c.add_error(e)
def test_view(self):
- c = console.State()
+ c = console.ConsoleState()
f = tflow()
c.add_browserconnect(f)
@@ -102,10 +102,10 @@ class uState(libpry.AutoTree):
connect -> request -> response
"""
- c = console.State()
+ c = console.ConsoleState()
bc = proxy.BrowserConnection("address", 22)
- f = console.Flow(bc)
+ f = console.ConsoleFlow(bc)
c.add_browserconnect(f)
assert c.get_focus() == (f, 0)
assert c.get_from_pos(0) == (f, 0)
@@ -113,7 +113,7 @@ class uState(libpry.AutoTree):
assert c.get_next(0) == (None, None)
bc2 = proxy.BrowserConnection("address", 22)
- f2 = console.Flow(bc2)
+ f2 = console.ConsoleFlow(bc2)
c.add_browserconnect(f2)
assert c.get_focus() == (f, 1)
assert c.get_next(0) == (f, 1)
@@ -143,7 +143,7 @@ class uState(libpry.AutoTree):
state.add_response(r)
def test_focus_view(self):
- c = console.State()
+ c = console.ConsoleState()
self._add_request(c)
self._add_response(c)
self._add_request(c)
@@ -155,7 +155,7 @@ class uState(libpry.AutoTree):
assert c.focus == 2
def test_delete_last(self):
- c = console.State()
+ c = console.ConsoleState()
f1 = tflow()
f2 = tflow()
c.add_browserconnect(f1)
@@ -165,14 +165,14 @@ class uState(libpry.AutoTree):
assert c.focus == 0
def test_kill_flow(self):
- c = console.State()
+ c = console.ConsoleState()
f = tflow()
c.add_browserconnect(f)
c.kill_flow(f)
assert not c.flow_list
def test_clear(self):
- c = console.State()
+ c = console.ConsoleState()
f = tflow()
c.add_browserconnect(f)
f.intercepting = True
@@ -212,7 +212,7 @@ class uFlow(libpry.AutoTree):
f.focus = True
assert f.get_text()
- f.connection = console.ReplayConnection()
+ f.connection = flow.ReplayConnection()
assert f.get_text()
f.response = None
@@ -251,6 +251,11 @@ class uFlow(libpry.AutoTree):
f.accept_intercept()
assert f.response.acked
+ def test_serialization(self):
+ f = console.ConsoleFlow(None)
+ f.request = treq()
+
+
class uformat_keyvals(libpry.AutoTree):
def test_simple(self):