This commit is contained in:
Maximilian Hils 2016-03-08 20:34:37 +01:00
commit 7fa95aabbb
19 changed files with 391 additions and 222 deletions

0
examples/__init__.py Normal file
View File

View File

@ -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)
for k, v in flow.request.query or {}]
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))
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({
@ -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

View File

@ -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",

View File

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

View File

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

View File

@ -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:

View File

@ -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,

View File

@ -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",
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;
var mode = settings.mode;
var intercept = settings.intercept;
exports.default = Footer;
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 })
);
}
});

View File

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

View File

@ -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")

View File

@ -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("""

View File

@ -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"]

View File

@ -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):

View File

@ -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: {

View File

@ -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();
}
});

View File

@ -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;
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>
);
}

View File

@ -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>
);

View File

@ -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 = [];

View File

@ -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>
);
}