refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,235 @@
/**
* @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
*/
import {Inject, Injectable, Sanitizer, SecurityContext} from '@angular/core';
import {DOCUMENT} from '../dom/dom_tokens';
import {sanitizeHtml} from './html_sanitizer';
import {sanitizeStyle} from './style_sanitizer';
import {sanitizeUrl} from './url_sanitizer';
export {SecurityContext};
/**
* Marker interface for a value that's safe to use in a particular context.
*
* @stable
*/
export interface SafeValue {}
/**
* Marker interface for a value that's safe to use as HTML.
*
* @stable
*/
export interface SafeHtml extends SafeValue {}
/**
* Marker interface for a value that's safe to use as style (CSS).
*
* @stable
*/
export interface SafeStyle extends SafeValue {}
/**
* Marker interface for a value that's safe to use as JavaScript.
*
* @stable
*/
export interface SafeScript extends SafeValue {}
/**
* Marker interface for a value that's safe to use as a URL linking to a document.
*
* @stable
*/
export interface SafeUrl extends SafeValue {}
/**
* Marker interface for a value that's safe to use as a URL to load executable code from.
*
* @stable
*/
export interface SafeResourceUrl extends SafeValue {}
/**
* DomSanitizer helps preventing Cross Site Scripting Security bugs (XSS) by sanitizing
* values to be safe to use in the different DOM contexts.
*
* For example, when binding a URL in an `<a [href]="someValue">` hyperlink, `someValue` will be
* sanitized so that an attacker cannot inject e.g. a `javascript:` URL that would execute code on
* the website.
*
* In specific situations, it might be necessary to disable sanitization, for example if the
* application genuinely needs to produce a `javascript:` style link with a dynamic value in it.
* Users can bypass security by constructing a value with one of the `bypassSecurityTrust...`
* methods, and then binding to that value from the template.
*
* These situations should be very rare, and extraordinary care must be taken to avoid creating a
* Cross Site Scripting (XSS) security bug!
*
* When using `bypassSecurityTrust...`, make sure to call the method as early as possible and as
* close as possible to the source of the value, to make it easy to verify no security bug is
* created by its use.
*
* It is not required (and not recommended) to bypass security if the value is safe, e.g. a URL that
* does not start with a suspicious protocol, or an HTML snippet that does not contain dangerous
* code. The sanitizer leaves safe values intact.
*
* @security Calling any of the `bypassSecurityTrust...` APIs disables Angular's built-in
* sanitization for the value passed in. Carefully check and audit all values and code paths going
* into this call. Make sure any user data is appropriately escaped for this security context.
* For more detail, see the [Security Guide](http://g.co/ng/security).
*
* @stable
*/
export abstract class DomSanitizer implements Sanitizer {
/**
* Sanitizes a value for use in the given SecurityContext.
*
* If value is trusted for the context, this method will unwrap the contained safe value and use
* it directly. Otherwise, value will be sanitized to be safe in the given context, for example
* by replacing URLs that have an unsafe protocol part (such as `javascript:`). The implementation
* is responsible to make sure that the value can definitely be safely used in the given context.
*/
abstract sanitize(context: SecurityContext, value: any): string;
/**
* Bypass security and trust the given value to be safe HTML. Only use this when the bound HTML
* is unsafe (e.g. contains `<script>` tags) and the code should be executed. The sanitizer will
* leave safe HTML intact, so in most situations this method should not be used.
*
* **WARNING:** calling this method with untrusted user data exposes your application to XSS
* security risks!
*/
abstract bypassSecurityTrustHtml(value: string): SafeHtml;
/**
* Bypass security and trust the given value to be safe style value (CSS).
*
* **WARNING:** calling this method with untrusted user data exposes your application to XSS
* security risks!
*/
abstract bypassSecurityTrustStyle(value: string): SafeStyle;
/**
* Bypass security and trust the given value to be safe JavaScript.
*
* **WARNING:** calling this method with untrusted user data exposes your application to XSS
* security risks!
*/
abstract bypassSecurityTrustScript(value: string): SafeScript;
/**
* Bypass security and trust the given value to be a safe style URL, i.e. a value that can be used
* in hyperlinks or `<img src>`.
*
* **WARNING:** calling this method with untrusted user data exposes your application to XSS
* security risks!
*/
abstract bypassSecurityTrustUrl(value: string): SafeUrl;
/**
* Bypass security and trust the given value to be a safe resource URL, i.e. a location that may
* be used to load executable code from, like `<script src>`, or `<iframe src>`.
*
* **WARNING:** calling this method with untrusted user data exposes your application to XSS
* security risks!
*/
abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl;
}
@Injectable()
export class DomSanitizerImpl extends DomSanitizer {
constructor(@Inject(DOCUMENT) private _doc: any) { super(); }
sanitize(ctx: SecurityContext, value: any): string {
if (value == null) return null;
switch (ctx) {
case SecurityContext.NONE:
return value;
case SecurityContext.HTML:
if (value instanceof SafeHtmlImpl) return value.changingThisBreaksApplicationSecurity;
this.checkNotSafeValue(value, 'HTML');
return sanitizeHtml(this._doc, String(value));
case SecurityContext.STYLE:
if (value instanceof SafeStyleImpl) return value.changingThisBreaksApplicationSecurity;
this.checkNotSafeValue(value, 'Style');
return sanitizeStyle(value);
case SecurityContext.SCRIPT:
if (value instanceof SafeScriptImpl) return value.changingThisBreaksApplicationSecurity;
this.checkNotSafeValue(value, 'Script');
throw new Error('unsafe value used in a script context');
case SecurityContext.URL:
if (value instanceof SafeResourceUrlImpl || value instanceof SafeUrlImpl) {
// Allow resource URLs in URL contexts, they are strictly more trusted.
return value.changingThisBreaksApplicationSecurity;
}
this.checkNotSafeValue(value, 'URL');
return sanitizeUrl(String(value));
case SecurityContext.RESOURCE_URL:
if (value instanceof SafeResourceUrlImpl) {
return value.changingThisBreaksApplicationSecurity;
}
this.checkNotSafeValue(value, 'ResourceURL');
throw new Error(
'unsafe value used in a resource URL context (see http://g.co/ng/security#xss)');
default:
throw new Error(`Unexpected SecurityContext ${ctx} (see http://g.co/ng/security#xss)`);
}
}
private checkNotSafeValue(value: any, expectedType: string) {
if (value instanceof SafeValueImpl) {
throw new Error(
`Required a safe ${expectedType}, got a ${value.getTypeName()} ` +
`(see http://g.co/ng/security#xss)`);
}
}
bypassSecurityTrustHtml(value: string): SafeHtml { return new SafeHtmlImpl(value); }
bypassSecurityTrustStyle(value: string): SafeStyle { return new SafeStyleImpl(value); }
bypassSecurityTrustScript(value: string): SafeScript { return new SafeScriptImpl(value); }
bypassSecurityTrustUrl(value: string): SafeUrl { return new SafeUrlImpl(value); }
bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl {
return new SafeResourceUrlImpl(value);
}
}
abstract class SafeValueImpl implements SafeValue {
constructor(public changingThisBreaksApplicationSecurity: string) {
// empty
}
abstract getTypeName(): string;
toString() {
return `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}` +
` (see http://g.co/ng/security#xss)`;
}
}
class SafeHtmlImpl extends SafeValueImpl implements SafeHtml {
getTypeName() { return 'HTML'; }
}
class SafeStyleImpl extends SafeValueImpl implements SafeStyle {
getTypeName() { return 'Style'; }
}
class SafeScriptImpl extends SafeValueImpl implements SafeScript {
getTypeName() { return 'Script'; }
}
class SafeUrlImpl extends SafeValueImpl implements SafeUrl {
getTypeName() { return 'URL'; }
}
class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
getTypeName() { return 'ResourceURL'; }
}

