Switch to use {} form in JS, also *actually* implement key-based tokens.
[csrf-magic.git] / csrf-magic.js
blobe898518881fee5aad21caee294637c8413bd9c2f
1 /**
2  * @file
3  *
4  * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory
5  * plays nice with other JavaScript libraries, needs testing though.
6  */
7 // Here are the basic overloaded method definitions
8 // The wrapper must be set BEFORE onreadystatechange is written to, since
9 // a bug in ActiveXObject prevents us from properly testing for it.
10 CsrfMagic = function(real) {
11     // try to make it ourselves, if you didn't pass it
12     if (!real) try { real = new XMLHttpRequest; } catch (e) {;}
13     if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;}
14     if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;}
15     if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;}
16     this.csrf = real;
17     // properties
18     var csrfMagic = this;
19     real.onreadystatechange = function() {
20         csrfMagic._updateProps();
21         return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null;
22     };
23     csrfMagic._updateProps();
26 CsrfMagic.prototype = {
28     open: function(method, url, async, username, password) {
29         if (method == 'POST') this.csrf_isPost = true;
30         // deal with Opera bug, thanks jQuery
31         if (username) return this.csrf_open(method, url, async, username, password);
32         else return this.csrf_open(method, url, async);
33     },
34     csrf_open: function(method, url, async, username, password) {
35         if (username) return this.csrf.open(method, url, async, username, password);
36         else return this.csrf.open(method, url, async);
37     },
39     send: function(data) {
40         if (!this.csrf_isPost) this.csrf_send(data);
41         prepend = csrfMagicName + '=' + csrfMagicToken + '&';
42         if (this.csrf_purportedLength === undefined) {
43             this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length);
44             delete this.csrf_purportedLength;
45         }
46         delete this.csrf_isPost;
47         return this.csrf_send(prepend + data);
48     },
49     csrf_send: function(data) {
50         return this.csrf.send(data);
51     },
53     setRequestHeader: function(header, value) {
54         // We have to auto-set this at the end, since we don't know how long the
55         // nonce is when added to the data.
56         if (this.csrf_isPost && header == "Content-length") {
57             this.csrf_purportedLength = value;
58             return;
59         }
60         return this.csrf_setRequestHeader(header, value);
61     },
62     csrf_setRequestHeader: function(header, value) {
63         return this.csrf.setRequestHeader(header, value);
64     },
66     abort: function() {
67         return this.csrf.abort();
68     },
69     getAllResponseHeaders: function() {
70         return this.csrf.getAllResponseHeaders();
71     },
72     getResponseHeader: function(header) {
73         return this.csrf.getResponseHeader(header);
74     } // ,
78 // proprietary
79 CsrfMagic.prototype._updateProps = function() {
80     this.readyState = this.csrf.readyState;
81     if (this.readyState == 4) {
82         this.responseText = this.csrf.responseText;
83         this.responseXML  = this.csrf.responseXML;
84         this.status       = this.csrf.status;
85         this.statusText   = this.csrf.statusText;
86     }
88 CsrfMagic.process = function(base) {
89     var prepend = csrfMagicName + '=' + csrfMagicToken;
90     if (base) return prepend + '&' + base;
91     return prepend;
94 // Sets things up for Mozilla/Opera/nice browsers
95 if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) {
96     var x = XMLHttpRequest.prototype;
97     var c = CsrfMagic.prototype;
99     // Save the original functions
100     x.csrf_open = x.open;
101     x.csrf_send = x.send;
102     x.csrf_setRequestHeader = x.setRequestHeader;
104     // Notice that CsrfMagic is itself an instantiatable object, but only
105     // open, send and setRequestHeader are necessary as decorators.
106     x.open = c.open;
107     x.send = c.send;
108     x.setRequestHeader = c.setRequestHeader;
109 } else {
110     // The only way we can do this is by modifying a library you have been
111     // using. We plan to support YUI, script.aculo.us, prototype, MooTools,
112     // jQuery, Ext and Dojo.
113     if (window.jQuery) {
114         // jQuery didn't implement a new XMLHttpRequest function, so we have
115         // to do this the hard way.
116         jQuery.csrf_ajax = jQuery.ajax;
117         jQuery.ajax = function( s ) {
118             if (s.type && s.type.toUpperCase() == 'POST') {
119                 s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
120                 if ( s.data && s.processData && typeof s.data != "string" ) {
121                     s.data = jQuery.param(s.data);
122                 }
123                 s.data = CsrfMagic.process(s.data);
124             }
125             return jQuery.csrf_ajax( s );
126         }
127     } else if (window.Prototype) {
128         // This works for script.aculo.us too
129         Ajax.csrf_getTransport = Ajax.getTransport;
130         Ajax.getTransport = function() {
131             return new CsrfMagic(Ajax.csrf_getTransport());
132         }
133     } else if (window.MooTools) {
134         Browser.csrf_Request = Browser.Request;
135         Browser.Request = function () {
136             return new CsrfMagic(Browser.csrf_Request());
137         }
138     } else if (window.YAHOO) {
139         YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject;
140         YAHOO.util.Connect.createXhrObject = function (transaction) {
141             obj = YAHOO.util.Connect.csrf_createXhrObject(transaction);
142             obj.conn = new CsrfMagic(obj.conn);
143             return obj;
144         }
145     } else if (window.Ext) {
146         // Ext can use other js libraries as loaders, so it has to come last
147         // Ext's implementation is pretty identical to Yahoo's, but we duplicate
148         // it for comprehensiveness's sake.
149         Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject;
150         Ext.lib.Ajax.createXhrObject = function (transaction) {
151             obj = Ext.lib.Ajax.csrf_createXhrObject(transaction);
152             obj.conn = new CsrfMagic(obj.conn);
153             return obj;
154         }
155     } else if (window.dojo) {
156         dojo.csrf__xhrObj = dojo._xhrObj;
157         dojo._xhrObj = function () {
158             return new CsrfMagic(dojo.csrf__xhrObj());
159         }
160     }