1/** 2 * Attaches the CSRF token (SS.csrfToken) to every POST the page sends. 3 * 4 * A page rendered without a session (SecurityComponent::deferToken()) ships 5 * SS.csrfToken empty and every `data[_Token][key]` input blank. The token is 6 * fetched from SS.csrfTokenUrl only when the visitor is about to need it, so a 7 * visitor who only reads pages never starts a session and their pages stay 8 * edge-cacheable. Fetching starts the session and sets its cookie; the value 9 * is written into SS.csrfToken and into the token inputs. 10 * 11 * The token is sha1(session id). It is fetched fresh at submit time -- not 12 * cached from page load -- so it matches the session the POST will run under 13 * even if the session was only just established. 14 */ 15(function () { 16 if (typeof SS == 'undefined' || !SS.csrfTokenUrl) { 17 return; 18 } 19 20 // Deferred = the page shipped no token. When it shipped one (a session 21 // already existed at render), that token is live and this shim stays out 22 // of the way -- forms already carry a valid token. 23 if (SS.csrfToken) { 24 return; 25 } 26 27 var TOKEN_INPUT = 'input[name="data[_Token][key]"]'; 28 var inFlight = null; 29 var readyForms = (typeof WeakSet !== 'undefined') ? new WeakSet() : null; 30 31 function fillInputs() { 32 if (!SS.csrfToken) { 33 return; 34 } 35 var inputs = document.querySelectorAll(TOKEN_INPUT); 36 for (var i = 0; i < inputs.length; i++) { 37 inputs[i].value = SS.csrfToken; 38 } 39 } 40 41 // Resolve to a token, fetching it (and starting the session) if needed. 42 // `forceFresh` re-fetches even when one is cached, so a submit always uses 43 // a token minted for the current session. 44 function getToken(forceFresh) { 45 if (SS.csrfToken && !forceFresh) { 46 return Promise.resolve(SS.csrfToken); 47 } 48 if (inFlight) { 49 return inFlight; 50 } 51 inFlight = new Promise(function (resolve) { 52 var xhr = new XMLHttpRequest(); 53 xhr.open('GET', SS.csrfTokenUrl, true); 54 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 55 xhr.onreadystatechange = function () { 56 if (xhr.readyState !== 4) { 57 return; 58 } 59 inFlight = null; 60 if (xhr.status === 200) { 61 try { 62 SS.csrfToken = JSON.parse(xhr.responseText).key || ''; 63 } catch (e) {} 64 } 65 fillInputs(); 66 resolve(SS.csrfToken); 67 }; 68 xhr.send(); 69 }); 70 return inFlight; 71 } 72 73 // Pre-warm on first interaction so the token is usually already fetched by 74 // the time a form is submitted. Passive, one-shot. 75 var prewarm = function () { 76 document.removeEventListener('pointerdown', prewarm, true); 77 document.removeEventListener('focusin', prewarm, true); 78 getToken(false); 79 }; 80 document.addEventListener('pointerdown', prewarm, true); 81 document.addEventListener('focusin', prewarm, true); 82 83 // Capture phase, ahead of the form's own submit handlers (including 84 // FormHelper.js / Login.js which bind on 'submit' and read the inputs or 85 // SS.csrfToken then). Hold the submit, fetch a fresh token, fill the 86 // inputs, then let the same submit through once. 87 document.addEventListener('submit', function (event) { 88 var form = event.target; 89 if (!form || !form.querySelector || (form.method || '').toLowerCase() !== 'post') { 90 return; 91 } 92 // Engage for every POST form, even ones without a token input: some 93 // (the header login popup) build their AJAX body by hand and pick the 94 // token up from SS.csrfToken via the ajaxSend hook, so it must be 95 // fetched before the form's own submit handler runs. 96 if (readyForms && readyForms.has(form)) { 97 readyForms.delete(form); // this is the re-dispatched submit; proceed 98 return; 99 } 100 event.preventDefault(); 101 event.stopImmediatePropagation(); 102 getToken(true).then(function () { 103 if (readyForms) { 104 readyForms.add(form); 105 } 106 if (form.requestSubmit) { 107 form.requestSubmit(event.submitter || undefined); 108 } else { 109 form.submit(); 110 } 111 }); 112 }, true); 113})(); 114 115if (typeof Ext != 'undefined' && Ext.onReady) { 116 117 Ext.require(['Ext.Ajax']); 118 119 Ext.onReady(function(){ 120 121 Ext.Ajax.on('beforerequest', function(conn, options, eOpts) { 122 if(!options.method || options.method.toUpperCase() == 'POST') { 123 if (!options.params) { 124 options.params = {}; 125 } 126 options.params['data[_Token][key]'] = SS.csrfToken; 127 } 128 }); 129 130 }); 131} 132 133if (typeof jQuery != 'undefined') { 134 135 $( document ).ajaxSend(function(event, jqXHR, ajaxOptions) { 136 137 var s = ajaxOptions; 138 139 if (s.type.toUpperCase() == 'POST') { 140 141 if (s.hasContent && s.contentType !== false) {
142 jqXHR.setRequestHeader( "Content-Type", s.contentType ); 143 } 144 145 if (!ajaxOptions.data) { 146 ajaxOptions.data = ''; 147 } 148 149 if($.type(ajaxOptions.data)=='object') { 150 ajaxOptions.data['data[_Token][key]'] = SS.csrfToken; 151 } else { 152 ajaxOptions.data += (ajaxOptions.data.length?'&':'') + 'data[_Token][key]=' + SS.csrfToken; 153 } 154 } 155 156 }); 157 158}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.