Properly do IE match (I can't believe this was broken).
[csrf-magic.git] / csrf-magic.js
blobd776b6a402268df633c2752f526e95f673d5f9ac
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  */
8 // Here are the basic overloaded method definitions
9 // The wrapper must be set BEFORE onreadystatechange is written to, since
10 // a bug in ActiveXObject prevents us from properly testing for it.
11 CsrfMagic = function(real) {
12     // try to make it ourselves, if you didn't pass it
13     if (!real) try { real = new XMLHttpRequest; } catch (e) {;}
14     if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;}
15     if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;}
16     if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;}
17     this.csrf = real;
18     // properties
19     var csrfMagic = this;
20     real.onreadystatechange = function() {
21         csrfMagic._updateProps();
22         return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null;
23     };
24     csrfMagic._updateProps();
27 CsrfMagic.prototype = {
29     open: function(method, url, async, username, password) {
30         if (method == 'POST') this.csrf_isPost = true;
31         // deal with Opera bug, thanks jQuery
32         if (username) return this.csrf_open(method, url, async, username, password);
33         else return this.csrf_open(method, url, async);
34     },
35     csrf_open: function(method, url, async, username, password) {
36         if (username) return this.csrf.open(method, url, async, username, password);
37         else return this.csrf.open(method, url, async);
38     },
40     send: function(data) {
41         if (!this.csrf_isPost) return this.csrf_send(data);
42         prepend = csrfMagicName + '=' + csrfMagicToken + '&';
43         if (this.csrf_purportedLength === undefined) {
44             this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length);
45             delete this.csrf_purportedLength;
46         }
47         delete this.csrf_isPost;
48         return this.csrf_send(prepend + data);
49     },
50     csrf_send: function(data) {
51         return this.csrf.send(data);
52     },
54     setRequestHeader: function(header, value) {
55         // We have to auto-set this at the end, since we don't know how long the
56         // nonce is when added to the data.
57         if (this.csrf_isPost && header == "Content-length") {
58             this.csrf_purportedLength = value;
59             return;
60         }
61         return this.csrf_setRequestHeader(header, value);
62     },
63     csrf_setRequestHeader: function(header, value) {
64         return this.csrf.setRequestHeader(header, value);
65     },
67     abort: function() {
68         return this.csrf.abort();
69     },
70     getAllResponseHeaders: function() {
71         return this.csrf.getAllResponseHeaders();
72     },
73     getResponseHeader: function(header) {
74         return this.csrf.getResponseHeader(header);
75     } // ,
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;
93 // callback function for when everything on the page has loaded
94 CsrfMagic.end = function() {
95     // This rewrites forms AGAIN, so in case buffering didn't work this
96     // certainly will.
97     forms = document.getElementsByTagName('form');
98     for (var i = 0; i < forms.length; i++) {
99         form = forms[i];
100         if (form.method.toUpperCase() !== 'POST') continue;
101         if (form.elements[csrfMagicName]) continue;
102         var input = document.createElement('input');
103         input.setAttribute('name',  csrfMagicName);
104         input.setAttribute('value', csrfMagicToken);
105         input.setAttribute('type',  'hidden');
106         form.appendChild(input);
107     }
110 // Sets things up for Mozilla/Opera/nice browsers
111 // We very specifically match against Internet Explorer, since they haven't
112 // implemented prototypes correctly yet.
113 if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') {
114     var x = XMLHttpRequest.prototype;
115     var c = CsrfMagic.prototype;
117     // Save the original functions
118     x.csrf_open = x.open;
119     x.csrf_send = x.send;
120     x.csrf_setRequestHeader = x.setRequestHeader;
122     // Notice that CsrfMagic is itself an instantiatable object, but only
123     // open, send and setRequestHeader are necessary as decorators.
124     x.open = c.open;
125     x.send = c.send;
126     x.setRequestHeader = c.setRequestHeader;
127 } else {
128     // The only way we can do this is by modifying a library you have been
129     // using. We support YUI, script.aculo.us, prototype, MooTools,
130     // jQuery, Ext and Dojo.
131     if (window.jQuery) {
132         // jQuery didn't implement a new XMLHttpRequest function, so we have
133         // to do this the hard way.
134         jQuery.csrf_ajax = jQuery.ajax;
135         jQuery.ajax = function( s ) {
136             if (s.type && s.type.toUpperCase() == 'POST') {
137                 s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
138                 if ( s.data && s.processData && typeof s.data != "string" ) {
139                     s.data = jQuery.param(s.data);
140                 }
141                 s.data = CsrfMagic.process(s.data);
142             }
143             return jQuery.csrf_ajax( s );
144         }
145     } else if (window.Prototype) {
146         // This works for script.aculo.us too
147         Ajax.csrf_getTransport = Ajax.getTransport;
148         Ajax.getTransport = function() {
149             return new CsrfMagic(Ajax.csrf_getTransport());
150         }
151     } else if (window.MooTools) {
152         Browser.csrf_Request = Browser.Request;
153         Browser.Request = function () {
154             return new CsrfMagic(Browser.csrf_Request());
155         }
156     } else if (window.YAHOO) {
157         YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject;
158         YAHOO.util.Connect.createXhrObject = function (transaction) {
159             obj = YAHOO.util.Connect.csrf_createXhrObject(transaction);
160             obj.conn = new CsrfMagic(obj.conn);
161             return obj;
162         }
163     } else if (window.Ext) {
164         // Ext can use other js libraries as loaders, so it has to come last
165         // Ext's implementation is pretty identical to Yahoo's, but we duplicate
166         // it for comprehensiveness's sake.
167         Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject;
168         Ext.lib.Ajax.createXhrObject = function (transaction) {
169             obj = Ext.lib.Ajax.csrf_createXhrObject(transaction);
170             obj.conn = new CsrfMagic(obj.conn);
171             return obj;
172         }
173     } else if (window.dojo) {
174         dojo.csrf__xhrObj = dojo._xhrObj;
175         dojo._xhrObj = function () {
176             return new CsrfMagic(dojo.csrf__xhrObj());
177         }
178     }