1// Minimal Element.prototype.setHTML polyfill. 2// Uses the native setHTML (default sanitizer) when available. 3// For browsers without it (as of writing: all of Safari/iOS, plus older 4// Chrome/Firefox), falls back to a DOM-based sanitizer that removes 5// XSS-unsafe elements and attributes (scripts, event handlers, javascript: 6// URLs, <base> hijacking, etc). Since Safari has no native support yet, 7// this fallback is the primary path for a large share of visitors, not 8// just a rare edge case - keep it in sync with the native default 9// Sanitizer's coverage rather than treating it as a stopgap. 10if ( ! ( 'setHTML' in Element.prototype ) ) { 11 const UNSAFE_TAGS = ['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'FRAME', 'APPLET', 'LINK', 'META', 'STYLE', 'BASE']; 12 const UNSAFE_ATTRS = ['srcdoc']; 13 const UNSAFE_SCHEMES = ['javascript:', 'vbscript:', 'data:text/html']; 14 const URL_ATTRS = ['href', 'src', 'action', 'formaction', 'xlink:href']; 15 16 Element.prototype.setHTML = function( input ) { 17 const template = document.createElement( 'template' ); 18 template.innerHTML = input; 19 const fragment = template.content; 20 21 const iterator = document.createNodeIterator( fragment, NodeFilter.SHOW_ELEMENT ); 22 let currentNode; 23 while ( ( currentNode = iterator.nextNode() ) ) { 24 const tagName = currentNode.tagName; 25 if ( UNSAFE_TAGS.includes( tagName ) ) { 26 currentNode.remove(); 27 continue; 28 } 29 30 const attrs = currentNode.attributes; 31 if ( attrs ) { 32 for ( let i = attrs.length - 1; i >= 0; i-- ) { 33 const attr = attrs[i]; 34 const attrName = attr.name.toLowerCase(); 35 // Browsers ignore ASCII tabs/newlines anywhere in a URL 36 // before resolving its scheme, so strip them here too - 37 // otherwise "jav\tascript:" slips past a plain startsWith check. 38 const attrValue = attr.value.trim().toLowerCase().replace( /[\t\n\r]/g, '' ); 39 40 const isEventHandler = attrName.startsWith( 'on' ) && attrName.length > 2; 41 const isUnsafeAttr = UNSAFE_ATTRS.includes( attrName ); 42 const isUnsafeUrl = URL_ATTRS.includes( attrName ) && 43 UNSAFE_SCHEMES.some( scheme => attrValue.startsWith( scheme ) ); 44 45 if ( isEventHandler || isUnsafeAttr || isUnsafeUrl ) { 46 currentNode.removeAttribute( attr.name ); 47 } 48 } 49 } 50 } 51 52 this.replaceChildren( fragment ); 53 }; 54}
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.