1/* 2 * The OWASP CSRFGuard Project, BSD License 3 * Copyright (c) 2011, Eric Sheridan ([email protected]) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of OWASP nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without specific 16 * prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30/** 31 * Issue 92: boolean check to avoid running the function multiple times. 32 * Happens if the file is included multiple times which results in 33 * Maximum call stack size exceeded 34 */ 35var owaspCSRFGuardScriptHasLoaded = owaspCSRFGuardScriptHasLoaded || {}; 36if (owaspCSRFGuardScriptHasLoaded !== true) { 37 (function () { 38 owaspCSRFGuardScriptHasLoaded = true; 39 40 /** 41 * Code to ensure our event always gets triggered when the DOM is updated. 42 * @param obj 43 * @param type 44 * @param fn 45 * @source http://www.dustindiaz.com/rock-solid-addevent/ 46 */ 47 function addEvent(obj, type, fn) { 48 if (obj.addEventListener) { 49 obj.addEventListener(type, fn, false); 50 EventCache.add(obj, type, fn); 51 } else if (obj.attachEvent) { 52 obj['e' + type + fn] = fn; 53 obj[type + fn] = function () { 54 obj['e' + type + fn](window.event); 55 }; 56 obj.attachEvent('on' + type, obj[type + fn]); 57 EventCache.add(obj, type, fn); 58 } else { 59 obj['on' + type] = obj['e' + type + fn]; 60 } 61 } 62 63 var EventCache = function () { 64 var listEvents = []; 65 return { 66 listEvents: listEvents, 67 add: function (node, sEventName, fHandler) { 68 listEvents.push(arguments); 69 }, 70 flush: function () { 71 var i, item; 72 for (i = listEvents.length - 1; i >= 0; i = i - 1) { 73 item = listEvents[i]; 74 if (item[0].removeEventListener) { 75 item[0].removeEventListener(item[1], item[2], item[3]); 76 } 77 78 if (item[1].substring(0, 2) !== 'on') { 79 item[1] = 'on' + item[1]; 80 } 81 82 if (item[0].detachEvent) { 83 item[0].detachEvent(item[1], item[2]); 84 } 85 } 86 } 87 }; 88 }(); 89 90 /* string utility functions */ 91 function startsWith(s, prefix) { 92 return s.indexOf(prefix) === 0; 93 } 94 95 function endsWith(s, suffix) { 96 return s.substring(s.length - suffix.length) === suffix; 97 } 98 99 /** 100 * hook using standards based prototype 101 */ 102 function hijackStandard() { 103 XMLHttpRequest.prototype._open = XMLHttpRequest.prototype.open; 104 XMLHttpRequest.prototype.open = function (method, url, async, user, pass) { 105 this.url = url; 106 107 this._open.apply(this, arguments); 108 }; 109 110 XMLHttpRequest.prototype._send = XMLHttpRequest.prototype.send; 111 XMLHttpRequest.prototype.send = function (data) { 112 if (this.onsend !== null) { 113 this.onsend.apply(this, arguments); 114 } 115 116 this._send.apply(this, arguments); 117 }; 118 } 119 120 /** 121 * ie does not properly support prototype - wrap completely 122 */ 123 function hijackExplorer() { 124 var xmlHttpRequest = window.XMLHttpRequest; 125 126 function allocXMLHttpRequest() { 127 this.base = xmlHttpRequest ? new xmlHttpRequest : new window.ActiveXObject('Microsoft.XMLHTTP'); 128 } 129 130 function initXMLHttpRequest() { 131 return new allocXMLHttpRequest; 132 } 133 134 initXMLHttpRequest.prototype = allocXMLHttpRequest.prototype; 135 136 /* constants */ 137 initXMLHttpRequest.UNSENT = 0; 138 initXMLHttpRequest.OPENED = 1; 139 initXMLHttpRequest.HEADERS_RECEIVED = 2; 140 initXMLHttpRequest.LOADING = 3; 141 initXMLHttpRequest.DONE = 4; 142 143 /* properties */ 144 initXMLHttpRequest.prototype.status = 0; 145 initXMLHttpRequest.prototype.statusText = ''; 146 initXMLHttpRequest.prototype.readyState = initXMLHttpRequest.UNSENT; 147 initXMLHttpRequest.prototype.responseText = ''; 148 initXMLHttpRequest.prototype.responseXML = null; 149 initXMLHttpRequest.prototype.onsend = null; 150 151 initXMLHttpRequest.url = null; 152 initXMLHttpRequest.onreadystatechange = null; 153 154 /* methods */ 155 initXMLHttpRequest.prototype.open = function (method, url, async, user, pass) { 156 var self = this; 157 this.url = url; 158 159 this.base.onreadystatechange = function () { 160 try { 161 self.status = self.base.status; 162 } catch (e) { 163 } 164 try { 165 self.statusText = self.base.statusText; 166 } catch (e) { 167 } 168 try { 169 self.readyState = self.base.readyState; 170 } catch (e) { 171 } 172 try { 173 self.responseText = self.base.responseText; 174 } catch (e) { 175 } 176 try { 177 self.responseXML = self.base.responseXML; 178 } catch (e) { 179 } 180 181 if (self.onreadystatechange !== null) { 182 self.onreadystatechange.apply(this, arguments); 183 } 184 }; 185 186 this.base.open(method, url, async, user, pass); 187 }; 188 189 initXMLHttpRequest.prototype.send = function (data) { 190 if (this.onsend !== null) { 191 this.onsend.apply(this, arguments); 192 } 193 194 this.base.send(data); 195 }; 196 197 initXMLHttpRequest.prototype.abort = function () { 198 this.base.abort(); 199 }; 200 201 initXMLHttpRequest.prototype.getAllResponseHeaders = function () { 202 return this.base.getAllResponseHeaders(); 203 }; 204 205 initXMLHttpRequest.prototype.getResponseHeader = function (name) { 206 return this.base.getResponseHeader(name); 207 }; 208 209 initXMLHttpRequest.prototype.setRequestHeader = function (name, value) { 210 return this.base.setRequestHeader(name, value); 211 }; 212 213 /* hook */ 214 window.XMLHttpRequest = initXMLHttpRequest; 215 } 216 217 /** 218 * check if valid domain based on domainStrict 219 */ 220 function isValidDomain(current, target) { 221 var result = false; 222 223 /* check exact or subdomain match */ 224 if (current === target) { 225 result = true; 226 }else if(current === 'yunque.e-chinalife.com'){ 227 result = true; 228 } else if(current === 'yunque.e-chinalife.clic'){ 229 result = true; 230 }else if(current === 'www.e-chinalife.com'){ 231 result = true; 232 }else if(current === 'infogxb.e-chinalife.com'){ 233 result = true; 234 }else if(current === 'infogxbnew.e-chinalife.com'){ 235 result = true; 236 }else if(current === 'infooperationnew.e-chinalife.com'){ 237 result = true; 238 }else if(current === 'yunquenew.e-chinalife.clic'){ 239 result = true; 240 }else if(current === 'yunque.e-chinalife.com'){ 241 result = true; 242 }else if (true === false) { 243 if (target.charAt(0) === '.') { 244 result = endsWith(current, target); 245 } else { 246 result = endsWith(current, '.' + target); 247 } 248 } 249 250 return result; 251 } 252 253 /** 254 * determine if uri/url points to valid domain 255 */ 256 function isValidUrl(src) { 257 var result = false; 258 var urlStartsWithProtocol = /^[a-zA-Z][a-zA-Z0-9.+-]*:/; 259 260 /* parse out domain to make sure it points to our own */ 261 if (src.substring(0, 7) === 'http://' || src.substring(0, 8) === 'https://') { 262 var token = '://'; 263 var index = src.indexOf(token); 264 var part = src.substring(index + token.length); 265 var domain = ''; 266 267 /* parse up to end, first slash, or anchor */ 268 for (var i = 0; i < part.length; i++) { 269 var character = part.charAt(i); 270 if (character === '/' || character === ':' || character === '#') { 271 break; 272 } else { 273 domain += character; 274 } 275 } 276 277 result = isValidDomain(document.domain, domain);
278 /* explicitly skip anchors */ 279 } else if (src.charAt(0) === '#') { 280 result = false; 281 /* ensure it is a local resource without a protocol */ 282 } else if (!startsWith(src, '//') && (src.charAt(0) === '/' || src.search(urlStartsWithProtocol) === -1)) { 283 result = true; 284 } 285 286 return result; 287 } 288 289 /* parse uri from url */ 290 function parseUri(url) { 291 var uri = ''; 292 var token = '://'; 293 var index = url.indexOf(token); 294 var part = ''; 295 296 /* 297 * ensure to skip protocol and prepend context path for non-qualified 298 * resources (ex: 'protect.html' vs 299 * '/Owasp.CsrfGuard.Test/protect.html'). 300 */ 301 if (index > 0) { 302 part = url.substring(index + token.length); 303 } else if (url.charAt(0) !== '/') { 304 part = '/jw/' + url; 305 } else { 306 part = url; 307 } 308 309 /* parse up to end or query string */ 310 var uriContext = (index === -1); 311 312 for (var i = 0; i < part.length; i++) { 313 var character = part.charAt(i); 314 315 if (character === '/') { 316 uriContext = true; 317 } else if (uriContext === true && (character === '?' || character === '#')) { 318 uriContext = false; 319 break; 320 } 321 322 if (uriContext === true) { 323 uri += character; 324 } 325 } 326 327 return uri; 328 } 329 330 function calculatePageTokenForUri(pageTokens, uri) { 331 let value = null; 332 Object.keys(pageTokens).forEach(function (pageTokenKey) { 333 var pageToken = pageTokens[pageTokenKey]; 334 335 if (uri === pageTokenKey) { 336 value = pageToken; 337 } else if (pageTokenKey.startsWith('^') && pageTokenKey.endsWith('$')) { // regex matching 338 if (new RegExp(pageTokenKey).test(uri)) { 339 value = pageToken; 340 } 341 } else if (pageTokenKey.startsWith('/*')) { // full path wildcard path matching 342 value = pageToken; 343 } else if (pageTokenKey.endsWith('/*') || pageTokenKey.startsWith('.*')) { // 'partial path wildcard' and 'extension' matching 344 // TODO implement 345 console.warn("'Extension' and 'partial path wildcard' matching for page tokens is not supported properly yet! " + 346 "Every resource will be assigned a new unique token instead of using the defined resource matcher token. " + 347 "Although this is not a security issue, in case of a large REST application it can have an impact on performance." + 348 "Consider using regular expressions instead."); 349 } 350 }); 351 return value; 352 } 353 354 /** 355 * inject tokens as hidden fields into forms 356 */ 357 function injectTokenForm(form, tokenName, tokenValue, pageTokens, injectGetForms) { 358 359 if (!injectGetForms) { 360 var method = form.getAttribute('method'); 361 362 if ((typeof method !== 'undefined') && method !== null && method.toLowerCase() === 'get') { 363 return; 364 } 365 } 366 367 var value = tokenValue; 368 var action = form.getAttribute('action'); 369 370 if (action !== null && isValidUrl(action)) { 371 var uri = parseUri(action); 372 const calculatedPageToken = calculatePageTokenForUri(pageTokens, uri); 373 value = calculatedPageToken == null ? tokenValue : calculatedPageToken; 374 375 let hiddenTokenFields = Object.keys(form.elements).filter(function (i) { 376 return form.elements[i].name === tokenName; 377 }); 378 379 if (hiddenTokenFields.length === 0) { 380 var hidden = document.createElement('input'); 381 382 hidden.setAttribute('type', 'hidden'); 383 hidden.setAttribute('name', tokenName); 384 hidden.setAttribute('value', value); 385 386 form.appendChild(hidden); 387 console.debug('Hidden input element [', hidden, '] was added to the form: ', form); 388 } else { 389 hiddenTokenFields.forEach(function (i) { 390 return form.elements[i].value = value; 391 }); 392 console.debug('Hidden token fields [', hiddenTokenFields, '] of form [', form, '] were updated with new token value: ', value); 393 } 394 } 395 } 396 397 /** 398 * inject tokens as query string parameters into url 399 */ 400 function injectTokenAttribute(element, attr, tokenName, tokenValue, pageTokens) { 401 402 const addTokenToLocation = function(location, tokenName, value) { 403 let newLocation; 404 if (location.indexOf('?') === -1) { 405 newLocation = location + '?' + tokenName + '=' + value; 406 } else { 407 newLocation = location + '&' + tokenName + '=' + value; 408 } 409 return newLocation; 410 } 411 412 const location = element.getAttribute && element.getAttribute(attr); 413 414 if (location != null && isValidUrl(location) && !isUnprotectedExtension(location)) { 415 const uri = parseUri(location); 416 const calculatedPageToken = calculatePageTokenForUri(pageTokens, uri); 417 const value = calculatedPageToken == null ? tokenValue : calculatedPageToken; 418 419 const tokenValueMatcher = new RegExp('(?:' + tokenName + '=)([^?|#|&]+)', 'g
419i'); 420 const tokenMatches = tokenValueMatcher.exec(location); 421 422 if (tokenMatches === null || tokenMatches.length === 0) { 423 let newLocation; 424 425 const anchorIndex = location.indexOf('#'); 426 if (anchorIndex !== -1) { 427 const baseLocation = location.split('#')[0]; 428 const anchor = location.substring(anchorIndex); 429 430 newLocation = addTokenToLocation(baseLocation, tokenName, value) + anchor; 431 } else { 432 newLocation = addTokenToLocation(location, tokenName, value); 433 } 434 435 try { 436 element.setAttribute(attr, newLocation); 437 console.debug('Attribute [', attr, '] with value [', newLocation, '] set for element: ', element); 438 } catch (e) { 439 // attempted to set/update unsupported attribute 440 } 441 } else { 442 let newLocation = location; 443 tokenMatches.slice(1).forEach(function (match) { 444 return newLocation = newLocation.replace(match, value); 445 }); 446 447 element.setAttribute(attr, newLocation); 448 console.debug('Attribute [', attr, '] with value [', newLocation, '] set for element: ', element); 449 } 450 } 451 } 452 453 /** 454 * Added to support isUnprotectedExtension(src) 455 * @param filename 456 * @return extension or EMPTY 457 */ 458 function getFileExtension(filename) { 459 var extension = ''; 460 /* take the part before the ';' if it exists (often for UrlRewriting - ex: ;JSESSIONID=x) */ 461 if (filename.indexOf(';') !== -1) { 462 filename = filename.split(';')[0]; 463 } 464 465 if (filename.indexOf('.') !== -1) { 466 extension = filename.substring(filename.lastIndexOf('.') + 1, filename.length) || filename; 467 } 468 return extension; 469 } 470 471 /** 472 * get the file extension and match it against a list of known static file extensions 473 * @param src 474 * @return 475 */ 476 function isUnprotectedExtension(src) { 477 var isSupported = false; 478 var exts = '';/* example(for properties): 'js,css,gif,png,ico,jpg' */ 479 if (exts !== '') { 480 var filename = parseUri(src); 481 var ext = getFileExtension(filename).toLowerCase(); 482 var e = exts.split(','); 483 for (var i = 0; i < e.length; i++) { 484 if (e[i] === ext) { 485 isSupported = true; 486 break; 487 } 488 } 489 } 490 return isSupported; 491 } 492 493 function injectToElements(domElements, tokenName, tokenValue, pageTokens) { 494 var len = domElements.length; 495 496 var injectForms = true; 497 var injectGetForms = false; 498 var injectFormAttributes = false; 499 var injectAttributes = false; 500 501 for (let i = 0; i < len; i++) { 502 let element = domElements[i]; 503 504 if (element.tagName && element.tagName.toLowerCase() === 'form') { 505 if (injectForms) { 506 injectTokenForm(element, tokenName, tokenValue, pageTokens, injectGetForms); 507 508 /* adjust array length after addition of new element */ 509 len = domElements.length; // TODO review 510 } 511 if (injectFormAttributes) { 512 injectTokenAttribute(element, 'action', tokenName, tokenValue, pageTokens); 513 } 514 /* inject into attribute */ 515 } else if (injectAttributes) { 516 injectTokenAttribute(element, 'src', tokenName, tokenValue, pageTokens); 517 injectTokenAttribute(element, 'href', tokenName, tokenValue, pageTokens); 518 } 519 } 520 } 521 522 /** 523 * inject csrf prevention tokens throughout dom 524 */ 525 function injectTokens(tokenName, tokenValue, existingPageTokens) { 526 /* obtain reference to page tokens if enabled */ 527 var pageTokens = {}; 528 529 if (false) { 530 pageTokens = existingPageTokens; 531 } 532 533 /* iterate over all elements and injection token */ 534 var all = document.all ? document.all : document.getElementsByTagName('*'); 535 536 injectToElements(all, tokenName, tokenValue, pageTokens); 537 } 538 539 /** 540 * obtain array of page specific tokens 541 */ 542 function requestPageTokens(tokenName, tokenValue, callback) { 543 const xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject('Microsoft.XMLHTTP'); 544 545 xhr.open('POST', '/jw/csrf', true); 546 547 /* if AJAX is enabled, the token header will be automatically added, no need to set it again */ 548 if (true !== true) { 549 if (tokenName !== undefined && tokenValue !== undefined) { 550 xhr.setRequestHeader(tokenName, tokenValue); 551 } 552 } 553 554 xhr.onreadystatechange = function () { 555 if (xhr.readyState === 4) { 556 if (xhr.status === 200) { 557 let pageTokens = JSON.parse(xhr.responseText)['pageTokens']; 558 console.debug('Received page tokens: ', pageTokens); 559 callback.call(this, pageTokens); 560 } else { 561 alert(xhr.status + ': CSRF check failed'); 562 } 563 } 564 }; 565 566 xhr.send(null); 567 } 568 569 function handleDynamicallyCreatedNodes() { 570 const dynamicNodeCreationEventName = ''; 571 572 if (dynamicNodeCreationEventName && dynamicNodeCreationEventName.length > 0) { 573 addEvent(window, dynamicNodeCreationEventName, function (event) { 574 injectToElements([event.detail], tokenName, masterTokenValue, pageTokenWrapper.pageTokens); 575 }); 576 } else { 577 if (MutationObserver) { 578 const formMutationObserver = new MutationObserver(function (mutations, observer) { 579 for (let i in mutations) { 580 const mutation = mutations[i]; 581 const addedNodes = mutation.addedNodes; 582 if (mutation.type === 'childList' && addedNodes.length && addedNodes.length > 0) { 583 injectToElements(addedNodes, tokenName, masterTokenValue, pageTokenWrapper.pageTokens); 584 } 585 } 586 }); 587 588 formMutationObserver.observe(document, {attributes: false, childList: true, subtree: true}); 589 addEvent(window, 'unload', formMutationObserver.disconnect); 590 } else { 591 addEvent(window, 'DOMNodeInserted', function (event) { 592 const target = event.target || event.srcElement; 593 if (event.type === 'DOMNodeInserted') { 594 injectToElements([target], tokenName, masterTokenValue, pageTokenWrapper.pageTokens); 595 } 596 }); 597 } 598 } 599 } 600 601 /* 602 * Only inject the tokens if the JavaScript was referenced from HTML that 603 * was served by us. Otherwise, the code was referenced from malicious HTML 604 * which may be trying to steal tokens using JavaScript hijacking techniques. 605 * The token is now removed and fetched using another POST request to solve, 606 * the token hijacking problem. 607 */ 608 if (isValidDomain(document.domain, 'yunque.e-chinalife.clic')) { 609 var tokenName = 'OWASP_CSRFTOKEN'; 610 var masterTokenValue = 'HFRA-H37O-UPPU-UZB4-U07S-5VRB-B0V4-EXSC'; 611 console.debug('Master token [' + tokenName + ']: ', masterTokenValue); 612 613 var isLoadedWrapper = {isDomContentLoaded: false}; 614 615 var pageTokenWrapper = {pageTokens: {}}; 616 617 addEvent(window, 'unload', EventCache.flush); 618 619 addEvent(window, 'DOMContentLoaded', function () { 620 isLoadedWrapper.isDomContentLoaded = true; 621 622 if (pageTokenWrapper.pageTokensLoaded) { 623 injectTokens(tokenName, masterTokenValue, pageTokenWrapper.pageTokens); 624 } 625 }); 626 627 if (false) { // TODO should it be invoked only after the DOMContentLoaded? 628 handleDynamicallyCreatedNodes(); 629 } 630 631 /* optionally include Ajax support */ 632 if (true) { 633 if (navigator.appName === 'Microsoft Internet Explorer') { 634 hijackExplorer(); 635 } else { 636 hijackStandard(); 637 } 638 639 /* CUSTOM START : keep track on headers set */ 640 if (XMLHttpRequest.prototype._setRequestHeader === undefined) { 641 XMLHttpRequest.prototype._setRequestHeader = XMLHttpRequest.prototype.setRequestHeader; 642 } 643 XMLHttpRequest.prototype.setRequestHeader = function(key, value) { 644 if (this._headers === undefined) { 645 this._headers = {}; 646 } 647 this._headers[key] = value; 648 this._setRequestHeader(key, value); 649 }; 650 /* CUSTOM END */ 651 652 XMLHttpRequest.prototype.onsend = function (data) { 653 /* CUSTOM START : It is handled with connection manager 654 655 addEvent(this, 'readystatechange', function () { 656 if (this.readyState === 4) { 657 let tokenResponseHeader = this.getResponseHeader(tokenName); 658 if (tokenResponseHeader != undefined) { 659 try { 660 let tokenTO = JSON.parse(tokenResponseHeader) 661 662 let newMasterToken = tokenTO['masterToken']; 663 if (newMasterToken !== undefined) { 664 masterTokenValue = newMasterToken; 665 console.debug('New master token value received: ', masterTokenValue); 666 } 667 668 let newPageTokens = tokenTO['pageTokens']; 669 if (newPageTokens !== undefined) { 670 Object.keys(newPageTokens).forEac
670h(function (key) { 671 return pageTokenWrapper.pageTokens[key] = newPageTokens[key]; 672 }); 673 console.debug('New page token value(s) received: ', newPageTokens); 674 } 675 676 injectTokens(tokenName, masterTokenValue, pageTokenWrapper.pageTokens); 677 } catch (e) { 678 console.error("Error while updating tokens from response header.") 679 } 680 } 681 } 682 }); 683 684 CUSTOM END */ 685 686 var computePageToken = function(pageTokens, modifiedUri) { 687 let result = null; 688 689 let pathWithoutLeadingSlash = window.location.pathname.substring(1); // e.g. deploymentName/service/endpoint 690 let pathArray = pathWithoutLeadingSlash.split('/'); 691 692 let builtPath = ''; 693 for (let i = 0; i < pathArray.length - 1; i++) { // the last part of the URI (endpoint) is disregarded because the modifiedUri parameter is used instead 694 builtPath += '/' + pathArray[i]; 695 let pageTokenValue = calculatePageTokenForUri(pageTokens, builtPath + modifiedUri); 696 if (pageTokenValue != undefined) { 697 result = pageTokenValue; 698 break; 699 } 700 } 701 702 return result; 703 }; 704 705 /** 706 * For the library to function correctly, all the URLs must start with a forward slash (/) 707 * Parameters must be removed from the URL 708 */ 709 var normalizeUrl = function(url) { 710 var removeParameters = function(currentUrl, symbol) { 711 let index = currentUrl.indexOf(symbol); 712 return index > 0 ? currentUrl.substring(0, index) : currentUrl; 713 } 714 715 /* 716 * TODO should other checks be done here like in the isValidUrl? 717 * Could the url parameter contain full URLs with protocol domain, port etc? 718 */ 719 let normalizedUrl = url.startsWith('/') ? url : '/' + url; 720 721 normalizedUrl = removeParameters(normalizedUrl, '?'); 722 normalizedUrl = removeParameters(normalizedUrl, '#'); 723 724 return normalizedUrl; 725 } 726 727 if (isValidUrl(this.url)) { 728 this.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 729 /* CUSTOM START : Handle by connection manager */ 730// let normalizedUrl = normalizeUrl(this.url); 731// 732// if (pageTokenWrapper.pageTokens === null) { 733// this.setRequestHeader(tokenName, masterTokenValue); 734// } else { 735// 736// let pageToken = calculatePageTokenForUri(pageTokenWrapper.pageTokens, normalizedUrl); 737// if (pageToken == undefined) { 738// let computedPageToken = computePageToken(pageTokenWrapper.pageTokens, normalizedUrl); 739// 740// if (computedPageToken === null) { 741// this.setRequestHeader(tokenName, masterTokenValue); 742// } else { 743// this.setRequestHeader(tokenName, computedPageToken); 744// } 745// } else { 746// this.setRequestHeader(tokenName, pageToken); 747// } 748 if (this._headers[ConnectionManager.tokenName] === undefined) { 749 this.setRequestHeader(ConnectionManager.tokenName, ConnectionManager.tokenValue); 750 } 751 752// } 753 /* CUSTOM END */ 754 } 755 }; 756 } 757 758 if (false) { 759 let pageTokenRequestCallback = function (receivedPageTokens) { 760 pageTokenWrapper.pageTokens = receivedPageTokens; 761 762 pageTokenWrapper.pageTokensLoaded = true; 763 764 if (isLoadedWrapper.isDomContentLoaded) { 765 injectTokens(tokenName, masterTokenValue, receivedPageTokens); 766 } 767 }; 768 769 requestPageTokens(tokenName, masterTokenValue, pageTokenRequestCallback); 770 } else { 771 /* update nodes in DOM after load */ 772 addEvent(window, 'DOMContentLoaded', function () { 773 /* CUSTOM START */ 774 injectTokens(ConnectionManager.tokenName, ConnectionManager.tokenValue, {}); 775 //injectTokens(tokenName, masterTokenValue, {}); 776 /* CUSTOM END */ 777 }); 778 } 779 } else { 780 alert('ç¸å ³é¡µé¢ä¸ºéä¸å½äººå¯¿å®æ¹ç½ç«ï¼è¯·æ³¨æçå«ï¼'); 781 } 782 })(); 783}
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.