Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -844,10 +844,42 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {

nodeName = nodeName_(this.$$element);

// sanitize a[href] and img[src] values
if ((nodeName === 'a' && key === 'href') ||
(nodeName === 'img' && key === 'src')) {
// sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
} else if (nodeName === 'img' && key === 'srcset') {
// sanitize img[srcset] values
var result = "";

// first check if there are spaces because it's not the same pattern
var trimedSrcset = trim(value);
var pattern = /\s/.test( trimedSrcset) ? /(\d+x\s*,|\d+w\s*,|\s+,|,\s+)/ : /(,)/;

// split srcset into tupple of uri and descriptor except for the last item
var rawUris = trimedSrcset.split(pattern);

// for each tupples
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
for (var i=0; i<nbrUrisWith2parts; i++) {
var innerIdx = i*2;
// sanitize the uri
result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
// add the descriptor
result += ( " " + trim(rawUris[innerIdx+1]));
}

// split the last item into uri and descriptor
var lastTupple = trim(rawUris[i*2]).split(/\s/);

// sanitize the last uri
result += $$sanitizeUri(trim(lastTupple[0]), true);

// and add the last descriptor if any
if( lastTupple.length === 2) {
result += (" " + trim(lastTupple[1]));
}
this[key] = value = result;
}

if (writeAttr !== false) {
Expand Down
64 changes: 64 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5603,6 +5603,70 @@ describe('$compile', function() {
});
});

describe('img[srcset] sanitization', function() {

it('should NOT require trusted values for img srcset', inject(function($rootScope, $compile, $sce) {
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = 'http://example.com/image.png';
$rootScope.$digest();
expect(element.attr('srcset')).toEqual('http://example.com/image.png');
// But it should accept trusted values anyway.
$rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png');
$rootScope.$digest();
expect(element.attr('srcset')).toEqual('http://example.com/image2.png');
}));

it('should use $$sanitizeUri', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function($compile, $rootScope) {
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "someUrl";

$$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
expect(element.attr('srcset')).toBe('someSanitizedUrl');
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
});
});

it('should sanitize all uris in srcset', inject(function($rootScope, $compile) {
/*jshint scripturl:true*/
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
var testSet = {
'http://example.com/image.png':'http://example.com/image.png',
' http://example.com/image.png':'http://example.com/image.png',
'http://example.com/image.png ':'http://example.com/image.png',
'http://example.com/image.png 128w':'http://example.com/image.png 128w',
'http://example.com/image.png 2x':'http://example.com/image.png 2x',
'http://example.com/image.png 1.5x':'http://example.com/image.png 1.5x',
'http://example.com/image1.png 1x,http://example.com/image2.png 2x':'http://example.com/image1.png 1x,http://example.com/image2.png 2x',
'http://example.com/image1.png 1x ,http://example.com/image2.png 2x':'http://example.com/image1.png 1x ,http://example.com/image2.png 2x',
'http://example.com/image1.png 1x, http://example.com/image2.png 2x':'http://example.com/image1.png 1x,http://example.com/image2.png 2x',
'http://example.com/image1.png 1x , http://example.com/image2.png 2x':'http://example.com/image1.png 1x ,http://example.com/image2.png 2x',
'http://example.com/image1.png 48w,http://example.com/image2.png 64w':'http://example.com/image1.png 48w,http://example.com/image2.png 64w',
'http://example.com/image1.png 1x,http://example.com/image2.png 64w':'http://example.com/image1.png 1x,http://example.com/image2.png 64w',
'http://example.com/image1.png,http://example.com/image2.png':'http://example.com/image1.png ,http://example.com/image2.png',
'http://example.com/image1.png ,http://example.com/image2.png':'http://example.com/image1.png ,http://example.com/image2.png',
'http://example.com/image1.png, http://example.com/image2.png':'http://example.com/image1.png ,http://example.com/image2.png',
'http://example.com/image1.png , http://example.com/image2.png':'http://example.com/image1.png ,http://example.com/image2.png',
'http://example.com/image1.png 1x, http://example.com/image2.png 2x, http://example.com/image3.png 3x':
'http://example.com/image1.png 1x,http://example.com/image2.png 2x,http://example.com/image3.png 3x',
'javascript:doEvilStuff() 2x': 'unsafe:javascript:doEvilStuff() 2x',
'http://example.com/image1.png 1x,javascript:doEvilStuff() 2x':'http://example.com/image1.png 1x,unsafe:javascript:doEvilStuff() 2x',
'http://example.com/image1.jpg?x=a,b 1x,http://example.com/ima,ge2.jpg 2x':'http://example.com/image1.jpg?x=a,b 1x,http://example.com/ima,ge2.jpg 2x'
};

forEach( testSet, function( ref, url) {
$rootScope.testUrl = url;
$rootScope.$digest();
expect(element.attr('srcset')).toEqual(ref);
});

}));
});

describe('a[href] sanitization', function() {

Expand Down
23 changes: 16 additions & 7 deletions test/ng/directive/ngSrcSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ describe('ngSrc', function() {
dealoc(element);
});

it('should not result empty string in img src', inject(function($rootScope, $compile) {
$rootScope.image = {};
element = $compile('<img ng-src="{{image.url}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('src')).not.toBe('');
expect(element.attr('src')).toBe(undefined);
}));
describe('img[ng-src]', function() {
it('should not result empty string in img src', inject(function($rootScope, $compile) {
$rootScope.image = {};
element = $compile('<img ng-src="{{image.url}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('src')).not.toBe('');
expect(element.attr('src')).toBe(undefined);
}));

it('should sanitize url', inject(function($rootScope, $compile) {
$rootScope.imageUrl = 'javascript:alert(1);';
element = $compile('<img ng-src="{{imageUrl}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('src')).toBe('unsafe:javascript:alert(1);');
}));
});

describe('iframe[ng-src]', function() {
it('should pass through src attributes for the same domain', inject(function($compile, $rootScope) {
Expand Down
16 changes: 16 additions & 0 deletions test/ng/directive/ngSrcsetSpec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/*jshint scripturl:true*/
'use strict';

describe('ngSrcset', function() {
Expand All @@ -13,4 +14,19 @@ describe('ngSrcset', function() {
$rootScope.$digest();
expect(element.attr('srcset')).toBeUndefined();
}));

it('should sanitize good urls', inject(function($rootScope, $compile) {
$rootScope.imageUrl = 'http://example.com/image1.png 1x, http://example.com/image2.png 2x';
element = $compile('<img ng-srcset="{{imageUrl}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('srcset')).toBe('http://example.com/image1.png 1x,http://example.com/image2.png 2x');
}));

it('should sanitize evil url', inject(function($rootScope, $compile) {
$rootScope.imageUrl = 'http://example.com/image1.png 1x, javascript:doEvilStuff() 2x';
element = $compile('<img ng-srcset="{{imageUrl}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('srcset')).toBe('http://example.com/image1.png 1x,unsafe:javascript:doEvilStuff() 2x');
}));
});