diff options
-rw-r--r-- | mitmproxy/web/app.py | 39 | ||||
-rw-r--r-- | mitmproxy/web/static/app.css | 45 | ||||
-rw-r--r-- | mitmproxy/web/static/app.js | 491 | ||||
-rw-r--r-- | mitmproxy/web/static/vendor.js | 19 | ||||
-rw-r--r-- | web/src/css/header.less | 18 | ||||
-rw-r--r-- | web/src/js/components/common.js | 56 | ||||
-rw-r--r-- | web/src/js/components/footer.js | 32 | ||||
-rw-r--r-- | web/src/js/components/header.js | 109 |
8 files changed, 599 insertions, 210 deletions
diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index df235ee1..e25c9086 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -283,11 +283,21 @@ class Events(RequestHandler): class Settings(RequestHandler): def get(self): + self.write(dict( data=dict( version=version.VERSION, mode=str(self.master.server.config.mode), - intercept=self.state.intercept_txt + intercept=self.state.intercept_txt, + showhost=self.master.options.showhost, + no_upstream_cert=self.master.server.config.no_upstream_cert, + rawtcp=self.master.server.config.rawtcp, + http2=self.master.server.config.http2, + anticache=self.master.options.anticache, + anticomp=self.master.options.anticomp, + stickyauth=self.master.stickyauth_txt, + stickycookie=self.master.stickycookie_txt, + stream= self.master.stream_large_bodies.max_size if self.master.stream_large_bodies else False ) )) @@ -297,6 +307,33 @@ class Settings(RequestHandler): if k == "intercept": self.state.set_intercept(v) update[k] = v + elif k == "showhost": + self.master.options.showhost = v + update[k] = v + elif k == "no_upstream_cert": + self.master.server.config.no_upstream_cert = v + update[k] = v + elif k == "rawtcp": + self.master.server.config.rawtcp = v + update[k] = v + elif k == "http2": + self.master.server.config.http2 = v + update[k] = v + elif k == "anticache": + self.master.options.anticache = v + update[k] = v + elif k == "anticomp": + self.master.options.anticomp = v + update[k] = v + elif k == "stickycookie": + self.master.set_stickycookie(v) + update[k] = v + elif k == "stickyauth": + self.master.set_stickyauth(v) + update[k] = v + elif k == "stream": + self.master.set_stream_large_bodies(v) + update[k] = v else: print("Warning: Unknown setting {}: {}".format(k, v)) diff --git a/mitmproxy/web/static/app.css b/mitmproxy/web/static/app.css index 824dd827..ae2e963f 100644 --- a/mitmproxy/web/static/app.css +++ b/mitmproxy/web/static/app.css @@ -146,6 +146,7 @@ header .menu { min-height: 1px; padding-left: 2.5px; padding-right: 2.5px; + margin-bottom: 5px; } @media (min-width: 768px) { .filter-input { @@ -163,8 +164,48 @@ header .menu { max-height: 500px; overflow-y: auto; } -.menu .btn { - margin: 2px 2px 2px 2px; +.menu .toggle-btn { + float: left; + width: 33.33333333%; + position: relative; + min-height: 1px; + padding-left: 2.5px; + padding-right: 2.5px; + margin-bottom: 5px; +} +@media (min-width: 768px) { + .menu .toggle-btn { + float: left; + width: 25%; + } +} +@media (min-width: 1200px) { + .menu .toggle-btn { + float: left; + width: 16.66666667%; + } +} +.menu .toggle-btn .btn { + width: 100%; +} +.menu .toggle-input-btn { + position: relative; + min-height: 1px; + padding-left: 2.5px; + padding-right: 2.5px; + margin-bottom: 5px; +} +@media (min-width: 768px) { + .menu .toggle-input-btn { + float: left; + width: 50%; + } +} +@media (min-width: 1200px) { + .menu .toggle-input-btn { + float: left; + width: 33.33333333%; + } } .flow-table { width: 100%; diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 1e8e313d..e02df55a 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -481,7 +481,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de Object.defineProperty(exports, "__esModule", { value: true }); -exports.ToggleComponent = exports.Splitter = exports.Router = undefined; +exports.ToggleInputButton = exports.ToggleButton = exports.Splitter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require("react"); @@ -491,37 +493,19 @@ var _reactDom = require("react-dom"); var _reactDom2 = _interopRequireDefault(_reactDom); +var _utils = require("../utils.js"); + var _lodash = require("lodash"); var _lodash2 = _interopRequireDefault(_lodash); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var Router = exports.Router = { - contextTypes: { - location: _react2.default.PropTypes.object, - router: _react2.default.PropTypes.object.isRequired - }, - updateLocation: function updateLocation(pathname, queryUpdate) { - if (pathname === undefined) { - pathname = this.context.location.pathname; - } - var query = this.context.location.query; - if (queryUpdate !== undefined) { - for (var i in queryUpdate) { - if (queryUpdate.hasOwnProperty(i)) { - query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. - } - } - } - this.context.router.replace({ pathname: pathname, query: query }); - }, - getQuery: function getQuery() { - // For whatever reason, react-router always returns the same object, which makes comparing - // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _lodash2.default.clone(this.context.location.query); - } -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Splitter = exports.Splitter = _react2.default.createClass({ displayName: "Splitter", @@ -631,28 +615,91 @@ var Splitter = exports.Splitter = _react2.default.createClass({ } }); -var ToggleComponent = exports.ToggleComponent = function ToggleComponent(props) { +var ToggleButton = exports.ToggleButton = function ToggleButton(props) { return _react2.default.createElement( "div", - { - className: "btn " + (props.checked ? "btn-primary" : "btn-default"), - onClick: props.onToggleChanged }, + { className: "input-group toggle-btn" }, _react2.default.createElement( - "span", - null, - _react2.default.createElement("i", { className: "fa " + (props.checked ? "fa-check-square-o" : "fa-square-o") }), - " ", - props.name + "div", + { + className: "btn " + (props.checked ? "btn-primary" : "btn-default"), + onClick: props.onToggleChanged }, + _react2.default.createElement( + "span", + { className: "fa " + (props.checked ? "fa-check-square-o" : "fa-square-o") }, + " ", + props.name + ) ) ); }; -ToggleComponent.propTypes = { +ToggleButton.propTypes = { + name: _react2.default.PropTypes.string.isRequired, + onToggleChanged: _react2.default.PropTypes.func.isRequired +}; + +var ToggleInputButton = exports.ToggleInputButton = function (_React$Component) { + _inherits(ToggleInputButton, _React$Component); + + function ToggleInputButton(props) { + _classCallCheck(this, ToggleInputButton); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ToggleInputButton).call(this, props)); + + _this.state = { txt: props.txt }; + return _this; + } + + _createClass(ToggleInputButton, [{ + key: "render", + value: function render() { + var _this2 = this; + + return _react2.default.createElement( + "div", + { className: "input-group toggle-input-btn" }, + _react2.default.createElement( + "span", + { + className: "input-group-btn", + onClick: function onClick() { + return _this2.props.onToggleChanged(_this2.state.txt); + } }, + _react2.default.createElement( + "div", + { className: "btn " + (this.props.checked ? "btn-primary" : "btn-default") }, + _react2.default.createElement("span", { className: "fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o") }), + " ", + this.props.name + ) + ), + _react2.default.createElement("input", { + className: "form-control", + placeholder: this.props.placeholder, + disabled: this.props.checked, + value: this.state.txt, + type: this.props.inputType, + onChange: function onChange(e) { + return _this2.setState({ txt: e.target.value }); + }, + onKeyDown: function onKeyDown(e) { + if (e.keyCode === _utils.Key.ENTER) _this2.props.onToggleChanged(_this2.state.txt);e.stopPropagation(); + } }) + ); + } + }]); + + return ToggleInputButton; +}(_react2.default.Component); + +ToggleInputButton.propTypes = { name: _react2.default.PropTypes.string.isRequired, + txt: _react2.default.PropTypes.string.isRequired, onToggleChanged: _react2.default.PropTypes.func.isRequired }; -},{"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ +},{"../utils.js":27,"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -931,8 +978,6 @@ var _shallowequal = require("shallowequal"); var _shallowequal2 = _interopRequireDefault(_shallowequal); -var _common = require("./common.js"); - var _actions = require("../actions.js"); var _AutoScroll = require("./helpers/AutoScroll"); @@ -1120,8 +1165,6 @@ var AutoScrollEventLog = (0, _AutoScroll2.default)(EventLogContents); var EventLog = _react2.default.createClass({ displayName: "EventLog", - - mixins: [_common.Router], getInitialState: function getInitialState() { return { filter: { @@ -1134,7 +1177,7 @@ var EventLog = _react2.default.createClass({ close: function close() { var d = {}; d[_actions.Query.SHOW_EVENTLOG] = undefined; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, toggleLevel: function toggleLevel(level) { var filter = _lodash2.default.extend({}, this.state.filter); @@ -1165,7 +1208,7 @@ var EventLog = _react2.default.createClass({ exports.default = EventLog; -},{"../actions.js":2,"../store/view.js":26,"./common.js":4,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],7:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":26,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1749,13 +1792,17 @@ var _utils2 = require("../../utils.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var ViewImage = _react2.default.createClass({ displayName: "ViewImage", + propTypes: { + flow: _react2.default.PropTypes.object.isRequired, + message: _react2.default.PropTypes.object.isRequired + }, statics: { + regex: /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i, matches: function matches(message) { - return image_regex.test(_utils.MessageUtils.getContentType(message)); + return ViewImage.regex.test(_utils.MessageUtils.getContentType(message)); } }, render: function render() { @@ -1768,7 +1815,13 @@ var ViewImage = _react2.default.createClass({ } }); -var RawMixin = { +var ContentLoader = _react2.default.createClass({ + displayName: "ContentLoader", + + propTypes: { + flow: _react2.default.PropTypes.object.isRequired, + message: _react2.default.PropTypes.object.isRequired + }, getInitialState: function getInitialState() { return { content: undefined, @@ -1816,43 +1869,53 @@ var RawMixin = { _react2.default.createElement("i", { className: "fa fa-spinner fa-spin" }) ); } - return this.renderContent(); + return _react2.default.cloneElement(this.props.children, { + content: this.state.content + }); } -}; +}); var ViewRaw = _react2.default.createClass({ displayName: "ViewRaw", - mixins: [RawMixin], + propTypes: { + content: _react2.default.PropTypes.string.isRequired + }, statics: { + textView: true, matches: function matches(message) { return true; } }, - renderContent: function renderContent() { + render: function render() { return _react2.default.createElement( "pre", null, - this.state.content + this.props.content ); } }); -var json_regex = /^application\/json$/i; var ViewJSON = _react2.default.createClass({ displayName: "ViewJSON", - mixins: [RawMixin], + propTypes: { + content: _react2.default.PropTypes.string.isRequired + }, statics: { + textView: true, + regex: /^application\/json$/i, matches: function matches(message) { - return json_regex.test(_utils.MessageUtils.getContentType(message)); + return ViewJSON.regex.test(_utils.MessageUtils.getContentType(message)); } }, - renderContent: function renderContent() { - var json = this.state.content; + render: function render() { + var json = this.props.content; try { json = JSON.stringify(JSON.parse(json), null, 2); - } catch (e) {} + } catch (e) { + // @noop + } return _react2.default.createElement( "pre", null, @@ -1864,6 +1927,10 @@ var ViewJSON = _react2.default.createClass({ var ViewAuto = _react2.default.createClass({ displayName: "ViewAuto", + propTypes: { + message: _react2.default.PropTypes.object.isRequired, + flow: _react2.default.PropTypes.object.isRequired + }, statics: { matches: function matches() { return false; // don't match itself @@ -1878,8 +1945,20 @@ var ViewAuto = _react2.default.createClass({ } }, render: function render() { + var _props = this.props; + var message = _props.message; + var flow = _props.flow; + var View = ViewAuto.findView(this.props.message); - return _react2.default.createElement(View, this.props); + if (View.textView) { + return _react2.default.createElement( + ContentLoader, + { message: message, flow: flow }, + _react2.default.createElement(View, { content: "" }) + ); + } else { + return _react2.default.createElement(View, { message: message, flow: flow }); + } } }); @@ -2004,6 +2083,10 @@ var ContentView = _react2.default.createClass({ } }, render: function render() { + var _props2 = this.props; + var flow = _props2.flow; + var message = _props2.message; + var message = this.props.message; if (message.contentLength === 0) { return _react2.default.createElement(ContentEmpty, this.props); @@ -2018,7 +2101,11 @@ var ContentView = _react2.default.createClass({ return _react2.default.createElement( "div", null, - _react2.default.createElement(this.state.View, this.props), + this.state.View.textView ? _react2.default.createElement( + ContentLoader, + { flow: flow, message: message }, + _react2.default.createElement(this.state.View, { content: "" }) + ) : _react2.default.createElement(this.state.View, { flow: flow, message: message }), _react2.default.createElement( "div", { className: "view-options text-center" }, @@ -2315,8 +2402,6 @@ var _react = require("react"); var _react2 = _interopRequireDefault(_react); -var _common = require("../common.js"); - var _nav = require("./nav.js"); var _nav2 = _interopRequireDefault(_nav); @@ -2343,7 +2428,6 @@ var allTabs = { var FlowView = _react2.default.createClass({ displayName: "FlowView", - mixins: [_common.StickyHeadMixin, _common.Router], getInitialState: function getInitialState() { return { prompt: false @@ -2367,7 +2451,7 @@ var FlowView = _react2.default.createClass({ this.selectTab(tabs[nextIndex]); }, selectTab: function selectTab(panel) { - this.updateLocation("/flows/" + this.props.flow.id + "/" + panel); + this.props.updateLocation("/flows/" + this.props.flow.id + "/" + panel); }, promptEdit: function promptEdit() { var options; @@ -2436,7 +2520,7 @@ var FlowView = _react2.default.createClass({ exports.default = FlowView; -},{"../common.js":4,"../prompt.js":19,"./details.js":10,"./messages.js":12,"./nav.js":13,"react":"react"}],12:[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", { @@ -2894,6 +2978,8 @@ var _react = require("react"); var _react2 = _interopRequireDefault(_react); +var _utils = require("../utils.js"); + var _common = require("./common.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -2906,6 +2992,15 @@ function Footer(_ref) { var settings = _ref.settings; var mode = settings.mode; var intercept = settings.intercept; + var showhost = settings.showhost; + var no_upstream_cert = settings.no_upstream_cert; + var rawtcp = settings.rawtcp; + var http2 = settings.http2; + var anticache = settings.anticache; + var anticomp = settings.anticomp; + var stickyauth = settings.stickyauth; + var stickycookie = settings.stickycookie; + var stream = settings.stream; return _react2.default.createElement( "footer", @@ -2921,19 +3016,65 @@ function Footer(_ref) { { className: "label label-success" }, "Intercept: ", intercept + ), + showhost && _react2.default.createElement( + "span", + { className: "label label-success" }, + "showhost" + ), + no_upstream_cert && _react2.default.createElement( + "span", + { className: "label label-success" }, + "no-upstream-cert" + ), + rawtcp && _react2.default.createElement( + "span", + { className: "label label-success" }, + "raw-tcp" + ), + !http2 && _react2.default.createElement( + "span", + { className: "label label-success" }, + "no-http2" + ), + anticache && _react2.default.createElement( + "span", + { className: "label label-success" }, + "anticache" + ), + anticomp && _react2.default.createElement( + "span", + { className: "label label-success" }, + "anticomp" + ), + stickyauth && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stickyauth: ", + stickyauth + ), + stickycookie && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stickycookie: ", + stickycookie + ), + stream && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stream: ", + (0, _utils.formatSize)(stream) ) ); } -},{"./common.js":4,"react":"react"}],15:[function(require,module,exports){ +},{"../utils.js":27,"./common.js":4,"react":"react"}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Header = exports.MainMenu = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); +exports.Header = exports.OptionMenu = exports.MainMenu = undefined; var _react = require("react"); @@ -2959,12 +3100,6 @@ var _actions = require("../actions.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - var FilterDocs = _react2.default.createClass({ displayName: "FilterDocs", @@ -3151,7 +3286,6 @@ var FilterInput = _react2.default.createClass({ var MainMenu = exports.MainMenu = _react2.default.createClass({ displayName: "MainMenu", - mixins: [_common.Router], propTypes: { settings: _react2.default.PropTypes.object.isRequired }, @@ -3162,19 +3296,19 @@ var MainMenu = exports.MainMenu = _react2.default.createClass({ onSearchChange: function onSearchChange(val) { var d = {}; d[_actions.Query.SEARCH] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onHighlightChange: function onHighlightChange(val) { var d = {}; d[_actions.Query.HIGHLIGHT] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onInterceptChange: function onInterceptChange(val) { _actions.SettingsActions.update({ intercept: val }); }, render: function render() { - var search = this.getQuery()[_actions.Query.SEARCH] || ""; - var highlight = this.getQuery()[_actions.Query.HIGHLIGHT] || ""; + var search = this.props.query[_actions.Query.SEARCH] || ""; + var highlight = this.props.query[_actions.Query.HIGHLIGHT] || ""; var intercept = this.props.settings.intercept || ""; return _react2.default.createElement( @@ -3217,78 +3351,122 @@ var ViewMenu = _react2.default.createClass({ title: "View", route: "flows" }, - mixins: [_common.Router], toggleEventLog: function toggleEventLog() { var d = {}; - if (this.getQuery()[_actions.Query.SHOW_EVENTLOG]) { + if (this.props.query[_actions.Query.SHOW_EVENTLOG]) { d[_actions.Query.SHOW_EVENTLOG] = undefined; } else { d[_actions.Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); console.log('toggleevent'); }, render: function render() { - var showEventLog = this.getQuery()[_actions.Query.SHOW_EVENTLOG]; + var showEventLog = this.props.query[_actions.Query.SHOW_EVENTLOG]; return _react2.default.createElement( "div", null, - _react2.default.createElement(_common.ToggleComponent, { - checked: showEventLog, - name: "Show Eventlog", - onToggleChanged: this.toggleEventLog }) + _react2.default.createElement( + "div", + { className: "menu-row" }, + _react2.default.createElement(_common.ToggleButton, { + checked: showEventLog, + name: "Show Eventlog", + onToggleChanged: this.toggleEventLog }) + ), + _react2.default.createElement("div", { className: "clearfix" }) ); } }); -var OptionMenu = function (_React$Component) { - _inherits(OptionMenu, _React$Component); - - function OptionMenu(props) { - _classCallCheck(this, OptionMenu); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(OptionMenu).call(this, props)); - - _this.state = { - options: [{ name: "--host", checked: true }, { name: "--no-upstream-cert", checked: false }, { name: "--http2", checked: false }, { name: "--anticache", checked: false }, { name: "--anticomp", checked: false }, { name: "--stickycookie", checked: true }, { name: "--stickyauth", checked: false }, { name: "--stream", checked: false }] - }; - return _this; - } - - _createClass(OptionMenu, [{ - key: "setOption", - value: function setOption(entry) { - console.log(entry.name); //TODO: get options from outside and remove state - entry.checked = !entry.checked; - this.setState({ options: this.state.options }); - } - }, { - key: "render", - value: function render() { - var _this2 = this; - - return _react2.default.createElement( - "div", - null, - this.state.options.map(function (entry, i) { - return _react2.default.createElement(_common.ToggleComponent, { - key: i, - checked: entry.checked, - name: entry.name, - onToggleChanged: function onToggleChanged() { - return _this2.setOption(entry); - } }); - }) - ); - } - }]); - - return OptionMenu; -}(_react2.default.Component); +var OptionMenu = exports.OptionMenu = function OptionMenu(props) { + var _props$settings = props.settings; + var mode = _props$settings.mode; + var intercept = _props$settings.intercept; + var showhost = _props$settings.showhost; + var no_upstream_cert = _props$settings.no_upstream_cert; + var rawtcp = _props$settings.rawtcp; + var http2 = _props$settings.http2; + var anticache = _props$settings.anticache; + var anticomp = _props$settings.anticomp; + var stickycookie = _props$settings.stickycookie; + var stickyauth = _props$settings.stickyauth; + var stream = _props$settings.stream; + return _react2.default.createElement( + "div", + null, + _react2.default.createElement( + "div", + { className: "menu-row" }, + _react2.default.createElement(_common.ToggleButton, { name: "showhost", + checked: showhost, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ showhost: !showhost }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "no_upstream_cert", + checked: no_upstream_cert, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ no_upstream_cert: !no_upstream_cert }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "rawtcp", + checked: rawtcp, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ rawtcp: !rawtcp }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "http2", + checked: http2, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ http2: !http2 }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "anticache", + checked: anticache, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ anticache: !anticache }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "anticomp", + checked: anticomp, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ anticomp: !anticomp }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stickyauth", placeholder: "Sticky auth filter", + checked: Boolean(stickyauth), + txt: stickyauth || "", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stickyauth: !stickyauth ? txt : null }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stickycookie", placeholder: "Sticky cookie filter", + checked: Boolean(stickycookie), + txt: stickycookie || "", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stickycookie: !stickycookie ? txt : null }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stream", placeholder: "stream...", + checked: Boolean(stream), + txt: stream || "", + inputType: "number", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stream: !stream ? txt : null }); + } + }) + ), + _react2.default.createElement("div", { className: "clearfix" }) + ); +}; OptionMenu.title = "Options"; +OptionMenu.propTypes = { + settings: _react2.default.PropTypes.object.isRequired +}; var ReportsMenu = _react2.default.createClass({ displayName: "ReportsMenu", @@ -3391,7 +3569,6 @@ var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */]; var Header = exports.Header = _react2.default.createClass({ displayName: "Header", - mixins: [_common.Router], propTypes: { settings: _react2.default.PropTypes.object.isRequired }, @@ -3402,7 +3579,7 @@ var Header = exports.Header = _react2.default.createClass({ }, handleClick: function handleClick(active, e) { e.preventDefault(); - this.updateLocation(active.route); + this.props.updateLocation(active.route); this.setState({ active: active }); }, render: function render() { @@ -3435,7 +3612,11 @@ var Header = exports.Header = _react2.default.createClass({ _react2.default.createElement( "div", { className: "menu" }, - _react2.default.createElement(this.state.active, { ref: "active", settings: this.props.settings }) + _react2.default.createElement(this.state.active, { + settings: this.props.settings, + updateLocation: this.props.updateLocation, + query: this.props.query + }) ) ); } @@ -3622,7 +3803,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de var MainView = _react2.default.createClass({ displayName: "MainView", - mixins: [_common.Router], contextTypes: { flowStore: _react2.default.PropTypes.object.isRequired }, @@ -3653,11 +3833,11 @@ var MainView = _react2.default.createClass({ }, getViewFilt: function getViewFilt() { try { - var filtStr = this.getQuery()[_actions.Query.SEARCH]; + var filtStr = this.props.query[_actions.Query.SEARCH]; var filt = filtStr ? _filt2.default.parse(filtStr) : function () { return true; }; - var highlightStr = this.getQuery()[_actions.Query.HIGHLIGHT]; + var highlightStr = this.props.query[_actions.Query.HIGHLIGHT]; var highlight = highlightStr ? _filt2.default.parse(highlightStr) : function () { return false; }; @@ -3710,10 +3890,10 @@ var MainView = _react2.default.createClass({ selectFlow: function selectFlow(flow) { if (flow) { var tab = this.props.routeParams.detailTab || "request"; - this.updateLocation("/flows/" + flow.id + "/" + tab); + this.props.updateLocation("/flows/" + flow.id + "/" + tab); this.refs.flowTable.scrollIntoView(flow); } else { - this.updateLocation("/flows"); + this.props.updateLocation("/flows"); } }, selectFlowRelative: function selectFlowRelative(shift) { @@ -3837,6 +4017,8 @@ var MainView = _react2.default.createClass({ key: "flowDetails", ref: "flowDetails", tab: this.props.routeParams.detailTab, + query: this.props.query, + updateLocation: this.props.updateLocation, flow: selected })]; } else { details = null; @@ -4054,13 +4236,34 @@ var Reports = _react2.default.createClass({ var ProxyAppMain = _react2.default.createClass({ displayName: "ProxyAppMain", - mixins: [_common.Router], childContextTypes: { flowStore: _react2.default.PropTypes.object.isRequired, eventStore: _react2.default.PropTypes.object.isRequired, returnFocus: _react2.default.PropTypes.func.isRequired, location: _react2.default.PropTypes.object.isRequired }, + contextTypes: { + router: _react2.default.PropTypes.object.isRequired + }, + updateLocation: function updateLocation(pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.props.location.pathname; + } + var query = this.props.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } + } + } + this.context.router.replace({ pathname: pathname, query: query }); + }, + getQuery: function getQuery() { + // For whatever reason, react-router always returns the same object, which makes comparing + // the current props with nextProps impossible. As a workaround, we just clone the query object. + return _lodash2.default.clone(this.props.location.query); + }, componentDidMount: function componentDidMount() { this.focus(); this.settingsStore.addListener("recalculate", this.onSettingsChange); @@ -4130,18 +4333,18 @@ var ProxyAppMain = _react2.default.createClass({ e.preventDefault(); }, render: function render() { + var query = this.getQuery(); var eventlog; if (this.props.location.query[_actions.Query.SHOW_EVENTLOG]) { - eventlog = [_react2.default.createElement(_common.Splitter, { key: "splitter", axis: "y" }), _react2.default.createElement(_eventlog2.default, { key: "eventlog" })]; + eventlog = [_react2.default.createElement(_common.Splitter, { key: "splitter", axis: "y" }), _react2.default.createElement(_eventlog2.default, { key: "eventlog", updateLocation: this.updateLocation })]; } else { eventlog = null; } - var children = _react2.default.cloneElement(this.props.children, { ref: "view", location: this.props.location }); return _react2.default.createElement( "div", { id: "container", tabIndex: "0", onKeyDown: this.onKeydown }, - _react2.default.createElement(_header.Header, { ref: "header", settings: this.state.settings }), - children, + _react2.default.createElement(_header.Header, { ref: "header", settings: this.state.settings, updateLocation: this.updateLocation, query: query }), + _react2.default.cloneElement(this.props.children, { ref: "view", location: this.props.location, updateLocation: this.updateLocation, query: query }), eventlog, _react2.default.createElement(_footer2.default, { settings: this.state.settings }) ); diff --git a/mitmproxy/web/static/vendor.js b/mitmproxy/web/static/vendor.js index 44bc86d8..4b2ff6bf 100644 --- a/mitmproxy/web/static/vendor.js +++ b/mitmproxy/web/static/vendor.js @@ -3894,6 +3894,7 @@ function compilePattern(pattern) { * - ** Consumes (greedy) all characters up to the next character * in the pattern, or to the end of the URL if there is none * + * The function calls callback(error, matched) when finished. * The return value is an object with the following properties: * * - remainingPathname @@ -6093,13 +6094,17 @@ function matchRouteDeep(route, location, remainingPathname, paramNames, paramVal // Only try to match the path if the route actually has a pattern, and if // we're not just searching for potential nested absolute paths. if (remainingPathname !== null && pattern) { - var matched = (0, _PatternUtils.matchPattern)(pattern, remainingPathname); - if (matched) { - remainingPathname = matched.remainingPathname; - paramNames = [].concat(paramNames, matched.paramNames); - paramValues = [].concat(paramValues, matched.paramValues); - } else { - remainingPathname = null; + try { + var matched = (0, _PatternUtils.matchPattern)(pattern, remainingPathname); + if (matched) { + remainingPathname = matched.remainingPathname; + paramNames = [].concat(paramNames, matched.paramNames); + paramValues = [].concat(paramValues, matched.paramValues); + } else { + remainingPathname = null; + } + } catch (error) { + callback(error); } // By assumption, pattern is non-empty here, which is the prerequisite for diff --git a/web/src/css/header.less b/web/src/css/header.less index 065471d1..b1bd9c04 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -18,6 +18,7 @@ header { .filter-input { .make-sm-column(3, @menu-row-gutter-width); + margin-bottom:5px; } .filter-input .popover { @@ -32,6 +33,19 @@ header { } } -.menu .btn { - margin: 2px 2px 2px 2px; +.menu .toggle-btn { + .make-xs-column(4, @menu-row-gutter-width); + .make-sm-column(3, @menu-row-gutter-width); + .make-lg-column(2, @menu-row-gutter-width); + margin-bottom:5px; +} + +.menu .toggle-btn .btn { + width: 100%; +} + +.menu .toggle-input-btn { + .make-sm-column(6, @menu-row-gutter-width); + .make-lg-column(4, @menu-row-gutter-width); + margin-bottom:5px; }
\ No newline at end of file diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index b257b82c..87c34ffc 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -1,5 +1,6 @@ import React from "react" import ReactDOM from "react-dom" +import {Key} from "../utils.js"; import _ from "lodash" export var Splitter = React.createClass({ @@ -107,14 +108,55 @@ export var Splitter = React.createClass({ } }); -export const ToggleComponent = (props) => - <div - className={"btn " + (props.checked ? "btn-primary" : "btn-default")} - onClick={props.onToggleChanged}> - <span><i className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}></i> {props.name}</span> - </div> +export const ToggleButton = (props) => + <div className="input-group toggle-btn"> + <div + className={"btn " + (props.checked ? "btn-primary" : "btn-default")} + onClick={props.onToggleChanged}> + <span className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}> {props.name}</span> + </div> + </div>; -ToggleComponent.propTypes = { +ToggleButton.propTypes = { name: React.PropTypes.string.isRequired, onToggleChanged: React.PropTypes.func.isRequired +}; + +export class ToggleInputButton extends React.Component { + constructor(props) { + super(props); + this.state = {txt: props.txt}; + } + + render() { + return ( + <div className="input-group toggle-input-btn"> + <span + className="input-group-btn" + onClick={() => this.props.onToggleChanged(this.state.txt)}> + <div className={"btn " + (this.props.checked ? "btn-primary" : "btn-default")}> + <span className={"fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o")}/> + {this.props.name} + </div> + </span> + <input + className="form-control" + placeholder={this.props.placeholder} + disabled={this.props.checked} + value={this.state.txt} + type={this.props.inputType} + onChange={e => this.setState({txt: e.target.value})} + onKeyDown={e => {if (e.keyCode === Key.ENTER) this.props.onToggleChanged(this.state.txt); e.stopPropagation()}}/> + </div> + ); + } } + +ToggleInputButton.propTypes = { + name: React.PropTypes.string.isRequired, + txt: React.PropTypes.string.isRequired, + onToggleChanged: React.PropTypes.func.isRequired +}; + + + diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js index e2d96288..8fe1081b 100644 --- a/web/src/js/components/footer.js +++ b/web/src/js/components/footer.js @@ -1,4 +1,5 @@ import React from "react"; +import {formatSize} from "../utils.js" import {SettingsState} from "./common.js"; Footer.propTypes = { @@ -6,7 +7,7 @@ Footer.propTypes = { }; export default function Footer({ settings }) { - const {mode, intercept} = settings; + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings; return ( <footer> {mode && mode != "regular" && ( @@ -15,6 +16,35 @@ export default function Footer({ settings }) { {intercept && ( <span className="label label-success">Intercept: {intercept}</span> )} + {showhost && ( + <span className="label label-success">showhost</span> + )} + {no_upstream_cert && ( + <span className="label label-success">no-upstream-cert</span> + )} + {rawtcp && ( + <span className="label label-success">raw-tcp</span> + )} + {!http2 && ( + <span className="label label-success">no-http2</span> + )} + {anticache && ( + <span className="label label-success">anticache</span> + )} + {anticomp && ( + <span className="label label-success">anticomp</span> + )} + {stickyauth && ( + <span className="label label-success">stickyauth: {stickyauth}</span> + )} + {stickycookie && ( + <span className="label label-success">stickycookie: {stickycookie}</span> + )} + {stream && ( + <span className="label label-success">stream: {formatSize(stream)}</span> + )} + + </footer> ); } diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 555babbb..643659c3 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,9 +4,10 @@ import $ from "jquery"; import Filt from "../filt/filt.js"; import {Key} from "../utils.js"; -import {ToggleComponent} from "./common.js"; +import {ToggleInputButton, ToggleButton} from "./common.js"; import {SettingsActions, FlowActions} from "../actions.js"; import {Query} from "../actions.js"; +import {SettingsState} from "./common.js"; var FilterDocs = React.createClass({ statics: { @@ -237,57 +238,74 @@ var ViewMenu = React.createClass({ render: function () { var showEventLog = this.props.query[Query.SHOW_EVENTLOG]; return ( - <div> - <ToggleComponent - checked={showEventLog} - name = "Show Eventlog" - onToggleChanged={this.toggleEventLog}/> - </div> + <div> + <div className="menu-row"> + <ToggleButton + checked={showEventLog} + name = "Show Eventlog" + onToggleChanged={this.toggleEventLog}/> + </div> + <div className="clearfix"></div> + </div> ); } }); - -class OptionMenu extends React.Component{ - static title = "Options"; - constructor(props){ - super(props); - this.state = { - options : - [ - {name: "--host", checked: true}, - {name: "--no-upstream-cert", checked: false}, - {name: "--http2", checked: false}, - {name: "--anticache", checked: false}, - {name: "--anticomp", checked: false}, - {name: "--stickycookie", checked: true}, - {name: "--stickyauth", checked: false}, - {name: "--stream", checked: false} - ] - } - } - setOption(entry){ - console.log(entry.name);//TODO: get options from outside and remove state - entry.checked = !entry.checked; - this.setState({options: this.state.options}); - } - render() { - return ( +export const OptionMenu = (props) => { + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickycookie, stickyauth, stream} = props.settings; + return ( <div> - {this.state.options.map((entry, i) => { - return ( - <ToggleComponent - key={i} - checked={entry.checked} - name = {entry.name} - onToggleChanged={() => this.setOption(entry)}/> - ); - })} + <div className="menu-row"> + <ToggleButton name="showhost" + checked={showhost} + onToggleChanged={() => SettingsActions.update({showhost: !showhost})} + /> + <ToggleButton name="no_upstream_cert" + checked={no_upstream_cert} + onToggleChanged={() => SettingsActions.update({no_upstream_cert: !no_upstream_cert})} + /> + <ToggleButton name="rawtcp" + checked={rawtcp} + onToggleChanged={() => SettingsActions.update({rawtcp: !rawtcp})} + /> + <ToggleButton name="http2" + checked={http2} + onToggleChanged={() => SettingsActions.update({http2: !http2})} + /> + <ToggleButton name="anticache" + checked={anticache} + onToggleChanged={() => SettingsActions.update({anticache: !anticache})} + /> + <ToggleButton name="anticomp" + checked={anticomp} + onToggleChanged={() => SettingsActions.update({anticomp: !anticomp})} + /> + <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" + checked={Boolean(stickyauth)} + txt={stickyauth || ""} + onToggleChanged={txt => SettingsActions.update({stickyauth: (!stickyauth ? txt : null)})} + /> + <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter" + checked={Boolean(stickycookie)} + txt={stickycookie || ""} + onToggleChanged={txt => SettingsActions.update({stickycookie: (!stickycookie ? txt : null)})} + /> + <ToggleInputButton name="stream" placeholder="stream..." + checked={Boolean(stream)} + txt={stream || ""} + inputType = "number" + onToggleChanged={txt => SettingsActions.update({stream: (!stream ? txt : null)})} + /> + </div> + <div className="clearfix"/> </div> - ); - } -} + ); +}; +OptionMenu.title = "Options"; +OptionMenu.propTypes = { + settings: React.PropTypes.object.isRequired +}; var ReportsMenu = React.createClass({ statics: { @@ -428,7 +446,6 @@ export var Header = React.createClass({ </nav> <div className="menu"> <this.state.active - ref="active" settings={this.props.settings} updateLocation={this.props.updateLocation} query={this.props.query} |