1/* ======================================================================== 2 * OctoberCMS: front-end JavaScript framework 3 * http://octobercms.com 4 * ======================================================================== 5 * Copyright 2016-2020 Alexey Bobkov, Samuel Georges 6 * ======================================================================== */ 7 8if (window.jQuery === undefined) { 9 throw new Error('The jQuery library is not loaded. The OctoberCMS framework cannot be initialized.'); 10} 11if (window.jQuery.request !== undefined) { 12 throw new Error('The OctoberCMS framework is already loaded.'); 13} 14 15+function ($) { "use strict"; 16 17 var Request = function (element, handler, options) { 18 var $el = this.$el = $(element); 19 this.options = options || {}; 20 21 /* 22 * Validate handler name 23 */ 24 if (handler === undefined) { 25 throw new Error('The request handler name is not specified.') 26 } 27 28 if (!handler.match(/^(?:\w+\:{2})?on*/)) { 29 throw new Error('Invalid handler name. The correct handler name format is: "onEvent".') 30 } 31 32 /* 33 * Prepare the options 34 */ 35 var $form = options.form ? $(options.form) : $el.closest('form'), 36 $triggerEl = !!$form.length ? $form : $el, 37 context = { handler: handler, options: options } 38 39 /* 40 * Validate the form client-side 41 */ 42 if ((options.browserValidate !== undefined) && typeof document.createElement('input').reportValidity == 'function' && $form && $form[0] && !$form[0].checkValidity()) { 43 $form[0].reportValidity(); 44 return false; 45 } 46 47 /* 48 * Execute the request 49 */ 50 $el.trigger('ajaxSetup', [context]) 51 var _event = jQuery.Event('oc.beforeRequest') 52 $triggerEl.trigger(_event, context) 53 if (_event.isDefaultPrevented()) return 54 55 var loading = options.loading !== undefined ? options.loading : null, 56 url = options.url !== undefined ? options.url : window.location.href, 57 isRedirect = options.redirect !== undefined && options.redirect.length, 58 useFlash = options.flash !== undefined, 59 useFiles = options.files !== undefined 60 61 if (useFiles && typeof FormData === 'undefined') { 62 console.warn('This browser does not support file uploads via FormData') 63 useFiles = false 64 } 65 66 if ($.type(loading) == 'string') { 67 loading = $(loading) 68 } 69 70 /* 71 * Request headers 72 */ 73 var requestHeaders = { 74 'X-OCTOBER-REQUEST-HANDLER': handler, 75 'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update) 76 } 77 78 if (useFlash) { 79 requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1 80 } 81 82 var csrfToken = getXSRFToken() 83 if (csrfToken) { 84 requestHeaders['X-XSRF-TOKEN'] = csrfToken 85 } 86 87 /* 88 * Request data 89 */ 90 var requestData, 91 inputName, 92 data = {} 93 94 $.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() { 95 $.extend(data, paramToObj('data-request-data', $(this).data('request-data'))) 96 }) 97 98 if ($el.is(':input') && !$form.length) { 99 inputName = $el.attr('name') 100 if (inputName !== undefined && options.data[inputName] === undefined) { 101 options.data[inputName] = $el.val() 102 } 103 } 104 105 if (options.data !== undefined && !$.isEmptyObject(options.data)) { 106 $.extend(data, options.data) 107 } 108 109 if (useFiles) { 110 requestData = new FormData($form.length ? $form.get(0) : undefined) 111 112 if ($el.is(':file') && inputName) { 113 $.each($el.prop('files'), function() { 114 requestData.append(inputName, this) 115 }) 116 117 delete data[inputName] 118 } 119 120 $.each(data, function(key) { 121 if (typeof Blob !== "undefined" && this instanceof Blob && this.filename) { 122 requestData.append(key, this, this.filename) 123 } else { 124 requestData.append(key, this) 125 } 126 }) 127 } 128 else { 129 requestData = [$form.serialize(), $.param(data)].filter(
129Boolean).join('&') 130 } 131 132 /* 133 * Request options 134 */ 135 var requestOptions = { 136 url: url, 137 crossDomain: false, 138 global: options.ajaxGlobal, 139 context: context, 140 headers: requestHeaders, 141 success: function(data, textStatus, jqXHR) { 142 /* 143 * Halt here if beforeUpdate() or data-request-before-update returns false 144 */ 145 if (this.options.beforeUpdate.apply(this, [data, textStatus, jqXHR]) === false) return 146 if (options.evalBeforeUpdate && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalBeforeUpdate+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') === false) return 147 148 /* 149 * Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called 150 */ 151 var _event = jQuery.Event('ajaxBeforeUpdate') 152 $triggerEl.trigger(_event, [context, data, textStatus, jqXHR]) 153 if (_event.isDefaultPrevented()) return 154 155 if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) { 156 $.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) { 157 requestOptions.handleFlashMessage(message, type) 158 }) 159 } 160 161 /* 162 * Proceed with the update process 163 */ 164 var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR) 165 166 updatePromise.done(function() { 167 $triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR]) 168 options.evalSuccess && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalSuccess+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 169 }) 170 171 return updatePromise 172 }, 173 error: function(jqXHR, textStatus, errorThrown) { 174 var errorMsg, 175 updatePromise = $.Deferred() 176 177 if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort') 178 return 179 180 /* 181 * Disable redirects 182 */ 183 isRedirect = false 184 options.redirect = null 185 186 /* 187 * Error 406 is a "smart error" that returns response object that is 188 * processed in the same fashion as a successful response. 189 */ 190 if (jqXHR.status == 406 && jqXHR.responseJSON) { 191 errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE'] 192 updatePromise = requestOptions.handleUpdateResponse(jqXHR.responseJSON, textStatus, jqXHR) 193 } 194 /* 195 * Standard error with standard response text 196 */ 197 else { 198 errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText 199 updatePromise.resolve() 200 } 201 202 updatePromise.done(function() { 203 $el.data('error-message', errorMsg) 204 205 /* 206 * Trigger 'ajaxError' on the form, halt if event.preventDefault() is called 207 */ 208 var _event = jQuery.Event('ajaxError') 209 $triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR]) 210 if (_event.isDefaultPrevented()) return 211 212 /* 213 * Halt here if the data-request-error attribute returns false 214 */ 215 if (options.evalError && eval('(function($el, context, errorMsg, textStatus, jqXHR) {'+options.evalError+'}.call($el.get(0), $el, context, errorMsg, textStatus, jqXHR))') === false) 216 return 217 218 requestOptions.handleErrorMessage(errorMsg) 219 }) 220 221 return updatePromise 222 }, 223 complete: function(data, textStatus, jqXHR) {
224 $triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR]) 225 options.evalComplete && eval('(function($el, context, data, textStatus, jqXHR) {'+options.evalComplete+'}.call($el.get(0), $el, context, data, textStatus, jqXHR))') 226 }, 227 228 /* 229 * Custom function, requests confirmation from the user 230 */ 231 handleConfirmMessage: function(message) { 232 var _event = jQuery.Event('ajaxConfirmMessage') 233 234 _event.promise = $.Deferred() 235 if ($(window).triggerHandler(_event, [message]) !== undefined) { 236 _event.promise.done(function() { 237 options.confirm = null 238 new Request(element, handler, options) 239 }) 240 return false 241 } 242 243 if (_event.isDefaultPrevented()) return 244 if (message) return confirm(message) 245 }, 246 247 /* 248 * Custom function, display an error message to the user 249 */ 250 handleErrorMessage: function(message) { 251 var _event = jQuery.Event('ajaxErrorMessage') 252 $(window).trigger(_event, [message]) 253 if (_event.isDefaultPrevented()) return 254 if (message) alert(message) 255 }, 256 257 /* 258 * Custom function, focus fields with errors 259 */ 260 handleValidationMessage: function(message, fields) { 261 $triggerEl.trigger('ajaxValidation', [context, message, fields]) 262 263 var isFirstInvalidField = true 264 $.each(fields, function focusErrorField(fieldName, fieldMessages) { 265 fieldName = fieldName.replace(/\.(\w+)/g, '[$1]') 266 267 var fieldElement = $form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [name$="['+fieldName+']"], [name$="['+fieldName+'][]"]').filter(':enabled').first() 268 if (fieldElement.length > 0) { 269 270 var _event = jQuery.Event('ajaxInvalidField') 271 $(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField]) 272 273 if (isFirstInvalidField) { 274 if (!_event.isDefaultPrevented()) fieldElement.focus() 275 isFirstInvalidField = false 276 } 277 } 278 }) 279 }, 280 281 /* 282 * Custom function, display a flash message to the user 283 */ 284 handleFlashMessage: function(message, type) {}, 285 286 /* 287 * Custom function, redirect the browser to another location 288 */ 289 handleRedirectResponse: function(url) { 290 window.location.assign(url) 291 }, 292 293 /* 294 * Custom function, handle any application specific response values 295 * Using a promisary object here in case injected assets need time to load 296 */ 297 handleUpdateResponse: function(data, textStatus, jqXHR) { 298 299 /* 300 * Update partials and finish request 301 */ 302 var updatePromise = $.Deferred().done(function() { 303 for (var partial in data) { 304 /* 305 * If a partial has been supplied on the client side that matches the server supplied key, look up 306 * it's selector and use that. If not, we assume it is an explicit selector reference. 307 */ 308 var selector = (options.update[partial]) ? options.update[partial] : partial 309 if ($.type(selector) == 'string' && selector.charAt(0) == '@') { 310 $(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 311 } 312 else if ($.type(selector) == 'string' && selector.charAt(0) == '^') { 313 $(selector.substring(1)).prepend(data[partial]).trigger('aja
313xUpdate', [context, data, textStatus, jqXHR]) 314 } 315 else { 316 $(selector).trigger('ajaxBeforeReplace') 317 $(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR]) 318 } 319 } 320 321 /* 322 * Wait for .html() method to finish rendering from partial updates 323 */ 324 setTimeout(function() { 325 $(window) 326 .trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR]) 327 .trigger('resize') 328 }, 0) 329 }) 330 331 /* 332 * Handle redirect 333 */ 334 if (data['X_OCTOBER_REDIRECT']) { 335 options.redirect = data['X_OCTOBER_REDIRECT'] 336 isRedirect = true 337 } 338 339 if (isRedirect) { 340 requestOptions.handleRedirectResponse(options.redirect) 341 } 342 343 /* 344 * Handle validation 345 */ 346 if (data['X_OCTOBER_ERROR_FIELDS']) { 347 requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS']) 348 } 349 350 /* 351 * Handle asset injection 352 */ 353 if (data['X_OCTOBER_ASSETS']) { 354 assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise)) 355 } 356 else { 357 updatePromise.resolve() 358 } 359 360 return updatePromise 361 } 362 } 363 364 if (useFiles) { 365 requestOptions.processData = requestOptions.contentType = false 366 } 367 368 /* 369 * Allow default business logic to be called from user functions 370 */ 371 context.success = requestOptions.success 372 context.error = requestOptions.error 373 context.complete = requestOptions.complete 374 requestOptions = $.extend(requestOptions, options) 375 requestOptions.data = requestData 376 377 /* 378 * Initiate request 379 */ 380 if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) { 381 return 382 } 383 384 if (loading) loading.show() 385 $(window).trigger('ajaxBeforeSend', [context]) 386 $el.trigger('ajaxPromise', [context]) 387 388 return $.ajax(requestOptions) 389 .fail(function(jqXHR, textStatus, errorThrown) { 390 if (!isRedirect) { 391 $el.trigger('ajaxFail', [context, textStatus, jqXHR]) 392 } 393 if (loading) loading.hide() 394 }) 395 .done(function(data, textStatus, jqXHR) { 396 if (!isRedirect) { 397 $el.trigger('ajaxDone', [context, data, textStatus, jqXHR]) 398 } 399 if (loading) loading.hide() 400 }) 401 .always(function(dataOrXhr, textStatus, xhrOrError) { 402 $el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError]) 403 }) 404 } 405 406 Request.DEFAULTS = { 407 update: {}, 408 type : 'POST', 409 beforeUpdate: function(data, textStatus, jqXHR) {}, 410 evalBeforeUpdate: null, 411 evalSuccess: null, 412 evalError: null, 413 evalComplete: null, 414 ajaxGlobal: false 415 } 416 417 /* 418 * Internal function, build a string of partials and their update elements. 419 */ 420 Request.prototype.extractPartials = function(update) { 421 var result = [] 422 423 for (var partial in update) 424 result.push(partial) 425 426 return result.join('&') 427 } 428 429 // REQUEST PLUGIN DEFINITION 430 // ============================ 431 432 var old = $.fn.request 433 434 $.fn.request = function(handler, option) { 435 var args = arguments 436 437 var $this = $(this).first() 438 var data = { 439 evalBeforeUpdate: $this.data('request-before-update'), 440 evalSuccess: $this.data('request-success'), 441 evalError: $this.data('request-error'), 442 evalComplete: $this.data('request-complete'), 443 ajaxGlobal: $this.data('request-ajax-global'), 444 confirm: $this.data('request-confirm'), 445 redirect: $this.data('request-redirect'), 446 loading: $this.data('request-loading'), 447 flash: $this.data('request-flash'), 448 files: $this.data('request-files'), 449 browserValidate: $this.data('browser-validate'), 450 form: $this.data('request-form'), 451 url: $this.data('request-url'), 452 update: paramToObj('data-request-update', $this.data('request-update')), 453 data: paramToObj('data-request-data', $this.data('request-data')) 454 } 455 if (!handler) handler = $this.data('request') 456 var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option)
457 return new Request($this, handler, options) 458 } 459 460 $.fn.request.Constructor = Request 461 462 $.request = function(handler, option) { 463 return $(document).request(handler, option) 464 } 465 466 // REQUEST NO CONFLICT 467 // ================= 468 469 $.fn.request.noConflict = function() { 470 $.fn.request = old 471 return this 472 } 473 474 // REQUEST DATA-API 475 // ============== 476 477 function paramToObj(name, value) { 478 if (value === undefined) value = '' 479 if (typeof value == 'object') return value 480 481 try { 482 return ocJSON("{" + value + "}") 483 } 484 catch (e) { 485 throw new Error('Error parsing the '+name+' attribute value. '+e) 486 } 487 } 488 489 function getXSRFToken() { 490 var cookieValue = null 491 if (document.cookie && document.cookie != '') { 492 var cookies = document.cookie.split(';') 493 for (var i = 0; i < cookies.length; i++) { 494 var cookie = jQuery.trim(cookies[i]) 495 if (cookie.substring(0, 11) == ('XSRF-TOKEN' + '=')) { 496 cookieValue = decodeURIComponent(cookie.substring(11)) 497 break 498 } 499 } 500 } 501 return cookieValue 502 } 503 504 $(document).on('change', 'select[data-request], input[type=radio][data-request], input[type=checkbox][data-request], input[type=file][data-request]', function documentOnChange() { 505 $(this).request() 506 }) 507 508 $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function documentOnClick(e) { 509 e.preventDefault() 510 511 $(this).request() 512 513 if ($(this).is('[type=submit]')) 514 return false 515 }) 516 517 $(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function documentOnKeydown(e) { 518 if (e.key === 'Enter') { 519 if (this.dataTrackInputTimer !== undefined) 520 window.clearTimeout(this.dataTrackInputTimer) 521 522 $(this).request() 523 return false 524 } 525 }) 526 527 $(document).on('input', 'input[data-request][data-track-input]', function documentOnKeyup(e) { 528 var 529 $el = $(this), 530 lastValue = $el.data('oc.lastvalue') 531 532 if (!$el.is('[type=email],[type=number],[type=password],[type=search],[type=text]')) 533 return 534 535 if (lastValue !== undefined && lastValue == this.value) 536 return 537 538 $el.data('oc.lastvalue', this.value) 539 540 if (this.dataTrackInputTimer !== undefined) 541 window.clearTimeout(this.dataTrackInputTimer) 542 543 var interval = $(this).data('track-input') 544 if (!interval) 545 interval = 300 546 547 var self = this 548 this.dataTrackInputTimer = window.setTimeout(function() { 549 if (self.lastDataTrackInputRequest) { 550 self.lastDataTrackInputRequest.abort(); 551 } 552 self.lastDataTrackInputRequest = $(self).request(); 553 }, interval) 554 }) 555 556 $(document).on('submit', '[data-request]', function documentOnSubmit() { 557 $(this).request() 558 return false 559 }) 560 561 $(window).on('beforeunload', function documentOnBeforeUnload() { 562 window.ocUnloading = true 563 }) 564 565 /* 566 * Invent our own event that unifies document.ready with window.ajaxUpdateComplete 567 * 568 * $(document).render(function() { }) 569 * $(document).on('render', function() { }) 570 */ 571 572 $(document).ready(function triggerRenderOnReady() { 573 $(document).trigger('render') 574 }) 575 576 $(window).on('ajaxUpdateComplete', function triggerRenderOnAjaxUpdateComplete() { 577 $(document).trigger('render') 578 }) 579 580 $.fn.render = function(callback) { 581 $(document).on('render', callback) 582 } 583 584}(window.jQuery); 585 586/* 587 * October CMS JSON Parser 588 */ 589+function(window) { "use strict"; 590 function parseKey(str, pos, quote) { 591 var key = ""; 592 for (var i = pos; i < str.length; i++) { 593 if (quote && quote === str[i]) { 594 return key; 595 } else if (!quote && (str[i] === " " || str[i] === ":")) { 596 return key; 597 } 598 599 key += str[i]; 600 601 if (str[i] === "\\" && i + 1 < str.length) { 602 key += str[i + 1]; 603 i++; 604 } 605 } 606 throw new Error("Broken JSON syntax near " + key); 607 } 608 609 function getBody(str, pos) { 610 // parse string body 611 if (str[pos] === "\"" || str[pos] === "'") { 612 var body = str[pos]; 613 for (var i = pos + 1; i < str.length; i++) { 614 if (str[i] === "\\") { 615 body += str[i]; 616 if (i + 1 < str.length) body += str[i + 1]; 617 i++; 618 } else if (str[i] === str[pos]) { 619 body += str[pos]; 620 return { 621 originLength: body.length, 622 body: body 623 }; 624 } else body += str[i]; 625 } 626 throw new Error("Broken JSON string body near " + body); 627 } 628 629 // parse true / false 630 if (str[pos] === "t") { 631 if (str.indexOf("true", pos) === pos) { 632 return { 633 originLength: "true".length, 634 body: "true" 635 }; 636 } 637 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 638 } 639 if (str[pos] === "f") { 640 if (str.indexOf("f", pos) === pos) { 641 return {
642 originLength: "false".length, 643 body: "false" 644 }; 645 } 646 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 647 } 648 649 // parse null 650 if (str[pos] === "n") { 651 if (str.indexOf("null", pos) === pos) { 652 return { 653 originLength: "null".length, 654 body: "null" 655 }; 656 } 657 throw new Error("Broken JSON boolean body near " + str.substr(0, pos + 10)); 658 } 659 660 // parse number 661 if (str[pos] === "-" || str[pos] === "+" || str[pos] === "." || (str[pos] >= "0" && str[pos] <= "9")) { 662 var body = ""; 663 for (var i = pos; i < str.length; i++) { 664 if (str[i] === "-" || str[i] === "+" || str[i] === "." || (str[i] >= "0" && str[i] <= "9")) { 665 body += str[i]; 666 } else { 667 return { 668 originLength: body.length, 669 body: body 670 }; 671 } 672 } 673 throw new Error("Broken JSON number body near " + body); 674 } 675 676 // parse object 677 if (str[pos] === "{" || str[pos] === "[") { 678 var stack = [str[pos]]; 679 var body = str[pos]; 680 for (var i = pos + 1; i < str.length; i++) { 681 body += str[i]; 682 if (str[i] === "\\") { 683 if (i + 1 < str.length) body += str[i + 1]; 684 i++; 685 } else if (str[i] === "\"") { 686 if (stack[stack.length - 1] === "\"") { 687 stack.pop(); 688 } else if (stack[stack.length - 1] !== "'") { 689 stack.push(str[i]); 690 } 691 } else if (str[i] === "'") { 692 if (stack[stack.length - 1] === "'") { 693 stack.pop(); 694 } else if (stack[stack.length - 1] !== "\"") { 695 stack.push(str[i]); 696 } 697 } else if (stack[stack.length - 1] !== "\"" && stack[stack.length - 1] !== "'") { 698 if (str[i] === "{") { 699 stack.push("{"); 700 } else if (str[i] === "}") { 701 if (stack[stack.length - 1] === "{") { 702 stack.pop(); 703 } else { 704 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 705 } 706 } else if (str[i] === "[") { 707 stack.push("["); 708 } else if (str[i] === "]") { 709 if (stack[stack.length - 1] === "[") { 710 stack.pop(); 711 } else { 712 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 713 } 714 } 715 } 716 if (!stack.length) { 717 return { 718 originLength: i - pos, 719 body: body 720 }; 721 } 722 } 723 throw new Error("Broken JSON " + (str[pos] === "{" ? "object" : "array") + " body near " + body); 724 } 725 throw new Error("Broken JSON body near " + str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50)); 726 } 727 728 function canBeKeyHead(ch) { 729 if (ch[0] === "\\") return false; 730 if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') return true; 731 if (ch[0] >= '0' && ch[0] <= '9') return true; 732 if (ch[0] === '$') return true; 733 if (ch.charCodeAt(0) > 255) return true; 734 return false; 735 } 736 737 function isBlankChar(ch) { 738 return ch === " " || ch === "\n" || ch === "\t"; 739 } 740 741 function parse(str) { 742 str = str.trim(); 743 if (!str.length) throw new Error("Broken JSON object."); 744 var result = ""; 745 746 /* 747 * the mistake ',' 748 */ 749 while (str && str[0] === ",") { 750 str = str.substr(1); 751 } 752 753 /* 754 * string 755 */ 756 if (str[0] === "\"" || str[0] === "'") { 757 if (str[str.length - 1] !== str[0]) { 758 throw new Error("Invalid string JSON object."); 759 } 760 761 var body = "\""; 762 for (var i = 1; i < str.length; i++) { 763 if (str[i] === "\\") { 764 if (str[i + 1] === "'") { 765 body += str[i + 1] 766 } else { 767 body += str[i]; 768 body += str[i + 1]; 769 } 770 i++; 771 } else if (str[i] === str[0]) { 772 body += "\""; 773 return body 774 } else if (str[i] === "\"") { 775 body += "\\\"" 776 } else body += str[i]; 777 } 778 throw new Error("Invalid string JSON object."); 779 } 780 781 /* 782 * boolean 783 */
784 if (str === "true" || str === "false") { 785 return str; 786 } 787 788 /* 789 * null 790 */ 791 if (str === "null") { 792 return "null"; 793 } 794 795 /* 796 * number 797 */ 798 var num = parseFloat(str); 799 if (!isNaN(num)) { 800 return num.toString(); 801 } 802 803 /* 804 * object 805 */ 806 if (str[0] === "{") { 807 var type = "needKey"; 808 var result = "{"; 809 810 for (var i = 1; i < str.length; i++) { 811 if (isBlankChar(str[i])) { 812 continue; 813 } else if (type === "needKey" && (str[i] === "\"" || str[i] === "'")) { 814 var key = parseKey(str, i + 1, str[i]); 815 result += "\"" + key + "\""; 816 i += key.length; 817 i += 1; 818 type = "afterKey"; 819 } else if (type === "needKey" && canBeKeyHead(str[i])) { 820 var key = parseKey(str, i); 821 result += "\""; 822 result += key; 823 result += "\""; 824 i += key.length - 1; 825 type = "afterKey"; 826 } else if (type === "afterKey" && str[i] === ":") { 827 result += ":"; 828 type = ":"; 829 } else if (type === ":") { 830 var body = getBody(str, i); 831 832 i = i + body.originLength - 1; 833 result += parse(body.body); 834 835 type = "afterBody"; 836 } else if (type === "afterBody" || type === "needKey") { 837 var last = i; 838 while (str[last] === "," || isBlankChar(str[last])) { 839 last++; 840 } 841 if (str[last] === "}" && last === str.length - 1) { 842 while (result[result.length - 1] === ",") { 843 result = result.substr(0, result.length - 1); 844 } 845 result += "}"; 846 return result; 847 } else if (last !== i && result !== "{") { 848 result += ","; 849 type = "needKey"; 850 i = last - 1; 851 } 852 } 853 } 854 throw new Error("Broken JSON object near " + result); 855 } 856 857 /* 858 * array 859 */ 860 if (str[0] === "[") { 861 var result = "["; 862 var type = "needBody"; 863 for (var i = 1; i < str.length; i++) { 864 if (" " === str[i] || "\n" === str[i] || "\t" === str[i]) { 865 continue; 866 } else if (type === "needBody") { 867 if (str[i] === ",") { 868 result += "null,"; 869 continue; 870 } 871 if (str[i] === "]" && i === str.length - 1) { 872 if (result[result.length - 1] === ",") result = result.substr(0, result.length - 1); 873 result += "]"; 874 return result; 875 } 876 877 var body = getBody(str, i); 878 879 i = i + body.originLength - 1; 880 result += parse(body.body); 881 882 type = "afterBody"; 883 } else if (type === "afterBody") { 884 if (str[i] === ",") { 885 result += ","; 886 type = "needBody"; 887 888 // deal with mistake "," 889 while (str[i + 1] === "," || isBlankChar(str[i + 1])) { 890 if (str[i + 1] === ",") result += "null,"; 891 i++; 892 } 893 } else if (str[i] === "]" && i === str.length - 1) { 894 result += "]"; 895 return result; 896 } 897 } 898 } 899 throw new Error("Broken JSON array near " + result); 900 } 901 } 902 903 // Global function 904 window.ocJSON = function(json) { 905 var jsonString = parse(json); 906 return JSON.parse(jsonString); 907 }; 908 909}(window); 910 911/* 912 * October CMS jQuery HTML Sanitizer 913 * @see https://gist.github.com/ufologist/5a0da51b2b9ef1b861c30254172ac3c9 914 */ 915+function(window) { "use strict"; 916 917 function trimAttributes(node) { 918 $.each(node.attributes, function() { 919 var attrName = this.name; 920 var attrValue = this.value; 921 922 /* 923 * remove attributes where the names start with "on" (for example: onload, onerror...) 924 * remove attributes where the value starts with the "javascript:" pseudo protocol (for example href="javascript:alert(1)") 925 */ 926 if (attrName.indexOf('on') == 0 || attrValue.indexOf('javascript:') == 0) { 927 $(node).removeAttr(attrName); 928 } 929 }); 930 } 931 932 function sanitize(html) { 933 /* 934 * [jQuery.parseHTML(data [, context ] [, keepScripts ])](http://api.jquery.com/jQuery.parseHTML/) added: 1.8 935 * Parses a string into an array of DOM nodes. 936 * 937 * By default, the context is the current document if not specified or given as null or undefined. If the HTML was to be used 938 * in another document such as an iframe, that frame's document could be used. 939 * 940 * As of 3.0 the default behavior is changed. 941 * 942 * If the context is not specified or given as null or undefined, a new document is used. 943 * This can potentially improve security because inline events will not execute when the HTML is parsed. Once the parsed HTML 944 * is injected into a document it does execute, but this gives tools a chance to traverse the created DOM and remove anything 945 * deemed unsafe. This improvement does not apply to internal uses of jQuery.parseHTML as they usually pass in the current 946 * document. Therefore, a statement like $( "#log" ).append( $( htmlString ) ) is still subject to the injection of malicious code. 947 * 948 * without context do not execute script 949 * $.parseHTML('<div><img src=1 onerror=alert(1)></div>'); 950 * $.parseHTML('<div><img src=1 onerror=alert(2)></div>', null); 951 * 952 * with context document execute script! 953 * $.parseHTML('<div><img src=1 onerror=alert(3)></div>', document); 954 * 955 * Most jQuery APIs that accept HTML strings will run scripts that are included in the HTML. jQuery.parseHTML does not run scripts 956 * in the parsed HTML unless keepScripts is explicitly true. However, it is still possible in most environments to execute scripts 957 * indirectly, for example via the <img onerror> attribute. 958 * 959 * will return [] 960 * $.parseHTML('<script>alert(1)<\/script>', null, false); 961 * 962 * will return [script DOM element] 963 * $.parseHTML('<script>alert(1)<\/script>', null, true); 964 */ 965 var output = $($.parseHTML('<div>' + html + '</div>', null, false));
966 output.find('*').each(function() { 967 trimAttributes(this); 968 }); 969 return output.html(); 970 } 971 972 // Global function 973 window.ocSanitize = function(html) { 974 return sanitize(html) 975 }; 976 977}(window);
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.