| 
									
										
										
										
											2016-06-23 09:47:54 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-17 14:09:19 -07:00
										 |  |  | import {isDevMode} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  | import {DomAdapter, getDOM} from '../dom/dom_adapter'; | 
					
						
							| 
									
										
										
										
											2017-02-14 16:14:40 -08:00
										 |  |  | import {DOCUMENT} from '../dom/dom_tokens'; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  | import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer'; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** A <body> element that can be safely used to parse untrusted HTML. Lazily initialized below. */ | 
					
						
							|  |  |  | let inertElement: HTMLElement = null; | 
					
						
							|  |  |  | /** Lazily initialized to make sure the DOM adapter gets set before use. */ | 
					
						
							|  |  |  | let DOM: DomAdapter = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Returns an HTML element that is guaranteed to not execute code when creating elements in it. */ | 
					
						
							|  |  |  | function getInertElement() { | 
					
						
							|  |  |  |   if (inertElement) return inertElement; | 
					
						
							|  |  |  |   DOM = getDOM(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Prefer using <template> element if supported.
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   const templateEl = DOM.createElement('template'); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   if ('content' in templateEl) return templateEl; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   const doc = DOM.createHtmlDocument(); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   inertElement = DOM.querySelector(doc, 'body'); | 
					
						
							|  |  |  |   if (inertElement == null) { | 
					
						
							|  |  |  |     // usually there should be only one body element in the document, but IE doesn't have any, so we
 | 
					
						
							|  |  |  |     // need to create one.
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     const html = DOM.createElement('html', doc); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     inertElement = DOM.createElement('body', doc); | 
					
						
							|  |  |  |     DOM.appendChild(html, inertElement); | 
					
						
							|  |  |  |     DOM.appendChild(doc, html); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return inertElement; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function tagSet(tags: string): {[k: string]: boolean} { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   const res: {[k: string]: boolean} = {}; | 
					
						
							|  |  |  |   for (const t of tags.split(',')) res[t] = true; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  | function merge(...sets: {[k: string]: boolean}[]): {[k: string]: boolean} { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   const res: {[k: string]: boolean} = {}; | 
					
						
							|  |  |  |   for (const s of sets) { | 
					
						
							|  |  |  |     for (const v in s) { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |       if (s.hasOwnProperty(v)) res[v] = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return res; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Good source of info about elements and attributes
 | 
					
						
							|  |  |  | // http://dev.w3.org/html5/spec/Overview.html#semantics
 | 
					
						
							|  |  |  | // http://simon.html5.org/html-elements
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Safe Void Elements - HTML5
 | 
					
						
							|  |  |  | // http://dev.w3.org/html5/spec/Overview.html#void-elements
 | 
					
						
							|  |  |  | const VOID_ELEMENTS = tagSet('area,br,col,hr,img,wbr'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Elements that you can, intentionally, leave open (and which close themselves)
 | 
					
						
							|  |  |  | // http://dev.w3.org/html5/spec/Overview.html#optional-tags
 | 
					
						
							|  |  |  | const OPTIONAL_END_TAG_BLOCK_ELEMENTS = tagSet('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'); | 
					
						
							|  |  |  | const OPTIONAL_END_TAG_INLINE_ELEMENTS = tagSet('rp,rt'); | 
					
						
							|  |  |  | const OPTIONAL_END_TAG_ELEMENTS = | 
					
						
							|  |  |  |     merge(OPTIONAL_END_TAG_INLINE_ELEMENTS, OPTIONAL_END_TAG_BLOCK_ELEMENTS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Safe Block Elements - HTML5
 | 
					
						
							|  |  |  | const BLOCK_ELEMENTS = merge( | 
					
						
							|  |  |  |     OPTIONAL_END_TAG_BLOCK_ELEMENTS, | 
					
						
							|  |  |  |     tagSet( | 
					
						
							|  |  |  |         'address,article,' + | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  |         'aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + | 
					
						
							|  |  |  |         'h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul')); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Inline Elements - HTML5
 | 
					
						
							|  |  |  | const INLINE_ELEMENTS = merge( | 
					
						
							|  |  |  |     OPTIONAL_END_TAG_INLINE_ELEMENTS, | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |     tagSet( | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  |         'a,abbr,acronym,audio,b,' + | 
					
						
							|  |  |  |         'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' + | 
					
						
							|  |  |  |         'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video')); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | const VALID_ELEMENTS = | 
					
						
							|  |  |  |     merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Attributes that have href and hence need to be sanitized
 | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  | const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Attributes that have special href set hence need to be sanitized
 | 
					
						
							|  |  |  | const SRCSET_ATTRS = tagSet('srcset'); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  | const HTML_ATTRS = tagSet( | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  |     'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + | 
					
						
							|  |  |  |     'compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,' + | 
					
						
							|  |  |  |     'ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,' + | 
					
						
							|  |  |  |     'scope,scrolling,shape,size,sizes,span,srclang,start,summary,tabindex,target,title,translate,type,usemap,' + | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |     'valign,value,vspace,width'); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-03 18:41:31 -07:00
										 |  |  | // NB: This currently conciously doesn't support SVG. SVG sanitization has had several security
 | 
					
						
							|  |  |  | // issues in the past, so it seems safer to leave it out if possible. If support for binding SVG via
 | 
					
						
							|  |  |  | // innerHTML is required, SVG attributes should be added here.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NB: Sanitization does not allow <form> elements or other active elements (<button> etc). Those
 | 
					
						
							|  |  |  | // can be sanitized, but they increase security surface area without a legitimate use case, so they
 | 
					
						
							|  |  |  | // are left out here.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-24 16:54:11 +02:00
										 |  |  | const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe | 
					
						
							|  |  |  |  * attributes. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class SanitizingHtmlSerializer { | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |   // Explicitly track if something was stripped, to avoid accidentally warning of sanitization just
 | 
					
						
							|  |  |  |   // because characters were re-encoded.
 | 
					
						
							|  |  |  |   public sanitizedSomething = false; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   private buf: string[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sanitizeChildren(el: Element): string { | 
					
						
							| 
									
										
										
										
											2016-05-03 18:41:31 -07:00
										 |  |  |     // This cannot use a TreeWalker, as it has to run on Angular's various DOM adapters.
 | 
					
						
							|  |  |  |     // However this code never accesses properties off of `document` before deleting its contents
 | 
					
						
							|  |  |  |     // again, so it shouldn't be vulnerable to DOM clobbering.
 | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     let current: Node = el.firstChild; | 
					
						
							|  |  |  |     while (current) { | 
					
						
							|  |  |  |       if (DOM.isElementNode(current)) { | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |         this.startElement(current as Element); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |       } else if (DOM.isTextNode(current)) { | 
					
						
							|  |  |  |         this.chars(DOM.nodeValue(current)); | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         // Strip non-element, non-text nodes.
 | 
					
						
							|  |  |  |         this.sanitizedSomething = true; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       if (DOM.firstChild(current)) { | 
					
						
							|  |  |  |         current = DOM.firstChild(current); | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       while (current) { | 
					
						
							|  |  |  |         // Leaving the element. Walk up and to the right, closing tags as we go.
 | 
					
						
							|  |  |  |         if (DOM.isElementNode(current)) { | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |           this.endElement(current as Element); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (DOM.nextSibling(current)) { | 
					
						
							|  |  |  |           current = DOM.nextSibling(current); | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         current = DOM.parentElement(current); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return this.buf.join(''); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |   private startElement(element: Element) { | 
					
						
							|  |  |  |     const tagName = DOM.nodeName(element).toLowerCase(); | 
					
						
							|  |  |  |     if (!VALID_ELEMENTS.hasOwnProperty(tagName)) { | 
					
						
							|  |  |  |       this.sanitizedSomething = true; | 
					
						
							|  |  |  |       return; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |     this.buf.push('<'); | 
					
						
							|  |  |  |     this.buf.push(tagName); | 
					
						
							|  |  |  |     DOM.attributeMap(element).forEach((value: string, attrName: string) => { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |       const lower = attrName.toLowerCase(); | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |       if (!VALID_ATTRS.hasOwnProperty(lower)) { | 
					
						
							|  |  |  |         this.sanitizedSomething = true; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // TODO(martinprobst): Special case image URIs for data:image/...
 | 
					
						
							|  |  |  |       if (URI_ATTRS[lower]) value = sanitizeUrl(value); | 
					
						
							|  |  |  |       if (SRCSET_ATTRS[lower]) value = sanitizeSrcset(value); | 
					
						
							|  |  |  |       this.buf.push(' '); | 
					
						
							|  |  |  |       this.buf.push(attrName); | 
					
						
							|  |  |  |       this.buf.push('="'); | 
					
						
							|  |  |  |       this.buf.push(encodeEntities(value)); | 
					
						
							|  |  |  |       this.buf.push('"'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     this.buf.push('>'); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |   private endElement(current: Element) { | 
					
						
							|  |  |  |     const tagName = DOM.nodeName(current).toLowerCase(); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) { | 
					
						
							|  |  |  |       this.buf.push('</'); | 
					
						
							|  |  |  |       this.buf.push(tagName); | 
					
						
							|  |  |  |       this.buf.push('>'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 15:45:15 -07:00
										 |  |  |   private chars(chars: any /** TODO #9100 */) { this.buf.push(encodeEntities(chars)); } | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Regular Expressions for parsing tags and attributes
 | 
					
						
							|  |  |  | const SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; | 
					
						
							|  |  |  | // ! to ~ is the ASCII range.
 | 
					
						
							|  |  |  | const NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Escapes all potentially dangerous characters, so that the | 
					
						
							|  |  |  |  * resulting string can be safely inserted into attribute or | 
					
						
							|  |  |  |  * element text. | 
					
						
							|  |  |  |  * @param value | 
					
						
							|  |  |  |  * @returns {string} escaped text | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  | function encodeEntities(value: string) { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   return value.replace(/&/g, '&') | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |       .replace( | 
					
						
							|  |  |  |           SURROGATE_PAIR_REGEXP, | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |           function(match: string) { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |             const hi = match.charCodeAt(0); | 
					
						
							|  |  |  |             const low = match.charCodeAt(1); | 
					
						
							| 
									
										
										
										
											2016-06-08 16:38:52 -07:00
										 |  |  |             return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |       .replace( | 
					
						
							|  |  |  |           NON_ALPHANUMERIC_REGEXP, | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |           function(match: string) { return '&#' + match.charCodeAt(0) + ';'; }) | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |       .replace(/</g, '<') | 
					
						
							|  |  |  |       .replace(/>/g, '>'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' | 
					
						
							|  |  |  |  * attribute to declare ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This is undesirable since we don't want to allow any of these custom attributes. This method | 
					
						
							|  |  |  |  * strips them all. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  | function stripCustomNsAttrs(el: Element) { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   DOM.attributeMap(el).forEach((_, attrName) => { | 
					
						
							|  |  |  |     if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) { | 
					
						
							|  |  |  |       DOM.removeAttribute(el, attrName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   for (const n of DOM.childNodesAsList(el)) { | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |     if (DOM.isElementNode(n)) stripCustomNsAttrs(n as Element); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to | 
					
						
							|  |  |  |  * the DOM in a browser environment. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-02-14 16:14:40 -08:00
										 |  |  | export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |   try { | 
					
						
							| 
									
										
										
										
											2016-06-23 22:06:19 +02:00
										 |  |  |     const containerEl = getInertElement(); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     // Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
 | 
					
						
							| 
									
										
										
										
											2016-06-23 22:06:19 +02:00
										 |  |  |     let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : ''; | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // mXSS protection. Repeatedly parse the document to make sure it stabilizes, so that a browser
 | 
					
						
							|  |  |  |     // trying to auto-correct incorrect HTML cannot cause formerly inert HTML to become dangerous.
 | 
					
						
							|  |  |  |     let mXSSAttempts = 5; | 
					
						
							|  |  |  |     let parsedHtml = unsafeHtml; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     do { | 
					
						
							|  |  |  |       if (mXSSAttempts === 0) { | 
					
						
							|  |  |  |         throw new Error('Failed to sanitize html because the input is unstable'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       mXSSAttempts--; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       unsafeHtml = parsedHtml; | 
					
						
							|  |  |  |       DOM.setInnerHTML(containerEl, unsafeHtml); | 
					
						
							| 
									
										
										
										
											2017-02-14 16:14:40 -08:00
										 |  |  |       if (defaultDoc.documentMode) { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |         // strip custom-namespaced attributes on IE<=11
 | 
					
						
							|  |  |  |         stripCustomNsAttrs(containerEl); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       parsedHtml = DOM.getInnerHTML(containerEl); | 
					
						
							|  |  |  |     } while (unsafeHtml !== parsedHtml); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     const sanitizer = new SanitizingHtmlSerializer(); | 
					
						
							|  |  |  |     const safeHtml = sanitizer.sanitizeChildren(DOM.getTemplateContent(containerEl) || containerEl); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Clear out the body element.
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |     const parent = DOM.getTemplateContent(containerEl) || containerEl; | 
					
						
							|  |  |  |     for (const child of DOM.childNodesAsList(parent)) { | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |       DOM.removeChild(parent, child); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 11:39:09 -07:00
										 |  |  |     if (isDevMode() && sanitizer.sanitizedSomething) { | 
					
						
							| 
									
										
										
										
											2016-06-28 18:13:46 -07:00
										 |  |  |       DOM.log('WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).'); | 
					
						
							| 
									
										
										
										
											2016-04-30 19:02:05 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return safeHtml; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     // In case anything goes wrong, clear out inertElement to reset the entire DOM structure.
 | 
					
						
							|  |  |  |     inertElement = null; | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |