diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 34c7a7539..bfd99c9ce 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -428,6 +428,11 @@ class FlowStore(FlowList): self.views = [] self._recalculate_views() + def get(self, flow_id): + for f in self._list: + if f.id == flow_id: + return f + def __contains__(self, f): return f in self._set diff --git a/libmproxy/protocol/primitives.py b/libmproxy/protocol/primitives.py index 49c71c9fe..11ebb97f7 100644 --- a/libmproxy/protocol/primitives.py +++ b/libmproxy/protocol/primitives.py @@ -96,6 +96,8 @@ class Flow(stateobject.StateObject): def copy(self): f = copy.copy(self) + f.id = str(uuid.uuid4()) + f.live = False f.client_conn = self.client_conn.copy() f.server_conn = self.server_conn.copy() @@ -142,6 +144,8 @@ class Flow(stateobject.StateObject): Intercept this Flow. Processing will stop until accept_intercept is called. """ + if self.intercepted: + return self.intercepted = True master.handle_intercept(self) @@ -149,6 +153,8 @@ class Flow(stateobject.StateObject): """ Continue with the flow - called after an intercept(). """ + if not self.intercepted: + return self.intercepted = False self.reply() master.handle_accept_intercept(self) diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py index ec3576db3..d981ab30d 100644 --- a/libmproxy/web/__init__.py +++ b/libmproxy/web/__init__.py @@ -35,7 +35,7 @@ class WebFlowView(flow.FlowView): app.ClientConnection.broadcast( type="flows", cmd="remove", - data=f.get_state(short=True) + data=f.id ) def _recalculate(self, flows): diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py index e9fbba8b4..c90922cb4 100644 --- a/libmproxy/web/app.py +++ b/libmproxy/web/app.py @@ -1,12 +1,13 @@ import os.path -import sys import tornado.web import tornado.websocket import logging import json -from .. import flow +class APIError(tornado.web.HTTPError): + pass + class IndexHandler(tornado.web.RequestHandler): def get(self): _ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645 @@ -36,38 +37,82 @@ class ClientConnection(WebSocketEventBroadcaster): connections = set() -class Flows(tornado.web.RequestHandler): +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( - data=[f.get_state(short=True) for f in self.application.state.flows] + data=[f.get_state(short=True) for f in self.state.flows] )) -class AcceptFlows(tornado.web.RequestHandler): +class ClearAll(RequestHandler): def post(self): - self.application.state.flows.accept_all(self.application.master) + self.state.clear() -class AcceptFlow(tornado.web.RequestHandler): +class AcceptFlows(RequestHandler): + def post(self): + self.state.flows.accept_all(self.master) + + +class AcceptFlow(RequestHandler): def post(self, flow_id): - flow_id = str(flow_id) - for flow in self.application.state.flows: - if flow.id == flow_id: - flow.accept_intercept(self.application.master) - break + self.flow.accept_intercept(self.master) -class Events(tornado.web.RequestHandler): + +class FlowHandler(RequestHandler): + def delete(self, flow_id): + self.flow.kill(self.master) + self.state.delete_flow(self.flow) + + +class DuplicateFlow(RequestHandler): + def post(self, flow_id): + self.master.duplicate_flow(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 Events(RequestHandler): def get(self): self.write(dict( - data=list(self.application.state.events) + data=list(self.state.events) )) -class Settings(tornado.web.RequestHandler): +class Settings(RequestHandler): def get(self): self.write(dict( data=dict( - intercept=self.application.state.intercept_txt + intercept=self.state.intercept_txt ) )) @@ -81,7 +126,7 @@ class Settings(tornado.web.RequestHandler): if k == "_xsrf": continue elif k == "intercept": - self.application.state.set_intercept(v[0]) + self.state.set_intercept(v[0]) update[k] = v[0] else: print "Warning: Unknown setting {}: {}".format(k, v) @@ -93,17 +138,7 @@ class Settings(tornado.web.RequestHandler): ) -class Clear(tornado.web.RequestHandler): - def post(self): - self.application.state.clear() - - class Application(tornado.web.Application): - - @property - def state(self): - return self.master.state - def __init__(self, master, debug): self.master = master handlers = [ @@ -112,9 +147,12 @@ class Application(tornado.web.Application): (r"/events", Events), (r"/flows", Flows), (r"/flows/accept", AcceptFlows), - (r"/flows/([0-9a-f\-]+)/accept", AcceptFlow), + (r"/flows/(?P[0-9a-f\-]+)", FlowHandler), + (r"/flows/(?P[0-9a-f\-]+)/accept", AcceptFlow), + (r"/flows/(?P[0-9a-f\-]+)/duplicate", DuplicateFlow), + (r"/flows/(?P[0-9a-f\-]+)/replay", ReplayFlow), (r"/settings", Settings), - (r"/clear", Clear), + (r"/clear", ClearAll), ] settings = dict( template_path=os.path.join(os.path.dirname(__file__), "templates"), diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js index b8835449a..ff961294e 100644 --- a/libmproxy/web/static/js/app.js +++ b/libmproxy/web/static/js/app.js @@ -1,81 +1,3 @@ -// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) -var AutoScrollMixin = { - componentWillUpdate: function () { - var node = this.getDOMNode(); - this._shouldScrollBottom = ( - node.scrollTop !== 0 && - node.scrollTop + node.clientHeight === node.scrollHeight - ); - }, - componentDidUpdate: function () { - if (this._shouldScrollBottom) { - var node = this.getDOMNode(); - node.scrollTop = node.scrollHeight; - } - }, -}; - - -var StickyHeadMixin = { - adjustHead: function () { - // Abusing CSS transforms to set the element - // referenced as head into some kind of position:sticky. - var head = this.refs.head.getDOMNode(); - head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; - } -}; - - -var Navigation = _.extend({}, ReactRouter.Navigation, { - setQuery: function (dict) { - var q = this.context.getCurrentQuery(); - for(var i in dict){ - if(dict.hasOwnProperty(i)){ - q[i] = dict[i] || undefined; //falsey values shall be removed. - } - } - q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); - }, - replaceWith: function(routeNameOrPath, params, query) { - if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); - } - if(params === undefined){ - params = this.context.getCurrentParams(); - } - if(query === undefined){ - query = this.context.getCurrentQuery(); - } - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); - } -}); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); - -var State = _.extend({}, ReactRouter.State, { - getInitialState: function () { - this._query = this.context.getCurrentQuery(); - this._queryWatches = []; - return null; - }, - onQueryChange: function (key, callback) { - this._queryWatches.push({ - key: key, - callback: callback - }); - }, - componentWillReceiveProps: function (nextProps, nextState) { - var q = this.context.getCurrentQuery(); - for (var i = 0; i < this._queryWatches.length; i++) { - var watch = this._queryWatches[i]; - if (this._query[watch.key] !== q[watch.key]) { - watch.callback(this._query[watch.key], q[watch.key], watch.key); - } - } - this._query = q; - } -}); - var Key = { UP: 38, DOWN: 40, @@ -92,7 +14,7 @@ var Key = { BACKSPACE: 8, }; // Add A-Z -for(var i=65; i <= 90; i++){ +for (var i = 65; i <= 90; i++) { Key[String.fromCharCode(i)] = i; } @@ -157,6 +79,31 @@ EventEmitter.prototype.removeListener = function (events, f) { } }.bind(this)); }; + + +function getCookie(name) { + var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + return r ? r[1] : undefined; +} +var xsrf = $.param({_xsrf: getCookie("_xsrf")}); + +//Tornado XSRF Protection. +jQuery.ajaxPrefilter(function (options) { + if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { + if (options.data) { + options.data += ("&" + xsrf); + } else { + options.data = xsrf; + } + } +}); +// Log AJAX Errors +$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { + var message = jqXHR.responseText; + console.error(message, arguments); + EventLogActions.add_event(thrownError + ": " + message); + window.alert(message); +}); const PayloadSources = { VIEW: "view", SERVER: "server" @@ -272,7 +219,18 @@ var FlowActions = { accept_all: function(){ jQuery.post("/flows/accept"); }, - + "delete": function(flow){ + jQuery.ajax({ + type:"DELETE", + url: "/flows/" + flow.id + }); + }, + duplicate: function(flow){ + jQuery.post("/flows/" + flow.id + "/duplicate"); + }, + replay: function(flow){ + jQuery.post("/flows/" + flow.id + "/replay"); + }, update: function (flow) { AppDispatcher.dispatchViewAction({ type: ActionTypes.FLOW_STORE, @@ -2140,7 +2098,7 @@ _.extend(ListStore.prototype, EventEmitter.prototype, { this.emit("update", elem); }, remove: function (elem_id) { - if (!(elem.id in this._pos_map)) { + if (!(elem_id in this._pos_map)) { return; } this.list.splice(this._pos_map[elem_id], 1); @@ -2317,8 +2275,8 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("update", this.update); this.store.removeListener("remove", this.remove); this.store.removeListener("recalculate", this.recalculate); - }, - recalculate: function (filt, sortfun) { + }, + recalculate: function (filt, sortfun) { if (filt) { this.filt = filt.bind(this); } @@ -2408,6 +2366,84 @@ function Connection(url) { } //React utils. For other utilities, see ../utils.js +// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) +var AutoScrollMixin = { + componentWillUpdate: function () { + var node = this.getDOMNode(); + this._shouldScrollBottom = ( + node.scrollTop !== 0 && + node.scrollTop + node.clientHeight === node.scrollHeight + ); + }, + componentDidUpdate: function () { + if (this._shouldScrollBottom) { + var node = this.getDOMNode(); + node.scrollTop = node.scrollHeight; + } + }, +}; + + +var StickyHeadMixin = { + adjustHead: function () { + // Abusing CSS transforms to set the element + // referenced as head into some kind of position:sticky. + var head = this.refs.head.getDOMNode(); + head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; + } +}; + + +var Navigation = _.extend({}, ReactRouter.Navigation, { + setQuery: function (dict) { + var q = this.context.getCurrentQuery(); + for(var i in dict){ + if(dict.hasOwnProperty(i)){ + q[i] = dict[i] || undefined; //falsey values shall be removed. + } + } + q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 + this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + }, + replaceWith: function(routeNameOrPath, params, query) { + if(routeNameOrPath === undefined){ + routeNameOrPath = this.context.getCurrentPath(); + } + if(params === undefined){ + params = this.context.getCurrentParams(); + } + if(query === undefined){ + query = this.context.getCurrentQuery(); + } + ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + } +}); +_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); + +var State = _.extend({}, ReactRouter.State, { + getInitialState: function () { + this._query = this.context.getCurrentQuery(); + this._queryWatches = []; + return null; + }, + onQueryChange: function (key, callback) { + this._queryWatches.push({ + key: key, + callback: callback + }); + }, + componentWillReceiveProps: function (nextProps, nextState) { + var q = this.context.getCurrentQuery(); + for (var i = 0; i < this._queryWatches.length; i++) { + var watch = this._queryWatches[i]; + if (this._query[watch.key] !== q[watch.key]) { + watch.callback(this._query[watch.key], q[watch.key], watch.key); + } + } + this._query = q; + } +}); + var Splitter = React.createClass({displayName: 'Splitter', getDefaultProps: function () { return { @@ -2512,23 +2548,6 @@ var Splitter = React.createClass({displayName: 'Splitter', ); } }); - -function getCookie(name) { - var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); - return r ? r[1] : undefined; -} -var xsrf = $.param({_xsrf: getCookie("_xsrf")}); - -//Tornado XSRF Protection. -$.ajaxPrefilter(function (options) { - if (["post","put","delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { - if (options.data) { - options.data += ("&" + xsrf); - } else { - options.data = xsrf; - } - } -}); var VirtualScrollMixin = { getInitialState: function () { return { @@ -3686,6 +3705,10 @@ var MainView = React.createClass({displayName: 'MainView', this.selectFlow(flows[index]); }, onKeyDown: function (e) { + var flow = this.getSelected(); + if(e.ctrlKey){ + return; + } switch (e.keyCode) { case Key.K: case Key.UP: @@ -3724,11 +3747,32 @@ var MainView = React.createClass({displayName: 'MainView', this.refs.flowDetails.nextTab(+1); } break; + case Key.C: + if (e.shiftKey) { + FlowActions.clear(); + } + break; + case Key.D: + if (flow) { + if (e.shiftKey) { + FlowActions.duplicate(flow); + } else { + var last_flow = this.state.view.index(flow) === this.state.view.list.length - 1; + this.selectFlowRelative(last_flow ? -1 : +1); + FlowActions.delete(flow); + } + } + break; case Key.A: if (e.shiftKey) { FlowActions.accept_all(); - } else if(this.getSelected()) { - FlowActions.accept(this.getSelected()); + } else if (flow) { + FlowActions.accept(flow); + } + break; + case Key.R: + if(!e.shiftKey && flow){ + FlowActions.replay(flow); } break; default: diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 091ae6c3c..83dcb8013 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -77,7 +77,18 @@ var FlowActions = { accept_all: function(){ jQuery.post("/flows/accept"); }, - + "delete": function(flow){ + jQuery.ajax({ + type:"DELETE", + url: "/flows/" + flow.id + }); + }, + duplicate: function(flow){ + jQuery.post("/flows/" + flow.id + "/duplicate"); + }, + replay: function(flow){ + jQuery.post("/flows/" + flow.id + "/replay"); + }, update: function (flow) { AppDispatcher.dispatchViewAction({ type: ActionTypes.FLOW_STORE, diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index f0e50176b..78415ad0e 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -105,6 +105,10 @@ var MainView = React.createClass({ this.selectFlow(flows[index]); }, onKeyDown: function (e) { + var flow = this.getSelected(); + if(e.ctrlKey){ + return; + } switch (e.keyCode) { case Key.K: case Key.UP: @@ -143,11 +147,32 @@ var MainView = React.createClass({ this.refs.flowDetails.nextTab(+1); } break; + case Key.C: + if (e.shiftKey) { + FlowActions.clear(); + } + break; + case Key.D: + if (flow) { + if (e.shiftKey) { + FlowActions.duplicate(flow); + } else { + var last_flow = this.state.view.index(flow) === this.state.view.list.length - 1; + this.selectFlowRelative(last_flow ? -1 : +1); + FlowActions.delete(flow); + } + } + break; case Key.A: if (e.shiftKey) { FlowActions.accept_all(); - } else if(this.getSelected()) { - FlowActions.accept(this.getSelected()); + } else if (flow) { + FlowActions.accept(flow); + } + break; + case Key.R: + if(!e.shiftKey && flow){ + FlowActions.replay(flow); } break; default: diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index 20dbda940..1714bfa9c 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -1,5 +1,83 @@ //React utils. For other utilities, see ../utils.js +// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) +var AutoScrollMixin = { + componentWillUpdate: function () { + var node = this.getDOMNode(); + this._shouldScrollBottom = ( + node.scrollTop !== 0 && + node.scrollTop + node.clientHeight === node.scrollHeight + ); + }, + componentDidUpdate: function () { + if (this._shouldScrollBottom) { + var node = this.getDOMNode(); + node.scrollTop = node.scrollHeight; + } + }, +}; + + +var StickyHeadMixin = { + adjustHead: function () { + // Abusing CSS transforms to set the element + // referenced as head into some kind of position:sticky. + var head = this.refs.head.getDOMNode(); + head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; + } +}; + + +var Navigation = _.extend({}, ReactRouter.Navigation, { + setQuery: function (dict) { + var q = this.context.getCurrentQuery(); + for(var i in dict){ + if(dict.hasOwnProperty(i)){ + q[i] = dict[i] || undefined; //falsey values shall be removed. + } + } + q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 + this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + }, + replaceWith: function(routeNameOrPath, params, query) { + if(routeNameOrPath === undefined){ + routeNameOrPath = this.context.getCurrentPath(); + } + if(params === undefined){ + params = this.context.getCurrentParams(); + } + if(query === undefined){ + query = this.context.getCurrentQuery(); + } + ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + } +}); +_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); + +var State = _.extend({}, ReactRouter.State, { + getInitialState: function () { + this._query = this.context.getCurrentQuery(); + this._queryWatches = []; + return null; + }, + onQueryChange: function (key, callback) { + this._queryWatches.push({ + key: key, + callback: callback + }); + }, + componentWillReceiveProps: function (nextProps, nextState) { + var q = this.context.getCurrentQuery(); + for (var i = 0; i < this._queryWatches.length; i++) { + var watch = this._queryWatches[i]; + if (this._query[watch.key] !== q[watch.key]) { + watch.callback(this._query[watch.key], q[watch.key], watch.key); + } + } + this._query = q; + } +}); + var Splitter = React.createClass({ getDefaultProps: function () { return { @@ -103,21 +181,4 @@ var Splitter = React.createClass({ ); } -}); - -function getCookie(name) { - var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); - return r ? r[1] : undefined; -} -var xsrf = $.param({_xsrf: getCookie("_xsrf")}); - -//Tornado XSRF Protection. -$.ajaxPrefilter(function (options) { - if (["post","put","delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { - if (options.data) { - options.data += ("&" + xsrf); - } else { - options.data = xsrf; - } - } }); \ No newline at end of file diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js index cc862aeb4..ce5c03383 100644 --- a/web/src/js/store/store.js +++ b/web/src/js/store/store.js @@ -19,7 +19,7 @@ _.extend(ListStore.prototype, EventEmitter.prototype, { this.emit("update", elem); }, remove: function (elem_id) { - if (!(elem.id in this._pos_map)) { + if (!(elem_id in this._pos_map)) { return; } this.list.splice(this._pos_map[elem_id], 1); diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js index 2b80292a8..9619f994d 100644 --- a/web/src/js/store/view.js +++ b/web/src/js/store/view.js @@ -32,8 +32,8 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("update", this.update); this.store.removeListener("remove", this.remove); this.store.removeListener("recalculate", this.recalculate); - }, - recalculate: function (filt, sortfun) { + }, + recalculate: function (filt, sortfun) { if (filt) { this.filt = filt.bind(this); } diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 082f72726..a85652431 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -1,81 +1,3 @@ -// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) -var AutoScrollMixin = { - componentWillUpdate: function () { - var node = this.getDOMNode(); - this._shouldScrollBottom = ( - node.scrollTop !== 0 && - node.scrollTop + node.clientHeight === node.scrollHeight - ); - }, - componentDidUpdate: function () { - if (this._shouldScrollBottom) { - var node = this.getDOMNode(); - node.scrollTop = node.scrollHeight; - } - }, -}; - - -var StickyHeadMixin = { - adjustHead: function () { - // Abusing CSS transforms to set the element - // referenced as head into some kind of position:sticky. - var head = this.refs.head.getDOMNode(); - head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; - } -}; - - -var Navigation = _.extend({}, ReactRouter.Navigation, { - setQuery: function (dict) { - var q = this.context.getCurrentQuery(); - for(var i in dict){ - if(dict.hasOwnProperty(i)){ - q[i] = dict[i] || undefined; //falsey values shall be removed. - } - } - q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); - }, - replaceWith: function(routeNameOrPath, params, query) { - if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); - } - if(params === undefined){ - params = this.context.getCurrentParams(); - } - if(query === undefined){ - query = this.context.getCurrentQuery(); - } - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); - } -}); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); - -var State = _.extend({}, ReactRouter.State, { - getInitialState: function () { - this._query = this.context.getCurrentQuery(); - this._queryWatches = []; - return null; - }, - onQueryChange: function (key, callback) { - this._queryWatches.push({ - key: key, - callback: callback - }); - }, - componentWillReceiveProps: function (nextProps, nextState) { - var q = this.context.getCurrentQuery(); - for (var i = 0; i < this._queryWatches.length; i++) { - var watch = this._queryWatches[i]; - if (this._query[watch.key] !== q[watch.key]) { - watch.callback(this._query[watch.key], q[watch.key], watch.key); - } - } - this._query = q; - } -}); - var Key = { UP: 38, DOWN: 40, @@ -92,7 +14,7 @@ var Key = { BACKSPACE: 8, }; // Add A-Z -for(var i=65; i <= 90; i++){ +for (var i = 65; i <= 90; i++) { Key[String.fromCharCode(i)] = i; } @@ -156,4 +78,29 @@ EventEmitter.prototype.removeListener = function (events, f) { this.listeners[event].splice(index, 1); } }.bind(this)); -}; \ No newline at end of file +}; + + +function getCookie(name) { + var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + return r ? r[1] : undefined; +} +var xsrf = $.param({_xsrf: getCookie("_xsrf")}); + +//Tornado XSRF Protection. +jQuery.ajaxPrefilter(function (options) { + if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") { + if (options.data) { + options.data += ("&" + xsrf); + } else { + options.data = xsrf; + } + } +}); +// Log AJAX Errors +$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { + var message = jqXHR.responseText; + console.error(message, arguments); + EventLogActions.add_event(thrownError + ": " + message); + window.alert(message); +}); \ No newline at end of file