View File

@ -0,0 +1,292 @@
/**
* @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
*/
import {isDevMode} from '@angular/core';
import {DomAdapter, getDOM} from '../dom/dom_adapter';
import {DOCUMENT} from '../dom/dom_tokens';
import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer';
/** 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.
const templateEl = DOM.createElement('template');
if ('content' in templateEl) return templateEl;
const doc = DOM.createHtmlDocument();
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.
const html = DOM.createElement('html', doc);
inertElement = DOM.createElement('body', doc);
DOM.appendChild(html, inertElement);
DOM.appendChild(doc, html);
}
return inertElement;
}
function tagSet(tags: string): {[k: string]: boolean} {
const res: {[k: string]: boolean} = {};
for (const t of tags.split(',')) res[t] = true;
return res;
}
function merge(...sets: {[k: string]: boolean}[]): {[k: string]: boolean} {
const res: {[k: string]: boolean} = {};
for (const s of sets) {
for (const v in s) {
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,' +
'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'));
// Inline Elements - HTML5
const INLINE_ELEMENTS = merge(
OPTIONAL_END_TAG_INLINE_ELEMENTS,
tagSet(
'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'));
const VALID_ELEMENTS =
merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS);
// Attributes that have href and hence need to be sanitized
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');
const HTML_ATTRS = tagSet(
'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,' +
'valign,value,vspace,width');
// 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.
const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS);
/**
* SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe
* attributes.
*/
class SanitizingHtmlSerializer {
// Explicitly track if something was stripped, to avoid accidentally warning of sanitization just
// because characters were re-encoded.
public sanitizedSomething = false;
private buf: string[] = [];
sanitizeChildren(el: Element): string {
// 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.
let current: Node = el.firstChild;
while (current) {
if (DOM.isElementNode(current)) {
this.startElement(current as Element);
} else if (DOM.isTextNode(current)) {
this.chars(DOM.nodeValue(current));
} else {
// Strip non-element, non-text nodes.
this.sanitizedSomething = true;
}
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)) {
this.endElement(current as Element);
}
if (DOM.nextSibling(current)) {
current = DOM.nextSibling(current);
break;
}
current = DOM.parentElement(current);
}
}
return this.buf.join('');
}
private startElement(element: Element) {
const tagName = DOM.nodeName(element).toLowerCase();
if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
this.sanitizedSomething = true;
return;
}
this.buf.push('<');
this.buf.push(tagName);
DOM.attributeMap(element).forEach((value: string, attrName: string) => {
const lower = attrName.toLowerCase();
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('>');
}
private endElement(current: Element) {
const tagName = DOM.nodeName(current).toLowerCase();
if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) {
this.buf.push('</');
this.buf.push(tagName);
this.buf.push('>');
}
}
private chars(chars: any /** TODO #9100 */) { this.buf.push(encodeEntities(chars)); }
}
// 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
*/
function encodeEntities(value: string) {
return value.replace(/&/g, '&amp;')
.replace(
SURROGATE_PAIR_REGEXP,
function(match: string) {
const hi = match.charCodeAt(0);
const low = match.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
})
.replace(
NON_ALPHANUMERIC_REGEXP,
function(match: string) { return '&#' + match.charCodeAt(0) + ';'; })
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
/**
* 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.
*/
function stripCustomNsAttrs(el: Element) {
DOM.attributeMap(el).forEach((_, attrName) => {
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
DOM.removeAttribute(el, attrName);
}
});
for (const n of DOM.childNodesAsList(el)) {
if (DOM.isElementNode(n)) stripCustomNsAttrs(n as Element);
}
}
/**
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
* the DOM in a browser environment.
*/
export function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
try {
const containerEl = getInertElement();
// Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : '';
// 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);
if (defaultDoc.documentMode) {
// strip custom-namespaced attributes on IE<=11
stripCustomNsAttrs(containerEl);
}
parsedHtml = DOM.getInnerHTML(containerEl);
} while (unsafeHtml !== parsedHtml);
const sanitizer = new SanitizingHtmlSerializer();
const safeHtml = sanitizer.sanitizeChildren(DOM.getTemplateContent(containerEl) || containerEl);
// Clear out the body element.
const parent = DOM.getTemplateContent(containerEl) || containerEl;
for (const child of DOM.childNodesAsList(parent)) {
DOM.removeChild(parent, child);
}
if (isDevMode() && sanitizer.sanitizedSomething) {
DOM.log('WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).');
}
return safeHtml;
} catch (e) {
// In case anything goes wrong, clear out inertElement to reset the entire DOM structure.
inertElement = null;
throw e;
}
}

