2011-07-17 08:05:43 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
2013-08-09 18:14:37 +00:00
|
|
|
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
2013-07-08 12:23:45 +00:00
|
|
|
|
2012-04-10 23:50:31 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc overview
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ngSanitize
|
2012-04-10 23:50:31 +00:00
|
|
|
* @description
|
2013-05-29 13:44:36 +00:00
|
|
|
*
|
2013-08-22 19:32:42 +00:00
|
|
|
* # ngSanitize
|
|
|
|
|
*
|
|
|
|
|
* The `ngSanitize` module provides functionality to sanitize HTML.
|
2013-05-29 13:44:36 +00:00
|
|
|
*
|
2013-08-22 19:32:42 +00:00
|
|
|
* {@installModule sanitize}
|
2013-05-29 13:44:36 +00:00
|
|
|
*
|
2013-10-17 02:48:32 +00:00
|
|
|
* <div doc-module-components="ngSanitize"></div>
|
|
|
|
|
*
|
2013-08-22 19:32:42 +00:00
|
|
|
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
2012-04-10 23:50:31 +00:00
|
|
|
*/
|
|
|
|
|
|
2010-10-19 04:20:47 +00:00
|
|
|
/*
|
|
|
|
|
* HTML Parser By Misko Hevery (misko@hevery.com)
|
|
|
|
|
* based on: HTML Parser By John Resig (ejohn.org)
|
|
|
|
|
* Original code by Erik Arvidsson, Mozilla Public License
|
|
|
|
|
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
|
|
|
*
|
|
|
|
|
* // Use like so:
|
|
|
|
|
* htmlParser(htmlString, {
|
|
|
|
|
* start: function(tag, attrs, unary) {},
|
|
|
|
|
* end: function(tag) {},
|
|
|
|
|
* chars: function(text) {},
|
|
|
|
|
* comment: function(text) {}
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
2011-11-21 22:09:52 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc service
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ngSanitize.$sanitize
|
2011-11-21 22:09:52 +00:00
|
|
|
* @function
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
|
|
|
|
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
|
|
|
|
* it into the returned string, however, since our parser is more strict than a typical browser
|
|
|
|
|
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
|
|
|
|
* browser, won't make it through the sanitizer.
|
2013-11-25 23:40:18 +00:00
|
|
|
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
|
|
|
|
|
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
|
2011-11-21 22:09:52 +00:00
|
|
|
*
|
|
|
|
|
* @param {string} html Html input.
|
|
|
|
|
* @returns {string} Sanitized html.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
2012-04-10 23:50:31 +00:00
|
|
|
<doc:example module="ngSanitize">
|
2013-10-22 21:41:21 +00:00
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope, $sce) {
|
|
|
|
|
$scope.snippet =
|
|
|
|
|
'<p style="color:blue">an html\n' +
|
|
|
|
|
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
|
|
|
|
'snippet</p>';
|
|
|
|
|
$scope.deliberatelyTrustDangerousSnippet = function() {
|
|
|
|
|
return $sce.trustAsHtml($scope.snippet);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
<div ng-controller="Ctrl">
|
|
|
|
|
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
|
|
|
|
<table>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>Directive</td>
|
|
|
|
|
<td>How</td>
|
|
|
|
|
<td>Source</td>
|
|
|
|
|
<td>Rendered</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr id="bind-html-with-sanitize">
|
|
|
|
|
<td>ng-bind-html</td>
|
|
|
|
|
<td>Automatically uses $sanitize</td>
|
|
|
|
|
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
|
|
|
|
<td><div ng-bind-html="snippet"></div></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr id="bind-html-with-trust">
|
|
|
|
|
<td>ng-bind-html</td>
|
|
|
|
|
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
|
|
|
|
|
<td>
|
|
|
|
|
<pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()">
|
|
|
|
|
</div></pre>
|
|
|
|
|
</td>
|
|
|
|
|
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr id="bind-default">
|
|
|
|
|
<td>ng-bind</td>
|
|
|
|
|
<td>Automatically escapes</td>
|
|
|
|
|
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
|
|
|
|
<td><div ng-bind="snippet"></div></td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should sanitize the html snippet by default', function() {
|
|
|
|
|
expect(using('#bind-html-with-sanitize').element('div').html()).
|
|
|
|
|
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should inline raw snippet if bound to a trusted value', function() {
|
|
|
|
|
expect(using('#bind-html-with-trust').element("div").html()).
|
|
|
|
|
toBe("<p style=\"color:blue\">an html\n" +
|
|
|
|
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
|
|
|
"snippet</p>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should escape snippet without any filter', function() {
|
|
|
|
|
expect(using('#bind-default').element('div').html()).
|
|
|
|
|
toBe("<p style=\"color:blue\">an html\n" +
|
|
|
|
|
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
|
|
|
|
"snippet</p>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should update', function() {
|
|
|
|
|
input('snippet').enter('new <b onclick="alert(1)">text</b>');
|
|
|
|
|
expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
|
|
|
|
|
expect(using('#bind-html-with-trust').element('div').html()).toBe(
|
|
|
|
|
'new <b onclick="alert(1)">text</b>');
|
|
|
|
|
expect(using('#bind-default').element('div').html()).toBe(
|
|
|
|
|
"new <b onclick=\"alert(1)\">text</b>");
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
2011-11-21 22:09:52 +00:00
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2013-11-25 23:40:18 +00:00
|
|
|
function $SanitizeProvider() {
|
|
|
|
|
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
|
|
|
|
return function(html) {
|
|
|
|
|
var buf = [];
|
|
|
|
|
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
|
|
|
|
|
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
|
|
|
|
|
}));
|
|
|
|
|
return buf.join('');
|
|
|
|
|
};
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sanitizeText(chars) {
|
2012-04-10 23:50:31 +00:00
|
|
|
var buf = [];
|
2013-11-25 23:40:18 +00:00
|
|
|
var writer = htmlSanitizeWriter(buf, angular.noop);
|
|
|
|
|
writer.chars(chars);
|
|
|
|
|
return buf.join('');
|
|
|
|
|
}
|
2012-04-10 23:50:31 +00:00
|
|
|
|
2011-11-21 22:09:52 +00:00
|
|
|
|
2010-10-19 04:20:47 +00:00
|
|
|
// Regular Expressions for parsing tags and attributes
|
2013-10-22 21:41:21 +00:00
|
|
|
var START_TAG_REGEXP =
|
|
|
|
|
/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
2011-02-03 20:25:51 +00:00
|
|
|
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
|
|
|
|
|
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
2010-10-19 04:20:47 +00:00
|
|
|
BEGIN_TAG_REGEXP = /^</,
|
|
|
|
|
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
|
|
|
|
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
2013-10-02 19:49:20 +00:00
|
|
|
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
|
2010-11-24 04:10:05 +00:00
|
|
|
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
2013-10-22 21:41:21 +00:00
|
|
|
// Match everything outside of normal chars and " (quote character)
|
|
|
|
|
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2011-10-20 16:43:00 +00:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
var voidElements = makeMap("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
|
|
|
|
|
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
|
|
|
|
|
optionalEndTagInlineElements = makeMap("rp,rt"),
|
2013-10-22 21:41:21 +00:00
|
|
|
optionalEndTagElements = angular.extend({},
|
|
|
|
|
optionalEndTagInlineElements,
|
|
|
|
|
optionalEndTagBlockElements);
|
2011-10-20 16:43:00 +00:00
|
|
|
|
|
|
|
|
// Safe Block Elements - HTML5
|
2013-10-22 21:41:21 +00:00
|
|
|
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
|
|
|
|
|
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
|
|
|
|
|
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
2011-10-20 16:43:00 +00:00
|
|
|
|
|
|
|
|
// Inline Elements - HTML5
|
2013-10-22 21:41:21 +00:00
|
|
|
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
|
|
|
|
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
|
|
|
|
|
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
|
2011-10-20 16:43:00 +00:00
|
|
|
|
|
|
|
|
|
2010-10-19 04:20:47 +00:00
|
|
|
// Special Elements (can contain anything)
|
|
|
|
|
var specialElements = makeMap("script,style");
|
2011-10-20 16:43:00 +00:00
|
|
|
|
2013-10-22 21:41:21 +00:00
|
|
|
var validElements = angular.extend({},
|
|
|
|
|
voidElements,
|
|
|
|
|
blockElements,
|
|
|
|
|
inlineElements,
|
|
|
|
|
optionalEndTagElements);
|
2010-11-24 04:10:05 +00:00
|
|
|
|
|
|
|
|
//Attributes that have href and hence need to be sanitized
|
2011-10-20 16:43:00 +00:00
|
|
|
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
|
2012-04-10 23:50:31 +00:00
|
|
|
var validAttrs = angular.extend({}, uriAttrs, makeMap(
|
2010-11-24 04:10:05 +00:00
|
|
|
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
2011-02-04 00:35:51 +00:00
|
|
|
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
|
|
|
|
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
2010-12-03 23:42:11 +00:00
|
|
|
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
2010-11-24 04:10:05 +00:00
|
|
|
'valign,value,vspace,width'));
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2012-04-10 23:50:31 +00:00
|
|
|
function makeMap(str) {
|
|
|
|
|
var obj = {}, items = str.split(','), i;
|
|
|
|
|
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-10-19 04:20:47 +00:00
|
|
|
/**
|
|
|
|
|
* @example
|
|
|
|
|
* htmlParser(htmlString, {
|
|
|
|
|
* start: function(tag, attrs, unary) {},
|
|
|
|
|
* end: function(tag) {},
|
|
|
|
|
* chars: function(text) {},
|
|
|
|
|
* comment: function(text) {}
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* @param {string} html string
|
|
|
|
|
* @param {object} handler
|
|
|
|
|
*/
|
2010-11-24 04:10:05 +00:00
|
|
|
function htmlParser( html, handler ) {
|
2010-10-19 04:20:47 +00:00
|
|
|
var index, chars, match, stack = [], last = html;
|
2011-10-07 18:27:49 +00:00
|
|
|
stack.last = function() { return stack[ stack.length - 1 ]; };
|
2010-10-19 04:20:47 +00:00
|
|
|
|
|
|
|
|
while ( html ) {
|
|
|
|
|
chars = true;
|
|
|
|
|
|
|
|
|
|
// Make sure we're not in a script or style element
|
|
|
|
|
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
|
|
|
|
|
|
|
|
|
// Comment
|
|
|
|
|
if ( html.indexOf("<!--") === 0 ) {
|
2013-08-17 23:09:28 +00:00
|
|
|
// comments containing -- are not allowed unless they terminate the comment
|
|
|
|
|
index = html.indexOf("--", 4);
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-08-17 23:09:28 +00:00
|
|
|
if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
|
2011-02-04 00:35:51 +00:00
|
|
|
if (handler.comment) handler.comment( html.substring( 4, index ) );
|
2010-10-19 04:20:47 +00:00
|
|
|
html = html.substring( index + 3 );
|
|
|
|
|
chars = false;
|
|
|
|
|
}
|
2013-10-02 19:49:20 +00:00
|
|
|
// DOCTYPE
|
|
|
|
|
} else if ( DOCTYPE_REGEXP.test(html) ) {
|
|
|
|
|
match = html.match( DOCTYPE_REGEXP );
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-10-02 19:49:20 +00:00
|
|
|
if ( match ) {
|
|
|
|
|
html = html.replace( match[0] , '');
|
|
|
|
|
chars = false;
|
|
|
|
|
}
|
2010-10-19 04:20:47 +00:00
|
|
|
// end tag
|
|
|
|
|
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
|
|
|
|
match = html.match( END_TAG_REGEXP );
|
|
|
|
|
|
|
|
|
|
if ( match ) {
|
|
|
|
|
html = html.substring( match[0].length );
|
|
|
|
|
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
|
|
|
|
chars = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start tag
|
|
|
|
|
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
|
|
|
|
match = html.match( START_TAG_REGEXP );
|
|
|
|
|
|
|
|
|
|
if ( match ) {
|
|
|
|
|
html = html.substring( match[0].length );
|
|
|
|
|
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
|
|
|
|
chars = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( chars ) {
|
|
|
|
|
index = html.indexOf("<");
|
|
|
|
|
|
|
|
|
|
var text = index < 0 ? html : html.substring( 0, index );
|
|
|
|
|
html = index < 0 ? "" : html.substring( index );
|
|
|
|
|
|
2011-02-04 00:35:51 +00:00
|
|
|
if (handler.chars) handler.chars( decodeEntities(text) );
|
2010-10-19 04:20:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
2013-10-22 21:41:21 +00:00
|
|
|
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
|
|
|
|
function(all, text){
|
|
|
|
|
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-10-22 21:41:21 +00:00
|
|
|
if (handler.chars) handler.chars( decodeEntities(text) );
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-10-22 21:41:21 +00:00
|
|
|
return "";
|
2010-10-19 04:20:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
parseEndTag( "", stack.last() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( html == last ) {
|
2013-10-22 21:41:21 +00:00
|
|
|
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
|
|
|
|
"of html: {0}", html);
|
2010-10-19 04:20:47 +00:00
|
|
|
}
|
|
|
|
|
last = html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up any remaining tags
|
|
|
|
|
parseEndTag();
|
|
|
|
|
|
|
|
|
|
function parseStartTag( tag, tagName, rest, unary ) {
|
2012-04-10 23:50:31 +00:00
|
|
|
tagName = angular.lowercase(tagName);
|
2010-10-19 04:20:47 +00:00
|
|
|
if ( blockElements[ tagName ] ) {
|
|
|
|
|
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
|
|
|
|
parseEndTag( "", stack.last() );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-20 16:43:00 +00:00
|
|
|
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
2010-10-19 04:20:47 +00:00
|
|
|
parseEndTag( "", tagName );
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-20 16:43:00 +00:00
|
|
|
unary = voidElements[ tagName ] || !!unary;
|
2010-10-19 04:20:47 +00:00
|
|
|
|
|
|
|
|
if ( !unary )
|
|
|
|
|
stack.push( tagName );
|
|
|
|
|
|
2010-11-24 04:10:05 +00:00
|
|
|
var attrs = {};
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-10-22 21:41:21 +00:00
|
|
|
rest.replace(ATTR_REGEXP,
|
|
|
|
|
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
|
|
|
|
var value = doubleQuotedValue
|
|
|
|
|
|| singleQuotedValue
|
|
|
|
|
|| unquotedValue
|
|
|
|
|
|| '';
|
2010-10-19 04:20:47 +00:00
|
|
|
|
2013-10-22 21:41:21 +00:00
|
|
|
attrs[name] = decodeEntities(value);
|
2010-11-24 04:10:05 +00:00
|
|
|
});
|
2011-02-04 00:35:51 +00:00
|
|
|
if (handler.start) handler.start( tagName, attrs, unary );
|
2010-10-19 04:20:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseEndTag( tag, tagName ) {
|
|
|
|
|
var pos = 0, i;
|
2012-04-10 23:50:31 +00:00
|
|
|
tagName = angular.lowercase(tagName);
|
2010-10-19 04:20:47 +00:00
|
|
|
if ( tagName )
|
|
|
|
|
// Find the closest opened tag of the same type
|
|
|
|
|
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
|
|
|
|
if ( stack[ pos ] == tagName )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if ( pos >= 0 ) {
|
|
|
|
|
// Close all the open elements, up the stack
|
|
|
|
|
for ( i = stack.length - 1; i >= pos; i-- )
|
2011-02-04 00:35:51 +00:00
|
|
|
if (handler.end) handler.end( stack[ i ] );
|
2010-10-19 04:20:47 +00:00
|
|
|
|
|
|
|
|
// Remove the open elements from the stack
|
|
|
|
|
stack.length = pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-12-06 17:56:49 +00:00
|
|
|
}
|
2010-10-19 04:20:47 +00:00
|
|
|
|
fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.
In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.
The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.
Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.
There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.
Closes #5193
Closes #5192
2013-12-03 10:39:09 +00:00
|
|
|
var hiddenPre=document.createElement("pre");
|
|
|
|
|
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
2010-11-24 04:10:05 +00:00
|
|
|
/**
|
|
|
|
|
* decodes all entities into regular string
|
|
|
|
|
* @param value
|
2010-12-07 04:48:47 +00:00
|
|
|
* @returns {string} A string with decoded entities.
|
2010-10-19 04:20:47 +00:00
|
|
|
*/
|
2010-11-24 04:10:05 +00:00
|
|
|
function decodeEntities(value) {
|
fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.
In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.
The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.
Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.
There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.
Closes #5193
Closes #5192
2013-12-03 10:39:09 +00:00
|
|
|
if (!value) { return ''; }
|
|
|
|
|
|
2013-11-25 23:40:18 +00:00
|
|
|
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.
In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.
The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.
Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.
There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.
Closes #5193
Closes #5192
2013-12-03 10:39:09 +00:00
|
|
|
// so we must capture them and reattach them afterward
|
2013-11-25 23:40:18 +00:00
|
|
|
var parts = spaceRe.exec(value);
|
fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.
In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.
The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.
Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.
There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.
Closes #5193
Closes #5192
2013-12-03 10:39:09 +00:00
|
|
|
var spaceBefore = parts[1];
|
|
|
|
|
var spaceAfter = parts[3];
|
|
|
|
|
var content = parts[2];
|
|
|
|
|
if (content) {
|
|
|
|
|
hiddenPre.innerHTML=content.replace(/</g,"<");
|
2013-11-24 20:13:51 +00:00
|
|
|
// innerText depends on styling as it doesn't display hidden elements.
|
|
|
|
|
// Therefore, it's better to use textContent not to cause unnecessary
|
|
|
|
|
// reflows. However, IE<9 don't support textContent so the innerText
|
|
|
|
|
// fallback is necessary.
|
|
|
|
|
content = 'textContent' in hiddenPre ?
|
|
|
|
|
hiddenPre.textContent : hiddenPre.innerText;
|
2013-11-25 23:40:18 +00:00
|
|
|
}
|
fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.
In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.
The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.
Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.
There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.
Closes #5193
Closes #5192
2013-12-03 10:39:09 +00:00
|
|
|
return spaceBefore + content + spaceAfter;
|
2010-11-24 04:10:05 +00:00
|
|
|
}
|
|
|
|
|
|
2010-10-19 04:20:47 +00:00
|
|
|
/**
|
2011-01-19 23:42:11 +00:00
|
|
|
* Escapes all potentially dangerous characters, so that the
|
2010-11-24 04:10:05 +00:00
|
|
|
* resulting string can be safely inserted into attribute or
|
|
|
|
|
* element text.
|
|
|
|
|
* @param value
|
|
|
|
|
* @returns escaped text
|
2010-10-19 04:20:47 +00:00
|
|
|
*/
|
2010-11-24 04:10:05 +00:00
|
|
|
function encodeEntities(value) {
|
|
|
|
|
return value.
|
|
|
|
|
replace(/&/g, '&').
|
|
|
|
|
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
|
|
|
|
return '&#' + value.charCodeAt(0) + ';';
|
|
|
|
|
}).
|
|
|
|
|
replace(/</g, '<').
|
|
|
|
|
replace(/>/g, '>');
|
2010-10-19 04:20:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* create an HTML/XML writer which writes to buffer
|
|
|
|
|
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
2010-10-27 04:06:24 +00:00
|
|
|
* @returns {object} in the form of {
|
2010-10-19 04:20:47 +00:00
|
|
|
* start: function(tag, attrs, unary) {},
|
|
|
|
|
* end: function(tag) {},
|
|
|
|
|
* chars: function(text) {},
|
|
|
|
|
* comment: function(text) {}
|
|
|
|
|
* }
|
|
|
|
|
*/
|
2013-11-25 23:40:18 +00:00
|
|
|
function htmlSanitizeWriter(buf, uriValidator){
|
2010-10-19 04:20:47 +00:00
|
|
|
var ignore = false;
|
2012-04-10 23:50:31 +00:00
|
|
|
var out = angular.bind(buf, buf.push);
|
2010-10-19 04:20:47 +00:00
|
|
|
return {
|
|
|
|
|
start: function(tag, attrs, unary){
|
2012-04-10 23:50:31 +00:00
|
|
|
tag = angular.lowercase(tag);
|
2010-10-19 04:20:47 +00:00
|
|
|
if (!ignore && specialElements[tag]) {
|
|
|
|
|
ignore = tag;
|
|
|
|
|
}
|
2013-10-22 21:41:21 +00:00
|
|
|
if (!ignore && validElements[tag] === true) {
|
2010-10-19 04:20:47 +00:00
|
|
|
out('<');
|
|
|
|
|
out(tag);
|
2012-04-10 23:50:31 +00:00
|
|
|
angular.forEach(attrs, function(value, key){
|
|
|
|
|
var lkey=angular.lowercase(key);
|
2013-11-25 23:40:18 +00:00
|
|
|
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
|
|
|
|
if (validAttrs[lkey] === true &&
|
|
|
|
|
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
2010-10-19 04:20:47 +00:00
|
|
|
out(' ');
|
|
|
|
|
out(key);
|
|
|
|
|
out('="');
|
2010-11-24 04:10:05 +00:00
|
|
|
out(encodeEntities(value));
|
2010-10-19 04:20:47 +00:00
|
|
|
out('"');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
out(unary ? '/>' : '>');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
end: function(tag){
|
2012-04-10 23:50:31 +00:00
|
|
|
tag = angular.lowercase(tag);
|
2013-10-22 21:41:21 +00:00
|
|
|
if (!ignore && validElements[tag] === true) {
|
2010-10-19 04:20:47 +00:00
|
|
|
out('</');
|
|
|
|
|
out(tag);
|
|
|
|
|
out('>');
|
|
|
|
|
}
|
|
|
|
|
if (tag == ignore) {
|
|
|
|
|
ignore = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
chars: function(chars){
|
|
|
|
|
if (!ignore) {
|
2010-11-24 04:10:05 +00:00
|
|
|
out(encodeEntities(chars));
|
2010-10-19 04:20:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2012-04-10 23:50:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// define ngSanitize module and register $sanitize service
|
2013-11-25 23:40:18 +00:00
|
|
|
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|