aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2016-03-08 20:34:37 +0100
committerMaximilian Hils <git@maximilianhils.com>2016-03-08 20:34:37 +0100
commit7fa95aabbbbcc7185f6d8a80652e499142812b93 (patch)
tree51ec52f0338f6c53b8c7b7c05db6cfe0cdb4f6f0
parent47fa843795dcc9ac6260592be04172d5596e5ff9 (diff)
parentea3742c3938248c273be159d15ac49b4d2884ed8 (diff)
downloadmitmproxy-7fa95aabbbbcc7185f6d8a80652e499142812b93.tar.gz
mitmproxy-7fa95aabbbbcc7185f6d8a80652e499142812b93.tar.bz2
mitmproxy-7fa95aabbbbcc7185f6d8a80652e499142812b93.zip
Merge branch 'master' of https://github.com/mitmproxy/mitmproxy
-rw-r--r--examples/__init__.py0
-rw-r--r--examples/har_extractor.py139
-rw-r--r--mitmproxy/cmdline.py19
-rw-r--r--mitmproxy/flow_export.py28
-rw-r--r--mitmproxy/models/http.py3
-rw-r--r--mitmproxy/protocol/http.py3
-rw-r--r--mitmproxy/proxy/config.py7
-rw-r--r--mitmproxy/web/static/app.js144
-rw-r--r--test/mitmproxy/data/har_extractor.har78
-rw-r--r--test/mitmproxy/test_cmdline.py11
-rw-r--r--test/mitmproxy/test_flow_export.py31
-rw-r--r--test/mitmproxy/test_har_extractor.py37
-rw-r--r--test/mitmproxy/test_proxy.py4
-rw-r--r--web/src/js/components/common.js34
-rw-r--r--web/src/js/components/editor.js7
-rw-r--r--web/src/js/components/footer.js31
-rw-r--r--web/src/js/components/header.js20
-rw-r--r--web/src/js/components/prompt.js7
-rw-r--r--web/src/js/components/proxyapp.js18
19 files changed, 395 insertions, 226 deletions
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/examples/__init__.py
diff --git a/examples/har_extractor.py b/examples/har_extractor.py
index e7718fe8..25661f7c 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.
"""
@@ -17,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__ = {}
@@ -67,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()
@@ -83,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
@@ -112,80 +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.fromtimestamp(
- flow.request.timestamp_start,
- tz=utc).isoformat()
+ started_date_time = datetime.utcfromtimestamp(
+ flow.request.timestamp_start).isoformat()
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_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_size = len(str(flow.response.headers))
+ for k, v in flow.request.query or {}]
+
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,
- },
- "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,
+
+ 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', '')
},
- "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:
+ "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') is None):
page_id = context.HARLog.create_page_id()
context.HARLog.add(
HAR.pages({
@@ -215,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()
@@ -239,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
diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py
index 3e9fa011..b1b860f8 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:
@@ -370,6 +380,15 @@ 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,
+ type=parse_upstream_auth,
+ 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/flow_export.py b/mitmproxy/flow_export.py
index 52145516..6333de57 100644
--- a/mitmproxy/flow_export.py
+++ b/mitmproxy/flow_export.py
@@ -1,7 +1,10 @@
+import json
import urllib
-import netlib.http
from textwrap import dedent
+import netlib.http
+from netlib.utils import parse_content_type
+
def curl_command(flow):
data = "curl "
@@ -53,8 +56,16 @@ def python_code(flow):
data = ""
if flow.request.body:
- data = "\ndata = '''%s'''\n" % flow.request.body
- args += "\n data=data,"
+ json_obj = is_json(flow.request.headers, flow.request.body)
+ if json_obj:
+ # 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:
+ data = "\ndata = '''%s'''\n" % flow.request.body
+ args += "\n data=data,"
code = code.format(
url=url,
@@ -71,3 +82,14 @@ def python_code(flow):
def raw_request(flow):
data = netlib.http.http1.assemble_request(flow.request)
return data
+
+
+def is_json(headers, content):
+ if headers:
+ ct = parse_content_type(headers.get("content-type", ""))
+ if ct and "%s/%s" % (ct[0], ct[1]) == "application/json":
+ try:
+ return json.loads(content)
+ except ValueError:
+ return False
+ return False
diff --git a/mitmproxy/models/http.py b/mitmproxy/models/http.py
index 394fe51a..0338945b 100644
--- a/mitmproxy/models/http.py
+++ b/mitmproxy/models/http.py
@@ -192,6 +192,9 @@ class HTTPRequest(MessageMixin, Request):
def __hash__(self):
return id(self)
+ def set_auth(self, auth):
+ self.data.headers.set_all("Proxy-Authorization", (auth,))
+
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 13d7903b..81e59fbb 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 490cf20c..149d4710 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,
diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js
index 3537d355..bb06970e 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.StickyHeadMixin = exports.AutoScrollMixin = undefined;
var _react = require("react");
@@ -520,39 +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 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 +675,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 +892,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 +911,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", {
@@ -2784,6 +2751,7 @@ exports.default = Nav;
Object.defineProperty(exports, "__esModule", {
value: true
});
+exports.default = Footer;
var _react = require("react");
@@ -2793,34 +2761,32 @@ var _common = require("./common.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-var Footer = _react2.default.createClass({
- displayName: "Footer",
-
- 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
- );
- }
-});
+Footer.propTypes = {
+ settings: _react2.default.PropTypes.object.isRequired
+};
-exports.default = Footer;
+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(
+ "span",
+ { className: "label label-success" },
+ mode,
+ " mode"
+ ),
+ intercept && _react2.default.createElement(
+ "span",
+ { className: "label label-success" },
+ "Intercept: ",
+ intercept
+ )
+ );
+}
},{"./common.js":4,"react":"react"}],15:[function(require,module,exports){
"use strict";
@@ -2924,7 +2890,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 +2959,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();
@@ -3038,7 +3006,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"
@@ -3059,7 +3030,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",
@@ -3237,6 +3208,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]
@@ -3277,7 +3251,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 })
)
);
}
@@ -3574,14 +3548,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 +3584,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 +3663,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", {
@@ -3753,7 +3727,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,
@@ -3761,10 +3734,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,
@@ -3776,15 +3755,18 @@ 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
};
},
focus: function focus() {
+ document.activeElement.blur();
+ window.getSelection().removeAllRanges();
_reactDom2.default.findDOMNode(this).focus();
},
getMainComponent: function getMainComponent() {
@@ -3829,10 +3811,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/test/mitmproxy/data/har_extractor.har b/test/mitmproxy/data/har_extractor.har
new file mode 100644
index 00000000..2f5099b3
--- /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_cmdline.py b/test/mitmproxy/test_cmdline.py
index 5a70f3e0..e75b7baf 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,16 @@ def test_parse_server_spec():
"http://")
+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():
x = cmdline.parse_setheader("/foo/bar/voing")
assert x == ("foo", "bar", "voing")
diff --git a/test/mitmproxy/test_flow_export.py b/test/mitmproxy/test_flow_export.py
index 2dce3fd6..3dc07427 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("""
diff --git a/test/mitmproxy/test_har_extractor.py b/test/mitmproxy/test_har_extractor.py
new file mode 100644
index 00000000..7838f713
--- /dev/null
+++ b/test/mitmproxy/test_har_extractor.py
@@ -0,0 +1,37 @@
+import json
+import netlib.tutils
+from . import tutils
+
+from examples import har_extractor
+
+
+class Context(object):
+ pass
+
+
+trequest = netlib.tutils.treq(
+ timestamp_start=746203272,
+ timestamp_end=746203272,
+)
+
+tresponse = netlib.tutils.tresp(
+ timestamp_start=746203272,
+ timestamp_end=746203272,
+)
+
+
+def test_start():
+ tutils.raises(ValueError, har_extractor.start, Context(), [])
+
+
+def test_response():
+ ctx = Context()
+ 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"]
diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py
index 34b75b62..fddb851e 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):
diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js
index 5fae7415..447e6eec 100644
--- a/web/src/js/components/common.js
+++ b/web/src/js/components/common.js
@@ -29,40 +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 ChildFocus = {
- contextTypes: {
- returnFocus: React.PropTypes.func
- },
- returnFocus: function () {
- ReactDOM.findDOMNode(this).blur();
- window.getSelection().removeAllRanges();
- this.context.returnFocus();
- }
-};
-
export var Router = {
contextTypes: {
diff --git a/web/src/js/components/editor.js b/web/src/js/components/editor.js
index c929a244..eed2f7c6 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/footer.js b/web/src/js/components/footer.js
index 415c2577..e2d96288 100644
--- a/web/src/js/components/footer.js
+++ b/web/src/js/components/footer.js
@@ -1,19 +1,20 @@
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 (
- <footer>
- {mode && mode != "regular" ? <span className="label label-success">{mode} mode</span> : null}
- &nbsp;
- {intercept ? <span className="label label-success">Intercept: {intercept}</span> : null}
- </footer>
- );
- }
-});
+Footer.propTypes = {
+ settings: React.PropTypes.object.isRequired,
+};
-export default Footer; \ No newline at end of file
+export default function Footer({ settings }) {
+ const {mode, intercept} = settings;
+ return (
+ <footer>
+ {mode && mode != "regular" && (
+ <span className="label label-success">{mode} mode</span>
+ )}
+ {intercept && (
+ <span className="label label-success">Intercept: {intercept}</span>
+ )}
+ </footer>
+ );
+}
diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js
index 3833a6ee..1af928a3 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} 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();
@@ -159,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"
@@ -180,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 (
<div>
@@ -349,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]
@@ -384,7 +392,7 @@ export var Header = React.createClass({
{header}
</nav>
<div className="menu">
- <this.state.active ref="active"/>
+ <this.state.active ref="active" settings={this.props.settings}/>
</div>
</header>
);
diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js
index 7b398038..e324f7d4 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 24f45ff5..d17a1522 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,15 +51,18 @@ 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
};
},
focus: function () {
+ document.activeElement.blur();
+ window.getSelection().removeAllRanges();
ReactDOM.findDOMNode(this).focus();
},
getMainComponent: function () {
@@ -104,10 +112,10 @@ var ProxyAppMain = React.createClass({
);
return (
<div id="container" tabIndex="0" onKeyDown={this.onKeydown}>
- <Header ref="header"/>
+ <Header ref="header" settings={this.state.settings}/>
{children}
{eventlog}
- <Footer/>
+ <Footer settings={this.state.settings}/>
</div>
);
}