4 * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory
5 * plays nice with other JavaScript libraries, needs testing though.
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) {;}
20 real.onreadystatechange = function() {
21 csrfMagic._updateProps();
22 return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null;
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);
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);
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;
47 delete this.csrf_isPost;
48 return this.csrf_send(prepend + data);
50 csrf_send: function(data) {
51 return this.csrf.send(data);
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;
61 return this.csrf_setRequestHeader(header, value);
63 csrf_setRequestHeader: function(header, value) {
64 return this.csrf.setRequestHeader(header, value);
68 return this.csrf.abort();
70 getAllResponseHeaders: function() {
71 return this.csrf.getAllResponseHeaders();
73 getResponseHeader: function(header) {
74 return this.csrf.getResponseHeader(header);
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;
88 CsrfMagic.process = function(base) {
89 var prepend = csrfMagicName + '=' + csrfMagicToken;
90 if (base) return prepend + '&' + base;
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
97 forms = document.getElementsByTagName('form');
98 for (var i = 0; i < forms.length; 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);
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.
126 x.setRequestHeader = c.setRequestHeader;
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.
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);
141 s.data = CsrfMagic.process(s.data);
143 return jQuery.csrf_ajax( s );
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());
151 } else if (window.MooTools) {
152 Browser.csrf_Request = Browser.Request;
153 Browser.Request = function () {
154 return new CsrfMagic(Browser.csrf_Request());
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);
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);
173 } else if (window.dojo) {
174 dojo.csrf__xhrObj = dojo._xhrObj;
175 dojo._xhrObj = function () {
176 return new CsrfMagic(dojo.csrf__xhrObj());