mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-01-30 14:58:38 +00:00
Merge branch 'master' of https://github.com/mitmproxy/mitmproxy
This commit is contained in:
commit
7fa95aabbb
0
examples/__init__.py
Normal file
0
examples/__init__.py
Normal 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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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 })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
78
test/mitmproxy/data/har_extractor.har
Normal file
78
test/mitmproxy/data/har_extractor.har
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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("""
|
||||
|
37
test/mitmproxy/test_har_extractor.py
Normal file
37
test/mitmproxy/test_har_extractor.py
Normal 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"]
|
@ -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):
|
||||
|
@ -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: {
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
@ -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}
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 = [];
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user