View File

@ -0,0 +1,106 @@
/**
* @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
*/
import {isDevMode} from '@angular/core';
import {getDOM} from '../dom/dom_adapter';
import {sanitizeUrl} from './url_sanitizer';
/**
* Regular expression for safe style values.
*
* Quotes (" and ') are allowed, but a check must be done elsewhere to ensure they're balanced.
*
* ',' allows multiple values to be assigned to the same property (e.g. background-attachment or
* font-family) and hence could allow multiple values to get injected, but that should pose no risk
* of XSS.
*
* The function expression checks only for XSS safety, not for CSS validity.
*
* This regular expression was taken from the Closure sanitization library, and augmented for
* transformation values.
*/
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?';
const COLOR_FNS = '(?:rgb|hsl)a?';
const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
const CSS3_FNS = '(?:calc|attr)';
const FN_ARGS = '\\([-0-9.%, #a-zA-Z]+\\)';
const SAFE_STYLE_VALUE = new RegExp(
`^(${VALUES}|` +
`(?:${TRANSFORMATION_FNS}|${COLOR_FNS}|${GRADIENTS}|${CSS3_FNS})` +
`${FN_ARGS})$`,
'g');
/**
* Matches a `url(...)` value with an arbitrary argument as long as it does
* not contain parentheses.
*
* The URL value still needs to be sanitized separately.
*
* `url(...)` values are a very common use case, e.g. for `background-image`. With carefully crafted
* CSS style rules, it is possible to construct an information leak with `url` values in CSS, e.g.
* by observing whether scroll bars are displayed, or character ranges used by a font face
* definition.
*
* Angular only allows binding CSS values (as opposed to entire CSS rules), so it is unlikely that
* binding a URL value without further cooperation from the page will cause an information leak, and
* if so, it is just a leak, not a full blown XSS vulnerability.
*
* Given the common use case, low likelihood of attack vector, and low impact of an attack, this
* code is permissive and allows URLs that sanitize otherwise.
*/
const URL_RE = /^url\(([^)]+)\)$/;
/**
* Checks that quotes (" and ') are properly balanced inside a string. Assumes
* that neither escape (\) nor any other character that could result in
* breaking out of a string parsing context are allowed;
* see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
*
* This code was taken from the Closure sanitization library.
*/
function hasBalancedQuotes(value: string) {
let outsideSingle = true;
let outsideDouble = true;
for (let i = 0; i < value.length; i++) {
const c = value.charAt(i);
if (c === '\'' && outsideDouble) {
outsideSingle = !outsideSingle;
} else if (c === '"' && outsideSingle) {
outsideDouble = !outsideDouble;
}
}
return outsideSingle && outsideDouble;
}
/**
* Sanitizes the given untrusted CSS style property value (i.e. not an entire object, just a single
* value) and returns a value that is safe to use in a browser environment.
*/
export function sanitizeStyle(value: string): string {
value = String(value).trim(); // Make sure it's actually a string.
if (!value) return '';
// Single url(...) values are supported, but only for URLs that sanitize cleanly. See above for
// reasoning behind this.
const urlMatch = value.match(URL_RE);
if ((urlMatch && sanitizeUrl(urlMatch[1]) === urlMatch[1]) ||
value.match(SAFE_STYLE_VALUE) && hasBalancedQuotes(value)) {
return value; // Safe style values.
}
if (isDevMode()) {
getDOM().log(
`WARNING: sanitizing unsafe style value ${value} (see http://g.co/ng/security#xss).`);
}
return 'unsafe';
}

