mirror of
https://github.com/zhigang1992/mitmproxy.git
synced 2026-04-23 11:56:52 +08:00
web: CSP, revert functionality, serve content
This commit is contained in:
@@ -150,7 +150,6 @@ class ConnectionItem(common.WWrap):
|
||||
f = self.master.duplicate_flow(self.flow)
|
||||
self.master.view_flow(f)
|
||||
elif key == "r":
|
||||
self.flow.backup()
|
||||
r = self.master.replay_request(self.flow)
|
||||
if r:
|
||||
self.master.statusbar.message(r)
|
||||
|
||||
@@ -763,7 +763,6 @@ class FlowView(common.WWrap):
|
||||
elif key == "p":
|
||||
self.view_prev_flow(self.flow)
|
||||
elif key == "r":
|
||||
self.flow.backup()
|
||||
r = self.master.replay_request(self.flow)
|
||||
if r:
|
||||
self.master.statusbar.message(r)
|
||||
|
||||
@@ -584,6 +584,7 @@ class State(object):
|
||||
|
||||
def revert(self, f):
|
||||
f.revert()
|
||||
self.update_flow(f)
|
||||
|
||||
def killall(self, master):
|
||||
self.flows.kill_all(master)
|
||||
@@ -821,6 +822,7 @@ class FlowMaster(controller.Master):
|
||||
if f.request.content == http.CONTENT_MISSING:
|
||||
return "Can't replay request with missing content..."
|
||||
if f.request:
|
||||
f.backup()
|
||||
f.request.is_replay = True
|
||||
if f.request.content:
|
||||
f.request.headers["Content-Length"] = [str(len(f.request.content))]
|
||||
|
||||
@@ -88,6 +88,11 @@ class Flow(stateobject.StateObject):
|
||||
def get_state(self, short=False):
|
||||
d = super(Flow, self).get_state(short)
|
||||
d.update(version=version.IVERSION)
|
||||
if self._backup and self._backup != d:
|
||||
if short:
|
||||
d.update(modified=True)
|
||||
else:
|
||||
d.update(backup=self._backup)
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os.path
|
||||
import re
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import logging
|
||||
@@ -9,7 +10,43 @@ from .. import version
|
||||
class APIError(tornado.web.HTTPError):
|
||||
pass
|
||||
|
||||
class IndexHandler(tornado.web.RequestHandler):
|
||||
|
||||
class RequestHandler(tornado.web.RequestHandler):
|
||||
def set_default_headers(self):
|
||||
super(RequestHandler, self).set_default_headers()
|
||||
self.set_header("Server", version.NAMEVERSION)
|
||||
self.set_header("X-Frame-Options", "DENY")
|
||||
self.add_header("X-XSS-Protection", "1; mode=block")
|
||||
self.add_header("X-Content-Type-Options", "nosniff")
|
||||
self.add_header("Content-Security-Policy", "default-src 'self'; "
|
||||
"connect-src 'self' ws://* ; "
|
||||
"style-src 'self' 'unsafe-inline'")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.application.master.state
|
||||
|
||||
@property
|
||||
def master(self):
|
||||
return self.application.master
|
||||
|
||||
@property
|
||||
def flow(self):
|
||||
flow_id = str(self.path_kwargs["flow_id"])
|
||||
flow = self.state.flows.get(flow_id)
|
||||
if flow:
|
||||
return flow
|
||||
else:
|
||||
raise APIError(400, "Flow not found.")
|
||||
|
||||
def write_error(self, status_code, **kwargs):
|
||||
if "exc_info" in kwargs and isinstance(kwargs["exc_info"][1], APIError):
|
||||
self.finish(kwargs["exc_info"][1].log_message)
|
||||
else:
|
||||
super(RequestHandler, self).write_error(status_code, **kwargs)
|
||||
|
||||
|
||||
class IndexHandler(RequestHandler):
|
||||
def get(self):
|
||||
_ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645
|
||||
self.render("index.html")
|
||||
@@ -38,31 +75,6 @@ class ClientConnection(WebSocketEventBroadcaster):
|
||||
connections = set()
|
||||
|
||||
|
||||
class RequestHandler(tornado.web.RequestHandler):
|
||||
@property
|
||||
def state(self):
|
||||
return self.application.master.state
|
||||
|
||||
@property
|
||||
def master(self):
|
||||
return self.application.master
|
||||
|
||||
@property
|
||||
def flow(self):
|
||||
flow_id = str(self.path_kwargs["flow_id"])
|
||||
flow = self.state.flows.get(flow_id)
|
||||
if flow:
|
||||
return flow
|
||||
else:
|
||||
raise APIError(400, "Flow not found.")
|
||||
|
||||
def write_error(self, status_code, **kwargs):
|
||||
if "exc_info" in kwargs and isinstance(kwargs["exc_info"][1], APIError):
|
||||
self.finish(kwargs["exc_info"][1].log_message)
|
||||
else:
|
||||
super(RequestHandler, self).write_error(status_code, **kwargs)
|
||||
|
||||
|
||||
class Flows(RequestHandler):
|
||||
def get(self):
|
||||
self.write(dict(
|
||||
@@ -95,13 +107,49 @@ class DuplicateFlow(RequestHandler):
|
||||
def post(self, flow_id):
|
||||
self.master.duplicate_flow(self.flow)
|
||||
|
||||
|
||||
class RevertFlow(RequestHandler):
|
||||
def post(self, flow_id):
|
||||
self.state.revert(self.flow)
|
||||
|
||||
|
||||
class ReplayFlow(RequestHandler):
|
||||
def post(self, flow_id):
|
||||
self.flow.backup()
|
||||
r = self.master.replay_request(self.flow)
|
||||
if r:
|
||||
raise APIError(400, r)
|
||||
|
||||
|
||||
class FlowContent(RequestHandler):
|
||||
def get(self, flow_id, message):
|
||||
message = getattr(self.flow, message)
|
||||
|
||||
if not message.content:
|
||||
raise APIError(400, "No content.")
|
||||
|
||||
content_encoding = message.headers.get_first("Content-Encoding", None)
|
||||
if content_encoding:
|
||||
content_encoding = re.sub(r"[^\w]", "", content_encoding)
|
||||
self.set_header("Content-Encoding", content_encoding)
|
||||
|
||||
original_cd = message.headers.get_first("Content-Disposition", None)
|
||||
filename = None
|
||||
if original_cd:
|
||||
filename = re.search("filename=([\w\" \.\-\(\)]+)", original_cd)
|
||||
if filename:
|
||||
filename = filename.group(1)
|
||||
if not filename:
|
||||
filename = self.flow.request.path.split("?")[0].split("/")[-1]
|
||||
|
||||
filename = re.sub(r"[^\w\" \.\-\(\)]", "", filename)
|
||||
cd = "attachment; filename={}".format(filename)
|
||||
self.set_header("Content-Disposition", cd)
|
||||
self.set_header("Content-Type", "application/text")
|
||||
self.set_header("X-Content-Type-Options", "nosniff")
|
||||
self.set_header("X-Frame-Options", "DENY")
|
||||
self.write(message.content)
|
||||
|
||||
|
||||
class Events(RequestHandler):
|
||||
def get(self):
|
||||
self.write(dict(
|
||||
@@ -154,6 +202,8 @@ class Application(tornado.web.Application):
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/accept", AcceptFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/duplicate", DuplicateFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow),
|
||||
(r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content", FlowContent),
|
||||
(r"/settings", Settings),
|
||||
(r"/clear", ClearAll),
|
||||
]
|
||||
@@ -164,5 +214,4 @@ class Application(tornado.web.Application):
|
||||
cookie_secret=os.urandom(256),
|
||||
debug=debug,
|
||||
)
|
||||
tornado.web.Application.__init__(self, handlers, **settings)
|
||||
|
||||
super(Application, self).__init__(handlers, **settings)
|
||||
@@ -231,6 +231,9 @@ var FlowActions = {
|
||||
replay: function(flow){
|
||||
jQuery.post("/flows/" + flow.id + "/replay");
|
||||
},
|
||||
revert: function(flow){
|
||||
jQuery.post("/flows/" + flow.id + "/revert");
|
||||
},
|
||||
update: function (flow) {
|
||||
AppDispatcher.dispatchViewAction({
|
||||
type: ActionTypes.FLOW_STORE,
|
||||
@@ -2891,7 +2894,7 @@ var FileMenu = React.createClass({displayName: 'FileMenu',
|
||||
React.createElement("li", {role: "presentation", className: "divider"}),
|
||||
React.createElement("li", null,
|
||||
React.createElement("a", {href: "http://mitm.it/", target: "_blank"},
|
||||
React.createElement("i", {className: "fa fa-fw fa-lock"}),
|
||||
React.createElement("i", {className: "fa fa-fw fa-external-link"}),
|
||||
"Install Certificates..."
|
||||
)
|
||||
)
|
||||
@@ -3258,70 +3261,23 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
|
||||
}
|
||||
});
|
||||
|
||||
var DeleteButton = React.createClass({displayName: 'DeleteButton',
|
||||
var NavAction = React.createClass({displayName: 'NavAction',
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.delete(this.props.flow);
|
||||
this.props.onClick();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
React.createElement("a", {title: "[d]elete Flow",
|
||||
React.createElement("a", {title: this.props.title,
|
||||
href: "#",
|
||||
className: "nav-action",
|
||||
onClick: this.onClick},
|
||||
React.createElement("i", {className: "fa fa-fw fa-trash"})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
var DuplicateButton = React.createClass({displayName: 'DuplicateButton',
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.duplicate(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
React.createElement("a", {title: "[D]uplicate Flow",
|
||||
href: "#",
|
||||
className: "nav-action",
|
||||
onClick: this.onClick},
|
||||
React.createElement("i", {className: "fa fa-fw fa-edit"})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
var ReplayButton = React.createClass({displayName: 'ReplayButton',
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.replay(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
React.createElement("a", {title: "[r]eplay Flow",
|
||||
href: "#",
|
||||
className: "nav-action",
|
||||
onClick: this.onClick},
|
||||
React.createElement("i", {className: "fa fa-fw fa-close"})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
var AcceptButton = React.createClass({displayName: 'AcceptButton',
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.accept(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
React.createElement("a", {title: "[a]ccept (resume) Flow",
|
||||
href: "#",
|
||||
className: "nav-action",
|
||||
onClick: this.onClick},
|
||||
React.createElement("i", {className: "fa fa-fw fa-play"})
|
||||
React.createElement("i", {className: "fa fa-fw " + this.props.icon})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
|
||||
render: function () {
|
||||
var flow = this.props.flow;
|
||||
@@ -3339,13 +3295,23 @@ var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
|
||||
onClick: onClick}, str);
|
||||
}.bind(this));
|
||||
|
||||
var acceptButton = null;
|
||||
if(flow.intercepted){
|
||||
acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: FlowActions.accept.bind(null, flow)})
|
||||
}
|
||||
var revertButton = null;
|
||||
if(flow.modified){
|
||||
revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: FlowActions.revert.bind(null, flow)})
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"},
|
||||
tabs,
|
||||
React.createElement(DeleteButton, {flow: flow}),
|
||||
React.createElement(DuplicateButton, {flow: flow}),
|
||||
React.createElement(ReplayButton, {flow: flow}),
|
||||
flow.intercepted ? React.createElement(AcceptButton, {flow: this.props.flow}) : null
|
||||
React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: FlowActions.delete.bind(null, flow)}),
|
||||
React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: FlowActions.duplicate.bind(null, flow)}),
|
||||
React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: FlowActions.replay.bind(null, flow)}),
|
||||
acceptButton,
|
||||
revertButton
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -3855,7 +3821,7 @@ var MainView = React.createClass({displayName: 'MainView',
|
||||
case Key.A:
|
||||
if (e.shiftKey) {
|
||||
FlowActions.accept_all();
|
||||
} else if (flow) {
|
||||
} else if (flow && flow.intercepted) {
|
||||
FlowActions.accept(flow);
|
||||
}
|
||||
break;
|
||||
@@ -3864,6 +3830,11 @@ var MainView = React.createClass({displayName: 'MainView',
|
||||
FlowActions.replay(flow);
|
||||
}
|
||||
break;
|
||||
case Key.V:
|
||||
if(e.shiftKey && flow && flow.modified) {
|
||||
FlowActions.revert(flow);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.debug("keydown", e.keyCode);
|
||||
return;
|
||||
|
||||
@@ -89,6 +89,9 @@ var FlowActions = {
|
||||
replay: function(flow){
|
||||
jQuery.post("/flows/" + flow.id + "/replay");
|
||||
},
|
||||
revert: function(flow){
|
||||
jQuery.post("/flows/" + flow.id + "/revert");
|
||||
},
|
||||
update: function (flow) {
|
||||
AppDispatcher.dispatchViewAction({
|
||||
type: ActionTypes.FLOW_STORE,
|
||||
|
||||
@@ -1,67 +1,20 @@
|
||||
var DeleteButton = React.createClass({
|
||||
var NavAction = React.createClass({
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.delete(this.props.flow);
|
||||
this.props.onClick();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<a title="[d]elete Flow"
|
||||
<a title={this.props.title}
|
||||
href="#"
|
||||
className="nav-action"
|
||||
onClick={this.onClick}>
|
||||
<i className="fa fa-fw fa-trash"></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
var DuplicateButton = React.createClass({
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.duplicate(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<a title="[D]uplicate Flow"
|
||||
href="#"
|
||||
className="nav-action"
|
||||
onClick={this.onClick}>
|
||||
<i className="fa fa-fw fa-edit"></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
var ReplayButton = React.createClass({
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.replay(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<a title="[r]eplay Flow"
|
||||
href="#"
|
||||
className="nav-action"
|
||||
onClick={this.onClick}>
|
||||
<i className="fa fa-fw fa-close"></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
var AcceptButton = React.createClass({
|
||||
onClick: function (e) {
|
||||
e.preventDefault();
|
||||
FlowActions.accept(this.props.flow);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<a title="[a]ccept (resume) Flow"
|
||||
href="#"
|
||||
className="nav-action"
|
||||
onClick={this.onClick}>
|
||||
<i className="fa fa-fw fa-play"></i>
|
||||
<i className={"fa fa-fw " + this.props.icon}></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailNav = React.createClass({
|
||||
render: function () {
|
||||
var flow = this.props.flow;
|
||||
@@ -79,13 +32,23 @@ var FlowDetailNav = React.createClass({
|
||||
onClick={onClick}>{str}</a>;
|
||||
}.bind(this));
|
||||
|
||||
var acceptButton = null;
|
||||
if(flow.intercepted){
|
||||
acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={FlowActions.accept.bind(null, flow)} />
|
||||
}
|
||||
var revertButton = null;
|
||||
if(flow.modified){
|
||||
revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={FlowActions.revert.bind(null, flow)} />
|
||||
}
|
||||
|
||||
return (
|
||||
<nav ref="head" className="nav-tabs nav-tabs-sm">
|
||||
{tabs}
|
||||
<DeleteButton flow={flow}/>
|
||||
<DuplicateButton flow={flow}/>
|
||||
<ReplayButton flow={flow}/>
|
||||
{ flow.intercepted ? <AcceptButton flow={this.props.flow}/> : null }
|
||||
<NavAction title="[d]elete flow" icon="fa-trash" onClick={FlowActions.delete.bind(null, flow)} />
|
||||
<NavAction title="[D]uplicate flow" icon="fa-copy" onClick={FlowActions.duplicate.bind(null, flow)} />
|
||||
<NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={FlowActions.replay.bind(null, flow)} />
|
||||
{acceptButton}
|
||||
{revertButton}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ var FileMenu = React.createClass({
|
||||
<li role="presentation" className="divider"></li>
|
||||
<li>
|
||||
<a href="http://mitm.it/" target="_blank">
|
||||
<i className="fa fa-fw fa-lock"></i>
|
||||
<i className="fa fa-fw fa-external-link"></i>
|
||||
Install Certificates...
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -171,7 +171,7 @@ var MainView = React.createClass({
|
||||
case Key.A:
|
||||
if (e.shiftKey) {
|
||||
FlowActions.accept_all();
|
||||
} else if (flow) {
|
||||
} else if (flow && flow.intercepted) {
|
||||
FlowActions.accept(flow);
|
||||
}
|
||||
break;
|
||||
@@ -180,6 +180,11 @@ var MainView = React.createClass({
|
||||
FlowActions.replay(flow);
|
||||
}
|
||||
break;
|
||||
case Key.V:
|
||||
if(e.shiftKey && flow && flow.modified) {
|
||||
FlowActions.revert(flow);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.debug("keydown", e.keyCode);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user