@@ -313,16 +313,78 @@ function $SanitizeProvider() {
313313 return obj ;
314314 }
315315
316- var inertBodyElement = ( function ( window ) {
317- var doc ;
318- if ( window . document && window . document . implementation ) {
319- doc = window . document . implementation . createHTMLDocument ( 'inert' ) ;
316+ /**
317+ * Create an inert document that contains the dirty HTML that needs sanitizing
318+ * Depending upon browser support we use one of three strategies for doing this.
319+ * Support: Safari 10.x -> XHR strategy
320+ * Support: Firefox -> DomParser strategy
321+ */
322+ var getInertBodyElement /* function(html: string): HTMLBodyElement */ = ( function ( window , document ) {
323+ var inertDocument ;
324+ if ( document && document . implementation ) {
325+ inertDocument = document . implementation . createHTMLDocument ( 'inert' ) ;
320326 } else {
321327 throw $sanitizeMinErr ( 'noinert' , 'Can\'t create an inert html document' ) ;
322328 }
323- var docElement = doc . documentElement || doc . getDocumentElement ( ) ;
324- return docElement . getElementsByTagName ( 'body' ) [ 0 ] ;
325- } ) ( window ) ;
329+ var inertBodyElement = ( inertDocument . documentElement || inertDocument . getDocumentElement ( ) ) . querySelector ( 'body' ) ;
330+
331+ // Check for the Safari 10.1 bug - which allows JS to run inside the SVG G element
332+ inertBodyElement . innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>' ;
333+ if ( ! inertBodyElement . querySelector ( 'svg' ) ) {
334+ return getInertBodyElement_XHR ;
335+ } else {
336+ // Check for the Firefox bug - which prevents the inner img JS from being sanitized
337+ inertBodyElement . innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">' ;
338+ if ( inertBodyElement . querySelector ( 'svg img' ) ) {
339+ return getInertBodyElement_DOMParser ;
340+ } else {
341+ return getInertBodyElement_InertDocument ;
342+ }
343+ }
344+
345+ function getInertBodyElement_XHR ( html ) {
346+ // We add this dummy element to ensure that the rest of the content is parsed as expected
347+ // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
348+ html = '<remove></remove>' + html ;
349+ try {
350+ html = encodeURI ( html ) ;
351+ } catch ( e ) {
352+ return undefined ;
353+ }
354+ var xhr = new window . XMLHttpRequest ( ) ;
355+ xhr . responseType = 'document' ;
356+ xhr . open ( 'GET' , 'data:text/html;charset=utf-8,' + html , false ) ;
357+ xhr . send ( null ) ;
358+ var body = xhr . response . body ;
359+ body . firstChild . remove ( ) ;
360+ return body ;
361+ }
362+
363+ function getInertBodyElement_DOMParser ( html ) {
364+ // We add this dummy element to ensure that the rest of the content is parsed as expected
365+ // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
366+ html = '<remove></remove>' + html ;
367+ try {
368+ var body = new window . DOMParser ( ) . parseFromString ( html , 'text/html' ) . body ;
369+ body . firstChild . remove ( ) ;
370+ return body ;
371+ } catch ( e ) {
372+ return undefined ;
373+ }
374+ }
375+
376+ function getInertBodyElement_InertDocument ( html ) {
377+ inertBodyElement . innerHTML = html ;
378+
379+ // Support: IE 9-11 only
380+ // strip custom-namespaced attributes on IE<=11
381+ if ( document . documentMode ) {
382+ stripCustomNsAttrs ( inertBodyElement ) ;
383+ }
384+
385+ return inertBodyElement ;
386+ }
387+ } ) ( window , window . document ) ;
326388
327389 /**
328390 * @example
@@ -342,7 +404,9 @@ function $SanitizeProvider() {
342404 } else if ( typeof html !== 'string' ) {
343405 html = '' + html ;
344406 }
345- inertBodyElement . innerHTML = html ;
407+
408+ var inertBodyElement = getInertBodyElement ( html ) ;
409+ if ( ! inertBodyElement ) return '' ;
346410
347411 //mXSS protection
348412 var mXSSAttempts = 5 ;
@@ -352,13 +416,9 @@ function $SanitizeProvider() {
352416 }
353417 mXSSAttempts -- ;
354418
355- // Support: IE 9-11 only
356- // strip custom-namespaced attributes on IE<=11
357- if ( window . document . documentMode ) {
358- stripCustomNsAttrs ( inertBodyElement ) ;
359- }
360- html = inertBodyElement . innerHTML ; //trigger mXSS
361- inertBodyElement . innerHTML = html ;
419+ // trigger mXSS if it is going to happen by reading and writing the innerHTML
420+ html = inertBodyElement . innerHTML ;
421+ inertBodyElement = getInertBodyElement ( html ) ;
362422 } while ( html !== inertBodyElement . innerHTML ) ;
363423
364424 var node = inertBodyElement . firstChild ;
0 commit comments