|
73 | 73 | } |
74 | 74 | }; |
75 | 75 |
|
| 76 | + /** |
| 77 | + * Checks whether a given image source URL is safe to use. |
| 78 | + * Only allows http, https, protocol-relative, data image URLs, or relative URLs |
| 79 | + * that do not begin with a dangerous scheme such as "javascript:", "vbscript:", or unsafe "data:". |
| 80 | + * |
| 81 | + * @param {string} src |
| 82 | + * @returns {boolean} |
| 83 | + */ |
| 84 | + function isSafeImageSrc(src) { |
| 85 | + if (!src || typeof src !== 'string') { |
| 86 | + return false; |
| 87 | + } |
| 88 | + var trimmed = $.trim ? $.trim(src) : src.replace(/^\s+|\s+$/g, ''); |
| 89 | + // Reject javascript:, vbscript:, and non-image data: URLs |
| 90 | + var lower = trimmed.toLowerCase(); |
| 91 | + if (lower.indexOf('javascript:') === 0 || |
| 92 | + lower.indexOf('vbscript:') === 0 || |
| 93 | + (lower.indexOf('data:') === 0 && lower.indexOf('data:image/') !== 0)) { |
| 94 | + return false; |
| 95 | + } |
| 96 | + // Allow common safe patterns: http, https, protocol-relative, data:image, or relative paths |
| 97 | + if (lower.indexOf('http:') === 0 || |
| 98 | + lower.indexOf('https:') === 0 || |
| 99 | + lower.indexOf('//') === 0 || |
| 100 | + lower.indexOf('data:image/') === 0 || |
| 101 | + lower.charAt(0) === '/' || |
| 102 | + (lower.charAt(0) !== '#' && lower.indexOf(':') === -1)) { |
| 103 | + return true; |
| 104 | + } |
| 105 | + return false; |
| 106 | + } |
| 107 | + |
76 | 108 | /** |
77 | 109 | * Checks an option element for data-image-src |
78 | 110 | * and adds that as an image tag within the widget option |
|
82 | 114 | */ |
83 | 115 | function insertImage(option, span) { |
84 | 116 | var optionImageSrc = option.getAttribute('data-image-src'); |
85 | | - if (optionImageSrc) { |
| 117 | + if (optionImageSrc && isSafeImageSrc(optionImageSrc)) { |
86 | 118 | var img = document.createElement('img'); |
87 | 119 | img.setAttribute('src', optionImageSrc); |
88 | 120 | span.insertBefore(img, span.firstChild); |
|
0 commit comments