1/** 2 * @file 3 * Colorbox JS. 4 */ 5 6(function ($, Drupal, drupalSettings, once) { 7 8 'use strict'; 9 10 Drupal.behaviors.initColorbox = { 11 attach: function (context, settings) { 12 if (typeof $.colorbox !== 'function' || typeof settings.colorbox === 'undefined') { 13 return; 14 } 15 16 if (settings.colorbox.mobiledetect && window.matchMedia) { 17 // Disable Colorbox for small screens. 18 var mq = window.matchMedia('(max-device-width: ' + settings.colorbox.mobiledevicewidth + ')'); 19 if (mq.matches) { 20 $.colorbox.remove(); 21 return; 22 } 23 } 24 25 settings.colorbox.rel = function () { 26 return $(this).data('colorbox-gallery'); 27 }; 28 29 settings.colorbox.html = function () { 30 var $modalContent = $(this).find('> .modal-content'); 31 return $modalContent.length ? $(this).find('> .modal-content').children().clone() : false; 32 }; 33 34 $(once('init-colorbox', '.colorbox', context)) 35 .each(function() { 36 // Only images are supported for the "colorbox" class. 37 // The "photo" setting forces the href attribute to be treated as an image. 38 var extendParams = { 39 photo: true 40 }; 41 // If a title attribute is supplied, sanitize it. 42 var title = $(this).attr('title'); 43 if (typeof title === 'undefined') { 44 title = this.dataset.cboxTitle; 45 } 46 if (title) { 47 extendParams.title = Drupal.colorbox.sanitizeMarkup(title); 48 } 49 $(this).colorbox($.extend({}, settings.colorbox, extendParams)); 50 51 // Only allow http or https protocol in hrefs. 52 var href = $(this).attr('href'); 53 var protocolRegex = /^(https?)/; 54 if (href && href.substring(0, 1) !== '/') { 55 var protocol = href.split(':')[0]; 56 // Use a regex to match http or https protocol. 57 if (!protocolRegex.test(protocol)) { 58 $(this).removeAttr('href'); 59 } 60 } 61 var dataHref = this.dataset.cboxHref; 62 if (dataHref && dataHref.substring(0, 1) !== '/') { 63 var dataProtocol = dataHref.split(':')[0]; 64 if (!protocolRegex.test(dataProtocol)) { 65 delete this.dataset.cboxHref; 66 } 67 } 68 69 // Since the sanitized title has been passed to colorbox settings, 70 // delete the unsanitized data-cbox-title attribute. 71 delete this.dataset.cboxTitle; 72 73 // Disallow dangerous data attributes. 74 delete this.dataset.cboxIframeAttrs; 75 delete this.dataset.cboxCreateiframe; 76 delete this.dataset.cboxCreateimg; 77 78 // If cboxImgAttrs is used, limit attributes to avoid rendering HTML. 79 let imgAttrs = this.dataset.cboxImgAttrs; 80 if (imgAttrs) { 81 const allowedAttrs = [ 82 'alt', 83 'width', 84 'height', 85 'title', 86 'loading', 87 'decoding', 88 'referrerpolicy', 89 'usemap', 90 'longdesc', 91 'role', 92 ]; 93 const ariaPattern = /^aria-[a-z-]+$/; 94 try { 95 imgAttrs = JSON.parse(imgAttrs); 96 if (typeof imgAttrs === 'object') { 97 // Limit attributes to allow list and aria-* attributes. 98 for (const attr in imgAttrs) { 99 const lowerAttr = attr.toLowerCase(); 100 if (!allowedAttrs.includes(lowerAttr) && !ariaPattern.test(lowerAttr)) { 101 delete imgAttrs[attr]; 102 } 103 } 104 this.dataset.cboxImgAttrs = JSON.stringify(imgAttrs); 105 } 106 else { 107 delete this.dataset.cboxImgAttrs; 108 } 109 } 110 catch (e) { 111 // Improper value. 112 delete this.dataset.cboxImgAttrs; 113 } 114 } 115 116 117 // Sanitize other data attributes. 118 var sanitizeDataList = ['cboxNext', 'cboxPrevious', 'cboxCurrent', 119 'cboxClose', 'cboxSlideshowstop', 'cboxSlideshowstart', 120 'cboxXhrError', 'cboxImgerror', 'cboxHtml' 121 ]; 122 for (var a of sanitizeDataList) { 123 if (this.dataset.hasOwnProperty(a)) { 124 this.dataset[a] = Drupal.colorbox.sanitizeMarkup(this.dataset[a]); 125 } 126 } 127 }); 128 129 $('.colorbox', context).colorbox({ 130 onComplete: function (e) { 131 var focus = $('#cboxContent').find('#cboxPrevious').css('display') !== 'none' ? $('#cboxContent').find('#cboxPrevious') : $('#cboxContent').find('#cboxClose'); 132 focus.focus(); 133 134 $('#cboxContent').on('keydown', function (e) { 135 var keyCode = e.keyCode || e.which; 136 var firstElement = $('#cboxContent').find('#cboxPrevious').last().is(':focus'); 137 var lastElement = $('#cboxContent').find('#cboxClose').first().is(':focus'); 138 if (keyCode === 9 && !e.shiftKey && lastElement) { 139 e.preventDefault(); 140 $('#cboxContent').find('#cboxPrevious').first().focus(); 141 } 142 else if (keyCode === 9 && e.shiftKey && firstElement) { 143 e.preventDefault(); 144 $('#cboxContent').find('#cboxClose').first().focus(); 145 } 146 }); 147 } 148 }); 149 } 150 }; 151 152 // Create colorbox namespace if it doesn't exist. 153 if (!Drupal.hasOwnProperty('colorbox')) { 154 Drupal.colorbox = {}; 155 } 156 157 /** 158 * Global function to allow sanitizing captions and control strings. 159 * 160 * @param markup 161 * String containing potential markup. 162 * @return @string 163 * Sanitized string with potentially dangerous markup removed. 164 */ 165 Drupal.colorbox.sanitizeMarkup = function(markup) { 166 // If DOMPurify installed, allow some HTML. Otherwise, treat as plain text. 167 if (typeof DOMPurify !== 'undefined') { 168 var purifyConfig = { 169 ALLOWED_TAGS: [ 170 'a', 171 'b', 172 'strong', 173 'i', 174 'em', 175 'u', 176 'cite', 177 'code', 178 'br' 179 ], 180 ALLOWED_ATTR: [ 181 'href', 182 'hreflang', 183 'title', 184 'target' 185 ] 186 } 187 if (drupalSettings.hasOwnProperty('dompurify_custom_config')) { 188 purifyConfig = drupalSettings.dompurify_custom_config; 189 } 190 return DOMPurify.sanitize(markup, purifyConfig); 191 } 192 else { 193 return Drupal.checkPlain(markup); 194 } 195 } 196 197})(jQuery, Drupal, drupalSettings, once);
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.