Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Aldo Cortesi 2016-06-07 12:56:51 +12:00
commit 75ebd638eb
23 changed files with 1590 additions and 1529 deletions

View File

@ -22,9 +22,9 @@ matrix:
git: git:
depth: 9999999 depth: 9999999
- python: 3.5 - python: 3.5
env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py" env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_base.py test/pathod/test_language_http.py"
- python: 3.5 - python: 3.5
env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py" NO_ALPN=1 env: SCOPE="netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_base.py test/pathod/test_language_http.py" NO_ALPN=1
- python: 2.7 - python: 2.7
env: DOCS=1 env: DOCS=1
script: 'cd docs && make html' script: 'cd docs && make html'

View File

@ -1212,251 +1212,221 @@ var _utils2 = require("../utils.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var TLSColumn = _react2.default.createClass({ function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
displayName: "TLSColumn",
statics: { function TLSColumn(_ref) {
Title: _react2.default.createClass({ var flow = _ref.flow;
displayName: "Title",
render: function render() { var ssl = flow.request.scheme === "https";
return _react2.default.createElement("th", _extends({}, this.props, { className: "col-tls " + (this.props.className || "") })); var classes = void 0;
} if (ssl) {
}), classes = "col-tls col-tls-https";
sortKeyFun: function sortKeyFun(flow) { } else {
return flow.request.scheme; classes = "col-tls col-tls-http";
}
},
render: function render() {
var flow = this.props.flow;
var ssl = flow.request.scheme === "https";
var classes;
if (ssl) {
classes = "col-tls col-tls-https";
} else {
classes = "col-tls col-tls-http";
}
return _react2.default.createElement("td", { className: classes });
} }
}); return _react2.default.createElement("td", { className: classes });
}
TLSColumn.Title = function (_ref2) {
var _ref2$className = _ref2.className;
var className = _ref2$className === undefined ? "" : _ref2$className;
var IconColumn = _react2.default.createClass({ var props = _objectWithoutProperties(_ref2, ["className"]);
displayName: "IconColumn",
statics: { return _react2.default.createElement("th", _extends({}, props, { className: "col-tls " + className }));
Title: _react2.default.createClass({ };
displayName: "Title", TLSColumn.sortKeyFun = function (flow) {
return flow.request.scheme;
};
render: function render() { function IconColumn(_ref3) {
return _react2.default.createElement("th", _extends({}, this.props, { className: "col-icon " + (this.props.className || "") })); var flow = _ref3.flow;
}
})
},
render: function render() {
var flow = this.props.flow;
var icon; var icon = void 0;
if (flow.response) { if (flow.response) {
var contentType = _utils.ResponseUtils.getContentType(flow.response); var contentType = _utils.ResponseUtils.getContentType(flow.response);
//TODO: We should assign a type to the flow somewhere else. //TODO: We should assign a type to the flow somewhere else.
if (flow.response.status_code === 304) { if (flow.response.status_code === 304) {
icon = "resource-icon-not-modified"; icon = "resource-icon-not-modified";
} else if (300 <= flow.response.status_code && flow.response.status_code < 400) { } else if (300 <= flow.response.status_code && flow.response.status_code < 400) {
icon = "resource-icon-redirect"; icon = "resource-icon-redirect";
} else if (contentType && contentType.indexOf("image") >= 0) { } else if (contentType && contentType.indexOf("image") >= 0) {
icon = "resource-icon-image"; icon = "resource-icon-image";
} else if (contentType && contentType.indexOf("javascript") >= 0) { } else if (contentType && contentType.indexOf("javascript") >= 0) {
icon = "resource-icon-js"; icon = "resource-icon-js";
} else if (contentType && contentType.indexOf("css") >= 0) { } else if (contentType && contentType.indexOf("css") >= 0) {
icon = "resource-icon-css"; icon = "resource-icon-css";
} else if (contentType && contentType.indexOf("html") >= 0) { } else if (contentType && contentType.indexOf("html") >= 0) {
icon = "resource-icon-document"; icon = "resource-icon-document";
}
} }
if (!icon) {
icon = "resource-icon-plain";
}
icon += " resource-icon";
return _react2.default.createElement(
"td",
{ className: "col-icon" },
_react2.default.createElement("div", { className: icon })
);
} }
}); if (!icon) {
icon = "resource-icon-plain";
var PathColumn = _react2.default.createClass({
displayName: "PathColumn",
statics: {
Title: _react2.default.createClass({
displayName: "Title",
render: function render() {
return _react2.default.createElement(
"th",
_extends({}, this.props, { className: "col-path " + (this.props.className || "") }),
"Path"
);
}
}),
sortKeyFun: function sortKeyFun(flow) {
return _utils.RequestUtils.pretty_url(flow.request);
}
},
render: function render() {
var flow = this.props.flow;
return _react2.default.createElement(
"td",
{ className: "col-path" },
flow.request.is_replay ? _react2.default.createElement("i", { className: "fa fa-fw fa-repeat pull-right" }) : null,
flow.intercepted ? _react2.default.createElement("i", { className: "fa fa-fw fa-pause pull-right" }) : null,
_utils.RequestUtils.pretty_url(flow.request)
);
} }
});
var MethodColumn = _react2.default.createClass({ icon += " resource-icon";
displayName: "MethodColumn", return _react2.default.createElement(
"td",
{ className: "col-icon" },
_react2.default.createElement("div", { className: icon })
);
}
IconColumn.Title = function (_ref4) {
var _ref4$className = _ref4.className;
var className = _ref4$className === undefined ? "" : _ref4$className;
statics: { var props = _objectWithoutProperties(_ref4, ["className"]);
Title: _react2.default.createClass({
displayName: "Title",
render: function render() { return _react2.default.createElement("th", _extends({}, props, { className: "col-icon " + className }));
return _react2.default.createElement( };
"th",
_extends({}, this.props, { className: "col-method " + (this.props.className || "") }), function PathColumn(_ref5) {
"Method" var flow = _ref5.flow;
);
} return _react2.default.createElement(
}), "td",
sortKeyFun: function sortKeyFun(flow) { { className: "col-path" },
return flow.request.method; flow.request.is_replay ? _react2.default.createElement("i", { className: "fa fa-fw fa-repeat pull-right" }) : null,
} flow.intercepted ? _react2.default.createElement("i", { className: "fa fa-fw fa-pause pull-right" }) : null,
}, _utils.RequestUtils.pretty_url(flow.request)
render: function render() { );
var flow = this.props.flow; }
return _react2.default.createElement( PathColumn.Title = function (_ref6) {
"td", var _ref6$className = _ref6.className;
{ className: "col-method" }, var className = _ref6$className === undefined ? "" : _ref6$className;
flow.request.method
); var props = _objectWithoutProperties(_ref6, ["className"]);
return _react2.default.createElement(
"th",
_extends({}, props, { className: "col-path " + className }),
"Path"
);
};
PathColumn.sortKeyFun = function (flow) {
return _utils.RequestUtils.pretty_url(flow.request);
};
function MethodColumn(_ref7) {
var flow = _ref7.flow;
return _react2.default.createElement(
"td",
{ className: "col-method" },
flow.request.method
);
}
MethodColumn.Title = function (_ref8) {
var _ref8$className = _ref8.className;
var className = _ref8$className === undefined ? "" : _ref8$className;
var props = _objectWithoutProperties(_ref8, ["className"]);
return _react2.default.createElement(
"th",
_extends({}, props, { className: "col-method " + className }),
"Method"
);
};
MethodColumn.sortKeyFun = function (flow) {
return flow.request.method;
};
function StatusColumn(_ref9) {
var flow = _ref9.flow;
var status = void 0;
if (flow.response) {
status = flow.response.status_code;
} else {
status = null;
} }
}); return _react2.default.createElement(
"td",
{ className: "col-status" },
status
);
}
StatusColumn.Title = function (_ref10) {
var _ref10$className = _ref10.className;
var className = _ref10$className === undefined ? "" : _ref10$className;
var StatusColumn = _react2.default.createClass({ var props = _objectWithoutProperties(_ref10, ["className"]);
displayName: "StatusColumn",
statics: { return _react2.default.createElement(
Title: _react2.default.createClass({ "th",
displayName: "Title", _extends({}, props, { className: "col-status " + className }),
"Status"
);
};
StatusColumn.sortKeyFun = function (flow) {
return flow.response ? flow.response.status_code : undefined;
};
render: function render() { function SizeColumn(_ref11) {
return _react2.default.createElement( var flow = _ref11.flow;
"th",
_extends({}, this.props, { className: "col-status " + (this.props.className || "") }), var total = flow.request.contentLength;
"Status" if (flow.response) {
); total += flow.response.contentLength || 0;
}
}),
sortKeyFun: function sortKeyFun(flow) {
return flow.response ? flow.response.status_code : undefined;
}
},
render: function render() {
var flow = this.props.flow;
var status;
if (flow.response) {
status = flow.response.status_code;
} else {
status = null;
}
return _react2.default.createElement(
"td",
{ className: "col-status" },
status
);
} }
}); var size = (0, _utils2.formatSize)(total);
return _react2.default.createElement(
"td",
{ className: "col-size" },
size
);
}
SizeColumn.Title = function (_ref12) {
var _ref12$className = _ref12.className;
var className = _ref12$className === undefined ? "" : _ref12$className;
var SizeColumn = _react2.default.createClass({ var props = _objectWithoutProperties(_ref12, ["className"]);
displayName: "SizeColumn",
statics: { return _react2.default.createElement(
Title: _react2.default.createClass({ "th",
displayName: "Title", _extends({}, props, { className: "col-size " + className }),
"Size"
render: function render() { );
return _react2.default.createElement( };
"th", SizeColumn.sortKeyFun = function (flow) {
_extends({}, this.props, { className: "col-size " + (this.props.className || "") }), var total = flow.request.contentLength;
"Size" if (flow.response) {
); total += flow.response.contentLength || 0;
}
}),
sortKeyFun: function sortKeyFun(flow) {
var total = flow.request.contentLength;
if (flow.response) {
total += flow.response.contentLength || 0;
}
return total;
}
},
render: function render() {
var flow = this.props.flow;
var total = flow.request.contentLength;
if (flow.response) {
total += flow.response.contentLength || 0;
}
var size = (0, _utils2.formatSize)(total);
return _react2.default.createElement(
"td",
{ className: "col-size" },
size
);
} }
}); return total;
};
var TimeColumn = _react2.default.createClass({ function TimeColumn(_ref13) {
displayName: "TimeColumn", var flow = _ref13.flow;
statics: { var time = void 0;
Title: _react2.default.createClass({ if (flow.response) {
displayName: "Title", time = (0, _utils2.formatTimeDelta)(1000 * (flow.response.timestamp_end - flow.request.timestamp_start));
} else {
render: function render() { time = "...";
return _react2.default.createElement(
"th",
_extends({}, this.props, { className: "col-time " + (this.props.className || "") }),
"Time"
);
}
}),
sortKeyFun: function sortKeyFun(flow) {
if (flow.response) {
return flow.response.timestamp_end - flow.request.timestamp_start;
}
}
},
render: function render() {
var flow = this.props.flow;
var time;
if (flow.response) {
time = (0, _utils2.formatTimeDelta)(1000 * (flow.response.timestamp_end - flow.request.timestamp_start));
} else {
time = "...";
}
return _react2.default.createElement(
"td",
{ className: "col-time" },
time
);
} }
}); return _react2.default.createElement(
"td",
{ className: "col-time" },
time
);
}
TimeColumn.Title = function (_ref14) {
var _ref14$className = _ref14.className;
var className = _ref14$className === undefined ? "" : _ref14$className;
var props = _objectWithoutProperties(_ref14, ["className"]);
return _react2.default.createElement(
"th",
_extends({}, props, { className: "col-time " + className }),
"Time"
);
};
TimeColumn.sortKeyFun = function (flow) {
return flow.response.timestamp_end - flow.request.timestamp_start;
};
var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, SizeColumn, TimeColumn]; var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, SizeColumn, TimeColumn];
@ -1525,12 +1495,17 @@ FlowRow.propTypes = {
selected: _react2.default.PropTypes.bool selected: _react2.default.PropTypes.bool
}; };
function FlowRow(props) { function FlowRow(_ref) {
var flow = props.flow; var flow = _ref.flow;
var selected = _ref.selected;
var highlight = _ref.highlight;
var columns = _ref.columns;
var selectFlow = _ref.selectFlow;
var className = (0, _classnames2.default)({ var className = (0, _classnames2.default)({
"selected": props.selected, "selected": selected,
"highlighted": props.highlight && parseFilter(props.highlight)(flow), "highlighted": highlight && parseFilter(highlight)(flow),
"intercepted": flow.intercepted, "intercepted": flow.intercepted,
"has-request": flow.request, "has-request": flow.request,
"has-response": flow.response "has-response": flow.response
@ -1539,10 +1514,10 @@ function FlowRow(props) {
return _react2.default.createElement( return _react2.default.createElement(
"tr", "tr",
{ className: className, onClick: function onClick() { { className: className, onClick: function onClick() {
return props.selectFlow(flow); return selectFlow(flow);
} }, } },
props.columns.map(function (Column) { columns.map(function (Column) {
return _react2.default.createElement(Column, { key: Column.displayName, flow: flow }); return _react2.default.createElement(Column, { key: Column.name, flow: flow });
}) })
); );
} }
@ -1553,8 +1528,6 @@ var FlowRowContainer = (0, _reactRedux.connect)(function (state, ownProps) {
highlight: state.flows.highlight, highlight: state.flows.highlight,
selected: state.flows.selected.indexOf(ownProps.flowId) >= 0 selected: state.flows.selected.indexOf(ownProps.flowId) >= 0
}; };
}, function (dispatch, ownProps) {
return {};
})(FlowRow); })(FlowRow);
var FlowTableHead = function (_React$Component) { var FlowTableHead = function (_React$Component) {
@ -1608,11 +1581,11 @@ var FlowTableHead = function (_React$Component) {
null, null,
this.props.columns.map(function (Column) { this.props.columns.map(function (Column) {
return _react2.default.createElement(Column.Title, { return _react2.default.createElement(Column.Title, {
key: Column.displayName, key: Column.name,
onClick: function onClick() { onClick: function onClick() {
return _this2.onClick(Column); return _this2.onClick(Column);
}, },
className: sortColumn === Column && sortType className: sortColumn === Column ? sortType : undefined
}); });
}) })
); );
@ -1769,8 +1742,6 @@ var FlowTableContainer = (0, _reactRedux.connect)(function (state) {
return { return {
flows: state.flows.view flows: state.flows.view
}; };
}, function (dispatch) {
return {};
})(FlowTable); })(FlowTable);
exports.default = FlowTableContainer; exports.default = FlowTableContainer;
@ -4757,22 +4728,23 @@ function makeList(actionType, fetchURL) {
list = [].concat(_toConsumableArray(state.list)); list = [].concat(_toConsumableArray(state.list));
itemIndex = state.indexOf[action.item.id]; itemIndex = state.indexOf[action.item.id];
list[itemIndex] = action.item; list[itemIndex] = action.item;
return _extends({}, defaultState, { return _extends({}, state, {
list: list list: list,
byId: _extends({}, state.byId, _defineProperty({}, action.item.id, action.item))
}); });
case REMOVE: case REMOVE:
list = [].concat(_toConsumableArray(state.list)); list = [].concat(_toConsumableArray(state.list));
itemIndex = state.indexOf[action.item.id]; itemIndex = state.indexOf[action.item.id];
list.splice(itemIndex, 1); list.splice(itemIndex, 1);
return _extends({}, defaultState, { return _extends({}, state, {
list: list, list: list,
byId: _extends({}, state.byId, _defineProperty({}, action.item.id, undefined)), byId: _extends({}, state.byId, _defineProperty({}, action.item.id, undefined)),
indexOf: _extends({}, state.indexOf, _defineProperty({}, action.item.id, undefined)) indexOf: _extends({}, state.indexOf, _defineProperty({}, action.item.id, undefined))
}); });
case REQUEST_LIST: case REQUEST_LIST:
return _extends({}, defaultState, { return _extends({}, state, {
isFetching: true isFetching: true
}); });
@ -4977,13 +4949,14 @@ function updateViewList(currentView, currentList, nextList, action) {
if (!isInView && shouldBeInView) return sortedInsert(currentView, sortFn, action.item); if (!isInView && shouldBeInView) return sortedInsert(currentView, sortFn, action.item);
if (isInView && !shouldBeInView) return sortedRemove(currentView, sortFn, action.item); if (isInView && !shouldBeInView) return sortedRemove(currentView, sortFn, action.item);
if (isInView && shouldBeInView && sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) { if (isInView && shouldBeInView) {
var _ret = function () { var _ret = function () {
var s = [].concat(_toConsumableArray(currentView)); var s = [].concat(_toConsumableArray(currentView));
s.sort(makeCompareFn(sortFn));
s.indexOf = function (x) { s.indexOf = function (x) {
return sortedIndexOf(s, x, sortFn); return sortedIndexOf(s, x, sortFn);
}; };
s[s.indexOf(currentItemState)] = nextItemState;
if (sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) s.sort(makeCompareFn(sortFn));
return { return {
v: s v: s
}; };

File diff suppressed because it is too large Load Diff

View File

@ -580,8 +580,10 @@ class _Connection(object):
@contextlib.contextmanager @contextlib.contextmanager
def _closer(client): def _closer(client):
yield try:
client.close() yield
finally:
client.close()
class TCPClient(_Connection): class TCPClient(_Connection):

View File

@ -261,7 +261,7 @@ class _Component(Token):
""" """
A value component of the primary specification of an message. A value component of the primary specification of an message.
Components produce byte values desribe the bytes of the message. Components produce byte values describing the bytes of the message.
""" """
def values(self, settings): # pragma: no cover def values(self, settings): # pragma: no cover
@ -272,9 +272,9 @@ class _Component(Token):
def string(self, settings=None): def string(self, settings=None):
""" """
A string representation of the object. A bytestring representation of the object.
""" """
return "".join(i[:] for i in self.values(settings or {})) return b"".join(i[:] for i in self.values(settings or {}))
class KeyValue(_Component): class KeyValue(_Component):
@ -391,7 +391,7 @@ class Integer(_Component):
"Integer value must be between %s and %s." % self.bounds, "Integer value must be between %s and %s." % self.bounds,
0, 0 0, 0
) )
self.value = str(value) self.value = str(value).encode()
@classmethod @classmethod
def expr(cls): def expr(cls):
@ -401,10 +401,10 @@ class Integer(_Component):
return e.setParseAction(lambda x: cls(*x)) return e.setParseAction(lambda x: cls(*x))
def values(self, settings): def values(self, settings):
return self.value return [self.value]
def spec(self): def spec(self):
return "%s%s" % (self.preamble, self.value) return "%s%s" % (self.preamble, self.value.decode())
def freeze(self, settings_): def freeze(self, settings_):
return self return self
@ -555,7 +555,7 @@ class NestedMessage(Token):
try: try:
self.parsed = self.nest_type( self.parsed = self.nest_type(
self.nest_type.expr().parseString( self.nest_type.expr().parseString(
value.val, value.val.decode(),
parseAll=True parseAll=True
) )
) )
@ -578,4 +578,4 @@ class NestedMessage(Token):
def freeze(self, settings): def freeze(self, settings):
f = self.parsed.freeze(settings).spec() f = self.parsed.freeze(settings).spec()
return self.__class__(TokValueLiteral(strutils.bytes_to_escaped_str(f))) return self.__class__(TokValueLiteral(strutils.bytes_to_escaped_str(f.encode())))

View File

@ -3,6 +3,7 @@ import random
import mmap import mmap
import six import six
import sys
DATATYPES = dict( DATATYPES = dict(
ascii_letters=string.ascii_letters.encode(), ascii_letters=string.ascii_letters.encode(),
@ -68,7 +69,7 @@ class RandomGenerator(object):
def __getitem__(self, x): def __getitem__(self, x):
chars = DATATYPES[self.dtype] chars = DATATYPES[self.dtype]
if isinstance(x, slice): if isinstance(x, slice):
return b"".join(rand_byte(chars) for _ in range(*x.indices(self.length))) return b"".join(rand_byte(chars) for _ in range(*x.indices(min(self.length, sys.maxsize))))
return rand_byte(chars) return rand_byte(chars)
def __repr__(self): def __repr__(self):

View File

@ -57,7 +57,7 @@ class _HeaderMixin(object):
unique_name = None unique_name = None
def format_header(self, key, value): def format_header(self, key, value):
return [key, ": ", value, "\r\n"] return [key, b": ", value, b"\r\n"]
def values(self, settings): def values(self, settings):
return self.format_header( return self.format_header(
@ -88,7 +88,7 @@ class ShortcutUserAgent(_HeaderMixin, base.OptionsOrValue):
def values(self, settings): def values(self, settings):
value = self.value.val value = self.value.val
if self.option_used: if self.option_used:
value = user_agents.get_by_shortcut(value.lower())[2] value = user_agents.get_by_shortcut(value.lower().decode())[2].encode()
return self.format_header( return self.format_header(
self.key.get_generator(settings), self.key.get_generator(settings),
@ -109,7 +109,7 @@ def get_header(val, headers):
class _HTTPMessage(message.Message): class _HTTPMessage(message.Message):
version = "HTTP/1.1" version = b"HTTP/1.1"
@property @property
def actions(self): def actions(self):
@ -133,10 +133,10 @@ class _HTTPMessage(message.Message):
def values(self, settings): def values(self, settings):
vals = self.preamble(settings) vals = self.preamble(settings)
vals.append("\r\n") vals.append(b"\r\n")
for h in self.headers: for h in self.headers:
vals.extend(h.values(settings)) vals.extend(h.values(settings))
vals.append("\r\n") vals.append(b"\r\n")
if self.body: if self.body:
vals.extend(self.body.values(settings)) vals.extend(self.body.values(settings))
return vals return vals
@ -171,18 +171,18 @@ class Response(_HTTPMessage):
return self.tok(Reason) return self.tok(Reason)
def preamble(self, settings): def preamble(self, settings):
l = [self.version, " "] l = [self.version, b" "]
l.extend(self.status_code.values(settings)) l.extend(self.status_code.values(settings))
status_code = int(self.status_code.value) status_code = int(self.status_code.value)
l.append(" ") l.append(b" ")
if self.reason: if self.reason:
l.extend(self.reason.values(settings)) l.extend(self.reason.values(settings))
else: else:
l.append( l.append(
status_codes.RESPONSES.get( status_codes.RESPONSES.get(
status_code, status_code,
"Unknown code" b"Unknown code"
) ).encode()
) )
return l return l
@ -205,8 +205,8 @@ class Response(_HTTPMessage):
if not get_header(i[0], self.headers): if not get_header(i[0], self.headers):
tokens.append( tokens.append(
Header( Header(
base.TokValueLiteral(i[0]), base.TokValueLiteral(i[0].decode()),
base.TokValueLiteral(i[1])) base.TokValueLiteral(i[1].decode()))
) )
if not self.raw: if not self.raw:
if not get_header("Content-Length", self.headers): if not get_header("Content-Length", self.headers):
@ -294,11 +294,11 @@ class Request(_HTTPMessage):
def preamble(self, settings): def preamble(self, settings):
v = self.method.values(settings) v = self.method.values(settings)
v.append(" ") v.append(b" ")
v.extend(self.path.values(settings)) v.extend(self.path.values(settings))
if self.nested_response: if self.nested_response:
v.append(self.nested_response.parsed.spec()) v.append(self.nested_response.parsed.spec())
v.append(" ") v.append(b" ")
v.append(self.version) v.append(self.version)
return v return v
@ -314,8 +314,8 @@ class Request(_HTTPMessage):
if not get_header(i[0], self.headers): if not get_header(i[0], self.headers):
tokens.append( tokens.append(
Header( Header(
base.TokValueLiteral(i[0]), base.TokValueLiteral(i[0].decode()),
base.TokValueLiteral(i[1]) base.TokValueLiteral(i[1].decode())
) )
) )
if not self.raw: if not self.raw:

View File

@ -1,5 +1,6 @@
import abc import abc
from . import actions, exceptions from . import actions, exceptions
from netlib import strutils
LOG_TRUNCATE = 1024 LOG_TRUNCATE = 1024
@ -49,7 +50,7 @@ class Message(object):
def preview_safe(self): def preview_safe(self):
""" """
Return a copy of this message that issafe for previews. Return a copy of this message that is safe for previews.
""" """
tokens = [i for i in self.tokens if not isinstance(i, actions.PauseAt)] tokens = [i for i in self.tokens if not isinstance(i, actions.PauseAt)]
return self.__class__(tokens) return self.__class__(tokens)
@ -80,10 +81,10 @@ class Message(object):
# We truncate at 1k. # We truncate at 1k.
if hasattr(v, "values"): if hasattr(v, "values"):
v = [x[:LOG_TRUNCATE] for x in v.values(settings)] v = [x[:LOG_TRUNCATE] for x in v.values(settings)]
v = "".join(v).encode("string_escape") v = strutils.bytes_to_escaped_str(b"".join(v))
elif hasattr(v, "__len__"): elif hasattr(v, "__len__"):
v = v[:LOG_TRUNCATE] v = v[:LOG_TRUNCATE]
v = v.encode("string_escape") v = strutils.bytes_to_escaped_str(v)
ret[i] = v ret[i] = v
ret["spec"] = self.spec() ret["spec"] = self.spec()
return ret return ret

View File

@ -247,7 +247,10 @@ class Pathoc(tcp.TCPClient):
def socks_connect(self, connect_to): def socks_connect(self, connect_to):
try: try:
client_greet = socks.ClientGreeting(socks.VERSION.SOCKS5, [socks.METHOD.NO_AUTHENTICATION_REQUIRED]) client_greet = socks.ClientGreeting(
socks.VERSION.SOCKS5,
[socks.METHOD.NO_AUTHENTICATION_REQUIRED]
)
client_greet.to_file(self.wfile) client_greet.to_file(self.wfile)
self.wfile.flush() self.wfile.flush()
@ -286,44 +289,47 @@ class Pathoc(tcp.TCPClient):
if self.use_http2 and not self.ssl: if self.use_http2 and not self.ssl:
raise NotImplementedError("HTTP2 without SSL is not supported.") raise NotImplementedError("HTTP2 without SSL is not supported.")
ret = tcp.TCPClient.connect(self) try:
ret = tcp.TCPClient.connect(self)
if connect_to:
self.http_connect(connect_to)
if connect_to: self.sslinfo = None
self.http_connect(connect_to) if self.ssl:
try:
alpn_protos = [b'http/1.1']
if self.use_http2:
alpn_protos.append(b'h2')
self.sslinfo = None self.convert_to_ssl(
if self.ssl: sni=self.sni,
try: cert=self.clientcert,
alpn_protos = [b'http/1.1'] method=self.ssl_version,
if self.use_http2: options=self.ssl_options,
alpn_protos.append(b'h2') cipher_list=self.ciphers,
alpn_protos=alpn_protos
)
except TlsException as v:
raise PathocError(str(v))
self.convert_to_ssl( self.sslinfo = SSLInfo(
sni=self.sni, self.connection.get_peer_cert_chain(),
cert=self.clientcert, self.get_current_cipher(),
method=self.ssl_version, self.get_alpn_proto_negotiated()
options=self.ssl_options,
cipher_list=self.ciphers,
alpn_protos=alpn_protos
) )
except TlsException as v: if showssl:
raise PathocError(str(v)) print(str(self.sslinfo), file=fp)
self.sslinfo = SSLInfo( if self.use_http2:
self.connection.get_peer_cert_chain(), self.protocol.check_alpn()
self.get_current_cipher(), if not self.http2_skip_connection_preface:
self.get_alpn_proto_negotiated() self.protocol.perform_client_connection_preface()
)
if showssl:
print(str(self.sslinfo), file=fp)
if self.use_http2: if self.timeout:
self.protocol.check_alpn() self.settimeout(self.timeout)
if not self.http2_skip_connection_preface: except Exception:
self.protocol.perform_client_connection_preface() self.close()
raise
if self.timeout:
self.settimeout(self.timeout)
return ret return ret
def stop(self): def stop(self):
@ -355,6 +361,7 @@ class Pathoc(tcp.TCPClient):
return return
if frm is None: if frm is None:
self.ws_framereader.join() self.ws_framereader.join()
self.ws_framereader = None
return return
yield frm yield frm

View File

@ -75,7 +75,7 @@ setup(
"passlib>=1.6.5, <1.7", "passlib>=1.6.5, <1.7",
"pyasn1>=0.1.9, <0.2", "pyasn1>=0.1.9, <0.2",
"pyOpenSSL>=16.0, <17.0", "pyOpenSSL>=16.0, <17.0",
"pyparsing>=2.0, <2.1", # 2.1.1 breaks our binaries, see https://sourceforge.net/p/pyparsing/bugs/93/ "pyparsing>=2.1.3, <2.2",
"pyperclip>=1.5.22, <1.6", "pyperclip>=1.5.22, <1.6",
"requests>=2.9.1, <2.10", "requests>=2.9.1, <2.10",
"six>=1.10, <1.11", "six>=1.10, <1.11",

View File

@ -55,8 +55,15 @@ class TestTokValueLiteral:
v = base.TokValueLiteral("f\x00oo") v = base.TokValueLiteral("f\x00oo")
assert v.spec() == repr(v) == r"'f\x00oo'" assert v.spec() == repr(v) == r"'f\x00oo'"
v = base.TokValueLiteral("\"") v = base.TokValueLiteral('"')
assert v.spec() == repr(v) == '\'"\'' assert v.spec() == repr(v) == """ '"' """.strip()
# While pyparsing has a escChar argument for QuotedString,
# escChar only performs scapes single-character escapes and does not work for e.g. r"\x02".
# Thus, we cannot use that option, which means we cannot have single quotes in strings.
# To fix this, we represent single quotes as r"\x07".
v = base.TokValueLiteral("'")
assert v.spec() == r"'\x27'"
def roundtrip(self, spec): def roundtrip(self, spec):
e = base.TokValueLiteral.expr() e = base.TokValueLiteral.expr()
@ -311,7 +318,7 @@ def test_options_or_value():
def test_integer(): def test_integer():
e = base.Integer.expr() e = base.Integer.expr()
v = e.parseString("200")[0] v = e.parseString("200")[0]
assert v.string() == "200" assert v.string() == b"200"
assert v.spec() == "200" assert v.spec() == "200"
assert v.freeze({}).value == v.value assert v.freeze({}).value == v.value

View File

@ -1,15 +1,15 @@
from six.moves import cStringIO as StringIO from six import BytesIO
from pathod import language from pathod import language
from pathod.language import http, base from pathod.language import http, base
import tutils import tutils
def parse_request(s): def parse_request(s):
return language.parse_pathoc(s).next() return next(language.parse_pathoc(s))
def test_make_error_response(): def test_make_error_response():
d = StringIO() d = BytesIO()
s = http.make_error_response("foo") s = http.make_error_response("foo")
language.serve(s, d, {}) language.serve(s, d, {})
@ -24,17 +24,17 @@ class TestRequest:
def test_simple(self): def test_simple(self):
r = parse_request('GET:"/foo"') r = parse_request('GET:"/foo"')
assert r.method.string() == "GET" assert r.method.string() == b"GET"
assert r.path.string() == "/foo" assert r.path.string() == b"/foo"
r = parse_request('GET:/foo') r = parse_request('GET:/foo')
assert r.path.string() == "/foo" assert r.path.string() == b"/foo"
r = parse_request('GET:@1k') r = parse_request('GET:@1k')
assert len(r.path.string()) == 1024 assert len(r.path.string()) == 1024
def test_multiple(self): def test_multiple(self):
r = list(language.parse_pathoc("GET:/ PUT:/")) r = list(language.parse_pathoc("GET:/ PUT:/"))
assert r[0].method.string() == "GET" assert r[0].method.string() == b"GET"
assert r[1].method.string() == "PUT" assert r[1].method.string() == b"PUT"
assert len(r) == 2 assert len(r) == 2
l = """ l = """
@ -54,8 +54,8 @@ class TestRequest:
""" """
r = list(language.parse_pathoc(l)) r = list(language.parse_pathoc(l))
assert len(r) == 2 assert len(r) == 2
assert r[0].method.string() == "GET" assert r[0].method.string() == b"GET"
assert r[1].method.string() == "PUT" assert r[1].method.string() == b"PUT"
l = """ l = """
get:"http://localhost:9999/p/200":ir,@1 get:"http://localhost:9999/p/200":ir,@1
@ -63,8 +63,8 @@ class TestRequest:
""" """
r = list(language.parse_pathoc(l)) r = list(language.parse_pathoc(l))
assert len(r) == 2 assert len(r) == 2
assert r[0].method.string() == "GET" assert r[0].method.string() == b"GET"
assert r[1].method.string() == "GET" assert r[1].method.string() == b"GET"
def test_nested_response(self): def test_nested_response(self):
l = "get:/p:s'200'" l = "get:/p:s'200'"
@ -75,7 +75,7 @@ class TestRequest:
assert r[0].values({}) assert r[0].values({})
def test_render(self): def test_render(self):
s = StringIO() s = BytesIO()
r = parse_request("GET:'/foo'") r = parse_request("GET:'/foo'")
assert language.serve( assert language.serve(
r, r,
@ -90,8 +90,8 @@ class TestRequest:
ir,@1 ir,@1
""" """
r = parse_request(l) r = parse_request(l)
assert r.method.string() == "GET" assert r.method.string() == b"GET"
assert r.path.string() == "/foo" assert r.path.string() == b"/foo"
assert r.actions assert r.actions
l = """ l = """
@ -106,8 +106,8 @@ class TestRequest:
ir,@1 ir,@1
""" """
r = parse_request(l) r = parse_request(l)
assert r.method.string() == "GET" assert r.method.string() == b"GET"
assert r.path.string().endswith("bar") assert r.path.string().endswith(b"bar")
assert r.actions assert r.actions
def test_spec(self): def test_spec(self):
@ -128,66 +128,66 @@ class TestRequest:
def test_websocket(self): def test_websocket(self):
r = parse_request('ws:/path/') r = parse_request('ws:/path/')
res = r.resolve(language.Settings()) res = r.resolve(language.Settings())
assert res.method.string().lower() == "get" assert res.method.string().lower() == b"get"
assert res.tok(http.Path).value.val == "/path/" assert res.tok(http.Path).value.val == b"/path/"
assert res.tok(http.Method).value.val.lower() == "get" assert res.tok(http.Method).value.val.lower() == b"get"
assert http.get_header("Upgrade", res.headers).value.val == "websocket" assert http.get_header(b"Upgrade", res.headers).value.val == b"websocket"
r = parse_request('ws:put:/path/') r = parse_request('ws:put:/path/')
res = r.resolve(language.Settings()) res = r.resolve(language.Settings())
assert r.method.string().lower() == "put" assert r.method.string().lower() == b"put"
assert res.tok(http.Path).value.val == "/path/" assert res.tok(http.Path).value.val == b"/path/"
assert res.tok(http.Method).value.val.lower() == "put" assert res.tok(http.Method).value.val.lower() == b"put"
assert http.get_header("Upgrade", res.headers).value.val == "websocket" assert http.get_header(b"Upgrade", res.headers).value.val == b"websocket"
class TestResponse: class TestResponse:
def dummy_response(self): def dummy_response(self):
return language.parse_pathod("400'msg'").next() return next(language.parse_pathod("400'msg'"))
def test_response(self): def test_response(self):
r = language.parse_pathod("400:m'msg'").next() r = next(language.parse_pathod("400:m'msg'"))
assert r.status_code.string() == "400" assert r.status_code.string() == b"400"
assert r.reason.string() == "msg" assert r.reason.string() == b"msg"
r = language.parse_pathod("400:m'msg':b@100b").next() r = next(language.parse_pathod("400:m'msg':b@100b"))
assert r.reason.string() == "msg" assert r.reason.string() == b"msg"
assert r.body.values({}) assert r.body.values({})
assert str(r) assert str(r)
r = language.parse_pathod("200").next() r = next(language.parse_pathod("200"))
assert r.status_code.string() == "200" assert r.status_code.string() == b"200"
assert not r.reason assert not r.reason
assert "OK" in [i[:] for i in r.preamble({})] assert b"OK" in [i[:] for i in r.preamble({})]
def test_render(self): def test_render(self):
s = StringIO() s = BytesIO()
r = language.parse_pathod("400:m'msg'").next() r = next(language.parse_pathod("400:m'msg'"))
assert language.serve(r, s, {}) assert language.serve(r, s, {})
r = language.parse_pathod("400:p0,100:dr").next() r = next(language.parse_pathod("400:p0,100:dr"))
assert "p0" in r.spec() assert "p0" in r.spec()
s = r.preview_safe() s = r.preview_safe()
assert "p0" not in s.spec() assert "p0" not in s.spec()
def test_raw(self): def test_raw(self):
s = StringIO() s = BytesIO()
r = language.parse_pathod("400:b'foo'").next() r = next(language.parse_pathod("400:b'foo'"))
language.serve(r, s, {}) language.serve(r, s, {})
v = s.getvalue() v = s.getvalue()
assert "Content-Length" in v assert b"Content-Length" in v
s = StringIO() s = BytesIO()
r = language.parse_pathod("400:b'foo':r").next() r = next(language.parse_pathod("400:b'foo':r"))
language.serve(r, s, {}) language.serve(r, s, {})
v = s.getvalue() v = s.getvalue()
assert "Content-Length" not in v assert b"Content-Length" not in v
def test_length(self): def test_length(self):
def testlen(x): def testlen(x):
s = StringIO() s = BytesIO()
x = x.next() x = next(x)
language.serve(x, s, language.Settings()) language.serve(x, s, language.Settings())
assert x.length(language.Settings()) == len(s.getvalue()) assert x.length(language.Settings()) == len(s.getvalue())
testlen(language.parse_pathod("400:m'msg':r")) testlen(language.parse_pathod("400:m'msg':r"))
@ -196,8 +196,8 @@ class TestResponse:
def test_maximum_length(self): def test_maximum_length(self):
def testlen(x): def testlen(x):
x = x.next() x = next(x)
s = StringIO() s = BytesIO()
m = x.maximum_length({}) m = x.maximum_length({})
language.serve(x, s, {}) language.serve(x, s, {})
assert m >= len(s.getvalue()) assert m >= len(s.getvalue())
@ -225,19 +225,19 @@ class TestResponse:
tutils.raises("ascii", language.parse_pathod, "foo:b\xf0") tutils.raises("ascii", language.parse_pathod, "foo:b\xf0")
def test_parse_header(self): def test_parse_header(self):
r = language.parse_pathod('400:h"foo"="bar"').next() r = next(language.parse_pathod('400:h"foo"="bar"'))
assert http.get_header("foo", r.headers) assert http.get_header(b"foo", r.headers)
def test_parse_pause_before(self): def test_parse_pause_before(self):
r = language.parse_pathod("400:p0,10").next() r = next(language.parse_pathod("400:p0,10"))
assert r.actions[0].spec() == "p0,10" assert r.actions[0].spec() == "p0,10"
def test_parse_pause_after(self): def test_parse_pause_after(self):
r = language.parse_pathod("400:pa,10").next() r = next(language.parse_pathod("400:pa,10"))
assert r.actions[0].spec() == "pa,10" assert r.actions[0].spec() == "pa,10"
def test_parse_pause_random(self): def test_parse_pause_random(self):
r = language.parse_pathod("400:pr,10").next() r = next(language.parse_pathod("400:pr,10"))
assert r.actions[0].spec() == "pr,10" assert r.actions[0].spec() == "pr,10"
def test_parse_stress(self): def test_parse_stress(self):
@ -245,29 +245,29 @@ class TestResponse:
# returns an int and a python 2.7 int on windows has 32bit precision. # returns an int and a python 2.7 int on windows has 32bit precision.
# Therefore, we should keep the body length < 2147483647 bytes in our # Therefore, we should keep the body length < 2147483647 bytes in our
# tests. # tests.
r = language.parse_pathod("400:b@1g").next() r = next(language.parse_pathod("400:b@1g"))
assert r.length({}) assert r.length({})
def test_spec(self): def test_spec(self):
def rt(s): def rt(s):
s = language.parse_pathod(s).next().spec() s = next(language.parse_pathod(s)).spec()
assert language.parse_pathod(s).next().spec() == s assert next(language.parse_pathod(s)).spec() == s
rt("400:b@100g") rt("400:b@100g")
rt("400") rt("400")
rt("400:da") rt("400:da")
def test_websockets(self): def test_websockets(self):
r = language.parse_pathod("ws").next() r = next(language.parse_pathod("ws"))
tutils.raises("no websocket key", r.resolve, language.Settings()) tutils.raises("no websocket key", r.resolve, language.Settings())
res = r.resolve(language.Settings(websocket_key="foo")) res = r.resolve(language.Settings(websocket_key=b"foo"))
assert res.status_code.string() == "101" assert res.status_code.string() == b"101"
def test_ctype_shortcut(): def test_ctype_shortcut():
e = http.ShortcutContentType.expr() e = http.ShortcutContentType.expr()
v = e.parseString("c'foo'")[0] v = e.parseString("c'foo'")[0]
assert v.key.val == "Content-Type" assert v.key.val == b"Content-Type"
assert v.value.val == "foo" assert v.value.val == b"foo"
s = v.spec() s = v.spec()
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
@ -282,8 +282,8 @@ def test_ctype_shortcut():
def test_location_shortcut(): def test_location_shortcut():
e = http.ShortcutLocation.expr() e = http.ShortcutLocation.expr()
v = e.parseString("l'foo'")[0] v = e.parseString("l'foo'")[0]
assert v.key.val == "Location" assert v.key.val == b"Location"
assert v.value.val == "foo" assert v.value.val == b"foo"
s = v.spec() s = v.spec()
assert s == e.parseString(s)[0].spec() assert s == e.parseString(s)[0].spec()
@ -296,23 +296,23 @@ def test_location_shortcut():
def test_shortcuts(): def test_shortcuts():
assert language.parse_pathod( assert next(language.parse_pathod(
"400:c'foo'").next().headers[0].key.val == "Content-Type" "400:c'foo'")).headers[0].key.val == b"Content-Type"
assert language.parse_pathod( assert next(language.parse_pathod(
"400:l'foo'").next().headers[0].key.val == "Location" "400:l'foo'")).headers[0].key.val == b"Location"
assert "Android" in tutils.render(parse_request("get:/:ua")) assert b"Android" in tutils.render(parse_request("get:/:ua"))
assert "User-Agent" in tutils.render(parse_request("get:/:ua")) assert b"User-Agent" in tutils.render(parse_request("get:/:ua"))
def test_user_agent(): def test_user_agent():
e = http.ShortcutUserAgent.expr() e = http.ShortcutUserAgent.expr()
v = e.parseString("ua")[0] v = e.parseString("ua")[0]
assert "Android" in v.string() assert b"Android" in v.string()
e = http.ShortcutUserAgent.expr() e = http.ShortcutUserAgent.expr()
v = e.parseString("u'a'")[0] v = e.parseString("u'a'")[0]
assert "Android" not in v.string() assert b"Android" not in v.string()
v = e.parseString("u@100'")[0] v = e.parseString("u@100'")[0]
assert len(str(v.freeze({}).value)) > 100 assert len(str(v.freeze({}).value)) > 100
@ -324,7 +324,7 @@ def test_user_agent():
def test_nested_response(): def test_nested_response():
e = http.NestedResponse.expr() e = http.NestedResponse.expr()
v = e.parseString("s'200'")[0] v = e.parseString("s'200'")[0]
assert v.value.val == "200" assert v.value.val == b"200"
tutils.raises( tutils.raises(
language.ParseException, language.ParseException,
e.parseString, e.parseString,
@ -340,9 +340,7 @@ def test_nested_response():
def test_nested_response_freeze(): def test_nested_response_freeze():
e = http.NestedResponse( e = http.NestedResponse(
base.TokValueLiteral( base.TokValueLiteral(
"200:b'foo':i10,'\\x27'".encode( r"200:b\'foo\':i10,\'\\x27\'"
"string_escape"
)
) )
) )
assert e.freeze({}) assert e.freeze({})

View File

@ -6,7 +6,7 @@ import tutils
def parse_request(s): def parse_request(s):
return language.parse_pathoc(s).next() return next(language.parse_pathoc(s))
class TestWebsocketFrame: class TestWebsocketFrame:
@ -93,9 +93,9 @@ class TestWebsocketFrame:
def test_rawbody(self): def test_rawbody(self):
frm = self.fr("wf:mask:r'foo'") frm = self.fr("wf:mask:r'foo'")
assert len(frm.payload) == 3 assert len(frm.payload) == 3
assert frm.payload != "foo" assert frm.payload != b"foo"
assert self.fr("wf:r'foo'").payload == "foo" assert self.fr("wf:r'foo'").payload == b"foo"
def test_construction_2(self): def test_construction_2(self):
# Simple server frame # Simple server frame
@ -109,7 +109,7 @@ class TestWebsocketFrame:
assert frm.header.masking_key assert frm.header.masking_key
frm = self.fr("wf:b'foo':k'abcd'", is_client=True) frm = self.fr("wf:b'foo':k'abcd'", is_client=True)
assert frm.header.mask assert frm.header.mask
assert frm.header.masking_key == 'abcd' assert frm.header.masking_key == b'abcd'
# Server frame, mask explicitly set # Server frame, mask explicitly set
frm = self.fr("wf:b'foo':mask") frm = self.fr("wf:b'foo':mask")
@ -117,7 +117,7 @@ class TestWebsocketFrame:
assert frm.header.masking_key assert frm.header.masking_key
frm = self.fr("wf:b'foo':k'abcd'") frm = self.fr("wf:b'foo':k'abcd'")
assert frm.header.mask assert frm.header.mask
assert frm.header.masking_key == 'abcd' assert frm.header.masking_key == b'abcd'
# Client frame, mask explicitly unset # Client frame, mask explicitly unset
frm = self.fr("wf:b'foo':-mask", is_client=True) frm = self.fr("wf:b'foo':-mask", is_client=True)
@ -128,7 +128,7 @@ class TestWebsocketFrame:
assert not frm.header.mask assert not frm.header.mask
# We're reading back a corrupted frame - the first 3 characters of the # We're reading back a corrupted frame - the first 3 characters of the
# mask is mis-interpreted as the payload # mask is mis-interpreted as the payload
assert frm.payload == "abc" assert frm.payload == b"abc"
def test_knone(self): def test_knone(self):
with tutils.raises("expected 4 bytes"): with tutils.raises("expected 4 bytes"):
@ -138,5 +138,5 @@ class TestWebsocketFrame:
assert self.fr("wf:l3:b'foo'").header.payload_length == 3 assert self.fr("wf:l3:b'foo'").header.payload_length == 3
frm = self.fr("wf:l2:b'foo'") frm = self.fr("wf:l2:b'foo'")
assert frm.header.payload_length == 2 assert frm.header.payload_length == 2
assert frm.payload == "fo" assert frm.payload == b"fo"
tutils.raises("expected 1024 bytes", self.fr, "wf:l1024:b'foo'") tutils.raises("expected 1024 bytes", self.fr, "wf:l1024:b'foo'")

View File

@ -17,43 +17,26 @@ def test_response():
class PathocTestDaemon(tutils.DaemonTests): class PathocTestDaemon(tutils.DaemonTests):
def tval( def tval(self, requests, timeout=None, showssl=False, **kwargs):
self,
requests,
showreq=False,
showresp=False,
explain=False,
showssl=False,
hexdump=False,
timeout=None,
ignorecodes=(),
ignoretimeout=None,
showsummary=True
):
s = StringIO() s = StringIO()
c = pathoc.Pathoc( c = pathoc.Pathoc(
("127.0.0.1", self.d.port), ("127.0.0.1", self.d.port),
ssl=self.ssl, ssl=self.ssl,
showreq=showreq, fp=s,
showresp=showresp, **kwargs
explain=explain,
hexdump=hexdump,
ignorecodes=ignorecodes,
ignoretimeout=ignoretimeout,
showsummary=showsummary,
fp=s
) )
with c.connect(showssl=showssl, fp=s): with c.connect(showssl=showssl, fp=s):
if timeout: if timeout:
c.settimeout(timeout) c.settimeout(timeout)
for i in requests: for i in requests:
r = language.parse_pathoc(i).next() r = language.parse_pathoc(i).next()
if explain: if kwargs.get("explain"):
r = r.freeze(language.Settings()) r = r.freeze(language.Settings())
try: try:
c.request(r) c.request(r)
except NetlibException: except NetlibException:
pass pass
self.d.wait_for_silence()
return s.getvalue() return s.getvalue()
@ -66,14 +49,10 @@ class TestDaemonSSL(PathocTestDaemon):
) )
def test_sni(self): def test_sni(self):
c = pathoc.Pathoc( self.tval(
("127.0.0.1", self.d.port), ["get:/p/200"],
ssl=True, sni="foobar.com"
sni="foobar.com",
fp=None
) )
with c.connect():
c.request("get:/p/200")
log = self.d.log() log = self.d.log()
assert log[0]["request"]["sni"] == "foobar.com" assert log[0]["request"]["sni"] == "foobar.com"
@ -81,15 +60,10 @@ class TestDaemonSSL(PathocTestDaemon):
assert "certificate chain" in self.tval(["get:/p/200"], showssl=True) assert "certificate chain" in self.tval(["get:/p/200"], showssl=True)
def test_clientcert(self): def test_clientcert(self):
c = pathoc.Pathoc( self.tval(
("127.0.0.1", self.d.port), ["get:/p/200"],
ssl=True,
clientcert=tutils.test_data.path("data/clientcert/client.pem"), clientcert=tutils.test_data.path("data/clientcert/client.pem"),
fp=None
) )
with c.connect():
c.request("get:/p/200")
log = self.d.log() log = self.d.log()
assert log[0]["request"]["clientcert"]["keyinfo"] assert log[0]["request"]["clientcert"]["keyinfo"]
@ -170,9 +144,7 @@ class TestDaemon(PathocTestDaemon):
assert "Invalid server response" in self.tval(["get:'/p/200:d2'"]) assert "Invalid server response" in self.tval(["get:'/p/200:d2'"])
def test_websocket_shutdown(self): def test_websocket_shutdown(self):
c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) self.tval(["ws:/"])
with c.connect():
c.request("ws:/")
def test_wait_finish(self): def test_wait_finish(self):
c = pathoc.Pathoc( c = pathoc.Pathoc(
@ -182,9 +154,10 @@ class TestDaemon(PathocTestDaemon):
) )
with c.connect(): with c.connect():
c.request("ws:/") c.request("ws:/")
c.request("wf:f'wf:x100'") c.request("wf:f'wf'")
[i for i in c.wait(timeout=0, finish=False)] # This should read a frame and close the websocket reader
[i for i in c.wait(timeout=0)] assert len([i for i in c.wait(timeout=5, finish=False)]) == 1
assert not [i for i in c.wait(timeout=0)]
def test_connect_fail(self): def test_connect_fail(self):
to = ("foobar", 80) to = ("foobar", 80)

View File

@ -145,14 +145,14 @@ class CommonTests(tutils.DaemonTests):
def test_invalid_first_line(self): def test_invalid_first_line(self):
c = tcp.TCPClient(("localhost", self.d.port)) c = tcp.TCPClient(("localhost", self.d.port))
c.connect() with c.connect():
if self.ssl: if self.ssl:
c.convert_to_ssl() c.convert_to_ssl()
c.wfile.write("foo\n\n\n") c.wfile.write("foo\n\n\n")
c.wfile.flush() c.wfile.flush()
l = self.d.last_log() l = self.d.last_log()
assert l["type"] == "error" assert l["type"] == "error"
assert "foo" in l["msg"] assert "foo" in l["msg"]
def test_invalid_content_length(self): def test_invalid_content_length(self):
tutils.raises( tutils.raises(
@ -238,12 +238,12 @@ class TestDaemonSSL(CommonTests):
c = tcp.TCPClient(("localhost", self.d.port)) c = tcp.TCPClient(("localhost", self.d.port))
c.rbufsize = 0 c.rbufsize = 0
c.wbufsize = 0 c.wbufsize = 0
c.connect() with c.connect():
c.wfile.write("\0\0\0\0") c.wfile.write("\0\0\0\0")
tutils.raises(TlsException, c.convert_to_ssl) tutils.raises(TlsException, c.convert_to_ssl)
l = self.d.last_log() l = self.d.last_log()
assert l["type"] == "error" assert l["type"] == "error"
assert "SSL" in l["msg"] assert "SSL" in l["msg"]
def test_ssl_cipher(self): def test_ssl_cipher(self):
r, _ = self.pathoc([r"get:/p/202"]) r, _ = self.pathoc([r"get:/p/202"])

View File

@ -3,6 +3,7 @@ import re
import shutil import shutil
import requests import requests
from six.moves import cStringIO as StringIO from six.moves import cStringIO as StringIO
from six import BytesIO
import urllib import urllib
from netlib import tcp from netlib import tcp
@ -147,6 +148,6 @@ test_data = utils.Data(__name__)
def render(r, settings=language.Settings()): def render(r, settings=language.Settings()):
r = r.resolve(settings) r = r.resolve(settings)
s = StringIO() s = BytesIO()
assert language.serve(r, s, settings) assert language.serve(r, s, settings)
return s.getvalue() return s.getvalue()

View File

@ -8,7 +8,7 @@ deps = -rrequirements.txt
commands = py.test -n 8 --timeout 60 ./test commands = py.test -n 8 --timeout 60 ./test
[testenv:py35] [testenv:py35]
commands = py.test -n 8 --timeout 60 test/netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py commands = py.test -n 8 --timeout 60 test/netlib test/mitmproxy/script test/pathod/test_utils.py test/pathod/test_log.py test/pathod/test_language_generators.py test/pathod/test_language_writer.py test/pathod/test_language_base.py test/pathod/test_language_http.py
[testenv:lint] [testenv:lint]
deps = flake8 deps = flake8

View File

@ -21,8 +21,8 @@
"flux": "^2.1.1", "flux": "^2.1.1",
"jquery": "^2.2.3", "jquery": "^2.2.3",
"lodash": "^4.11.2", "lodash": "^4.11.2",
"react": "^15.0.2", "react": "^15.1.0",
"react-dom": "^15.0.2", "react-dom": "^15.1.0",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-router": "^2.4.0", "react-router": "^2.4.0",
"redux": "^3.5.2", "redux": "^3.5.2",
@ -53,7 +53,6 @@
"gulp-sourcemaps": "^1.6.0", "gulp-sourcemaps": "^1.6.0",
"gulp-util": "^3.0.7", "gulp-util": "^3.0.7",
"jest": "^0.1.40", "jest": "^0.1.40",
"redux-thunk": "^2.1.0",
"uglifyify": "^3.0.1", "uglifyify": "^3.0.1",
"vinyl-buffer": "^1.0.0", "vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0", "vinyl-source-stream": "^1.1.0",

View File

@ -1,190 +1,121 @@
import React from "react"; import React from "react"
import {RequestUtils, ResponseUtils} from "../flow/utils.js"; import {RequestUtils, ResponseUtils} from "../flow/utils.js"
import {formatSize, formatTimeDelta} from "../utils.js"; import {formatSize, formatTimeDelta} from "../utils.js"
var TLSColumn = React.createClass({
statics: { function TLSColumn({flow}) {
Title: React.createClass({ let ssl = (flow.request.scheme === "https")
render: function(){ let classes
return <th {...this.props} className={"col-tls " + (this.props.className || "") }></th>; if (ssl) {
} classes = "col-tls col-tls-https"
}), } else {
sortKeyFun: function(flow){ classes = "col-tls col-tls-http"
return flow.request.scheme;
}
},
render: function () {
var flow = this.props.flow;
var ssl = (flow.request.scheme === "https");
var classes;
if (ssl) {
classes = "col-tls col-tls-https";
} else {
classes = "col-tls col-tls-http";
}
return <td className={classes}></td>;
} }
}); return <td className={classes}></td>
}
TLSColumn.Title = ({className = "", ...props}) => <th {...props} className={"col-tls " + className }></th>
TLSColumn.sortKeyFun = flow => flow.request.scheme
var IconColumn = React.createClass({ function IconColumn({flow}) {
statics: { let icon
Title: React.createClass({ if (flow.response) {
render: function(){ var contentType = ResponseUtils.getContentType(flow.response)
return <th {...this.props} className={"col-icon " + (this.props.className || "") }></th>;
}
})
},
render: function () {
var flow = this.props.flow;
var icon; //TODO: We should assign a type to the flow somewhere else.
if (flow.response) { if (flow.response.status_code === 304) {
var contentType = ResponseUtils.getContentType(flow.response); icon = "resource-icon-not-modified"
} else if (300 <= flow.response.status_code && flow.response.status_code < 400) {
//TODO: We should assign a type to the flow somewhere else. icon = "resource-icon-redirect"
if (flow.response.status_code === 304) { } else if (contentType && contentType.indexOf("image") >= 0) {
icon = "resource-icon-not-modified"; icon = "resource-icon-image"
} else if (300 <= flow.response.status_code && flow.response.status_code < 400) { } else if (contentType && contentType.indexOf("javascript") >= 0) {
icon = "resource-icon-redirect"; icon = "resource-icon-js"
} else if (contentType && contentType.indexOf("image") >= 0) { } else if (contentType && contentType.indexOf("css") >= 0) {
icon = "resource-icon-image"; icon = "resource-icon-css"
} else if (contentType && contentType.indexOf("javascript") >= 0) { } else if (contentType && contentType.indexOf("html") >= 0) {
icon = "resource-icon-js"; icon = "resource-icon-document"
} else if (contentType && contentType.indexOf("css") >= 0) {
icon = "resource-icon-css";
} else if (contentType && contentType.indexOf("html") >= 0) {
icon = "resource-icon-document";
}
} }
if (!icon) {
icon = "resource-icon-plain";
}
icon += " resource-icon";
return <td className="col-icon">
<div className={icon}></div>
</td>;
} }
}); if (!icon) {
icon = "resource-icon-plain"
var PathColumn = React.createClass({
statics: {
Title: React.createClass({
render: function(){
return <th {...this.props} className={"col-path " + (this.props.className || "") }>Path</th>;
}
}),
sortKeyFun: function(flow){
return RequestUtils.pretty_url(flow.request);
}
},
render: function () {
var flow = this.props.flow;
return <td className="col-path">
{flow.request.is_replay ? <i className="fa fa-fw fa-repeat pull-right"></i> : null}
{flow.intercepted ? <i className="fa fa-fw fa-pause pull-right"></i> : null}
{ RequestUtils.pretty_url(flow.request) }
</td>;
} }
});
icon += " resource-icon"
return <td className="col-icon">
<div className={icon}></div>
</td>
}
IconColumn.Title = ({className = "", ...props}) => <th {...props} className={"col-icon " + className }></th>
var MethodColumn = React.createClass({ function PathColumn({flow}) {
statics: { return <td className="col-path">
Title: React.createClass({ {flow.request.is_replay ? <i className="fa fa-fw fa-repeat pull-right"></i> : null}
render: function(){ {flow.intercepted ? <i className="fa fa-fw fa-pause pull-right"></i> : null}
return <th {...this.props} className={"col-method " + (this.props.className || "") }>Method</th>; { RequestUtils.pretty_url(flow.request) }
} </td>
}), }
sortKeyFun: function(flow){ PathColumn.Title = ({className = "", ...props}) =>
return flow.request.method; <th {...props} className={"col-path " + className }>Path</th>
} PathColumn.sortKeyFun = flow => RequestUtils.pretty_url(flow.request)
},
render: function () {
var flow = this.props.flow; function MethodColumn({flow}) {
return <td className="col-method">{flow.request.method}</td>; return <td className="col-method">{flow.request.method}</td>
}
MethodColumn.Title = ({className = "", ...props}) =>
<th {...props} className={"col-method " + className }>Method</th>
MethodColumn.sortKeyFun = flow => flow.request.method
function StatusColumn({flow}) {
let status
if (flow.response) {
status = flow.response.status_code
} else {
status = null
} }
}); return <td className="col-status">{status}</td>
}
StatusColumn.Title = ({className = "", ...props}) =>
<th {...props} className={"col-status " + className }>Status</th>
StatusColumn.sortKeyFun = flow => flow.response ? flow.response.status_code : undefined
var StatusColumn = React.createClass({ function SizeColumn({flow}) {
statics: { let total = flow.request.contentLength
Title: React.createClass({ if (flow.response) {
render: function(){ total += flow.response.contentLength || 0
return <th {...this.props} className={"col-status " + (this.props.className || "") }>Status</th>;
}
}),
sortKeyFun: function(flow){
return flow.response ? flow.response.status_code : undefined;
}
},
render: function () {
var flow = this.props.flow;
var status;
if (flow.response) {
status = flow.response.status_code;
} else {
status = null;
}
return <td className="col-status">{status}</td>;
} }
}); let size = formatSize(total)
return <td className="col-size">{size}</td>
}
var SizeColumn = React.createClass({ SizeColumn.Title = ({className = "", ...props}) =>
statics: { <th {...props} className={"col-size " + className }>Size</th>
Title: React.createClass({ SizeColumn.sortKeyFun = flow => {
render: function(){ let total = flow.request.contentLength
return <th {...this.props} className={"col-size " + (this.props.className || "") }>Size</th>; if (flow.response) {
} total += flow.response.contentLength || 0
}),
sortKeyFun: function(flow){
var total = flow.request.contentLength;
if (flow.response) {
total += flow.response.contentLength || 0;
}
return total;
}
},
render: function () {
var flow = this.props.flow;
var total = flow.request.contentLength;
if (flow.response) {
total += flow.response.contentLength || 0;
}
var size = formatSize(total);
return <td className="col-size">{size}</td>;
} }
}); return total
}
var TimeColumn = React.createClass({ function TimeColumn({flow}) {
statics: { let time
Title: React.createClass({ if (flow.response) {
render: function(){ time = formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))
return <th {...this.props} className={"col-time " + (this.props.className || "") }>Time</th>; } else {
} time = "..."
}),
sortKeyFun: function(flow){
if(flow.response) {
return flow.response.timestamp_end - flow.request.timestamp_start;
}
}
},
render: function () {
var flow = this.props.flow;
var time;
if (flow.response) {
time = formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start));
} else {
time = "...";
}
return <td className="col-time">{time}</td>;
} }
}); return <td className="col-time">{time}</td>
}
TimeColumn.Title = ({className = "", ...props}) =>
<th {...props} className={"col-time " + className }>Time</th>
TimeColumn.sortKeyFun = flow => flow.response.timestamp_end - flow.request.timestamp_start
var all_columns = [ var all_columns = [
@ -195,6 +126,6 @@ var all_columns = [
StatusColumn, StatusColumn,
SizeColumn, SizeColumn,
TimeColumn TimeColumn
]; ]
export default all_columns; export default all_columns

View File

@ -19,21 +19,20 @@ FlowRow.propTypes = {
selected: React.PropTypes.bool, selected: React.PropTypes.bool,
}; };
function FlowRow(props) { function FlowRow({flow, selected, highlight, columns, selectFlow}) {
const flow = props.flow;
const className = classNames({ const className = classNames({
"selected": props.selected, "selected": selected,
"highlighted": props.highlight && parseFilter(props.highlight)(flow), "highlighted": highlight && parseFilter(highlight)(flow),
"intercepted": flow.intercepted, "intercepted": flow.intercepted,
"has-request": flow.request, "has-request": flow.request,
"has-response": flow.response, "has-response": flow.response,
}); });
return ( return (
<tr className={className} onClick={() => props.selectFlow(flow)}> <tr className={className} onClick={() => selectFlow(flow)}>
{props.columns.map(Column => ( {columns.map(Column => (
<Column key={Column.displayName} flow={flow}/> <Column key={Column.name} flow={flow}/>
))} ))}
</tr> </tr>
); );
@ -44,11 +43,8 @@ const FlowRowContainer = connect(
flow: state.flows.all.byId[ownProps.flowId], flow: state.flows.all.byId[ownProps.flowId],
highlight: state.flows.highlight, highlight: state.flows.highlight,
selected: state.flows.selected.indexOf(ownProps.flowId) >= 0 selected: state.flows.selected.indexOf(ownProps.flowId) >= 0
}),
(dispatch, ownProps) => ({
}) })
)(FlowRow); )(FlowRow)
class FlowTableHead extends React.Component { class FlowTableHead extends React.Component {
@ -59,7 +55,7 @@ class FlowTableHead extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = { sortColumn: undefined, sortDesc: false }; this.state = {sortColumn: undefined, sortDesc: false};
} }
onClick(Column) { onClick(Column) {
@ -69,20 +65,20 @@ class FlowTableHead extends React.Component {
if (Column === this.state.sortColumn) { if (Column === this.state.sortColumn) {
sortDesc = !sortDesc; sortDesc = !sortDesc;
this.setState({ sortDesc }); this.setState({sortDesc});
} else { } else {
this.setState({ sortColumn: hasSort && Column, sortDesc: false }); this.setState({sortColumn: hasSort && Column, sortDesc: false});
} }
let sortKeyFun = Column.sortKeyFun; let sortKeyFun = Column.sortKeyFun;
if (sortDesc) { if (sortDesc) {
sortKeyFun = hasSort && function() { sortKeyFun = hasSort && function () {
const k = Column.sortKeyFun.apply(this, arguments); const k = Column.sortKeyFun.apply(this, arguments);
if (_.isString(k)) { if (_.isString(k)) {
return reverseString("" + k); return reverseString("" + k);
} }
return -k; return -k;
}; };
} }
this.props.setSortKeyFun(sortKeyFun); this.props.setSortKeyFun(sortKeyFun);
@ -95,9 +91,9 @@ class FlowTableHead extends React.Component {
<tr> <tr>
{this.props.columns.map(Column => ( {this.props.columns.map(Column => (
<Column.Title <Column.Title
key={Column.displayName} key={Column.name}
onClick={() => this.onClick(Column)} onClick={() => this.onClick(Column)}
className={sortColumn === Column && sortType} className={sortColumn === Column ? sortType : undefined}
/> />
))} ))}
</tr> </tr>
@ -118,7 +114,7 @@ class FlowTable extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = { vScroll: calcVScroll() }; this.state = {vScroll: calcVScroll()};
this.onViewportUpdate = this.onViewportUpdate.bind(this); this.onViewportUpdate = this.onViewportUpdate.bind(this);
} }
@ -132,7 +128,7 @@ class FlowTable extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if(nextProps.selected && nextProps.selected !== this.props.selected){ if (nextProps.selected && nextProps.selected !== this.props.selected) {
window.setTimeout(() => this.scrollIntoView(nextProps.selected), 1) window.setTimeout(() => this.scrollIntoView(nextProps.selected), 1)
} }
} }
@ -154,7 +150,7 @@ class FlowTable extends React.Component {
if (!shallowEqual(this.state.vScroll, vScroll) || if (!shallowEqual(this.state.vScroll, vScroll) ||
this.state.viewportTop !== viewportTop) { this.state.viewportTop !== viewportTop) {
this.setState({ vScroll, viewportTop }); this.setState({vScroll, viewportTop});
} }
} }
@ -190,22 +186,22 @@ class FlowTable extends React.Component {
<div className="flow-table" onScroll={this.onViewportUpdate}> <div className="flow-table" onScroll={this.onViewportUpdate}>
<table> <table>
<thead ref="head" style={{ transform }}> <thead ref="head" style={{ transform }}>
<FlowTableHead <FlowTableHead
columns={flowtable_columns} columns={flowtable_columns}
setSortKeyFun={this.props.setSortKeyFun} setSortKeyFun={this.props.setSortKeyFun}
/> />
</thead> </thead>
<tbody> <tbody>
<tr style={{ height: vScroll.paddingTop }}></tr> <tr style={{ height: vScroll.paddingTop }}></tr>
{flows.map(flow => ( {flows.map(flow => (
<FlowRowContainer <FlowRowContainer
key={flow.id} key={flow.id}
flowId={flow.id} flowId={flow.id}
columns={flowtable_columns} columns={flowtable_columns}
selectFlow={this.props.selectFlow} selectFlow={this.props.selectFlow}
/> />
))} ))}
<tr style={{ height: vScroll.paddingBottom }}></tr> <tr style={{ height: vScroll.paddingBottom }}></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -221,8 +217,6 @@ const parseFilter = _.memoize(Filt.parse)
const FlowTableContainer = connect( const FlowTableContainer = connect(
state => ({ state => ({
flows: state.flows.view, flows: state.flows.view,
}),
dispatch => ({
}) })
)(FlowTable) )(FlowTable)

View File

@ -62,8 +62,9 @@ export default function makeList(actionType, fetchURL) {
itemIndex = state.indexOf[action.item.id] itemIndex = state.indexOf[action.item.id]
list[itemIndex] = action.item list[itemIndex] = action.item
return { return {
...defaultState, ...state,
list list,
byId: {...state.byId, [action.item.id]: action.item},
} }
case REMOVE: case REMOVE:
@ -71,7 +72,7 @@ export default function makeList(actionType, fetchURL) {
itemIndex = state.indexOf[action.item.id] itemIndex = state.indexOf[action.item.id]
list.splice(itemIndex, 1) list.splice(itemIndex, 1)
return { return {
...defaultState, ...state,
list, list,
byId: {...state.byId, [action.item.id]: undefined}, byId: {...state.byId, [action.item.id]: undefined},
indexOf: {...state.indexOf, [action.item.id]: undefined}, indexOf: {...state.indexOf, [action.item.id]: undefined},
@ -79,7 +80,7 @@ export default function makeList(actionType, fetchURL) {
case REQUEST_LIST: case REQUEST_LIST:
return { return {
...defaultState, ...state,
isFetching: true isFetching: true
} }

View File

@ -43,12 +43,8 @@ const sortedRemove = (list, sortFn, item) => {
} }
export function sortedIndexOf(list, value, sortFn) { export function sortedIndexOf(list, value, sortFn) {
if (sortFn === false){ if (!sortFn) {
let i = 0 sortFn = x => 0 // This triggers the linear search for flows that have the same sort value.
while (i < list.length && list[i].id !== value.id){
i++
}
return i
} }
let low = 0, let low = 0,
@ -57,7 +53,7 @@ export function sortedIndexOf(list, value, sortFn) {
mid; mid;
while (low < high) { while (low < high) {
mid = (low + high) >>> 1; mid = (low + high) >>> 1;
if ((sortFn(list[mid]) < val) ) { if (sortFn(list[mid]) < val) {
low = mid + 1 low = mid + 1
} else { } else {
high = mid high = mid
@ -96,10 +92,12 @@ export function updateViewList(currentView, currentList, nextList, action, filte
return sortedInsert(currentView, sortFn, action.item) return sortedInsert(currentView, sortFn, action.item)
if (isInView && !shouldBeInView) if (isInView && !shouldBeInView)
return sortedRemove(currentView, sortFn, action.item) return sortedRemove(currentView, sortFn, action.item)
if (isInView && shouldBeInView && sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) { if (isInView && shouldBeInView) {
let s = [...currentView] let s = [...currentView]
s.sort(makeCompareFn(sortFn))
s.indexOf = x => sortedIndexOf(s, x, sortFn) s.indexOf = x => sortedIndexOf(s, x, sortFn)
s[s.indexOf(currentItemState)] = nextItemState
if (sortFn && sortFn(currentItemState) !== sortFn(nextItemState))
s.sort(makeCompareFn(sortFn))
return s return s
} }
return currentView return currentView