From 0077e0f8d91ba207574e359247f02e3d3203476a Mon Sep 17 00:00:00 2001 From: yonder Date: Tue, 1 Mar 2016 17:40:32 +0800 Subject: [PATCH 01/19] Add upstream proxy authentication --- mitmproxy/cmdline.py | 8 ++++++++ mitmproxy/models/http.py | 5 +++++ mitmproxy/protocol/http.py | 3 +++ mitmproxy/proxy/config.py | 7 ++++++- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index 3e9fa011c..e6f0bc20b 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -370,6 +370,14 @@ def proxy_options(parser): If your OpenSSL version supports ALPN, HTTP/2 is enabled by default. """ ) + parser.add_argument( + "--upstream-auth", + action="store", dest="upstream_auth", default=None, + help=""" + Proxy Authentication: + username:password + """ + ) rawtcp = group.add_mutually_exclusive_group() rawtcp.add_argument("--raw-tcp", action="store_true", dest="rawtcp") rawtcp.add_argument("--no-raw-tcp", action="store_false", dest="rawtcp", diff --git a/mitmproxy/models/http.py b/mitmproxy/models/http.py index 394fe51aa..5d79306b2 100644 --- a/mitmproxy/models/http.py +++ b/mitmproxy/models/http.py @@ -2,6 +2,7 @@ from __future__ import (absolute_import, print_function, division) import Cookie import copy import warnings +import base64 from email.utils import parsedate_tz, formatdate, mktime_tz import time @@ -192,6 +193,10 @@ class HTTPRequest(MessageMixin, Request): def __hash__(self): return id(self) + def set_auth(self, auth): + auth_str = "Basic" + " " + base64.b64encode(auth) + self.data.headers.set_all("Proxy-Authorization", (auth_str,)) + def replace(self, pattern, repl, *args, **kwargs): """ Replaces a regular expression pattern with repl in the headers, the diff --git a/mitmproxy/protocol/http.py b/mitmproxy/protocol/http.py index 13d7903bf..81e59fbb3 100644 --- a/mitmproxy/protocol/http.py +++ b/mitmproxy/protocol/http.py @@ -179,6 +179,9 @@ class HttpLayer(Layer): try: flow = HTTPFlow(self.client_conn, self.server_conn, live=self) flow.request = request + # set upstream auth + if self.mode == "upstream" and self.config.upstream_auth is not None: + flow.request.set_auth(self.config.upstream_auth) self.process_request_hook(flow) if not flow.response: diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index 490cf20c9..149d47105 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -53,6 +53,7 @@ class ProxyConfig: body_size_limit=None, mode="regular", upstream_server=None, + upstream_auth = None, authenticator=None, ignore_hosts=tuple(), tcp_hosts=tuple(), @@ -77,8 +78,10 @@ class ProxyConfig: self.mode = mode if upstream_server: self.upstream_server = ServerSpec(upstream_server[0], Address.wrap(upstream_server[1])) + self.upstream_auth = upstream_auth else: self.upstream_server = None + self.upstream_auth = None self.check_ignore = HostMatcher(ignore_hosts) self.check_tcp = HostMatcher(tcp_hosts) @@ -110,7 +113,7 @@ def process_proxy_options(parser, options): body_size_limit = utils.parse_size(options.body_size_limit) c = 0 - mode, upstream_server = "regular", None + mode, upstream_server, upstream_auth = "regular", None, None if options.transparent_proxy: c += 1 if not platform.resolver: @@ -127,6 +130,7 @@ def process_proxy_options(parser, options): c += 1 mode = "upstream" upstream_server = options.upstream_proxy + upstream_auth = options.upstream_auth if c > 1: return parser.error( "Transparent, SOCKS5, reverse and upstream proxy mode " @@ -189,6 +193,7 @@ def process_proxy_options(parser, options): body_size_limit=body_size_limit, mode=mode, upstream_server=upstream_server, + upstream_auth=upstream_auth, ignore_hosts=options.ignore_hosts, tcp_hosts=options.tcp_hosts, http2=options.http2, From ac252064208b65381b2c2a68e373538c3b2130ce Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Sun, 21 Feb 2016 20:44:03 +0530 Subject: [PATCH 02/19] Indent JSON body while exporting it as code Fixes #968 --- mitmproxy/flow_export.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow_export.py index 52145516f..97d5f403b 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow_export.py @@ -1,7 +1,25 @@ import urllib -import netlib.http from textwrap import dedent +import netlib.http +from . import contentviews + + +def prettify(data, headers=None): + if not headers: + return data + + cv = contentviews.get_content_view( + contentviews.get("Auto"), + data, + headers=headers, + ) + + if cv[0] == "JSON": + return "\n".join(l[0][1] for l in cv[1]) + else: + return data + def curl_command(flow): data = "curl " @@ -53,7 +71,7 @@ def python_code(flow): data = "" if flow.request.body: - data = "\ndata = '''%s'''\n" % flow.request.body + data = "\ndata = '''%s'''\n" % prettify(flow.request.body, flow.request.headers) args += "\n data=data," code = code.format( From 1548185a17a824273f28a9a20298b2f4a8df4569 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Tue, 1 Mar 2016 18:38:55 +0530 Subject: [PATCH 03/19] Use requests' json parameter --- mitmproxy/flow_export.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow_export.py index 97d5f403b..b3bef62f7 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow_export.py @@ -5,22 +5,6 @@ import netlib.http from . import contentviews -def prettify(data, headers=None): - if not headers: - return data - - cv = contentviews.get_content_view( - contentviews.get("Auto"), - data, - headers=headers, - ) - - if cv[0] == "JSON": - return "\n".join(l[0][1] for l in cv[1]) - else: - return data - - def curl_command(flow): data = "curl " @@ -71,8 +55,18 @@ def python_code(flow): data = "" if flow.request.body: - data = "\ndata = '''%s'''\n" % prettify(flow.request.body, flow.request.headers) - args += "\n data=data," + cv = contentviews.get_content_view( + viewmode=contentviews.get("Auto"), + data=flow.request.body, + headers=flow.request.headers, + ) + + if cv[0] == "JSON": + data = "\njson = %s\n" % "\n".join(l[0][1] for l in cv[1]) + args += "\n json=json," + else: + data = "\ndata = '''%s'''\n" % flow.request.body + args += "\n data=data," code = code.format( url=url, From 27faea2355218c90e5baa962b15653c17faf2f85 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Tue, 1 Mar 2016 20:20:18 +0530 Subject: [PATCH 04/19] Fixup HAR Extractor --- examples/har_extractor.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/har_extractor.py b/examples/har_extractor.py index e7718fe80..90c7bbf0d 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -1,5 +1,4 @@ """ - This inline script utilizes harparser.HAR from https://github.com/JustusW/harparser to generate a HAR log object. """ @@ -122,26 +121,38 @@ def response(context, flow): if item > -1: full_time += item - started_date_time = datetime.fromtimestamp( - flow.request.timestamp_start, - tz=utc).isoformat() + started_date_time = datetime.utcfromtimestamp( + flow.request.timestamp_start).isoformat() + + request_query_string = "" + if flow.request.query: + request_query_string = [{"name": k, "value": v} + for k, v in flow.request.query] - request_query_string = [{"name": k, "value": v} - for k, v in flow.request.query] request_http_version = flow.request.http_version # Cookies are shaped as tuples by MITMProxy. request_cookies = [{"name": k.strip(), "value": v[0]} for k, v in flow.request.cookies.items()] - request_headers = [{"name": k, "value": v} for k, v in flow.request.headers] + + request_headers = "" + if flow.request.headers: + request_headers = [{"name": k, "value": v} + for k, v in flow.request.headers.fields] + request_headers_size = len(str(flow.request.headers)) request_body_size = len(flow.request.content) response_http_version = flow.response.http_version + # Cookies are shaped as tuples by MITMProxy. response_cookies = [{"name": k.strip(), "value": v[0]} for k, v in flow.response.cookies.items()] - response_headers = [{"name": k, "value": v} - for k, v in flow.response.headers] + + response_headers = "" + if flow.response.headers: + response_headers = [{"name": k, "value": v} + for k, v in flow.response.headers.fields] + response_headers_size = len(str(flow.response.headers)) response_body_size = len(flow.response.content) response_body_decoded_size = len(flow.response.get_decoded_content()) From 1f41719bbcf0366f98fd4838b19467eaf361faa6 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Wed, 2 Mar 2016 11:25:15 +0530 Subject: [PATCH 05/19] Remove some duplication and make more pythonic har_extractor --- examples/har_extractor.py | 136 ++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 79 deletions(-) diff --git a/examples/har_extractor.py b/examples/har_extractor.py index 90c7bbf0d..43232ea9b 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -82,17 +82,17 @@ def response(context, flow): # Calculate the connect_time for this server_conn. Afterwards add it to # seen list, in order to avoid the connect_time being present in entries # that use an existing connection. - connect_time = flow.server_conn.timestamp_tcp_setup - \ - flow.server_conn.timestamp_start + connect_time = (flow.server_conn.timestamp_tcp_setup - + flow.server_conn.timestamp_start) context.seen_server.add(flow.server_conn) if flow.server_conn.timestamp_ssl_setup is not None: # Get the ssl_time for this server_conn as the difference between # the start of the successful tcp setup and the successful ssl - # setup. If no ssl setup has been made it is left as -1 since it + # setup. If no ssl setup has been made it is left as -1 since it # doesn't apply to this connection. - ssl_time = flow.server_conn.timestamp_ssl_setup - \ - flow.server_conn.timestamp_tcp_setup + ssl_time = (flow.server_conn.timestamp_ssl_setup - + flow.server_conn.timestamp_tcp_setup) # Calculate the raw timings from the different timestamps present in the # request and response object. For lack of a way to measure it dns timings @@ -111,92 +111,58 @@ def response(context, flow): # HAR timings are integers in ms, so we have to re-encode the raw timings to # that format. - timings = dict([(key, int(1000 * value)) - for key, value in timings_raw.iteritems()]) + timings = dict([(k, int(1000 * v)) for k, v in timings_raw.iteritems()]) - # The full_time is the sum of all timings. Timings set to -1 will be ignored - # as per spec. - full_time = 0 - for item in timings.values(): - if item > -1: - full_time += item + # The full_time is the sum of all timings. + # Timings set to -1 will be ignored as per spec. + full_time = sum(v for v in timings.values() if v > -1) started_date_time = datetime.utcfromtimestamp( flow.request.timestamp_start).isoformat() - request_query_string = "" - if flow.request.query: - request_query_string = [{"name": k, "value": v} - for k, v in flow.request.query] + request_query_string = [{"name": k, "value": v} + for k, v in flow.request.query or {}] - request_http_version = flow.request.http_version - # Cookies are shaped as tuples by MITMProxy. - request_cookies = [{"name": k.strip(), "value": v[0]} - for k, v in flow.request.cookies.items()] - - request_headers = "" - if flow.request.headers: - request_headers = [{"name": k, "value": v} - for k, v in flow.request.headers.fields] - - request_headers_size = len(str(flow.request.headers)) - request_body_size = len(flow.request.content) - - response_http_version = flow.response.http_version - - # Cookies are shaped as tuples by MITMProxy. - response_cookies = [{"name": k.strip(), "value": v[0]} - for k, v in flow.response.cookies.items()] - - response_headers = "" - if flow.response.headers: - response_headers = [{"name": k, "value": v} - for k, v in flow.response.headers.fields] - - response_headers_size = len(str(flow.response.headers)) response_body_size = len(flow.response.content) response_body_decoded_size = len(flow.response.get_decoded_content()) response_body_compression = response_body_decoded_size - response_body_size - response_mime_type = flow.response.headers.get('Content-Type', '') - response_redirect_url = flow.response.headers.get('Location', '') - entry = HAR.entries( - { - "startedDateTime": started_date_time, - "time": full_time, - "request": { - "method": flow.request.method, - "url": flow.request.url, - "httpVersion": request_http_version, - "cookies": request_cookies, - "headers": request_headers, - "queryString": request_query_string, - "headersSize": request_headers_size, - "bodySize": request_body_size, + entry = HAR.entries({ + "startedDateTime": started_date_time, + "time": full_time, + "request": { + "method": flow.request.method, + "url": flow.request.url, + "httpVersion": flow.request.http_version, + "cookies": format_cookies(flow.request.cookies), + "headers": format_headers(flow.request.headers), + "queryString": request_query_string, + "headersSize": len(str(flow.request.headers)), + "bodySize": len(flow.request.content), + }, + "response": { + "status": flow.response.status_code, + "statusText": flow.response.msg, + "httpVersion": flow.response.http_version, + "cookies": format_cookies(flow.response.cookies), + "headers": format_headers(flow.response.headers), + "content": { + "size": response_body_size, + "compression": response_body_compression, + "mimeType": flow.response.headers.get('Content-Type', '') }, - "response": { - "status": flow.response.status_code, - "statusText": flow.response.msg, - "httpVersion": response_http_version, - "cookies": response_cookies, - "headers": response_headers, - "content": { - "size": response_body_size, - "compression": response_body_compression, - "mimeType": response_mime_type}, - "redirectURL": response_redirect_url, - "headersSize": response_headers_size, - "bodySize": response_body_size, - }, - "cache": {}, - "timings": timings, - }) + "redirectURL": flow.response.headers.get('Location', ''), + "headersSize": len(str(flow.response.headers)), + "bodySize": response_body_size, + }, + "cache": {}, + "timings": timings, + }) - # If the current url is in the page list of context.HARLog or does not have - # a referrer we add it as a new pages object. - if flow.request.url in context.HARLog.get_page_list() or flow.request.headers.get( - 'Referer', - None) is None: + # If the current url is in the page list of context.HARLog or + # does not have a referrer, we add it as a new pages object. + if (flow.request.url in context.HARLog.get_page_list() or + flow.request.headers.get('Referer') is None): page_id = context.HARLog.create_page_id() context.HARLog.add( HAR.pages({ @@ -250,6 +216,18 @@ def done(context): ) +def format_cookies(obj): + if obj: + return [{"name": k.strip(), "value": v[0]} for k, v in obj.items()] + return "" + + +def format_headers(obj): + if obj: + return [{"name": k, "value": v} for k, v in obj.fields] + return "" + + def print_attributes(obj, filter_string=None, hide_privates=False): """ Useful helper method to quickly get all attributes of an object and its From 1d002182e318be64190c4616ac4931e98feb31eb Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 2 Mar 2016 22:37:32 +0800 Subject: [PATCH 06/19] [web] remove common.ChildFocus --- mitmproxy/web/static/app.js | 41 +++++++++++++------------------ web/src/js/components/common.js | 12 --------- web/src/js/components/editor.js | 7 +++--- web/src/js/components/header.js | 8 +++--- web/src/js/components/prompt.js | 7 +++--- web/src/js/components/proxyapp.js | 2 ++ 6 files changed, 32 insertions(+), 45 deletions(-) diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 3537d3552..313a07fe0 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -481,7 +481,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de Object.defineProperty(exports, "__esModule", { value: true }); -exports.Splitter = exports.Router = exports.ChildFocus = exports.SettingsState = exports.StickyHeadMixin = exports.AutoScrollMixin = undefined; +exports.Splitter = exports.Router = exports.SettingsState = exports.StickyHeadMixin = exports.AutoScrollMixin = undefined; var _react = require("react"); @@ -542,17 +542,6 @@ var SettingsState = exports.SettingsState = { } }; -var ChildFocus = exports.ChildFocus = { - contextTypes: { - returnFocus: _react2.default.PropTypes.func - }, - returnFocus: function returnFocus() { - _reactDom2.default.findDOMNode(this).blur(); - window.getSelection().removeAllRanges(); - this.context.returnFocus(); - } -}; - var Router = exports.Router = { contextTypes: { location: _react2.default.PropTypes.object, @@ -708,8 +697,6 @@ var _reactDom = require("react-dom"); var _reactDom2 = _interopRequireDefault(_reactDom); -var _common = require("./common.js"); - var _utils = require("../utils.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -927,7 +914,9 @@ var ValidateEditor = _react2.default.createClass({ var ValueEditor = exports.ValueEditor = _react2.default.createClass({ displayName: "ValueEditor", - mixins: [_common.ChildFocus], + contextTypes: { + returnFocus: _react2.default.PropTypes.func + }, propTypes: { content: _react2.default.PropTypes.string.isRequired, onDone: _react2.default.PropTypes.func.isRequired, @@ -944,11 +933,11 @@ var ValueEditor = exports.ValueEditor = _react2.default.createClass({ _reactDom2.default.findDOMNode(this).focus(); }, onStop: function onStop() { - this.returnFocus(); + this.context.returnFocus(); } }); -},{"../utils.js":26,"./common.js":4,"react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){ +},{"../utils.js":26,"react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2924,7 +2913,9 @@ var FilterDocs = _react2.default.createClass({ var FilterInput = _react2.default.createClass({ displayName: "FilterInput", - mixins: [_common.ChildFocus], + contextTypes: { + returnFocus: _react2.default.PropTypes.func + }, getInitialState: function getInitialState() { // Consider both focus and mouseover for showing/hiding the tooltip, // because onBlur of the input is triggered before the click on the tooltip @@ -2991,7 +2982,7 @@ var FilterInput = _react2.default.createClass({ }, blur: function blur() { _reactDom2.default.findDOMNode(this.refs.input).blur(); - this.returnFocus(); + this.context.returnFocus(); }, select: function select() { _reactDom2.default.findDOMNode(this.refs.input).select(); @@ -3574,14 +3565,14 @@ var _lodash2 = _interopRequireDefault(_lodash); var _utils = require("../utils.js"); -var _common = require("./common.js"); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var Prompt = _react2.default.createClass({ displayName: "Prompt", - mixins: [_common.ChildFocus], + contextTypes: { + returnFocus: _react2.default.PropTypes.func + }, propTypes: { options: _react2.default.PropTypes.array.isRequired, done: _react2.default.PropTypes.func.isRequired, @@ -3610,7 +3601,7 @@ var Prompt = _react2.default.createClass({ }, done: function done(ret) { this.props.done(ret); - this.returnFocus(); + this.context.returnFocus(); }, getOptions: function getOptions() { var opts = []; @@ -3689,7 +3680,7 @@ var Prompt = _react2.default.createClass({ exports.default = Prompt; -},{"../utils.js":26,"./common.js":4,"lodash":"lodash","react":"react","react-dom":"react-dom"}],18:[function(require,module,exports){ +},{"../utils.js":26,"lodash":"lodash","react":"react","react-dom":"react-dom"}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3785,6 +3776,8 @@ var ProxyAppMain = _react2.default.createClass({ }; }, focus: function focus() { + document.activeElement.blur(); + window.getSelection().removeAllRanges(); _reactDom2.default.findDOMNode(this).focus(); }, getMainComponent: function getMainComponent() { diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index 5fae7415c..293cff497 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -52,18 +52,6 @@ export var SettingsState = { }; -export var ChildFocus = { - contextTypes: { - returnFocus: React.PropTypes.func - }, - returnFocus: function () { - ReactDOM.findDOMNode(this).blur(); - window.getSelection().removeAllRanges(); - this.context.returnFocus(); - } -}; - - export var Router = { contextTypes: { location: React.PropTypes.object, diff --git a/web/src/js/components/editor.js b/web/src/js/components/editor.js index c929a244d..eed2f7c69 100644 --- a/web/src/js/components/editor.js +++ b/web/src/js/components/editor.js @@ -1,6 +1,5 @@ import React from "react"; import ReactDOM from 'react-dom'; -import {ChildFocus} from "./common.js"; import {Key} from "../utils.js"; var contentToHtml = function (content) { @@ -214,7 +213,9 @@ var ValidateEditor = React.createClass({ Text Editor with mitmweb-specific convenience features */ export var ValueEditor = React.createClass({ - mixins: [ChildFocus], + contextTypes: { + returnFocus: React.PropTypes.func + }, propTypes: { content: React.PropTypes.string.isRequired, onDone: React.PropTypes.func.isRequired, @@ -232,6 +233,6 @@ export var ValueEditor = React.createClass({ ReactDOM.findDOMNode(this).focus(); }, onStop: function () { - this.returnFocus(); + this.context.returnFocus(); } }); \ No newline at end of file diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 3833a6eec..96b522f0a 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,7 +4,7 @@ import $ from "jquery"; import Filt from "../filt/filt.js"; import {Key} from "../utils.js"; -import {Router, SettingsState, ChildFocus} from "./common.js"; +import {Router, SettingsState} from "./common.js"; import {SettingsActions, FlowActions} from "../actions.js"; import {Query} from "../actions.js"; @@ -51,7 +51,9 @@ var FilterDocs = React.createClass({ } }); var FilterInput = React.createClass({ - mixins: [ChildFocus], + contextTypes: { + returnFocus: React.PropTypes.func + }, getInitialState: function () { // Consider both focus and mouseover for showing/hiding the tooltip, // because onBlur of the input is triggered before the click on the tooltip @@ -118,7 +120,7 @@ var FilterInput = React.createClass({ }, blur: function () { ReactDOM.findDOMNode(this.refs.input).blur(); - this.returnFocus(); + this.context.returnFocus(); }, select: function () { ReactDOM.findDOMNode(this.refs.input).select(); diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js index 7b3980380..e324f7d48 100644 --- a/web/src/js/components/prompt.js +++ b/web/src/js/components/prompt.js @@ -3,10 +3,11 @@ import ReactDOM from 'react-dom'; import _ from "lodash"; import {Key} from "../utils.js"; -import {ChildFocus} from "./common.js" var Prompt = React.createClass({ - mixins: [ChildFocus], + contextTypes: { + returnFocus: React.PropTypes.func + }, propTypes: { options: React.PropTypes.array.isRequired, done: React.PropTypes.func.isRequired, @@ -35,7 +36,7 @@ var Prompt = React.createClass({ }, done: function (ret) { this.props.done(ret); - this.returnFocus(); + this.context.returnFocus(); }, getOptions: function () { var opts = []; diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js index 24f45ff55..97acfbde2 100644 --- a/web/src/js/components/proxyapp.js +++ b/web/src/js/components/proxyapp.js @@ -55,6 +55,8 @@ var ProxyAppMain = React.createClass({ }; }, focus: function () { + document.activeElement.blur(); + window.getSelection().removeAllRanges(); ReactDOM.findDOMNode(this).focus(); }, getMainComponent: function () { From b8ae666c78aef9a6ef0e67179d38980247d0e1a5 Mon Sep 17 00:00:00 2001 From: yonder Date: Wed, 2 Mar 2016 12:19:33 -0500 Subject: [PATCH 07/19] Add test for upstream-auth --- mitmproxy/cmdline.py | 11 +++++++++++ mitmproxy/models/http.py | 4 +--- test/mitmproxy/test_cmdline.py | 7 +++++++ test/mitmproxy/test_proxy.py | 4 ++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index e6f0bc20b..eb15a1d24 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import os import re +import base64 import configargparse @@ -117,6 +118,15 @@ def parse_server_spec(url): return config.ServerSpec(scheme, address) +def parse_upstream_auth(auth): + pattern = re.compile(":") + if pattern.search(auth) is None: + raise configargparse.ArgumentTypeError( + "Invalid upstream auth specification: %s" % auth + ) + return "Basic" + " " + base64.b64encode(auth) + + def get_common_options(options): stickycookie, stickyauth = None, None if options.stickycookie_filt: @@ -373,6 +383,7 @@ def proxy_options(parser): parser.add_argument( "--upstream-auth", action="store", dest="upstream_auth", default=None, + type=parse_upstream_auth, help=""" Proxy Authentication: username:password diff --git a/mitmproxy/models/http.py b/mitmproxy/models/http.py index 5d79306b2..0338945b1 100644 --- a/mitmproxy/models/http.py +++ b/mitmproxy/models/http.py @@ -2,7 +2,6 @@ from __future__ import (absolute_import, print_function, division) import Cookie import copy import warnings -import base64 from email.utils import parsedate_tz, formatdate, mktime_tz import time @@ -194,8 +193,7 @@ class HTTPRequest(MessageMixin, Request): return id(self) def set_auth(self, auth): - auth_str = "Basic" + " " + base64.b64encode(auth) - self.data.headers.set_all("Proxy-Authorization", (auth_str,)) + self.data.headers.set_all("Proxy-Authorization", (auth,)) def replace(self, pattern, repl, *args, **kwargs): """ diff --git a/test/mitmproxy/test_cmdline.py b/test/mitmproxy/test_cmdline.py index 5a70f3e0f..1546bb419 100644 --- a/test/mitmproxy/test_cmdline.py +++ b/test/mitmproxy/test_cmdline.py @@ -1,4 +1,5 @@ import argparse +import base64 from mitmproxy import cmdline from . import tutils @@ -53,6 +54,12 @@ def test_parse_server_spec(): "http://") +def test_parse_upstream_auth(): + tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, "") + assert cmdline.parse_upstream_auth( + "test:test") == "Basic" + " " + base64.b64encode("test:test") + + def test_parse_setheaders(): x = cmdline.parse_setheader("/foo/bar/voing") assert x == ("foo", "bar", "voing") diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 34b75b62b..fddb851e8 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -92,6 +92,10 @@ class TestProcessProxyOptions: self.assert_err("expected one argument", "-U") self.assert_err("Invalid server specification", "-U", "upstream") + self.assert_noerr("--upstream-auth", "test:test") + self.assert_err("expected one argument", "--upstream-auth") + self.assert_err("Invalid upstream auth specification", "--upstream-auth", "test") + self.assert_err("not allowed with", "-R", "http://localhost", "-T") def test_socks_auth(self): From ec61cfddfd284a13d7bcf2807ba2e95c1a7f1d51 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Thu, 3 Mar 2016 06:41:26 +0530 Subject: [PATCH 08/19] Use json instead of contentviews --- mitmproxy/flow_export.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow_export.py index b3bef62f7..f5b3e2ec1 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow_export.py @@ -1,8 +1,9 @@ +import json import urllib from textwrap import dedent import netlib.http -from . import contentviews +from netlib.utils import parse_content_type def curl_command(flow): @@ -55,14 +56,9 @@ def python_code(flow): data = "" if flow.request.body: - cv = contentviews.get_content_view( - viewmode=contentviews.get("Auto"), - data=flow.request.body, - headers=flow.request.headers, - ) - - if cv[0] == "JSON": - data = "\njson = %s\n" % "\n".join(l[0][1] for l in cv[1]) + if is_json(flow.request.headers): + data = json.dumps(json.loads(flow.request.body), indent=4) + data = "\njson = %s\n" % data args += "\n json=json," else: data = "\ndata = '''%s'''\n" % flow.request.body @@ -83,3 +79,10 @@ def python_code(flow): def raw_request(flow): data = netlib.http.http1.assemble_request(flow.request) return data + + +def is_json(headers): + if headers: + ct = parse_content_type(headers.get("content-type", "")) + return ct and "%s/%s" % (ct[0], ct[1]) == "application/json" + return False From 01a99f2b252c2a4b467d26c795173a026df6d4d7 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 01:01:16 +0530 Subject: [PATCH 09/19] Handle the case of correct json content-type but malformed body --- mitmproxy/flow_export.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow_export.py index f5b3e2ec1..49e2d7046 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow_export.py @@ -56,8 +56,9 @@ def python_code(flow): data = "" if flow.request.body: - if is_json(flow.request.headers): - data = json.dumps(json.loads(flow.request.body), indent=4) + json_obj = is_json(flow.request.headers, flow.request.body) + if json_obj: + data = json.dumps(json_obj, indent=4) data = "\njson = %s\n" % data args += "\n json=json," else: @@ -81,8 +82,12 @@ def raw_request(flow): return data -def is_json(headers): +def is_json(headers, content): if headers: ct = parse_content_type(headers.get("content-type", "")) - return ct and "%s/%s" % (ct[0], ct[1]) == "application/json" + if ct and "%s/%s" % (ct[0], ct[1]) == "application/json": + try: + return json.loads(content) + except ValueError: + return False return False From 9540ede11222cf1e0d796d34b82481173405c520 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 01:05:35 +0530 Subject: [PATCH 10/19] Use separators param in json.dumps() to avoid trailing whitespace --- mitmproxy/flow_export.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow_export.py index 49e2d7046..6333de573 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow_export.py @@ -58,7 +58,9 @@ def python_code(flow): if flow.request.body: json_obj = is_json(flow.request.headers, flow.request.body) if json_obj: - data = json.dumps(json_obj, indent=4) + # Without the separators field json.dumps() produces + # trailing white spaces: https://bugs.python.org/issue16333 + data = json.dumps(json_obj, indent=4, separators=(',', ': ')) data = "\njson = %s\n" % data args += "\n json=json," else: From 48f1c9afc2e5d02c8de717e2fc9030a7884d493b Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 01:06:09 +0530 Subject: [PATCH 11/19] Add a test for json request --- test/mitmproxy/test_flow_export.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/mitmproxy/test_flow_export.py b/test/mitmproxy/test_flow_export.py index 2dce3fd69..3dc074277 100644 --- a/test/mitmproxy/test_flow_export.py +++ b/test/mitmproxy/test_flow_export.py @@ -1,6 +1,8 @@ +import json from textwrap import dedent import netlib.tutils +from netlib.http import Headers from mitmproxy import flow_export from . import tutils @@ -81,6 +83,35 @@ class TestExportPythonCode(): """).strip() assert flow_export.python_code(flow) == result + def test_post_json(self): + req_post.content = '{"name": "example", "email": "example@example.com"}' + req_post.headers = Headers(content_type="application/json") + flow = tutils.tflow(req=req_post) + result = dedent(""" + import requests + + url = 'http://address/path' + + headers = { + 'content-type': 'application/json', + } + + json = { + "name": "example", + "email": "example@example.com" + } + + response = requests.request( + method='POST', + url=url, + headers=headers, + json=json, + ) + + print(response.text) + """).strip() + assert flow_export.python_code(flow) == result + def test_patch(self): flow = tutils.tflow(req=req_patch) result = dedent(""" From c982f579dd755adef2456bc2ad1d3fce06cbfd58 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 02:32:01 +0530 Subject: [PATCH 12/19] Add basic test for har_extractor --- examples/__init__.py | 0 test/mitmproxy/test_har_extractor.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 examples/__init__.py create mode 100644 test/mitmproxy/test_har_extractor.py diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/mitmproxy/test_har_extractor.py b/test/mitmproxy/test_har_extractor.py new file mode 100644 index 000000000..da68ac4de --- /dev/null +++ b/test/mitmproxy/test_har_extractor.py @@ -0,0 +1,10 @@ +from . import tutils + +from mitmproxy import script, flow +from examples import har_extractor + + +def test_start(): + fm = flow.FlowMaster(None, flow.State()) + ctx = script.ScriptContext(fm) + tutils.raises(ValueError, har_extractor.start, ctx, []) From 1c8059937927d28d620785c8e9d05d034a442ce1 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 03:02:33 +0530 Subject: [PATCH 13/19] Use default empty page_list --- examples/har_extractor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/har_extractor.py b/examples/har_extractor.py index 43232ea9b..be9b986b5 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -16,7 +16,7 @@ class _HARLog(HAR.log): __page_count__ = 0 __page_ref__ = {} - def __init__(self, page_list): + def __init__(self, page_list=[]): self.__page_list__ = page_list self.__page_count__ = 0 self.__page_ref__ = {} @@ -66,7 +66,7 @@ def start(context, argv): '(- will output to stdout, filenames ending with .zhar ' 'will result in compressed har)' ) - context.HARLog = _HARLog(['https://github.com']) + context.HARLog = _HARLog() context.seen_server = set() From 7108d727055324d08ba82cbce5c91173ee599201 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 03:02:49 +0530 Subject: [PATCH 14/19] Fix pprint import --- examples/har_extractor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/har_extractor.py b/examples/har_extractor.py index be9b986b5..25661f7c7 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -192,7 +192,7 @@ def done(context): """ Called once on script shutdown, after any other events. """ - from pprint import pprint + import pprint import json json_dump = context.HARLog.json() From 6a4f1ae7c7ea4b5036937312b837184162edaed3 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 03:10:29 +0530 Subject: [PATCH 15/19] Test har_extractor.response --- test/mitmproxy/data/har_extractor.har | 78 +++++++++++++++++++++++++++ test/mitmproxy/test_har_extractor.py | 27 ++++++++++ 2 files changed, 105 insertions(+) create mode 100644 test/mitmproxy/data/har_extractor.har diff --git a/test/mitmproxy/data/har_extractor.har b/test/mitmproxy/data/har_extractor.har new file mode 100644 index 000000000..2f5099b30 --- /dev/null +++ b/test/mitmproxy/data/har_extractor.har @@ -0,0 +1,78 @@ +{ + "test_response": { + "log": { + "__page_count__": 1, + "version": "1.2", + "creator": { + "comment": "", + "version": "0.1", + "name": "MITMPROXY HARExtractor" + }, + "pages": [ + { + "startedDateTime": "1993-08-24T14:41:12", + "id": "autopage_1", + "title": "http://address:22/path" + } + ], + "entries": [ + { + "pageref": "autopage_1", + "startedDateTime": "1993-08-24T14:41:12", + "cache": {}, + "request": { + "cookies": [], + "url": "http://address:22/path", + "queryString": [], + "headers": [ + { + "name": "header", + "value": "qvalue" + }, + { + "name": "content-length", + "value": "7" + } + ], + "headersSize": 35, + "httpVersion": "HTTP/1.1", + "method": "GET", + "bodySize": 7 + }, + "timings": { + "receive": 0, + "ssl": 1000, + "connect": 1000, + "send": 0, + "wait": 0 + }, + "time": 2000, + "response": { + "status": 200, + "cookies": [], + "statusText": "OK", + "content": { + "mimeType": "", + "compression": 0, + "size": 7 + }, + "headers": [ + { + "name": "content-length", + "value": "7" + }, + { + "name": "header-response", + "value": "svalue" + } + ], + "headersSize": 44, + "redirectURL": "", + "httpVersion": "HTTP/1.1", + "bodySize": 7 + } + } + ] + } + } +} \ No newline at end of file diff --git a/test/mitmproxy/test_har_extractor.py b/test/mitmproxy/test_har_extractor.py index da68ac4de..26a092e6c 100644 --- a/test/mitmproxy/test_har_extractor.py +++ b/test/mitmproxy/test_har_extractor.py @@ -1,10 +1,37 @@ +import json +import netlib.tutils from . import tutils from mitmproxy import script, flow from examples import har_extractor +trequest = netlib.tutils.treq( + timestamp_start=746203272, + timestamp_end=746203272, +) + +tresponse = netlib.tutils.tresp( + timestamp_start=746203272, + timestamp_end=746203272, +) + + def test_start(): fm = flow.FlowMaster(None, flow.State()) ctx = script.ScriptContext(fm) tutils.raises(ValueError, har_extractor.start, ctx, []) + + +def test_response(): + fm = flow.FlowMaster(None, flow.State()) + ctx = script.ScriptContext(fm) + ctx.HARLog = har_extractor._HARLog([]) + ctx.seen_server = set() + + fl = tutils.tflow(req=trequest, resp=tresponse) + har_extractor.response(ctx, fl) + + with open(tutils.test_data.path("data/har_extractor.har")) as fp: + test_data = json.load(fp) + assert json.loads(ctx.HARLog.json()) == test_data["test_response"] From d17cd5c6eca72fdddafb73028687c8b0152a5fe6 Mon Sep 17 00:00:00 2001 From: Shadab Zafar Date: Fri, 4 Mar 2016 03:13:40 +0530 Subject: [PATCH 16/19] Use custom Context object --- test/mitmproxy/test_har_extractor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/mitmproxy/test_har_extractor.py b/test/mitmproxy/test_har_extractor.py index 26a092e6c..7838f7133 100644 --- a/test/mitmproxy/test_har_extractor.py +++ b/test/mitmproxy/test_har_extractor.py @@ -2,10 +2,13 @@ import json import netlib.tutils from . import tutils -from mitmproxy import script, flow from examples import har_extractor +class Context(object): + pass + + trequest = netlib.tutils.treq( timestamp_start=746203272, timestamp_end=746203272, @@ -18,14 +21,11 @@ tresponse = netlib.tutils.tresp( def test_start(): - fm = flow.FlowMaster(None, flow.State()) - ctx = script.ScriptContext(fm) - tutils.raises(ValueError, har_extractor.start, ctx, []) + tutils.raises(ValueError, har_extractor.start, Context(), []) def test_response(): - fm = flow.FlowMaster(None, flow.State()) - ctx = script.ScriptContext(fm) + ctx = Context() ctx.HARLog = har_extractor._HARLog([]) ctx.seen_server = set() From 0e27dfd9c132e1f8768ae33a33ae8a1c55541948 Mon Sep 17 00:00:00 2001 From: yonder Date: Fri, 4 Mar 2016 11:12:03 -0500 Subject: [PATCH 17/19] Improve test case --- mitmproxy/cmdline.py | 2 +- test/mitmproxy/test_cmdline.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index eb15a1d24..b1b860f83 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -119,7 +119,7 @@ def parse_server_spec(url): def parse_upstream_auth(auth): - pattern = re.compile(":") + pattern = re.compile(".+:") if pattern.search(auth) is None: raise configargparse.ArgumentTypeError( "Invalid upstream auth specification: %s" % auth diff --git a/test/mitmproxy/test_cmdline.py b/test/mitmproxy/test_cmdline.py index 1546bb419..e75b7baf0 100644 --- a/test/mitmproxy/test_cmdline.py +++ b/test/mitmproxy/test_cmdline.py @@ -56,8 +56,12 @@ def test_parse_server_spec(): def test_parse_upstream_auth(): tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, "") + tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, ":") + tutils.raises("Invalid upstream auth specification", cmdline.parse_upstream_auth, ":test") assert cmdline.parse_upstream_auth( "test:test") == "Basic" + " " + base64.b64encode("test:test") + assert cmdline.parse_upstream_auth( + "test:") == "Basic" + " " + base64.b64encode("test:") def test_parse_setheaders(): From 0545326dc0319923c0746af0dd0746e98528c39c Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 6 Mar 2016 20:20:08 +0800 Subject: [PATCH 18/19] [web] eliminate SettingsState --- mitmproxy/web/static/app.js | 104 ++++++++++++++---------------- web/src/js/components/common.js | 22 ------- web/src/js/components/footer.js | 29 ++++----- web/src/js/components/header.js | 14 ++-- web/src/js/components/proxyapp.js | 16 +++-- 5 files changed, 82 insertions(+), 103 deletions(-) diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 313a07fe0..997d8ed4c 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -481,7 +481,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de Object.defineProperty(exports, "__esModule", { value: true }); -exports.Splitter = exports.Router = exports.SettingsState = exports.StickyHeadMixin = exports.AutoScrollMixin = undefined; +exports.Splitter = exports.Router = exports.StickyHeadMixin = exports.AutoScrollMixin = undefined; var _react = require("react"); @@ -520,28 +520,6 @@ var StickyHeadMixin = exports.StickyHeadMixin = { } }; -var SettingsState = exports.SettingsState = { - contextTypes: { - settingsStore: _react2.default.PropTypes.object.isRequired - }, - getInitialState: function getInitialState() { - return { - settings: this.context.settingsStore.dict - }; - }, - componentDidMount: function componentDidMount() { - this.context.settingsStore.addListener("recalculate", this.onSettingsChange); - }, - componentWillUnmount: function componentWillUnmount() { - this.context.settingsStore.removeListener("recalculate", this.onSettingsChange); - }, - onSettingsChange: function onSettingsChange() { - this.setState({ - settings: this.context.settingsStore.dict - }); - } -}; - var Router = exports.Router = { contextTypes: { location: _react2.default.PropTypes.object, @@ -2773,6 +2751,7 @@ exports.default = Nav; Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = Footer; var _react = require("react"); @@ -2782,34 +2761,33 @@ var _common = require("./common.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var Footer = _react2.default.createClass({ - displayName: "Footer", +Footer.propTypes = { + settings: _react2.default.PropTypes.object.isRequired +}; - mixins: [_common.SettingsState], - render: function render() { - var mode = this.state.settings.mode; - var intercept = this.state.settings.intercept; - return _react2.default.createElement( - "footer", - null, - mode && mode != "regular" ? _react2.default.createElement( - "span", - { className: "label label-success" }, - mode, - " mode" - ) : null, - " ", - intercept ? _react2.default.createElement( - "span", - { className: "label label-success" }, - "Intercept: ", - intercept - ) : null - ); - } -}); +function Footer(_ref) { + var settings = _ref.settings; -exports.default = Footer; + var mode = settings.mode; + var intercept = settings.intercept; + return _react2.default.createElement( + "footer", + null, + mode && mode != "regular" ? _react2.default.createElement( + "span", + { className: "label label-success" }, + mode, + " mode" + ) : null, + " ", + intercept ? _react2.default.createElement( + "span", + { className: "label label-success" }, + "Intercept: ", + intercept + ) : null + ); +} },{"./common.js":4,"react":"react"}],15:[function(require,module,exports){ "use strict"; @@ -3029,7 +3007,10 @@ var FilterInput = _react2.default.createClass({ var MainMenu = exports.MainMenu = _react2.default.createClass({ displayName: "MainMenu", - mixins: [_common.Router, _common.SettingsState], + mixins: [_common.Router], + propTypes: { + settings: _react2.default.PropTypes.object.isRequired + }, statics: { title: "Start", route: "flows" @@ -3050,7 +3031,7 @@ var MainMenu = exports.MainMenu = _react2.default.createClass({ render: function render() { var search = this.getQuery()[_actions.Query.SEARCH] || ""; var highlight = this.getQuery()[_actions.Query.HIGHLIGHT] || ""; - var intercept = this.state.settings.intercept || ""; + var intercept = this.props.settings.intercept || ""; return _react2.default.createElement( "div", @@ -3228,6 +3209,9 @@ var Header = exports.Header = _react2.default.createClass({ displayName: "Header", mixins: [_common.Router], + propTypes: { + settings: _react2.default.PropTypes.object.isRequired + }, getInitialState: function getInitialState() { return { active: header_entries[0] @@ -3268,7 +3252,7 @@ var Header = exports.Header = _react2.default.createClass({ _react2.default.createElement( "div", { className: "menu" }, - _react2.default.createElement(this.state.active, { ref: "active" }) + _react2.default.createElement(this.state.active, { ref: "active", settings: this.props.settings }) ) ); } @@ -3744,7 +3728,6 @@ var ProxyAppMain = _react2.default.createClass({ mixins: [_common.Router], childContextTypes: { - settingsStore: _react2.default.PropTypes.object.isRequired, flowStore: _react2.default.PropTypes.object.isRequired, eventStore: _react2.default.PropTypes.object.isRequired, returnFocus: _react2.default.PropTypes.func.isRequired, @@ -3752,10 +3735,16 @@ var ProxyAppMain = _react2.default.createClass({ }, componentDidMount: function componentDidMount() { this.focus(); + this.settingsStore.addListener("recalculate", this.onSettingsChange); + }, + componentWillUnmount: function componentWillUnmount() { + this.settingsStore.removeListener("recalculate", this.onSettingsChange); + }, + onSettingsChange: function onSettingsChange() { + this.setState({ settings: this.settingsStore.dict }); }, getChildContext: function getChildContext() { return { - settingsStore: this.state.settingsStore, flowStore: this.state.flowStore, eventStore: this.state.eventStore, returnFocus: this.focus, @@ -3767,10 +3756,11 @@ var ProxyAppMain = _react2.default.createClass({ var flowStore = new _store.FlowStore(); var settingsStore = new _store.SettingsStore(); + this.settingsStore = settingsStore; // Default Settings before fetch _lodash2.default.extend(settingsStore.dict, {}); return { - settingsStore: settingsStore, + settings: settingsStore.dict, flowStore: flowStore, eventStore: eventStore }; @@ -3822,10 +3812,10 @@ var ProxyAppMain = _react2.default.createClass({ return _react2.default.createElement( "div", { id: "container", tabIndex: "0", onKeyDown: this.onKeydown }, - _react2.default.createElement(_header.Header, { ref: "header" }), + _react2.default.createElement(_header.Header, { ref: "header", settings: this.state.settings }), children, eventlog, - _react2.default.createElement(_footer2.default, null) + _react2.default.createElement(_footer2.default, { settings: this.state.settings }) ); } }); diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index 293cff497..447e6eece 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -29,28 +29,6 @@ export var StickyHeadMixin = { } }; -export var SettingsState = { - contextTypes: { - settingsStore: React.PropTypes.object.isRequired - }, - getInitialState: function () { - return { - settings: this.context.settingsStore.dict - }; - }, - componentDidMount: function () { - this.context.settingsStore.addListener("recalculate", this.onSettingsChange); - }, - componentWillUnmount: function () { - this.context.settingsStore.removeListener("recalculate", this.onSettingsChange); - }, - onSettingsChange: function () { - this.setState({ - settings: this.context.settingsStore.dict - }); - }, -}; - export var Router = { contextTypes: { diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js index 415c2577b..7d313b027 100644 --- a/web/src/js/components/footer.js +++ b/web/src/js/components/footer.js @@ -1,19 +1,18 @@ import React from "react"; import {SettingsState} from "./common.js"; -var Footer = React.createClass({ - mixins: [SettingsState], - render: function () { - var mode = this.state.settings.mode; - var intercept = this.state.settings.intercept; - return ( -
- {mode && mode != "regular" ? {mode} mode : null} -   - {intercept ? Intercept: {intercept} : null} -
- ); - } -}); +Footer.propTypes = { + settings: React.PropTypes.object.isRequired, +}; -export default Footer; \ No newline at end of file +export default function Footer({ settings }) { + const mode = settings.mode; + const intercept = settings.intercept; + return ( +
+ {mode && mode != "regular" ? {mode} mode : null} +   + {intercept ? Intercept: {intercept} : null} +
+ ); +} diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 96b522f0a..1af928a3b 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,7 +4,7 @@ import $ from "jquery"; import Filt from "../filt/filt.js"; import {Key} from "../utils.js"; -import {Router, SettingsState} from "./common.js"; +import {Router} from "./common.js"; import {SettingsActions, FlowActions} from "../actions.js"; import {Query} from "../actions.js"; @@ -161,7 +161,10 @@ var FilterInput = React.createClass({ }); export var MainMenu = React.createClass({ - mixins: [Router, SettingsState], + mixins: [Router], + propTypes: { + settings: React.PropTypes.object.isRequired, + }, statics: { title: "Start", route: "flows" @@ -182,7 +185,7 @@ export var MainMenu = React.createClass({ render: function () { var search = this.getQuery()[Query.SEARCH] || ""; var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; - var intercept = this.state.settings.intercept || ""; + var intercept = this.props.settings.intercept || ""; return (
@@ -351,6 +354,9 @@ var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; export var Header = React.createClass({ mixins: [Router], + propTypes: { + settings: React.PropTypes.object.isRequired, + }, getInitialState: function () { return { active: header_entries[0] @@ -386,7 +392,7 @@ export var Header = React.createClass({ {header}
- +
); diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js index 97acfbde2..d17a15223 100644 --- a/web/src/js/components/proxyapp.js +++ b/web/src/js/components/proxyapp.js @@ -23,7 +23,6 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ mixins: [Router], childContextTypes: { - settingsStore: React.PropTypes.object.isRequired, flowStore: React.PropTypes.object.isRequired, eventStore: React.PropTypes.object.isRequired, returnFocus: React.PropTypes.func.isRequired, @@ -31,10 +30,16 @@ var ProxyAppMain = React.createClass({ }, componentDidMount: function () { this.focus(); + this.settingsStore.addListener("recalculate", this.onSettingsChange); + }, + componentWillUnmount: function () { + this.settingsStore.removeListener("recalculate", this.onSettingsChange); + }, + onSettingsChange: function () { + this.setState({ settings: this.settingsStore.dict }); }, getChildContext: function () { return { - settingsStore: this.state.settingsStore, flowStore: this.state.flowStore, eventStore: this.state.eventStore, returnFocus: this.focus, @@ -46,10 +51,11 @@ var ProxyAppMain = React.createClass({ var flowStore = new FlowStore(); var settingsStore = new SettingsStore(); + this.settingsStore = settingsStore; // Default Settings before fetch _.extend(settingsStore.dict, {}); return { - settingsStore: settingsStore, + settings: settingsStore.dict, flowStore: flowStore, eventStore: eventStore }; @@ -106,10 +112,10 @@ var ProxyAppMain = React.createClass({ ); return (
-
+
{children} {eventlog} -
+
); } From ca9a493a1c20651d111ab06f43437d97b7104705 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 8 Mar 2016 00:57:01 +0800 Subject: [PATCH 19/19] [web] uniform code style for Footer component --- mitmproxy/web/static/app.js | 11 +++++------ web/src/js/components/footer.js | 12 +++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 997d8ed4c..bb06970e1 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -2767,25 +2767,24 @@ Footer.propTypes = { function Footer(_ref) { var settings = _ref.settings; - var mode = settings.mode; var intercept = settings.intercept; + return _react2.default.createElement( "footer", null, - mode && mode != "regular" ? _react2.default.createElement( + mode && mode != "regular" && _react2.default.createElement( "span", { className: "label label-success" }, mode, " mode" - ) : null, - " ", - intercept ? _react2.default.createElement( + ), + intercept && _react2.default.createElement( "span", { className: "label label-success" }, "Intercept: ", intercept - ) : null + ) ); } diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js index 7d313b027..e2d962881 100644 --- a/web/src/js/components/footer.js +++ b/web/src/js/components/footer.js @@ -6,13 +6,15 @@ Footer.propTypes = { }; export default function Footer({ settings }) { - const mode = settings.mode; - const intercept = settings.intercept; + const {mode, intercept} = settings; return ( ); }