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}  | 
