aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Bosch2013-11-25 15:40:18 -0800
committerTobias Bosch2013-11-26 14:29:38 -0800
commit333523483f3ce6dd3177b697a5e5a7177ca364c8 (patch)
tree34cd700540680a7d9276cd1fe231b6f72fd232ae
parent68ceb17272bdd2ebc838565070973d93704f4427 (diff)
downloadangular.js-333523483f3ce6dd3177b697a5e5a7177ca364c8.tar.bz2
fix($sanitize): Use same whitelist mechanism as $compile does.
`$sanitize` now uses the same mechanism as `$compile` to validate uris. By this, the validation in `$sanitize` is more general and can be configured in the same way as the one in `$compile`. Changes - Creates the new private service `$$sanitizeUri`. - Moves related specs from `compileSpec.js` into `sanitizeUriSpec.js`. - Refactors the `linky` filter to be less dependent on `$sanitize` internal functions. Fixes #3748.
-rwxr-xr-xangularFiles.js1
-rw-r--r--src/AngularPublic.js5
-rw-r--r--src/ng/compile.js34
-rw-r--r--src/ng/sanitizeUri.js74
-rw-r--r--src/ngSanitize/filter/linky.js44
-rw-r--r--src/ngSanitize/sanitize.js46
-rwxr-xr-xtest/ng/compileSpec.js296
-rw-r--r--test/ng/sanitizeUriSpec.js230
-rw-r--r--test/ngSanitize/sanitizeSpec.js159
9 files changed, 550 insertions, 339 deletions
diff --git a/angularFiles.js b/angularFiles.js
index 9aa2ef7e..ad968341 100755
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -27,6 +27,7 @@ angularFiles = {
'src/ng/parse.js',
'src/ng/q.js',
'src/ng/rootScope.js',
+ 'src/ng/sanitizeUri.js',
'src/ng/sce.js',
'src/ng/sniffer.js',
'src/ng/timeout.js',
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index eb97b4c5..d2c325c5 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -65,6 +65,7 @@
$ParseProvider,
$RootScopeProvider,
$QProvider,
+ $$SanitizeUriProvider,
$SceProvider,
$SceDelegateProvider,
$SnifferProvider,
@@ -136,6 +137,10 @@ function publishExternalAPI(angular){
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
+ // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
+ $provide.provider({
+ $$sanitizeUri: $$SanitizeUriProvider
+ });
$provide.provider('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 13fb9682..54d2dc9f 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -493,14 +493,12 @@ var $compileMinErr = minErr('$compile');
*
* @description
*/
-$CompileProvider.$inject = ['$provide'];
-function $CompileProvider($provide) {
+$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
+function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
- CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
- aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
- imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -584,10 +582,11 @@ function $CompileProvider($provide) {
*/
this.aHrefSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
- aHrefSanitizationWhitelist = regexp;
+ $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
return this;
+ } else {
+ return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
}
- return aHrefSanitizationWhitelist;
};
@@ -614,18 +613,18 @@ function $CompileProvider($provide) {
*/
this.imgSrcSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
- imgSrcSanitizationWhitelist = regexp;
+ $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
return this;
+ } else {
+ return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
}
- return imgSrcSanitizationWhitelist;
};
-
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
- '$controller', '$rootScope', '$document', '$sce', '$animate',
+ '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
- $controller, $rootScope, $document, $sce, $animate) {
+ $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -730,16 +729,7 @@ function $CompileProvider($provide) {
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
- // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
- if (!msie || msie >= 8 ) {
- normalizedVal = urlResolve(value).href;
- if (normalizedVal !== '') {
- if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
- (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
- this[key] = value = 'unsafe:' + normalizedVal;
- }
- }
- }
+ this[key] = value = $$sanitizeUri(value, key === 'src');
}
if (writeAttr !== false) {
diff --git a/src/ng/sanitizeUri.js b/src/ng/sanitizeUri.js
new file mode 100644
index 00000000..97325094
--- /dev/null
+++ b/src/ng/sanitizeUri.js
@@ -0,0 +1,74 @@
+'use strict';
+
+/**
+ * @description
+ * Private service to sanitize uris for links and images. Used by $compile and $sanitize.
+ */
+function $$SanitizeUriProvider() {
+ var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
+ imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
+
+ /**
+ * @description
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
+ * urls during a[href] sanitization.
+ *
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ *
+ * Any url about to be assigned to a[href] via data-binding is first normalized and turned into
+ * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
+ *
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
+ * chaining otherwise.
+ */
+ this.aHrefSanitizationWhitelist = function(regexp) {
+ if (isDefined(regexp)) {
+ aHrefSanitizationWhitelist = regexp;
+ return this;
+ }
+ return aHrefSanitizationWhitelist;
+ };
+
+
+ /**
+ * @description
+ * Retrieves or overrides the default regular expression that is used for whitelisting of safe
+ * urls during img[src] sanitization.
+ *
+ * The sanitization is a security measure aimed at prevent XSS attacks via html links.
+ *
+ * Any url about to be assigned to img[src] via data-binding is first normalized and turned into
+ * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
+ * regular expression. If a match is found, the original url is written into the dom. Otherwise,
+ * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
+ *
+ * @param {RegExp=} regexp New regexp to whitelist urls with.
+ * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
+ * chaining otherwise.
+ */
+ this.imgSrcSanitizationWhitelist = function(regexp) {
+ if (isDefined(regexp)) {
+ imgSrcSanitizationWhitelist = regexp;
+ return this;
+ }
+ return imgSrcSanitizationWhitelist;
+ };
+
+ this.$get = function() {
+ return function sanitizeUri(uri, isImage) {
+ var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
+ var normalizedVal;
+ // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
+ if (!msie || msie >= 8 ) {
+ normalizedVal = urlResolve(uri).href;
+ if (normalizedVal !== '' && !normalizedVal.match(regex)) {
+ return 'unsafe:'+normalizedVal;
+ }
+ }
+ return uri;
+ };
+ };
+}
diff --git a/src/ngSanitize/filter/linky.js b/src/ngSanitize/filter/linky.js
index 39494f2d..2c05d84e 100644
--- a/src/ngSanitize/filter/linky.js
+++ b/src/ngSanitize/filter/linky.js
@@ -1,6 +1,6 @@
'use strict';
-/* global htmlSanitizeWriter: false */
+/* global sanitizeText: false */
/**
* @ngdoc filter
@@ -100,7 +100,7 @@
</doc:scenario>
</doc:example>
*/
-angular.module('ngSanitize').filter('linky', function() {
+angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
MAILTO_REGEXP = /^mailto:/;
@@ -110,28 +110,40 @@ angular.module('ngSanitize').filter('linky', function() {
var match;
var raw = text;
var html = [];
- // TODO(vojta): use $sanitize instead
- var writer = htmlSanitizeWriter(html);
var url;
var i;
- var properties = {};
- if (angular.isDefined(target)) {
- properties.target = target;
- }
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
- writer.chars(raw.substr(0, i));
- properties.href = url;
- writer.start('a', properties);
- writer.chars(match[0].replace(MAILTO_REGEXP, ''));
- writer.end('a');
+ addText(raw.substr(0, i));
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
- writer.chars(raw);
- return html.join('');
+ addText(raw);
+ return $sanitize(html.join(''));
+
+ function addText(text) {
+ if (!text) {
+ return;
+ }
+ html.push(sanitizeText(text));
+ }
+
+ function addLink(url, text) {
+ html.push('<a ');
+ if (angular.isDefined(target)) {
+ html.push('target="');
+ html.push(target);
+ html.push('" ');
+ }
+ html.push('href="');
+ html.push(url);
+ html.push('">');
+ addText(text);
+ html.push('</a>');
+ }
};
-});
+}]);
diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js
index 7bd9aae3..5d378b02 100644
--- a/src/ngSanitize/sanitize.js
+++ b/src/ngSanitize/sanitize.js
@@ -46,6 +46,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* 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.
+ * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
+ * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
@@ -128,11 +130,24 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</doc:scenario>
</doc:example>
*/
-var $sanitize = function(html) {
+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) {
var buf = [];
- htmlParser(html, htmlSanitizeWriter(buf));
- return buf.join('');
-};
+ var writer = htmlSanitizeWriter(buf, angular.noop);
+ writer.chars(chars);
+ return buf.join('');
+}
// Regular Expressions for parsing tags and attributes
@@ -145,7 +160,6 @@ var START_TAG_REGEXP =
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
- URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
@@ -353,8 +367,18 @@ function htmlParser( html, handler ) {
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
- hiddenPre.innerHTML=value.replace(/</g,"&lt;");
- return hiddenPre.innerText || hiddenPre.textContent || '';
+ if (!value) {
+ return '';
+ }
+ // Note: IE8 does not preserve spaces at the start/end of innerHTML
+ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
+ var parts = spaceRe.exec(value);
+ parts[0] = '';
+ if (parts[2]) {
+ hiddenPre.innerHTML=parts[2].replace(/</g,"&lt;");
+ parts[2] = hiddenPre.innerText || hiddenPre.textContent;
+ }
+ return parts.join('');
}
/**
@@ -384,7 +408,7 @@ function encodeEntities(value) {
* comment: function(text) {}
* }
*/
-function htmlSanitizeWriter(buf){
+function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
@@ -398,7 +422,9 @@ function htmlSanitizeWriter(buf){
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
- if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
+ if (validAttrs[lkey] === true &&
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
@@ -430,4 +456,4 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service
-angular.module('ngSanitize', []).value('$sanitize', $sanitize);
+angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 80788b02..6de96f65 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -3834,6 +3834,7 @@ describe('$compile', function() {
describe('img[src] sanitization', function() {
+
it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = 'http://example.com/image.png';
@@ -3845,127 +3846,6 @@ describe('$compile', function() {
expect(element.attr('src')).toEqual('http://example.com/image2.png');
}));
- it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
- }));
-
- it('should sanitize non-image data: urls', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
- $rootScope.$apply();
- expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
- $rootScope.testUrl = "data:,foo";
- $rootScope.$apply();
- expect(element.attr('src')).toBe("unsafe:data:,foo");
- }));
-
-
- it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{dataUri}}"></img>')($rootScope);
-
- // image data uri
- // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
- $rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
- }));
-
-
- // Fails on IE <= 10 with "TypeError: Access is denied" when trying to set img[src]
- if (!msie || msie > 10) {
- it('should sanitize mailto: urls', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "mailto:foo@bar.com";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('unsafe:mailto:foo@bar.com');
- }));
- }
-
- it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
-
- // case-sensitive
- $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
-
- // tab in protocol
- $rootScope.testUrl = "java\u0009script:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
-
- // space before
- $rootScope.testUrl = " javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
-
- // ws chars before
- $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
-
- // post-fixed with proper url
- $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
- $rootScope.$apply();
- expect(element[0].src).toBeOneOf(
- 'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
- 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
- );
- }));
-
- it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
- element = $compile('<img ng-src="{{testUrl}}"></img>')($rootScope);
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
-
- expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
- }));
-
-
- it('should not sanitize valid urls', inject(function($compile, $rootScope) {
- element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
-
- $rootScope.testUrl = "foo/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('foo/bar');
-
- $rootScope.testUrl = "/foo/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('/foo/bar');
-
- $rootScope.testUrl = "../foo/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('../foo/bar');
-
- $rootScope.testUrl = "#foo";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('#foo');
-
- $rootScope.testUrl = "http://foo.com/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('http://foo.com/bar');
-
- $rootScope.testUrl = " http://foo.com/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe(' http://foo.com/bar');
-
- $rootScope.testUrl = "https://foo.com/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('https://foo.com/bar');
-
- $rootScope.testUrl = "ftp://foo.com/bar";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('ftp://foo.com/bar');
-
- $rootScope.testUrl = "file:///foo/bar.html";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('file:///foo/bar.html');
- }));
-
-
it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
element = $compile('<img title="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -3974,141 +3854,42 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
+ it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() {
+ module(function($compileProvider, $$sanitizeUriProvider) {
+ var newRe = /javascript:/,
+ returnVal;
+ expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist());
- it('should allow reconfiguration of the src whitelist', function() {
- module(function($compileProvider) {
- expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
- var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/);
+ returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
+ expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
+ expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
});
+ inject(function() {
+ // needed to the module definition above is run...
+ });
+ });
+ it('should use $$sanitizeUri', function() {
+ var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
+ module(function($provide) {
+ $provide.value('$$sanitizeUri', $$sanitizeUri);
+ });
inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
+ $rootScope.testUrl = "someUrl";
- // Fails on IE <= 11 with "TypeError: Object doesn't support this property or method" when
- // trying to set img[src]
- if (!msie || msie > 11) {
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element.attr('src')).toBe('javascript:doEvilStuff()');
- }
-
- $rootScope.testUrl = "http://recon/figured";
+ $$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
- expect(element.attr('src')).toBe('unsafe:http://recon/figured');
+ expect(element.attr('src')).toBe('someSanitizedUrl');
+ expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
});
});
-
});
describe('a[href] sanitization', function() {
- it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
- element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
-
- expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
- }));
-
-
- it('should sanitize data: urls', inject(function($compile, $rootScope) {
- element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "data:evilPayload";
- $rootScope.$apply();
-
- expect(element.attr('href')).toBe('unsafe:data:evilPayload');
- }));
-
-
- it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
- element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
-
- // case-sensitive
- $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
-
- // tab in protocol
- $rootScope.testUrl = "java\u0009script:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
-
- // space before
- $rootScope.testUrl = " javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
-
- // ws chars before
- $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
-
- // post-fixed with proper url
- $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
- $rootScope.$apply();
- expect(element[0].href).toBeOneOf(
- 'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
- 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
- );
- }));
-
-
- it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
- element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
-
- expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
- }));
-
-
- it('should not sanitize valid urls', inject(function($compile, $rootScope) {
- element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
-
- $rootScope.testUrl = "foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('foo/bar');
-
- $rootScope.testUrl = "/foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('/foo/bar');
-
- $rootScope.testUrl = "../foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('../foo/bar');
-
- $rootScope.testUrl = "#foo";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('#foo');
-
- $rootScope.testUrl = "http://foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('http://foo/bar');
-
- $rootScope.testUrl = " http://foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe(' http://foo/bar');
-
- $rootScope.testUrl = "https://foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('https://foo/bar');
-
- $rootScope.testUrl = "ftp://foo/bar";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('ftp://foo/bar');
-
- $rootScope.testUrl = "mailto:foo@bar.com";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('mailto:foo@bar.com');
-
- $rootScope.testUrl = "file:///foo/bar.html";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('file:///foo/bar.html');
- }));
-
-
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4117,7 +3898,6 @@ describe('$compile', function() {
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
}));
-
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4126,26 +3906,38 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
+ it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() {
+ module(function($compileProvider, $$sanitizeUriProvider) {
+ var newRe = /javascript:/,
+ returnVal;
+ expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist());
- it('should allow reconfiguration of the href whitelist', function() {
- module(function($compileProvider) {
- expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
- var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/);
+ returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
+ expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe);
+ expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe);
+ });
+ inject(function() {
+ // needed to the module definition above is run...
});
+ });
+ it('should use $$sanitizeUri', function() {
+ var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
+ module(function($provide) {
+ $provide.value('$$sanitizeUri', $$sanitizeUri);
+ });
inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
+ $rootScope.testUrl = "someUrl";
- $rootScope.testUrl = "javascript:doEvilStuff()";
- $rootScope.$apply();
- expect(element.attr('href')).toBe('javascript:doEvilStuff()');
-
- $rootScope.testUrl = "http://recon/figured";
+ $$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
- expect(element.attr('href')).toBe('unsafe:http://recon/figured');
+ expect(element.attr('href')).toBe('someSanitizedUrl');
+ expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
});
});
+
});
describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {
diff --git a/test/ng/sanitizeUriSpec.js b/test/ng/sanitizeUriSpec.js
new file mode 100644
index 00000000..b9f6a0e2
--- /dev/null
+++ b/test/ng/sanitizeUriSpec.js
@@ -0,0 +1,230 @@
+'use strict';
+
+describe('sanitizeUri', function() {
+ var sanitizeHref, sanitizeImg, sanitizeUriProvider, testUrl;
+ beforeEach(function() {
+ module(function(_$$sanitizeUriProvider_) {
+ sanitizeUriProvider = _$$sanitizeUriProvider_;
+ });
+ inject(function($$sanitizeUri) {
+ sanitizeHref = function(uri) {
+ return $$sanitizeUri(uri, false);
+ };
+ sanitizeImg = function(uri) {
+ return $$sanitizeUri(uri, true);
+ }
+ });
+ });
+
+ function isEvilInCurrentBrowser(uri) {
+ var a = document.createElement('a');
+ a.setAttribute('href', uri);
+ return a.href.substring(0, 4) !== 'http';
+ }
+
+ describe('img[src] sanitization', function() {
+
+ it('should sanitize javascript: urls', function() {
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+ });
+
+ it('should sanitize non-image data: urls', function() {
+ testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
+ expect(sanitizeImg(testUrl)).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
+
+ testUrl = "data:,foo";
+ expect(sanitizeImg(testUrl)).toBe("unsafe:data:,foo");
+ });
+
+ it('should not sanitize data: URIs for images', function() {
+ // image data uri
+ // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
+ testUrl = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+ expect(sanitizeImg(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
+ });
+
+ it('should sanitize mailto: urls', function() {
+ testUrl = "mailto:foo@bar.com";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:mailto:foo@bar.com');
+ });
+
+ it('should sanitize obfuscated javascript: urls', function() {
+ // case-sensitive
+ testUrl = "JaVaScRiPt:doEvilStuff()";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+
+ // tab in protocol
+ testUrl = "java\u0009script:doEvilStuff()";
+ if (isEvilInCurrentBrowser(testUrl)) {
+ expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
+ }
+
+ // space before
+ testUrl = " javascript:doEvilStuff()";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+
+ // ws chars before
+ testUrl = " \u000e javascript:doEvilStuff()";
+ if (isEvilInCurrentBrowser(testUrl)) {
+ expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
+ }
+
+ // post-fixed with proper url
+ testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
+ expect(sanitizeImg(testUrl)).toBeOneOf(
+ 'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
+ 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
+ );
+ });
+
+ it('should sanitize ng-src bindings as well', function() {
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+ });
+
+
+ it('should not sanitize valid urls', function() {
+ testUrl = "foo/bar";
+ expect(sanitizeImg(testUrl)).toBe('foo/bar');
+
+ testUrl = "/foo/bar";
+ expect(sanitizeImg(testUrl)).toBe('/foo/bar');
+
+ testUrl = "../foo/bar";
+ expect(sanitizeImg(testUrl)).toBe('../foo/bar');
+
+ testUrl = "#foo";
+ expect(sanitizeImg(testUrl)).toBe('#foo');
+
+ testUrl = "http://foo.com/bar";
+ expect(sanitizeImg(testUrl)).toBe('http://foo.com/bar');
+
+ testUrl = " http://foo.com/bar";
+ expect(sanitizeImg(testUrl)).toBe(' http://foo.com/bar');
+
+ testUrl = "https://foo.com/bar";
+ expect(sanitizeImg(testUrl)).toBe('https://foo.com/bar');
+
+ testUrl = "ftp://foo.com/bar";
+ expect(sanitizeImg(testUrl)).toBe('ftp://foo.com/bar');
+
+ testUrl = "file:///foo/bar.html";
+ expect(sanitizeImg(testUrl)).toBe('file:///foo/bar.html');
+ });
+
+
+ it('should allow reconfiguration of the src whitelist', function() {
+ var returnVal;
+ expect(sanitizeUriProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
+ returnVal = sanitizeUriProvider.imgSrcSanitizationWhitelist(/javascript:/);
+ expect(returnVal).toBe(sanitizeUriProvider);
+
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeImg(testUrl)).toBe('javascript:doEvilStuff()');
+
+ testUrl = "http://recon/figured";
+ expect(sanitizeImg(testUrl)).toBe('unsafe:http://recon/figured');
+ });
+
+ });
+
+
+ describe('a[href] sanitization', function() {
+
+ it('should sanitize javascript: urls', inject(function() {
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+ }));
+
+
+ it('should sanitize data: urls', inject(function() {
+ testUrl = "data:evilPayload";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:data:evilPayload');
+ }));
+
+
+ it('should sanitize obfuscated javascript: urls', inject(function() {
+ // case-sensitive
+ testUrl = "JaVaScRiPt:doEvilStuff()";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+
+ // tab in protocol
+ testUrl = "java\u0009script:doEvilStuff()";
+ if (isEvilInCurrentBrowser(testUrl)) {
+ expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
+ }
+
+ // space before
+ testUrl = " javascript:doEvilStuff()";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+
+ // ws chars before
+ testUrl = " \u000e javascript:doEvilStuff()";
+ if (isEvilInCurrentBrowser(testUrl)) {
+ expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
+ }
+
+ // post-fixed with proper url
+ testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
+ expect(sanitizeHref(testUrl)).toBeOneOf(
+ 'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
+ 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
+ );
+ }));
+
+
+ it('should sanitize ngHref bindings as well', inject(function() {
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
+ }));
+
+
+ it('should not sanitize valid urls', inject(function() {
+ testUrl = "foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('foo/bar');
+
+ testUrl = "/foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('/foo/bar');
+
+ testUrl = "../foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('../foo/bar');
+
+ testUrl = "#foo";
+ expect(sanitizeHref(testUrl)).toBe('#foo');
+
+ testUrl = "http://foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('http://foo/bar');
+
+ testUrl = " http://foo/bar";
+ expect(sanitizeHref(testUrl)).toBe(' http://foo/bar');
+
+ testUrl = "https://foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('https://foo/bar');
+
+ testUrl = "ftp://foo/bar";
+ expect(sanitizeHref(testUrl)).toBe('ftp://foo/bar');
+
+ testUrl = "mailto:foo@bar.com";
+ expect(sanitizeHref(testUrl)).toBe('mailto:foo@bar.com');
+
+ testUrl = "file:///foo/bar.html";
+ expect(sanitizeHref(testUrl)).toBe('file:///foo/bar.html');
+ }));
+
+ it('should allow reconfiguration of the href whitelist', function() {
+ var returnVal;
+ expect(sanitizeUriProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
+ returnVal = sanitizeUriProvider.aHrefSanitizationWhitelist(/javascript:/);
+ expect(returnVal).toBe(sanitizeUriProvider);
+
+ testUrl = "javascript:doEvilStuff()";
+ expect(sanitizeHref(testUrl)).toBe('javascript:doEvilStuff()');
+
+ testUrl = "http://recon/figured";
+ expect(sanitizeHref(testUrl)).toBe('unsafe:http://recon/figured');
+ });
+
+ });
+
+}); \ No newline at end of file
diff --git a/test/ngSanitize/sanitizeSpec.js b/test/ngSanitize/sanitizeSpec.js
index 3d586830..1958ec0f 100644
--- a/test/ngSanitize/sanitizeSpec.js
+++ b/test/ngSanitize/sanitizeSpec.js
@@ -5,12 +5,15 @@ describe('HTML', function() {
var expectHTML;
beforeEach(module('ngSanitize'));
-
- beforeEach(inject(function($sanitize) {
+ beforeEach(function() {
expectHTML = function(html){
- return expect($sanitize(html));
+ var sanitize;
+ inject(function($sanitize) {
+ sanitize = $sanitize;
+ });
+ return expect(sanitize(html));
};
- }));
+ });
describe('htmlParser', function() {
if (angular.isUndefined(window.htmlParser)) return;
@@ -183,13 +186,22 @@ describe('HTML', function() {
toEqual('');
});
+ it('should keep spaces as prefix/postfix', function() {
+ expectHTML(' a ').toEqual(' a ');
+ });
+
+ it('should allow multiline strings', function() {
+ expectHTML('\na\n').toEqual('&#10;a\&#10;');
+ });
+
describe('htmlSanitizerWriter', function() {
if (angular.isUndefined(window.htmlSanitizeWriter)) return;
- var writer, html;
+ var writer, html, uriValidator;
beforeEach(function() {
html = '';
- writer = htmlSanitizeWriter({push:function(text){html+=text;}});
+ uriValidator = jasmine.createSpy('uriValidator');
+ writer = htmlSanitizeWriter({push:function(text){html+=text;}}, uriValidator);
});
it('should write basic HTML', function() {
@@ -258,41 +270,106 @@ describe('HTML', function() {
});
});
- describe('isUri', function() {
+ describe('uri validation', function() {
+ it('should call the uri validator', function() {
+ writer.start('a', {href:'someUrl'}, false);
+ expect(uriValidator).toHaveBeenCalledWith('someUrl', false);
+ uriValidator.reset();
+ writer.start('img', {src:'someImgUrl'}, false);
+ expect(uriValidator).toHaveBeenCalledWith('someImgUrl', true);
+ uriValidator.reset();
+ writer.start('someTag', {src:'someNonUrl'}, false);
+ expect(uriValidator).not.toHaveBeenCalled();
+ });
- function isUri(value) {
- return value.match(URI_REGEXP);
- }
+ it('should drop non valid uri attributes', function() {
+ uriValidator.andReturn(false);
+ writer.start('a', {href:'someUrl'}, false);
+ expect(html).toEqual('<a>');
- it('should be URI', function() {
- expect(isUri('http://abc')).toBeTruthy();
- expect(isUri('HTTP://abc')).toBeTruthy();
- expect(isUri('https://abc')).toBeTruthy();
- expect(isUri('HTTPS://abc')).toBeTruthy();
- expect(isUri('ftp://abc')).toBeTruthy();
- expect(isUri('FTP://abc')).toBeTruthy();
- expect(isUri('mailto:me@example.com')).toBeTruthy();
- expect(isUri('MAILTO:me@example.com')).toBeTruthy();
- expect(isUri('tel:123-123-1234')).toBeTruthy();
- expect(isUri('TEL:123-123-1234')).toBeTruthy();
- expect(isUri('#anchor')).toBeTruthy();
+ html = '';
+ uriValidator.andReturn(true);
+ writer.start('a', {href:'someUrl'}, false);
+ expect(html).toEqual('<a href="someUrl">');
});
+ });
+ });
- it('should not be URI', function() {
- expect(isUri('')).toBeFalsy();
- expect(isUri('javascript:alert')).toBeFalsy();
+ describe('uri checking', function() {
+ beforeEach(function() {
+ this.addMatchers({
+ toBeValidUrl: function() {
+ var sanitize;
+ inject(function($sanitize) {
+ sanitize = $sanitize;
+ });
+ var input = '<a href="'+this.actual+'"></a>';
+ return sanitize(input) === input;
+ },
+ toBeValidImageSrc: function() {
+ var sanitize;
+ inject(function($sanitize) {
+ sanitize = $sanitize;
+ });
+ var input = '<img src="'+this.actual+'"/>';
+ return sanitize(input) === input;
+ }
});
});
- describe('javascript URL attribute', function() {
- beforeEach(function() {
- this.addMatchers({
- toBeValidUrl: function() {
- return URI_REGEXP.exec(this.actual);
- }
- });
+ it('should use $$sanitizeUri for links', function() {
+ var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
+ module(function($provide) {
+ $provide.value('$$sanitizeUri', $$sanitizeUri);
});
+ inject(function() {
+ $$sanitizeUri.andReturn('someUri');
+ expectHTML('<a href="someUri"></a>').toEqual('<a href="someUri"></a>');
+ expect($$sanitizeUri).toHaveBeenCalledWith('someUri', false);
+
+ $$sanitizeUri.andReturn('unsafe:someUri');
+ expectHTML('<a href="someUri"></a>').toEqual('<a></a>');
+ });
+ });
+
+ it('should use $$sanitizeUri for links', function() {
+ var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
+ module(function($provide) {
+ $provide.value('$$sanitizeUri', $$sanitizeUri);
+ });
+ inject(function() {
+ $$sanitizeUri.andReturn('someUri');
+
+ expectHTML('<img src="someUri"/>').toEqual('<img src="someUri"/>');
+ expect($$sanitizeUri).toHaveBeenCalledWith('someUri', true);
+
+ $$sanitizeUri.andReturn('unsafe:someUri');
+ expectHTML('<img src="someUri"/>').toEqual('<img/>');
+ });
+ });
+
+ it('should be URI', function() {
+ expect('').toBeValidUrl();
+ expect('http://abc').toBeValidUrl();
+ expect('HTTP://abc').toBeValidUrl();
+ expect('https://abc').toBeValidUrl();
+ expect('HTTPS://abc').toBeValidUrl();
+ expect('ftp://abc').toBeValidUrl();
+ expect('FTP://abc').toBeValidUrl();
+ expect('mailto:me@example.com').toBeValidUrl();
+ expect('MAILTO:me@example.com').toBeValidUrl();
+ expect('tel:123-123-1234').toBeValidUrl();
+ expect('TEL:123-123-1234').toBeValidUrl();
+ expect('#anchor').toBeValidUrl();
+ expect('/page1.md').toBeValidUrl();
+ });
+
+ it('should not be URI', function() {
+ expect('javascript:alert').not.toBeValidUrl();
+ });
+
+ describe('javascript URLs', function() {
it('should ignore javascript:', function() {
expect('JavaScript:abc').not.toBeValidUrl();
expect(' \n Java\n Script:abc').not.toBeValidUrl();
@@ -318,15 +395,19 @@ describe('HTML', function() {
});
it('should ignore hex encoded whitespace javascript:', function() {
- expect('jav&#x09;ascript:alert("A");').not.toBeValidUrl();
- expect('jav&#x0A;ascript:alert("B");').not.toBeValidUrl();
- expect('jav&#x0A ascript:alert("C");').not.toBeValidUrl();
- expect('jav\u0000ascript:alert("D");').not.toBeValidUrl();
- expect('java\u0000\u0000script:alert("D");').not.toBeValidUrl();
- expect(' &#14; java\u0000\u0000script:alert("D");').not.toBeValidUrl();
+ expect('jav&#x09;ascript:alert();').not.toBeValidUrl();
+ expect('jav&#x0A;ascript:alert();').not.toBeValidUrl();
+ expect('jav&#x0A ascript:alert();').not.toBeValidUrl();
+ expect('jav\u0000ascript:alert();').not.toBeValidUrl();
+ expect('java\u0000\u0000script:alert();').not.toBeValidUrl();
+ expect(' &#14; java\u0000\u0000script:alert();').not.toBeValidUrl();
});
});
+ });
-
+ describe('sanitizeText', function() {
+ it('should escape text', function() {
+ expect(sanitizeText('a<div>&</div>c')).toEqual('a&lt;div&gt;&amp;&lt;/div&gt;c');
+ });
});
});