Project

General

Profile

Download (7.14 KB) Statistics
| Branch: | Tag: | Revision:
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

    
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();
25
}
26

    
27
CsrfMagic.prototype = {
28

    
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
    },
39

    
40
    send: function(data) {
41
        if (!this.csrf_isPost) return this.csrf_send(data);
42
        prepend = csrfMagicName + '=' + csrfMagicToken + '&';
43
    //    XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers
44
    //    if (this.csrf_purportedLength === undefined) {
45
    //        this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length);
46
    //        delete this.csrf_purportedLength;
47
    //    }
48
        delete this.csrf_isPost;
49
        return this.csrf_send(prepend + data);
50
    },
51
    csrf_send: function(data) {
52
        return this.csrf.send(data);
53
    },
54

    
55
    setRequestHeader: function(header, value) {
56
        // We have to auto-set this at the end, since we don't know how long the
57
        // nonce is when added to the data.
58
        if (this.csrf_isPost && header == "Content-length") {
59
            this.csrf_purportedLength = value;
60
            return;
61
        }
62
        return this.csrf_setRequestHeader(header, value);
63
    },
64
    csrf_setRequestHeader: function(header, value) {
65
        return this.csrf.setRequestHeader(header, value);
66
    },
67

    
68
    abort: function() {
69
        return this.csrf.abort();
70
    },
71
    getAllResponseHeaders: function() {
72
        return this.csrf.getAllResponseHeaders();
73
    },
74
    getResponseHeader: function(header) {
75
        return this.csrf.getResponseHeader(header);
76
    } // ,
77
}
78

    
79
// proprietary
80
CsrfMagic.prototype._updateProps = function() {
81
    this.readyState = this.csrf.readyState;
82
    if (this.readyState == 4) {
83
        this.responseText = this.csrf.responseText;
84
        this.responseXML  = this.csrf.responseXML;
85
        this.status       = this.csrf.status;
86
        this.statusText   = this.csrf.statusText;
87
    }
88
}
89
CsrfMagic.process = function(base) {
90
    if(typeof base == 'object') {
91
        base[csrfMagicName] = csrfMagicToken;
92
        return base;
93
    }
94
    var prepend = csrfMagicName + '=' + csrfMagicToken;
95
    if (base) return prepend + '&' + base;
96
    return prepend;
97
}
98
// callback function for when everything on the page has loaded
99
CsrfMagic.end = function() {
100
    // This rewrites forms AGAIN, so in case buffering didn't work this
101
    // certainly will.
102
    forms = document.getElementsByTagName('form');
103
    for (var i = 0; i < forms.length; i++) {
104
        form = forms[i];
105
        if (form.method.toUpperCase() !== 'POST') continue;
106
        if (form.elements[csrfMagicName]) continue;
107
        var input = document.createElement('input');
108
        input.setAttribute('name',  csrfMagicName);
109
        input.setAttribute('value', csrfMagicToken);
110
        input.setAttribute('type',  'hidden');
111
        form.appendChild(input);
112
    }
113
}
114

    
115
// Sets things up for Mozilla/Opera/nice browsers
116
// We very specifically match against Internet Explorer, since they haven't
117
// implemented prototypes correctly yet.
118
if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') {
119
    var x = XMLHttpRequest.prototype;
120
    var c = CsrfMagic.prototype;
121

    
122
    // Save the original functions
123
    x.csrf_open = x.open;
124
    x.csrf_send = x.send;
125
    x.csrf_setRequestHeader = x.setRequestHeader;
126

    
127
    // Notice that CsrfMagic is itself an instantiatable object, but only
128
    // open, send and setRequestHeader are necessary as decorators.
129
    x.open = c.open;
130
    x.send = c.send;
131
    x.setRequestHeader = c.setRequestHeader;
132
} else {
133
    // The only way we can do this is by modifying a library you have been
134
    // using. We support YUI, script.aculo.us, prototype, MooTools,
135
    // jQuery, Ext and Dojo.
136
    if (window.jQuery) {
137
        // jQuery didn't implement a new XMLHttpRequest function, so we have
138
        // to do this the hard way.
139
        jQuery.csrf_ajax = jQuery.ajax;
140
        jQuery.ajax = function( s ) {
141
            if (s.type && s.type.toUpperCase() == 'POST') {
142
                s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
143
                if ( s.data && s.processData && typeof s.data != "string" ) {
144
                    s.data = jQuery.param(s.data);
145
                }
146
                s.data = CsrfMagic.process(s.data);
147
            }
148
            return jQuery.csrf_ajax( s );
149
        }
150
    }
151
    if (window.Prototype) {
152
        // This works for script.aculo.us too
153
        Ajax.csrf_getTransport = Ajax.getTransport;
154
        Ajax.getTransport = function() {
155
            return new CsrfMagic(Ajax.csrf_getTransport());
156
        }
157
    }
158
    if (window.MooTools) {
159
        Browser.csrf_Request = Browser.Request;
160
        Browser.Request = function () {
161
            return new CsrfMagic(Browser.csrf_Request());
162
        }
163
    }
164
    if (window.YAHOO) {
165
        // old YUI API
166
        YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject;
167
        YAHOO.util.Connect.createXhrObject = function (transaction) {
168
            obj = YAHOO.util.Connect.csrf_createXhrObject(transaction);
169
            obj.conn = new CsrfMagic(obj.conn);
170
            return obj;
171
        }
172
    }
173
    if (window.Ext) {
174
        // Ext can use other js libraries as loaders, so it has to come last
175
        // Ext's implementation is pretty identical to Yahoo's, but we duplicate
176
        // it for comprehensiveness's sake.
177
        Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject;
178
        Ext.lib.Ajax.createXhrObject = function (transaction) {
179
            obj = Ext.lib.Ajax.csrf_createXhrObject(transaction);
180
            obj.conn = new CsrfMagic(obj.conn);
181
            return obj;
182
        }
183
    }
184
    if (window.dojo) {
185
        // NOTE: this doesn't work with latest dojo
186
        dojo.csrf__xhrObj = dojo._xhrObj;
187
        dojo._xhrObj = function () {
188
            return new CsrfMagic(dojo.csrf__xhrObj());
189
        }
190
    }
191
}
(1-1/2)