aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/web
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/web')
-rw-r--r--libmproxy/web/app.py107
-rw-r--r--libmproxy/web/static/js/app.js87
2 files changed, 107 insertions, 87 deletions
diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py
index b5ec41c9..27e9aefc 100644
--- a/libmproxy/web/app.py
+++ b/libmproxy/web/app.py
@@ -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) \ No newline at end of file
diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js
index 92f48d14..984db943 100644
--- a/libmproxy/web/static/js/app.js
+++ b/libmproxy/web/static/js/app.js
@@ -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',
- onClick: function (e) {
- e.preventDefault();
- FlowActions.delete(this.props.flow);
- },
- render: function () {
- return (
- React.createElement("a", {title: "[d]elete Flow",
- href: "#",
- className: "nav-action",
- onClick: this.onClick},
- React.createElement("i", {className: "fa fa-fw fa-trash"})
- )
- );
- }
-});
-var DuplicateButton = React.createClass({displayName: 'DuplicateButton',
+var NavAction = React.createClass({displayName: 'NavAction',
onClick: function (e) {
e.preventDefault();
- FlowActions.duplicate(this.props.flow);
+ this.props.onClick();
},
render: function () {
return (
- React.createElement("a", {title: "[D]uplicate Flow",
+ React.createElement("a", {title: this.props.title,
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;