mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-02-07 10:40:09 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
75ebd638eb
@ -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'
|
||||||
|
@ -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
@ -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):
|
||||||
|
@ -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())))
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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({})
|
||||||
|
@ -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'")
|
||||||
|
@ -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)
|
||||||
|
@ -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"])
|
||||||
|
@ -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()
|
||||||
|
2
tox.ini
2
tox.ini
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user