aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--README.rst45
-rwxr-xr-xdev.sh5
-rw-r--r--mitmproxy/console/__init__.py33
-rw-r--r--mitmproxy/console/flowlist.py7
-rw-r--r--mitmproxy/console/statusbar.py4
-rw-r--r--mitmproxy/dump.py8
-rw-r--r--mitmproxy/web/app.py23
-rw-r--r--mitmproxy/web/static/app.js362
-rw-r--r--mitmproxy/web/static/vendor.js231
-rw-r--r--netlib/tcp.py43
-rw-r--r--pathod/language/generators.py57
-rw-r--r--pathod/pathoc.py3
-rw-r--r--pathod/pathod.py18
-rw-r--r--pathod/protocols/websockets.py2
-rw-r--r--pathod/test.py49
-rw-r--r--setup.cfg3
-rw-r--r--test/pathod/test_app.py6
-rw-r--r--test/pathod/test_language_generators.py13
-rw-r--r--test/pathod/test_pathod.py22
-rw-r--r--test/pathod/test_test.py4
-rw-r--r--test/pathod/tutils.py48
-rw-r--r--web/package.json1
-rw-r--r--web/src/js/actions.js12
-rw-r--r--web/src/js/components/header.js37
-rw-r--r--web/src/js/store/store.js2
-rw-r--r--web/src/js/utils.js16
27 files changed, 650 insertions, 408 deletions
diff --git a/.travis.yml b/.travis.yml
index 9d4dea88..5ec8b3bd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,9 +22,9 @@ matrix:
git:
depth: 9999999
- python: 3.5
- env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py"
+ env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py ./test/pathod/test_language_generators.py"
- python: 3.5
- env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py" NO_ALPN=1
+ env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py ./test/pathod/test_language_generators.py" NO_ALPN=1
- python: 2.7
env: DOCS=1
script: 'cd docs && make html'
diff --git a/README.rst b/README.rst
index 854c4ae6..4f84effd 100644
--- a/README.rst
+++ b/README.rst
@@ -3,19 +3,24 @@ mitmproxy
|travis| |coveralls| |latest_release| |python_versions|
-This repository contains the **mitmproxy** and **pathod** projects, as well as their shared networking library, **netlib**.
+This repository contains the **mitmproxy** and **pathod** projects, as well as
+their shared networking library, **netlib**.
-``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console interface.
+``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console
+interface.
``mitmdump`` is the command-line version of mitmproxy. Think tcpdump for HTTP.
-``pathoc`` and ``pathod`` are perverse HTTP client and server applications designed to let you craft almost any conceivable HTTP request, including ones that creatively violate the standards.
+``pathoc`` and ``pathod`` are perverse HTTP client and server applications
+designed to let you craft almost any conceivable HTTP request, including ones
+that creatively violate the standards.
Documentation & Help
--------------------
-Documentation, tutorials and precompiled binaries can be found on the mitmproxy and pathod websites.
+Documentation, tutorials and precompiled binaries can be found on the mitmproxy
+and pathod websites.
|mitmproxy_site| |pathod_site|
@@ -28,12 +33,19 @@ You can join our developer chat on Slack.
|slack|
+Installation
+------------
+
+The installation instructions are `here <http://docs.mitmproxy.org/en/stable/install.html>`_.
+If you want to contribute changes, keep on reading.
+
+
Hacking
-------
To get started hacking on mitmproxy, make sure you have Python_ 2.7.x. with
-virtualenv_ installed (you can find installation instructions for virtualenv here_).
-Then do the following:
+virtualenv_ installed (you can find installation instructions for virtualenv
+here_). Then do the following:
.. code-block:: text
@@ -42,10 +54,11 @@ Then do the following:
./dev.sh
-The *dev* script will create a virtualenv environment in a directory called "venv",
-and install all mandatory and optional dependencies into it.
-The primary mitmproxy components - mitmproxy, netlib and pathod - are installed as "editable",
-so any changes to the source in the repository will be reflected live in the virtualenv.
+The *dev* script will create a virtualenv environment in a directory called
+"venv", and install all mandatory and optional dependencies into it. The
+primary mitmproxy components - mitmproxy, netlib and pathod - are installed as
+"editable", so any changes to the source in the repository will be reflected
+live in the virtualenv.
To confirm that you're up and running, activate the virtualenv, and run the
mitmproxy test suite:
@@ -56,9 +69,9 @@ mitmproxy test suite:
py.test
Note that the main executables for the project - ``mitmdump``, ``mitmproxy``,
-``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the virtualenv. After activating the
-virtualenv, they will be on your $PATH, and you can run them like any other
-command:
+``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the
+virtualenv. After activating the virtualenv, they will be on your $PATH, and
+you can run them like any other command:
.. code-block:: text
@@ -85,9 +98,9 @@ suite. The project tries to maintain 100% test coverage.
Documentation
-------------
-The mitmproxy documentation is build using Sphinx_, which is installed automatically if you set up a development
-environment as described above.
-After installation, you can render the documentation like this:
+The mitmproxy documentation is build using Sphinx_, which is installed
+automatically if you set up a development environment as described above. After
+installation, you can render the documentation like this:
.. code-block:: text
diff --git a/dev.sh b/dev.sh
index 111f09bc..889ffecb 100755
--- a/dev.sh
+++ b/dev.sh
@@ -1,5 +1,6 @@
#!/bin/sh
set -e
+set -x
PYVERSION=$1
VENV="venv$1"
@@ -8,8 +9,8 @@ echo "Creating dev environment in $VENV using Python $PYVERSION"
python$PYVERSION -m virtualenv "$VENV" --always-copy
. "$VENV/bin/activate"
-pip$PYVERSION install -q -U pip setuptools
-pip$PYVERSION install -q -r requirements.txt
+pip$PYVERSION install -U pip setuptools
+pip$PYVERSION install -r requirements.txt
echo ""
echo "* Virtualenv created in $VENV and all dependencies installed."
diff --git a/mitmproxy/console/__init__.py b/mitmproxy/console/__init__.py
index 00fb4b1b..63692ec0 100644
--- a/mitmproxy/console/__init__.py
+++ b/mitmproxy/console/__init__.py
@@ -44,6 +44,8 @@ class ConsoleState(flow.State):
self.default_body_view = contentviews.get("Auto")
self.flowsettings = weakref.WeakKeyDictionary()
self.last_search = None
+ self.last_filter = None
+ self.mark_filter = False
def __setattr__(self, name, value):
self.__dict__[name] = value
@@ -117,6 +119,37 @@ class ConsoleState(flow.State):
self.set_focus(self.focus)
return ret
+ def filter_marked(self, m):
+ def actual_func(x):
+ if x.id in m:
+ return True
+ return False
+ return actual_func
+
+ def enable_marked_filter(self):
+ self.last_filter = self.limit_txt
+ marked_flows = []
+ for f in self.flows:
+ if self.flow_marked(f):
+ marked_flows.append(f.id)
+ if len(marked_flows) > 0:
+ f = self.filter_marked(marked_flows)
+ self.view._close()
+ self.view = flow.FlowView(self.flows, f)
+ self.focus = 0
+ self.set_focus(self.focus)
+ self.mark_filter = True
+
+ def disable_marked_filter(self):
+ if self.last_filter is None:
+ self.view = flow.FlowView(self.flows, None)
+ else:
+ self.set_limit(self.last_filter)
+ self.focus = 0
+ self.set_focus(self.focus)
+ self.last_filter = None
+ self.mark_filter = False
+
def clear(self):
marked_flows = []
for f in self.flows:
diff --git a/mitmproxy/console/flowlist.py b/mitmproxy/console/flowlist.py
index eb1e76fb..8c20c4b6 100644
--- a/mitmproxy/console/flowlist.py
+++ b/mitmproxy/console/flowlist.py
@@ -22,6 +22,7 @@ def _mkhelp():
("l", "set limit filter pattern"),
("L", "load saved flows"),
("m", "toggle flow mark"),
+ ("M", "toggle marked flow view"),
("n", "create a new request"),
("P", "copy flow to clipboard"),
("r", "replay request"),
@@ -198,6 +199,12 @@ class ConnectionItem(urwid.WidgetWrap):
else:
self.state.set_flow_marked(self.flow, True)
signals.flowlist_change.send(self)
+ elif key == "M":
+ if self.state.mark_filter:
+ self.state.disable_marked_filter()
+ else:
+ self.state.enable_marked_filter()
+ signals.flowlist_change.send(self)
elif key == "r":
r = self.master.replay_request(self.flow)
if r:
diff --git a/mitmproxy/console/statusbar.py b/mitmproxy/console/statusbar.py
index b3e1517f..af8089b6 100644
--- a/mitmproxy/console/statusbar.py
+++ b/mitmproxy/console/statusbar.py
@@ -168,6 +168,10 @@ class StatusBar(urwid.WidgetWrap):
r.append("[")
r.append(("heading_key", "l"))
r.append(":%s]" % self.master.state.limit_txt)
+ if self.master.state.mark_filter:
+ r.append("[")
+ r.append(("heading_key", "Marked Flows"))
+ r.append("]")
if self.master.stickycookie_txt:
r.append("[")
r.append(("heading_key", "t"))
diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py
index b1005ee7..cc6896ed 100644
--- a/mitmproxy/dump.py
+++ b/mitmproxy/dump.py
@@ -333,17 +333,15 @@ class DumpMaster(flow.FlowMaster):
@controller.handler
def request(self, f):
- flow.FlowMaster.request(self, f)
- self.state.delete_flow(f)
+ f = flow.FlowMaster.request(self, f)
if f:
- f.reply()
+ self.state.delete_flow(f)
return f
@controller.handler
def response(self, f):
- flow.FlowMaster.response(self, f)
+ f = flow.FlowMaster.response(self, f)
if f:
- f.reply()
self._process_flow(f)
return f
diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py
index 43b2bad1..dce85df3 100644
--- a/mitmproxy/web/app.py
+++ b/mitmproxy/web/app.py
@@ -8,6 +8,8 @@ import re
import six
import tornado.websocket
+from io import BytesIO
+from mitmproxy.flow import FlowWriter, FlowReader
from mitmproxy import filt
from mitmproxy import version
@@ -159,6 +161,26 @@ class Flows(RequestHandler):
data=[_strip_content(f.get_state()) for f in self.state.flows]
))
+class DumpFlows(RequestHandler):
+ def get(self):
+ self.set_header("Content-Disposition", "attachment; filename=flows")
+ self.set_header("Content-Type", "application/octet-stream")
+
+ bio = BytesIO()
+ fw = FlowWriter(bio)
+ for f in self.state.flows:
+ fw.add(f)
+
+ self.write(bio.getvalue())
+ bio.close()
+
+ def post(self):
+ self.state.clear()
+
+ content = self.request.files.values()[0][0]["body"]
+ bio = BytesIO(content)
+ self.state.load_flows(FlowReader(bio).stream())
+ bio.close()
class ClearAll(RequestHandler):
@@ -356,6 +378,7 @@ class Application(tornado.web.Application):
(r"/updates", ClientConnection),
(r"/events", Events),
(r"/flows", Flows),
+ (r"/flows/dump", DumpFlows),
(r"/flows/accept", AcceptFlows),
(r"/flows/(?P<flow_id>[0-9a-f\-]+)", FlowHandler),
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/accept", AcceptFlow),
diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js
index e1e4b22b..b78dd337 100644
--- a/mitmproxy/web/static/app.js
+++ b/mitmproxy/web/static/app.js
@@ -301,235 +301,6 @@ function isUndefined(arg) {
},{}],2:[function(require,module,exports){
"use strict";
-function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
-
-function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
-
-var repeat = function repeat(str, times) {
- return new Array(times + 1).join(str);
-};
-var pad = function pad(num, maxLength) {
- return repeat("0", maxLength - num.toString().length) + num;
-};
-var formatTime = function formatTime(time) {
- return "@ " + pad(time.getHours(), 2) + ":" + pad(time.getMinutes(), 2) + ":" + pad(time.getSeconds(), 2) + "." + pad(time.getMilliseconds(), 3);
-};
-
-// Use the new performance api to get better precision if available
-var timer = typeof performance !== "undefined" && typeof performance.now === "function" ? performance : Date;
-
-/**
- * parse the level option of createLogger
- *
- * @property {string | function | object} level - console[level]
- * @property {object} action
- * @property {array} payload
- * @property {string} type
- */
-
-function getLogLevel(level, action, payload, type) {
- switch (typeof level === "undefined" ? "undefined" : _typeof(level)) {
- case "object":
- return typeof level[type] === "function" ? level[type].apply(level, _toConsumableArray(payload)) : level[type];
- case "function":
- return level(action);
- default:
- return level;
- }
-}
-
-/**
- * Creates logger with followed options
- *
- * @namespace
- * @property {object} options - options for logger
- * @property {string | function | object} options.level - console[level]
- * @property {boolean} options.duration - print duration of each action?
- * @property {boolean} options.timestamp - print timestamp with each action?
- * @property {object} options.colors - custom colors
- * @property {object} options.logger - implementation of the `console` API
- * @property {boolean} options.logErrors - should errors in action execution be caught, logged, and re-thrown?
- * @property {boolean} options.collapsed - is group collapsed?
- * @property {boolean} options.predicate - condition which resolves logger behavior
- * @property {function} options.stateTransformer - transform state before print
- * @property {function} options.actionTransformer - transform action before print
- * @property {function} options.errorTransformer - transform error before print
- */
-
-function createLogger() {
- var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
- var _options$level = options.level;
- var level = _options$level === undefined ? "log" : _options$level;
- var _options$logger = options.logger;
- var logger = _options$logger === undefined ? console : _options$logger;
- var _options$logErrors = options.logErrors;
- var logErrors = _options$logErrors === undefined ? true : _options$logErrors;
- var collapsed = options.collapsed;
- var predicate = options.predicate;
- var _options$duration = options.duration;
- var duration = _options$duration === undefined ? false : _options$duration;
- var _options$timestamp = options.timestamp;
- var timestamp = _options$timestamp === undefined ? true : _options$timestamp;
- var transformer = options.transformer;
- var _options$stateTransfo = options.stateTransformer;
- var // deprecated
- stateTransformer = _options$stateTransfo === undefined ? function (state) {
- return state;
- } : _options$stateTransfo;
- var _options$actionTransf = options.actionTransformer;
- var actionTransformer = _options$actionTransf === undefined ? function (actn) {
- return actn;
- } : _options$actionTransf;
- var _options$errorTransfo = options.errorTransformer;
- var errorTransformer = _options$errorTransfo === undefined ? function (error) {
- return error;
- } : _options$errorTransfo;
- var _options$colors = options.colors;
- var colors = _options$colors === undefined ? {
- title: function title() {
- return "#000000";
- },
- prevState: function prevState() {
- return "#9E9E9E";
- },
- action: function action() {
- return "#03A9F4";
- },
- nextState: function nextState() {
- return "#4CAF50";
- },
- error: function error() {
- return "#F20404";
- }
- } : _options$colors;
-
- // exit if console undefined
-
- if (typeof logger === "undefined") {
- return function () {
- return function (next) {
- return function (action) {
- return next(action);
- };
- };
- };
- }
-
- if (transformer) {
- console.error("Option 'transformer' is deprecated, use stateTransformer instead");
- }
-
- var logBuffer = [];
- function printBuffer() {
- logBuffer.forEach(function (logEntry, key) {
- var started = logEntry.started;
- var startedTime = logEntry.startedTime;
- var action = logEntry.action;
- var prevState = logEntry.prevState;
- var error = logEntry.error;
- var took = logEntry.took;
- var nextState = logEntry.nextState;
-
- var nextEntry = logBuffer[key + 1];
- if (nextEntry) {
- nextState = nextEntry.prevState;
- took = nextEntry.started - started;
- }
- // message
- var formattedAction = actionTransformer(action);
- var isCollapsed = typeof collapsed === "function" ? collapsed(function () {
- return nextState;
- }, action) : collapsed;
-
- var formattedTime = formatTime(startedTime);
- var titleCSS = colors.title ? "color: " + colors.title(formattedAction) + ";" : null;
- var title = "action " + (timestamp ? formattedTime : "") + " " + formattedAction.type + " " + (duration ? "(in " + took.toFixed(2) + " ms)" : "");
-
- // render
- try {
- if (isCollapsed) {
- if (colors.title) logger.groupCollapsed("%c " + title, titleCSS);else logger.groupCollapsed(title);
- } else {
- if (colors.title) logger.group("%c " + title, titleCSS);else logger.group(title);
- }
- } catch (e) {
- logger.log(title);
- }
-
- var prevStateLevel = getLogLevel(level, formattedAction, [prevState], "prevState");
- var actionLevel = getLogLevel(level, formattedAction, [formattedAction], "action");
- var errorLevel = getLogLevel(level, formattedAction, [error, prevState], "error");
- var nextStateLevel = getLogLevel(level, formattedAction, [nextState], "nextState");
-
- if (prevStateLevel) {
- if (colors.prevState) logger[prevStateLevel]("%c prev state", "color: " + colors.prevState(prevState) + "; font-weight: bold", prevState);else logger[prevStateLevel]("prev state", prevState);
- }
-
- if (actionLevel) {
- if (colors.action) logger[actionLevel]("%c action", "color: " + colors.action(formattedAction) + "; font-weight: bold", formattedAction);else logger[actionLevel]("action", formattedAction);
- }
-
- if (error && errorLevel) {
- if (colors.error) logger[errorLevel]("%c error", "color: " + colors.error(error, prevState) + "; font-weight: bold", error);else logger[errorLevel]("error", error);
- }
-
- if (nextStateLevel) {
- if (colors.nextState) logger[nextStateLevel]("%c next state", "color: " + colors.nextState(nextState) + "; font-weight: bold", nextState);else logger[nextStateLevel]("next state", nextState);
- }
-
- try {
- logger.groupEnd();
- } catch (e) {
- logger.log("—— log end ——");
- }
- });
- logBuffer.length = 0;
- }
-
- return function (_ref) {
- var getState = _ref.getState;
- return function (next) {
- return function (action) {
- // exit early if predicate function returns false
- if (typeof predicate === "function" && !predicate(getState, action)) {
- return next(action);
- }
-
- var logEntry = {};
- logBuffer.push(logEntry);
-
- logEntry.started = timer.now();
- logEntry.startedTime = new Date();
- logEntry.prevState = stateTransformer(getState());
- logEntry.action = action;
-
- var returnedValue = undefined;
- if (logErrors) {
- try {
- returnedValue = next(action);
- } catch (e) {
- logEntry.error = errorTransformer(e);
- }
- } else {
- returnedValue = next(action);
- }
-
- logEntry.took = timer.now() - logEntry.started;
- logEntry.nextState = stateTransformer(getState());
-
- printBuffer();
-
- if (logEntry.error) throw logEntry.error;
- return returnedValue;
- };
- };
- };
-}
-
-module.exports = createLogger;
-},{}],3:[function(require,module,exports){
-"use strict";
-
Object.defineProperty(exports, "__esModule", {
value: true
});
@@ -539,12 +310,10 @@ var _jquery = require("jquery");
var _jquery2 = _interopRequireDefault(_jquery);
-var _lodash = require("lodash");
-
-var _lodash2 = _interopRequireDefault(_lodash);
-
var _dispatcher = require("./dispatcher.js");
+var _utils = require("./utils.js");
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var ActionTypes = exports.ActionTypes = {
@@ -662,6 +431,18 @@ var FlowActions = exports.FlowActions = {
},
clear: function clear() {
_jquery2.default.post("/clear");
+ },
+ download: function download() {
+ return window.location = "/flows/dump";
+ },
+
+ upload: function upload(file) {
+ var data = new FormData();
+ data.append('file', file);
+ (0, _utils.fetchApi)("/flows/dump", {
+ method: 'post',
+ body: data
+ });
}
};
@@ -671,7 +452,7 @@ var Query = exports.Query = {
SHOW_EVENTLOG: "e"
};
-},{"./dispatcher.js":23,"jquery":"jquery","lodash":"lodash"}],4:[function(require,module,exports){
+},{"./dispatcher.js":22,"./utils.js":31,"jquery":"jquery"}],3:[function(require,module,exports){
'use strict';
var _react = require('react');
@@ -720,7 +501,7 @@ document.addEventListener('DOMContentLoaded', function () {
), document.getElementById("mitmproxy"));
});
-},{"./components/proxyapp.js":21,"./connection":22,"./ducks/eventLog":24,"./ducks/index":25,"react":"react","react-dom":"react-dom","react-redux":"react-redux","redux":"redux","redux-logger":2}],5:[function(require,module,exports){
+},{"./components/proxyapp.js":20,"./connection":21,"./ducks/eventLog":23,"./ducks/index":24,"react":"react","react-dom":"react-dom","react-redux":"react-redux","redux":"redux","redux-logger":"redux-logger"}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -939,7 +720,7 @@ ToggleInputButton.propTypes = {
onToggleChanged: _react2.default.PropTypes.func.isRequired
};
-},{"../utils.js":32,"lodash":"lodash","react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){
+},{"../utils.js":31,"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -1197,7 +978,7 @@ var ValueEditor = exports.ValueEditor = _react2.default.createClass({
}
});
-},{"../utils.js":32,"react":"react","react-dom":"react-dom"}],7:[function(require,module,exports){
+},{"../utils.js":31,"react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -1424,7 +1205,7 @@ var EventLogContainer = (0, _reactRedux.connect)(undefined, function (dispatch)
exports.default = EventLogContainer;
-},{"../ducks/eventLog":24,"./common":5,"./helpers/AutoScroll":17,"./helpers/VirtualScroll":18,"react":"react","react-dom":"react-dom","react-redux":"react-redux","shallowequal":"shallowequal"}],8:[function(require,module,exports){
+},{"../ducks/eventLog":23,"./common":4,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"react":"react","react-dom":"react-dom","react-redux":"react-redux","shallowequal":"shallowequal"}],7:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -1693,7 +1474,7 @@ var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn
exports.default = all_columns;
-},{"../flow/utils.js":29,"../utils.js":32,"react":"react"}],9:[function(require,module,exports){
+},{"../flow/utils.js":28,"../utils.js":31,"react":"react"}],8:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -1985,7 +1766,7 @@ FlowTable.defaultProps = {
};
exports.default = (0, _AutoScroll2.default)(FlowTable);
-},{"../utils.js":32,"./flowtable-columns.js":8,"./helpers/AutoScroll":17,"./helpers/VirtualScroll":18,"classnames":"classnames","lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],10:[function(require,module,exports){
+},{"../utils.js":31,"./flowtable-columns.js":7,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"classnames":"classnames","lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],9:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -2339,7 +2120,7 @@ var ContentView = _react2.default.createClass({
exports.default = ContentView;
-},{"../../flow/utils.js":29,"../../utils.js":32,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){
+},{"../../flow/utils.js":28,"../../utils.js":31,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -2607,7 +2388,7 @@ var Details = _react2.default.createClass({
exports.default = Details;
-},{"../../utils.js":32,"lodash":"lodash","react":"react"}],12:[function(require,module,exports){
+},{"../../utils.js":31,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -2736,7 +2517,7 @@ var FlowView = _react2.default.createClass({
exports.default = FlowView;
-},{"../prompt.js":20,"./details.js":11,"./messages.js":13,"./nav.js":14,"react":"react"}],13:[function(require,module,exports){
+},{"../prompt.js":19,"./details.js":10,"./messages.js":12,"./nav.js":13,"react":"react"}],12:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3101,7 +2882,7 @@ var Error = exports.Error = _react2.default.createClass({
}
});
-},{"../../actions.js":3,"../../flow/utils.js":29,"../../utils.js":32,"../editor.js":6,"./contentview.js":10,"lodash":"lodash","react":"react","react-dom":"react-dom"}],14:[function(require,module,exports){
+},{"../../actions.js":2,"../../flow/utils.js":28,"../../utils.js":31,"../editor.js":5,"./contentview.js":9,"lodash":"lodash","react":"react","react-dom":"react-dom"}],13:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3182,7 +2963,7 @@ var Nav = _react2.default.createClass({
exports.default = Nav;
-},{"../../actions.js":3,"react":"react"}],15:[function(require,module,exports){
+},{"../../actions.js":2,"react":"react"}],14:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3284,7 +3065,7 @@ function Footer(_ref) {
);
}
-},{"../utils.js":32,"./common.js":5,"react":"react"}],16:[function(require,module,exports){
+},{"../utils.js":31,"./common.js":4,"react":"react"}],15:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3718,18 +3499,27 @@ var FileMenu = _react2.default.createClass({
}
},
handleOpenClick: function handleOpenClick(e) {
+ this.fileInput.click();
+ e.preventDefault();
+ },
+ handleOpenFile: function handleOpenFile(e) {
+ if (e.target.files.length > 0) {
+ _actions.FlowActions.upload(e.target.files[0]);
+ this.fileInput.value = "";
+ }
e.preventDefault();
- console.error("unimplemented: handleOpenClick");
},
handleSaveClick: function handleSaveClick(e) {
e.preventDefault();
- console.error("unimplemented: handleSaveClick");
+ _actions.FlowActions.download();
},
handleShutdownClick: function handleShutdownClick(e) {
e.preventDefault();
console.error("unimplemented: handleShutdownClick");
},
render: function render() {
+ var _this = this;
+
var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : "");
return _react2.default.createElement(
@@ -3753,6 +3543,29 @@ var FileMenu = _react2.default.createClass({
"New"
)
),
+ _react2.default.createElement(
+ "li",
+ null,
+ _react2.default.createElement(
+ "a",
+ { href: "#", onClick: this.handleOpenClick },
+ _react2.default.createElement("i", { className: "fa fa-fw fa-folder-open" }),
+ "Open..."
+ ),
+ _react2.default.createElement("input", { ref: function ref(_ref) {
+ return _this.fileInput = _ref;
+ }, className: "hidden", type: "file", onChange: this.handleOpenFile })
+ ),
+ _react2.default.createElement(
+ "li",
+ null,
+ _react2.default.createElement(
+ "a",
+ { href: "#", onClick: this.handleSaveClick },
+ _react2.default.createElement("i", { className: "fa fa-fw fa-floppy-o" }),
+ "Save..."
+ )
+ ),
_react2.default.createElement("li", { role: "presentation", className: "divider" }),
_react2.default.createElement(
"li",
@@ -3827,7 +3640,7 @@ var Header = exports.Header = _react2.default.createClass({
}
});
-},{"../actions.js":3,"../filt/filt.js":28,"../utils.js":32,"./common.js":5,"./eventlog":7,"jquery":"jquery","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],17:[function(require,module,exports){
+},{"../actions.js":2,"../filt/filt.js":27,"../utils.js":31,"./common.js":4,"./eventlog":6,"jquery":"jquery","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3893,7 +3706,7 @@ exports.default = function (Component) {
}(Component), _class.displayName = Component.name, _temp), Component);
};
-},{"react":"react","react-dom":"react-dom"}],18:[function(require,module,exports){
+},{"react":"react","react-dom":"react-dom"}],17:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -3972,7 +3785,7 @@ function calcVScroll(opts) {
return { start: start, end: end, paddingTop: paddingTop, paddingBottom: paddingBottom };
}
-},{}],19:[function(require,module,exports){
+},{}],18:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -4243,7 +4056,7 @@ var MainView = _react2.default.createClass({
exports.default = MainView;
-},{"../actions.js":3,"../filt/filt.js":28,"../store/view.js":31,"../utils.js":32,"./common.js":5,"./flowtable.js":9,"./flowview/index.js":12,"react":"react"}],20:[function(require,module,exports){
+},{"../actions.js":2,"../filt/filt.js":27,"../store/view.js":30,"../utils.js":31,"./common.js":4,"./flowtable.js":8,"./flowview/index.js":11,"react":"react"}],19:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -4379,7 +4192,7 @@ var Prompt = _react2.default.createClass({
exports.default = Prompt;
-},{"../utils.js":32,"lodash":"lodash","react":"react","react-dom":"react-dom"}],21:[function(require,module,exports){
+},{"../utils.js":31,"lodash":"lodash","react":"react","react-dom":"react-dom"}],20:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -4575,7 +4388,7 @@ var App = exports.App = _react2.default.createElement(
)
);
-},{"../store/store.js":30,"../utils.js":32,"./common.js":5,"./eventlog.js":7,"./footer.js":15,"./header.js":16,"./mainview.js":19,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router"}],22:[function(require,module,exports){
+},{"../store/store.js":29,"../utils.js":31,"./common.js":4,"./eventlog.js":6,"./footer.js":14,"./header.js":15,"./mainview.js":18,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router"}],21:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -4621,7 +4434,7 @@ function Connection(url, dispatch) {
return ws;
}
-},{"./actions.js":3,"./dispatcher.js":23,"./ducks/websocket":27}],23:[function(require,module,exports){
+},{"./actions.js":2,"./dispatcher.js":22,"./ducks/websocket":26}],22:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -4650,7 +4463,7 @@ AppDispatcher.dispatchServerAction = function (action) {
this.dispatch(action);
};
-},{"flux":"flux"}],24:[function(require,module,exports){
+},{"flux":"flux"}],23:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
@@ -4734,7 +4547,7 @@ function addLogEntry(message) {
};
}
-},{"./list":26}],25:[function(require,module,exports){
+},{"./list":25}],24:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
@@ -4760,7 +4573,7 @@ var rootReducer = (0, _redux.combineReducers)({
exports.default = rootReducer;
-},{"./eventLog.js":24,"./websocket.js":27,"redux":"redux"}],26:[function(require,module,exports){
+},{"./eventLog.js":23,"./websocket.js":26,"redux":"redux"}],25:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
@@ -4800,7 +4613,7 @@ function getList() {
}
}
-},{}],27:[function(require,module,exports){
+},{}],26:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
@@ -4841,7 +4654,7 @@ function disconnected() {
return { type: DISCONNECTED };
}
-},{}],28:[function(require,module,exports){
+},{}],27:[function(require,module,exports){
"use strict";
module.exports = function () {
@@ -6745,7 +6558,7 @@ module.exports = function () {
};
}();
-},{"../flow/utils.js":29}],29:[function(require,module,exports){
+},{"../flow/utils.js":28}],28:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -6879,7 +6692,7 @@ var parseHttpVersion = exports.parseHttpVersion = function parseHttpVersion(http
});
};
-},{"jquery":"jquery","lodash":"lodash"}],30:[function(require,module,exports){
+},{"jquery":"jquery","lodash":"lodash"}],29:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -7013,7 +6826,7 @@ _lodash2.default.extend(LiveStoreMixin.prototype, {
this._fetchxhr = _jquery2.default.getJSON("/" + this.type).done(function (message) {
this.handle_fetch(message.data);
}.bind(this)).fail(function () {
- EventLogActions.add_event("Could not fetch " + this.type);
+ _actions.EventLogActions.add_event("Could not fetch " + this.type);
}.bind(this));
}
},
@@ -7066,7 +6879,7 @@ _lodash2.default.extend(EventLogStore.prototype, LiveListStore.prototype, {
}
});
-},{"../actions.js":3,"../dispatcher.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],31:[function(require,module,exports){
+},{"../actions.js":2,"../dispatcher.js":22,"events":1,"jquery":"jquery","lodash":"lodash"}],30:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@@ -7196,14 +7009,18 @@ _lodash2.default.extend(StoreView.prototype, _events.EventEmitter.prototype, {
}
});
-},{"../utils.js":32,"events":1,"lodash":"lodash"}],32:[function(require,module,exports){
+},{"../utils.js":31,"events":1,"lodash":"lodash"}],31:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.formatTimeStamp = exports.formatTimeDelta = exports.formatSize = exports.Key = undefined;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
exports.reverseString = reverseString;
+exports.fetchApi = fetchApi;
var _jquery = require("jquery");
@@ -7288,7 +7105,7 @@ function getCookie(name) {
var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b"));
return r ? r[1] : undefined;
}
-var xsrf = _jquery2.default.param({ _xsrf: getCookie("_xsrf") });
+var xsrf = "_xsrf=" + getCookie("_xsrf");
//Tornado XSRF Protection.
_jquery2.default.ajaxPrefilter(function (options) {
@@ -7311,7 +7128,18 @@ _jquery2.default.ajaxPrefilter(function (options) {
alert(message);
});
-},{"./actions.js":3,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[4])
+function fetchApi(url, options) {
+ if (url.indexOf("?") === -1) {
+ url += "?" + xsrf;
+ } else {
+ url += "&" + xsrf;
+ }
+ return fetch(url, _extends({}, options, {
+ credentials: 'same-origin'
+ }));
+}
+
+},{"./actions.js":2,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[3])
//# sourceMappingURL=app.js.map
diff --git a/mitmproxy/web/static/vendor.js b/mitmproxy/web/static/vendor.js
index 03ff5871..61596f06 100644
--- a/mitmproxy/web/static/vendor.js
+++ b/mitmproxy/web/static/vendor.js
@@ -53352,7 +53352,236 @@ exports.createMemoryHistory = _createMemoryHistory3.default;
module.exports = require('./lib/React');
-},{"./lib/React":102}],"redux":[function(require,module,exports){
+},{"./lib/React":102}],"redux-logger":[function(require,module,exports){
+"use strict";
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
+
+var repeat = function repeat(str, times) {
+ return new Array(times + 1).join(str);
+};
+var pad = function pad(num, maxLength) {
+ return repeat("0", maxLength - num.toString().length) + num;
+};
+var formatTime = function formatTime(time) {
+ return "@ " + pad(time.getHours(), 2) + ":" + pad(time.getMinutes(), 2) + ":" + pad(time.getSeconds(), 2) + "." + pad(time.getMilliseconds(), 3);
+};
+
+// Use the new performance api to get better precision if available
+var timer = typeof performance !== "undefined" && typeof performance.now === "function" ? performance : Date;
+
+/**
+ * parse the level option of createLogger
+ *
+ * @property {string | function | object} level - console[level]
+ * @property {object} action
+ * @property {array} payload
+ * @property {string} type
+ */
+
+function getLogLevel(level, action, payload, type) {
+ switch (typeof level === "undefined" ? "undefined" : _typeof(level)) {
+ case "object":
+ return typeof level[type] === "function" ? level[type].apply(level, _toConsumableArray(payload)) : level[type];
+ case "function":
+ return level(action);
+ default:
+ return level;
+ }
+}
+
+/**
+ * Creates logger with followed options
+ *
+ * @namespace
+ * @property {object} options - options for logger
+ * @property {string | function | object} options.level - console[level]
+ * @property {boolean} options.duration - print duration of each action?
+ * @property {boolean} options.timestamp - print timestamp with each action?
+ * @property {object} options.colors - custom colors
+ * @property {object} options.logger - implementation of the `console` API
+ * @property {boolean} options.logErrors - should errors in action execution be caught, logged, and re-thrown?
+ * @property {boolean} options.collapsed - is group collapsed?
+ * @property {boolean} options.predicate - condition which resolves logger behavior
+ * @property {function} options.stateTransformer - transform state before print
+ * @property {function} options.actionTransformer - transform action before print
+ * @property {function} options.errorTransformer - transform error before print
+ */
+
+function createLogger() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+ var _options$level = options.level;
+ var level = _options$level === undefined ? "log" : _options$level;
+ var _options$logger = options.logger;
+ var logger = _options$logger === undefined ? console : _options$logger;
+ var _options$logErrors = options.logErrors;
+ var logErrors = _options$logErrors === undefined ? true : _options$logErrors;
+ var collapsed = options.collapsed;
+ var predicate = options.predicate;
+ var _options$duration = options.duration;
+ var duration = _options$duration === undefined ? false : _options$duration;
+ var _options$timestamp = options.timestamp;
+ var timestamp = _options$timestamp === undefined ? true : _options$timestamp;
+ var transformer = options.transformer;
+ var _options$stateTransfo = options.stateTransformer;
+ var // deprecated
+ stateTransformer = _options$stateTransfo === undefined ? function (state) {
+ return state;
+ } : _options$stateTransfo;
+ var _options$actionTransf = options.actionTransformer;
+ var actionTransformer = _options$actionTransf === undefined ? function (actn) {
+ return actn;
+ } : _options$actionTransf;
+ var _options$errorTransfo = options.errorTransformer;
+ var errorTransformer = _options$errorTransfo === undefined ? function (error) {
+ return error;
+ } : _options$errorTransfo;
+ var _options$colors = options.colors;
+ var colors = _options$colors === undefined ? {
+ title: function title() {
+ return "#000000";
+ },
+ prevState: function prevState() {
+ return "#9E9E9E";
+ },
+ action: function action() {
+ return "#03A9F4";
+ },
+ nextState: function nextState() {
+ return "#4CAF50";
+ },
+ error: function error() {
+ return "#F20404";
+ }
+ } : _options$colors;
+
+ // exit if console undefined
+
+ if (typeof logger === "undefined") {
+ return function () {
+ return function (next) {
+ return function (action) {
+ return next(action);
+ };
+ };
+ };
+ }
+
+ if (transformer) {
+ console.error("Option 'transformer' is deprecated, use stateTransformer instead");
+ }
+
+ var logBuffer = [];
+ function printBuffer() {
+ logBuffer.forEach(function (logEntry, key) {
+ var started = logEntry.started;
+ var startedTime = logEntry.startedTime;
+ var action = logEntry.action;
+ var prevState = logEntry.prevState;
+ var error = logEntry.error;
+ var took = logEntry.took;
+ var nextState = logEntry.nextState;
+
+ var nextEntry = logBuffer[key + 1];
+ if (nextEntry) {
+ nextState = nextEntry.prevState;
+ took = nextEntry.started - started;
+ }
+ // message
+ var formattedAction = actionTransformer(action);
+ var isCollapsed = typeof collapsed === "function" ? collapsed(function () {
+ return nextState;
+ }, action) : collapsed;
+
+ var formattedTime = formatTime(startedTime);
+ var titleCSS = colors.title ? "color: " + colors.title(formattedAction) + ";" : null;
+ var title = "action " + (timestamp ? formattedTime : "") + " " + formattedAction.type + " " + (duration ? "(in " + took.toFixed(2) + " ms)" : "");
+
+ // render
+ try {
+ if (isCollapsed) {
+ if (colors.title) logger.groupCollapsed("%c " + title, titleCSS);else logger.groupCollapsed(title);
+ } else {
+ if (colors.title) logger.group("%c " + title, titleCSS);else logger.group(title);
+ }
+ } catch (e) {
+ logger.log(title);
+ }
+
+ var prevStateLevel = getLogLevel(level, formattedAction, [prevState], "prevState");
+ var actionLevel = getLogLevel(level, formattedAction, [formattedAction], "action");
+ var errorLevel = getLogLevel(level, formattedAction, [error, prevState], "error");
+ var nextStateLevel = getLogLevel(level, formattedAction, [nextState], "nextState");
+
+ if (prevStateLevel) {
+ if (colors.prevState) logger[prevStateLevel]("%c prev state", "color: " + colors.prevState(prevState) + "; font-weight: bold", prevState);else logger[prevStateLevel]("prev state", prevState);
+ }
+
+ if (actionLevel) {
+ if (colors.action) logger[actionLevel]("%c action", "color: " + colors.action(formattedAction) + "; font-weight: bold", formattedAction);else logger[actionLevel]("action", formattedAction);
+ }
+
+ if (error && errorLevel) {
+ if (colors.error) logger[errorLevel]("%c error", "color: " + colors.error(error, prevState) + "; font-weight: bold", error);else logger[errorLevel]("error", error);
+ }
+
+ if (nextStateLevel) {
+ if (colors.nextState) logger[nextStateLevel]("%c next state", "color: " + colors.nextState(nextState) + "; font-weight: bold", nextState);else logger[nextStateLevel]("next state", nextState);
+ }
+
+ try {
+ logger.groupEnd();
+ } catch (e) {
+ logger.log("—— log end ——");
+ }
+ });
+ logBuffer.length = 0;
+ }
+
+ return function (_ref) {
+ var getState = _ref.getState;
+ return function (next) {
+ return function (action) {
+ // exit early if predicate function returns false
+ if (typeof predicate === "function" && !predicate(getState, action)) {
+ return next(action);
+ }
+
+ var logEntry = {};
+ logBuffer.push(logEntry);
+
+ logEntry.started = timer.now();
+ logEntry.startedTime = new Date();
+ logEntry.prevState = stateTransformer(getState());
+ logEntry.action = action;
+
+ var returnedValue = undefined;
+ if (logErrors) {
+ try {
+ returnedValue = next(action);
+ } catch (e) {
+ logEntry.error = errorTransformer(e);
+ }
+ } else {
+ returnedValue = next(action);
+ }
+
+ logEntry.took = timer.now() - logEntry.started;
+ logEntry.nextState = stateTransformer(getState());
+
+ printBuffer();
+
+ if (logEntry.error) throw logEntry.error;
+ return returnedValue;
+ };
+ };
+ };
+}
+
+module.exports = createLogger;
+},{}],"redux":[function(require,module,exports){
(function (process){
'use strict';
diff --git a/netlib/tcp.py b/netlib/tcp.py
index 914aa701..de12102e 100644
--- a/netlib/tcp.py
+++ b/netlib/tcp.py
@@ -6,6 +6,7 @@ import sys
import threading
import time
import traceback
+import contextlib
import binascii
from six.moves import range
@@ -577,6 +578,12 @@ class _Connection(object):
return context
+@contextlib.contextmanager
+def _closer(client):
+ yield
+ client.close()
+
+
class TCPClient(_Connection):
def __init__(self, address, source_address=None):
@@ -708,6 +715,7 @@ class TCPClient(_Connection):
self.connection = connection
self.ip_address = Address(connection.getpeername())
self._makefile()
+ return _closer(self)
def settimeout(self, n):
self.connection.settimeout(n)
@@ -833,6 +841,25 @@ class BaseHandler(_Connection):
return b""
+class Counter:
+ def __init__(self):
+ self._count = 0
+ self._lock = threading.Lock()
+
+ @property
+ def count(self):
+ with self._lock:
+ return self._count
+
+ def __enter__(self):
+ with self._lock:
+ self._count += 1
+
+ def __exit__(self, *args):
+ with self._lock:
+ self._count -= 1
+
+
class TCPServer(object):
request_queue_size = 20
@@ -845,15 +872,17 @@ class TCPServer(object):
self.socket.bind(self.address())
self.address = Address.wrap(self.socket.getsockname())
self.socket.listen(self.request_queue_size)
+ self.handler_counter = Counter()
def connection_thread(self, connection, client_address):
- client_address = Address(client_address)
- try:
- self.handle_client_connection(connection, client_address)
- except:
- self.handle_error(connection, client_address)
- finally:
- close_socket(connection)
+ with self.handler_counter:
+ client_address = Address(client_address)
+ try:
+ self.handle_client_connection(connection, client_address)
+ except:
+ self.handle_error(connection, client_address)
+ finally:
+ close_socket(connection)
def serve_forever(self, poll_interval=0.1):
self.__is_shut_down.clear()
diff --git a/pathod/language/generators.py b/pathod/language/generators.py
index a17e7052..9fff3082 100644
--- a/pathod/language/generators.py
+++ b/pathod/language/generators.py
@@ -2,17 +2,19 @@ import string
import random
import mmap
+import six
+
DATATYPES = dict(
- ascii_letters=string.ascii_letters,
- ascii_lowercase=string.ascii_lowercase,
- ascii_uppercase=string.ascii_uppercase,
- digits=string.digits,
- hexdigits=string.hexdigits,
- octdigits=string.octdigits,
- punctuation=string.punctuation,
- whitespace=string.whitespace,
- ascii=string.printable,
- bytes="".join(chr(i) for i in range(256))
+ ascii_letters=string.ascii_letters.encode(),
+ ascii_lowercase=string.ascii_lowercase.encode(),
+ ascii_uppercase=string.ascii_uppercase.encode(),
+ digits=string.digits.encode(),
+ hexdigits=string.hexdigits.encode(),
+ octdigits=string.octdigits.encode(),
+ punctuation=string.punctuation.encode(),
+ whitespace=string.whitespace.encode(),
+ ascii=string.printable.encode(),
+ bytes=bytes(bytearray(range(256)))
)
@@ -35,16 +37,25 @@ class TransformGenerator(object):
def __getitem__(self, x):
d = self.gen.__getitem__(x)
+ if isinstance(x, slice):
+ return self.transform(x.start, d)
return self.transform(x, d)
- def __getslice__(self, a, b):
- d = self.gen.__getslice__(a, b)
- return self.transform(a, d)
-
def __repr__(self):
return "'transform(%s)'" % self.gen
+def rand_byte(chars):
+ """
+ Return a random character as byte from a charset.
+ """
+ # bytearray has consistent behaviour on both Python 2 and 3
+ # while bytes does not
+ if six.PY2:
+ return random.choice(chars)
+ return bytes([random.choice(chars)])
+
+
class RandomGenerator(object):
def __init__(self, dtype, length):
@@ -55,12 +66,10 @@ class RandomGenerator(object):
return self.length
def __getitem__(self, x):
- return random.choice(DATATYPES[self.dtype])
-
- def __getslice__(self, a, b):
- b = min(b, self.length)
chars = DATATYPES[self.dtype]
- return "".join(random.choice(chars) for x in range(a, b))
+ if isinstance(x, slice):
+ return b"".join(rand_byte(chars) for _ in range(*x.indices(self.length)))
+ return rand_byte(chars)
def __repr__(self):
return "%s random from %s" % (self.length, self.dtype)
@@ -70,17 +79,17 @@ class FileGenerator(object):
def __init__(self, path):
self.path = path
- self.fp = file(path, "rb")
+ self.fp = open(path, "rb")
self.map = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ)
def __len__(self):
return len(self.map)
def __getitem__(self, x):
- return self.map.__getitem__(x)
-
- def __getslice__(self, a, b):
- return self.map.__getslice__(a, b)
+ if isinstance(x, slice):
+ return self.map.__getitem__(x)
+ # A slice of length 1 returns a byte object (not an integer)
+ return self.map.__getitem__(slice(x, x + 1 or self.map.size()))
def __repr__(self):
return "<%s" % self.path
diff --git a/pathod/pathoc.py b/pathod/pathoc.py
index 2b7d053c..5cfb4591 100644
--- a/pathod/pathoc.py
+++ b/pathod/pathoc.py
@@ -286,7 +286,7 @@ class Pathoc(tcp.TCPClient):
if self.use_http2 and not self.ssl:
raise NotImplementedError("HTTP2 without SSL is not supported.")
- tcp.TCPClient.connect(self)
+ ret = tcp.TCPClient.connect(self)
if connect_to:
self.http_connect(connect_to)
@@ -324,6 +324,7 @@ class Pathoc(tcp.TCPClient):
if self.timeout:
self.settimeout(self.timeout)
+ return ret
def stop(self):
if self.ws_framereader:
diff --git a/pathod/pathod.py b/pathod/pathod.py
index 7795df0e..0449c0c1 100644
--- a/pathod/pathod.py
+++ b/pathod/pathod.py
@@ -353,6 +353,8 @@ class Pathod(tcp.TCPServer):
staticdir=self.staticdir
)
+ self.loglock = threading.Lock()
+
def check_policy(self, req, settings):
"""
A policy check that verifies the request size is within limits.
@@ -403,8 +405,7 @@ class Pathod(tcp.TCPServer):
def add_log(self, d):
if not self.noapi:
- lock = threading.Lock()
- with lock:
+ with self.loglock:
d["id"] = self.logid
self.log.insert(0, d)
if len(self.log) > self.LOGBUF:
@@ -413,17 +414,18 @@ class Pathod(tcp.TCPServer):
return d["id"]
def clear_log(self):
- lock = threading.Lock()
- with lock:
+ with self.loglock:
self.log = []
def log_by_id(self, identifier):
- for i in self.log:
- if i["id"] == identifier:
- return i
+ with self.loglock:
+ for i in self.log:
+ if i["id"] == identifier:
+ return i
def get_log(self):
- return self.log
+ with self.loglock:
+ return self.log
def main(args): # pragma: no cover
diff --git a/pathod/protocols/websockets.py b/pathod/protocols/websockets.py
index 134d27bc..2b60e618 100644
--- a/pathod/protocols/websockets.py
+++ b/pathod/protocols/websockets.py
@@ -18,7 +18,7 @@ class WebsocketsProtocol:
frm = websockets.Frame.from_file(self.pathod_handler.rfile)
except NetlibException as e:
lg("Error reading websocket frame: %s" % e)
- break
+ return None, None
ended = time.time()
lg(frm.human_readable())
retlog = dict(
diff --git a/pathod/test.py b/pathod/test.py
index 23b7a5b6..11462729 100644
--- a/pathod/test.py
+++ b/pathod/test.py
@@ -1,12 +1,14 @@
from six.moves import cStringIO as StringIO
import threading
+import time
+
from six.moves import queue
-import requests
-import requests.packages.urllib3
from . import pathod
-requests.packages.urllib3.disable_warnings()
+
+class TimeoutError(Exception):
+ pass
class Daemon:
@@ -39,39 +41,51 @@ class Daemon:
"""
return "%s/p/%s" % (self.urlbase, spec)
- def info(self):
- """
- Return some basic info about the remote daemon.
- """
- resp = requests.get("%s/api/info" % self.urlbase, verify=False)
- return resp.json()
-
def text_log(self):
return self.logfp.getvalue()
+ def wait_for_silence(self, timeout=5):
+ start = time.time()
+ while 1:
+ if time.time() - start >= timeout:
+ raise TimeoutError(
+ "%s service threads still alive" %
+ self.thread.server.handler_counter.count
+ )
+ if self.thread.server.handler_counter.count == 0:
+ return
+
+ def expect_log(self, n, timeout=5):
+ l = []
+ start = time.time()
+ while True:
+ l = self.log()
+ if time.time() - start >= timeout:
+ return None
+ if len(l) >= n:
+ break
+ return l
+
def last_log(self):
"""
Returns the last logged request, or None.
"""
- l = self.log()
+ l = self.expect_log(1)
if not l:
return None
- return l[0]
+ return l[-1]
def log(self):
"""
Return the log buffer as a list of dictionaries.
"""
- resp = requests.get("%s/api/log" % self.urlbase, verify=False)
- return resp.json()["log"]
+ return self.thread.server.get_log()
def clear_log(self):
"""
Clear the log.
"""
- self.logfp.truncate(0)
- resp = requests.get("%s/api/clear_log" % self.urlbase, verify=False)
- return resp.ok
+ return self.thread.server.clear_log()
def shutdown(self):
"""
@@ -88,6 +102,7 @@ class _PaThread(threading.Thread):
self.name = "PathodThread"
self.iface, self.q, self.ssl = iface, q, ssl
self.daemonargs = daemonargs
+ self.server = None
def run(self):
self.server = pathod.Pathod(
diff --git a/setup.cfg b/setup.cfg
index e83ca0ab..eeaac0c8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -11,8 +11,7 @@ addopts = --capture=no
[coverage:run]
branch = True
-include = *mitmproxy*, *netlib*, *pathod*
-omit = *contrib*, *tnetstring*, *platform*, *console*, *main.py
+omit = *contrib*, *tnetstring*, *platform*, *main.py
[coverage:report]
show_missing = True
diff --git a/test/pathod/test_app.py b/test/pathod/test_app.py
index ac89c44c..fbaa773c 100644
--- a/test/pathod/test_app.py
+++ b/test/pathod/test_app.py
@@ -11,11 +11,11 @@ class TestApp(tutils.DaemonTests):
def test_about(self):
r = self.getpath("/about")
- assert r.ok
+ assert r.status_code == 200
def test_download(self):
r = self.getpath("/download")
- assert r.ok
+ assert r.status_code == 200
def test_docs(self):
assert self.getpath("/docs/pathod").status_code == 200
@@ -27,7 +27,7 @@ class TestApp(tutils.DaemonTests):
def test_log(self):
assert self.getpath("/log").status_code == 200
assert self.get("200:da").status_code == 200
- id = self.d.log()[0]["id"]
+ id = self.d.expect_log(1)[0]["id"]
assert self.getpath("/log").status_code == 200
assert self.getpath("/log/%s" % id).status_code == 200
assert self.getpath("/log/9999999").status_code == 404
diff --git a/test/pathod/test_language_generators.py b/test/pathod/test_language_generators.py
index 0fceae85..51f55991 100644
--- a/test/pathod/test_language_generators.py
+++ b/test/pathod/test_language_generators.py
@@ -7,24 +7,27 @@ import tutils
def test_randomgenerator():
g = generators.RandomGenerator("bytes", 100)
assert repr(g)
+ assert g[0]
+ assert len(g[0]) == 1
assert len(g[:10]) == 10
assert len(g[1:10]) == 9
assert len(g[:1000]) == 100
assert len(g[1000:1001]) == 0
- assert g[0]
def test_filegenerator():
with tutils.tmpdir() as t:
path = os.path.join(t, "foo")
f = open(path, "wb")
- f.write("x" * 10000)
+ f.write(b"x" * 10000)
f.close()
g = generators.FileGenerator(path)
assert len(g) == 10000
- assert g[0] == "x"
- assert g[-1] == "x"
- assert g[0:5] == "xxxxx"
+ assert g[0] == b"x"
+ assert g[-1] == b"x"
+ assert g[0:5] == b"xxxxx"
+ assert len(g[1:10]) == 9
+ assert len(g[10000:10001]) == 0
assert repr(g)
# remove all references to FileGenerator instance to close the file
# handle.
diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py
index 4d969158..ec9c169f 100644
--- a/test/pathod/test_pathod.py
+++ b/test/pathod/test_pathod.py
@@ -1,7 +1,6 @@
from six.moves import cStringIO as StringIO
-import pytest
-from pathod import pathod, version
+from pathod import pathod
from netlib import tcp
from netlib.exceptions import HttpException, TlsException
import tutils
@@ -129,7 +128,6 @@ class CommonTests(tutils.DaemonTests):
assert self.d.last_log()
# FIXME: Other binary data elements
- @pytest.mark.skip(reason="race condition")
def test_sizelimit(self):
r = self.get("200:b@1g")
assert r.status_code == 800
@@ -140,21 +138,15 @@ class CommonTests(tutils.DaemonTests):
r, _ = self.pathoc([r"get:'/p/200':i0,'\r\n'"])
assert r[0].status_code == 200
- def test_info(self):
- assert tuple(self.d.info()["version"]) == version.IVERSION
-
- @pytest.mark.skip(reason="race condition")
def test_logs(self):
- assert self.d.clear_log()
- assert not self.d.last_log()
+ self.d.clear_log()
assert self.get("202:da")
- assert len(self.d.log()) == 1
- assert self.d.clear_log()
+ assert self.d.expect_log(1)
+ self.d.clear_log()
assert len(self.d.log()) == 0
def test_disconnect(self):
- rsp = self.get("202:b@100k:d200")
- assert len(rsp.content) < 200
+ tutils.raises("unexpected eof", self.get, "202:b@100k:d200")
def test_parserr(self):
rsp = self.get("400:msg,b:")
@@ -166,7 +158,7 @@ class CommonTests(tutils.DaemonTests):
assert rsp.content.strip() == "testfile"
def test_anchor(self):
- rsp = self.getpath("anchor/foo")
+ rsp = self.getpath("/anchor/foo")
assert rsp.status_code == 202
def test_invalid_first_line(self):
@@ -223,7 +215,6 @@ class CommonTests(tutils.DaemonTests):
)
assert r[1].payload == "test"
- @pytest.mark.skip(reason="race condition")
def test_websocket_frame_reflect_error(self):
r, _ = self.pathoc(
["ws:/p/", "wf:-mask:knone:f'wf:b@10':i13,'a'"],
@@ -233,7 +224,6 @@ class CommonTests(tutils.DaemonTests):
# FIXME: Race Condition?
assert "Parse error" in self.d.text_log()
- @pytest.mark.skip(reason="race condition")
def test_websocket_frame_disconnect_error(self):
self.pathoc(["ws:/p/", "wf:b@10:d3"], ws_read_limit=0)
assert self.d.last_log()
diff --git a/test/pathod/test_test.py b/test/pathod/test_test.py
index cee286a4..6399894e 100644
--- a/test/pathod/test_test.py
+++ b/test/pathod/test_test.py
@@ -2,6 +2,10 @@ import logging
import requests
from pathod import test
import tutils
+
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
logging.disable(logging.CRITICAL)
diff --git a/test/pathod/tutils.py b/test/pathod/tutils.py
index f7bb22e5..b9f38d86 100644
--- a/test/pathod/tutils.py
+++ b/test/pathod/tutils.py
@@ -3,6 +3,7 @@ import re
import shutil
import requests
from six.moves import cStringIO as StringIO
+import urllib
from netlib import tcp
from netlib import utils
@@ -63,10 +64,11 @@ class DaemonTests(object):
shutil.rmtree(cls.confdir)
def teardown(self):
+ self.d.wait_for_silence()
if not (self.noweb or self.noapi):
self.d.clear_log()
- def getpath(self, path, params=None):
+ def _getpath(self, path, params=None):
scheme = "https" if self.ssl else "http"
resp = requests.get(
"%s://localhost:%s/%s" % (
@@ -79,9 +81,29 @@ class DaemonTests(object):
)
return resp
+ def getpath(self, path, params=None):
+ logfp = StringIO()
+ c = pathoc.Pathoc(
+ ("localhost", self.d.port),
+ ssl=self.ssl,
+ fp=logfp,
+ )
+ with c.connect():
+ if params:
+ path = path + "?" + urllib.urlencode(params)
+ resp = c.request("get:%s" % path)
+ return resp
+
def get(self, spec):
- resp = requests.get(self.d.p(spec), verify=False)
- return resp
+ logfp = StringIO()
+ c = pathoc.Pathoc(
+ ("localhost", self.d.port),
+ ssl=self.ssl,
+ fp=logfp,
+ )
+ with c.connect():
+ resp = c.request("get:/p/%s" % urllib.quote(spec).encode("string_escape"))
+ return resp
def pathoc(
self,
@@ -106,16 +128,16 @@ class DaemonTests(object):
fp=logfp,
use_http2=use_http2,
)
- c.connect(connect_to)
- ret = []
- for i in specs:
- resp = c.request(i)
- if resp:
- ret.append(resp)
- for frm in c.wait():
- ret.append(frm)
- c.stop()
- return ret, logfp.getvalue()
+ with c.connect(connect_to):
+ ret = []
+ for i in specs:
+ resp = c.request(i)
+ if resp:
+ ret.append(resp)
+ for frm in c.wait():
+ ret.append(frm)
+ c.stop()
+ return ret, logfp.getvalue()
tmpdir = tutils.tmpdir
diff --git a/web/package.json b/web/package.json
index b7f6ab2a..bb32a2dc 100644
--- a/web/package.json
+++ b/web/package.json
@@ -34,6 +34,7 @@
"babel-eslint": "^6.0.4",
"babel-jest": "^12.0.2",
"babel-plugin-transform-class-properties": "^6.6.0",
+ "babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babelify": "^7.3.0",
diff --git a/web/src/js/actions.js b/web/src/js/actions.js
index 6ded4c44..c77cdf73 100644
--- a/web/src/js/actions.js
+++ b/web/src/js/actions.js
@@ -1,6 +1,6 @@
import $ from "jquery";
-import _ from "lodash";
import {AppDispatcher} from "./dispatcher.js";
+import {fetchApi} from "./utils.js";
export var ActionTypes = {
// Connection
@@ -117,6 +117,16 @@ export var FlowActions = {
},
clear: function(){
$.post("/clear");
+ },
+ download: () => window.location = "/flows/dump",
+
+ upload: (file) => {
+ let data = new FormData();
+ data.append('file', file);
+ fetchApi("/flows/dump", {
+ method: 'post',
+ body: data
+ })
}
};
diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js
index e329b3f5..4152e95c 100644
--- a/web/src/js/components/header.js
+++ b/web/src/js/components/header.js
@@ -331,12 +331,19 @@ var FileMenu = React.createClass({
}
},
handleOpenClick: function (e) {
+ this.fileInput.click();
+ e.preventDefault();
+ },
+ handleOpenFile: function (e) {
+ if (e.target.files.length > 0) {
+ FlowActions.upload(e.target.files[0]);
+ this.fileInput.value = "";
+ }
e.preventDefault();
- console.error("unimplemented: handleOpenClick");
},
handleSaveClick: function (e) {
e.preventDefault();
- console.error("unimplemented: handleSaveClick");
+ FlowActions.download();
},
handleShutdownClick: function (e) {
e.preventDefault();
@@ -355,6 +362,20 @@ var FileMenu = React.createClass({
New
</a>
</li>
+ <li>
+ <a href="#" onClick={this.handleOpenClick}>
+ <i className="fa fa-fw fa-folder-open"></i>
+ Open...
+ </a>
+ <input ref={(ref) => this.fileInput = ref} className="hidden" type="file" onChange={this.handleOpenFile}/>
+
+ </li>
+ <li>
+ <a href="#" onClick={this.handleSaveClick}>
+ <i className="fa fa-fw fa-floppy-o"></i>
+ Save...
+ </a>
+ </li>
<li role="presentation" className="divider"></li>
<li>
<a href="http://mitm.it/" target="_blank">
@@ -363,18 +384,6 @@ var FileMenu = React.createClass({
</a>
</li>
{/*
- <li>
- <a href="#" onClick={this.handleOpenClick}>
- <i className="fa fa-fw fa-folder-open"></i>
- Open
- </a>
- </li>
- <li>
- <a href="#" onClick={this.handleSaveClick}>
- <i className="fa fa-fw fa-save"></i>
- Save
- </a>
- </li>
<li role="presentation" className="divider"></li>
<li>
<a href="#" onClick={this.handleShutdownClick}>
diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js
index e41b2eef..a16a0369 100644
--- a/web/src/js/store/store.js
+++ b/web/src/js/store/store.js
@@ -2,7 +2,7 @@
import _ from "lodash";
import $ from "jquery";
import {EventEmitter} from 'events';
-
+import { EventLogActions } from "../actions.js"
import {ActionTypes, StoreCmds} from "../actions.js";
import {AppDispatcher} from "../dispatcher.js";
diff --git a/web/src/js/utils.js b/web/src/js/utils.js
index 2627cf58..97737b20 100644
--- a/web/src/js/utils.js
+++ b/web/src/js/utils.js
@@ -80,7 +80,7 @@ function getCookie(name) {
var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b"));
return r ? r[1] : undefined;
}
-var xsrf = $.param({_xsrf: getCookie("_xsrf")});
+const xsrf = `_xsrf=${getCookie("_xsrf")}`;
//Tornado XSRF Protection.
$.ajaxPrefilter(function (options) {
@@ -101,4 +101,16 @@ $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
console.error(thrownError, message, arguments);
actions.EventLogActions.add_event(thrownError + ": " + message);
alert(message);
-}); \ No newline at end of file
+});
+
+export function fetchApi(url, options) {
+ if(url.indexOf("?") === -1){
+ url += "?" + xsrf;
+ } else {
+ url += "&" + xsrf;
+ }
+ return fetch(url, {
+ ...options,
+ credentials: 'same-origin'
+ });
+} \ No newline at end of file