View File

@ -0,0 +1,63 @@
/**
* @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
*/
import {isDevMode} from '@angular/core';
import {getDOM} from '../dom/dom_adapter';
/**
* A pattern that recognizes a commonly useful subset of URLs that are safe.
*
* This regular expression matches a subset of URLs that will not cause script
* execution if used in URL context within a HTML document. Specifically, this
* regular expression matches if (comment from here on and regex copied from
* Soy's EscapingConventions):
* (1) Either a protocol in a whitelist (http, https, mailto or ftp).
* (2) or no protocol. A protocol must be followed by a colon. The below
* allows that by allowing colons only after one of the characters [/?#].
* A colon after a hash (#) must be in the fragment.
* Otherwise, a colon after a (?) must be in a query.
* Otherwise, a colon after a single solidus (/) must be in a path.
* Otherwise, a colon after a double solidus (//) must be in the authority
* (before port).
*
* The pattern disallows &, used in HTML entity declarations before
* one of the characters in [/?#]. This disallows HTML entities used in the
* protocol name, which should never happen, e.g. "h&#116;tp" for "http".
* It also disallows HTML entities in the first path part of a relative path,
* e.g. "foo&lt;bar/baz". Our existing escaping functions should not produce
* that. More importantly, it disallows masking of a colon,
* e.g. "javascript&#58;...".
*
* This regular expression was taken from the Closure sanitization library.
*/
const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi;
/* A pattern that matches safe srcset values */
const SAFE_SRCSET_PATTERN = /^(?:(?:https?|file):|[^&:/?#]*(?:[/?#]|$))/gi;
/** A pattern that matches safe data URLs. Only matches image, video and audio types. */
const DATA_URL_PATTERN =
/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+\/]+=*$/i;
export function sanitizeUrl(url: string): string {
url = String(url);
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url;
if (isDevMode()) {
getDOM().log(`WARNING: sanitizing unsafe URL value ${url} (see http://g.co/ng/security#xss)`);
}
return 'unsafe:' + url;
}
export function sanitizeSrcset(srcset: string): string {
srcset = String(srcset);
return srcset.split(',').map((srcset) => sanitizeUrl(srcset.trim())).join(', ');
}