Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(select): block xss on md-select-label (#10023)
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn authored Dec 1, 2016
1 parent b8357dc commit f7ecb4f
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 8 deletions.
42 changes: 35 additions & 7 deletions src/components/select/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ angular.module('material.components.select', [
* @param {expression=} md-on-open Expression to be evaluated when opening the select.
* Will hide the select options and show a spinner until the evaluated promise resolves.
* @param {expression=} md-selected-text Expression to be evaluated that will return a string
* to be displayed as a placeholder in the select input box when it is closed.
* to be displayed as a placeholder in the select input box when it is closed. The value
* will be treated as *text* (not html).
* @param {expression=} md-selected-html Expression to be evaluated that will return a string
* to be displayed as a placeholder in the select input box when it is closed. The value
* will be treated as *html*. The value must either be explicitly marked as trustedHtml or
* the ngSanitize module must be loaded.
* @param {string=} placeholder Placeholder hint text.
* @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the
* floating label. **Note:** This attribute is only evaluated once; it is not watched.
Expand Down Expand Up @@ -174,7 +179,8 @@ angular.module('material.components.select', [
* </div>
* </hljs>
*/
function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $parse) {
function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $parse, $sce,
$injector) {
var keyCodes = $mdConstant.KEY_CODE;
var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW];

Expand Down Expand Up @@ -337,17 +343,39 @@ function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $
mdSelectCtrl.setLabelText = function(text) {
mdSelectCtrl.setIsPlaceholder(!text);

if (attr.mdSelectedText) {
text = $parse(attr.mdSelectedText)(scope);
} else {
// Whether the select label has been given via user content rather than the internal
// template of <md-option>
var isSelectLabelFromUser = false;

if (attr.mdSelectedText && attr.mdSelectedHtml) {
throw Error('md-select cannot have both `md-selected-text` and `md-selected-html`');
}

if (attr.mdSelectedText || attr.mdSelectedHtml) {
text = $parse(attr.mdSelectedText || attr.mdSelectedHtml)(scope);
isSelectLabelFromUser = true;
} else if (!text) {
// Use placeholder attribute, otherwise fallback to the md-input-container label
var tmpPlaceholder = attr.placeholder ||
(containerCtrl && containerCtrl.label ? containerCtrl.label.text() : '');
text = text || tmpPlaceholder || '';

text = tmpPlaceholder || '';
isSelectLabelFromUser = true;
}

var target = valueEl.children().eq(0);
target.html(text);

if (attr.mdSelectedHtml) {
// Using getTrustedHtml will run the content through $sanitize if it is not already
// explicitly trusted. If the ngSanitize module is not loaded, this will
// *correctly* throw an sce error.
target.html($sce.getTrustedHtml(text));
} else if (isSelectLabelFromUser) {
target.text(text);
} else {
// If we've reached this point, the text is not user-provided.
target.html(text);
}
};

mdSelectCtrl.setIsPlaceholder = function(isPlaceholder) {
Expand Down
67 changes: 66 additions & 1 deletion src/components/select/select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ describe('<md-select>', function() {
var body, $document, $rootScope, $compile, $timeout, $material;

beforeEach(function() {
module('material.components.select', 'material.components.input');
module('material.components.select', 'material.components.input', 'ngSanitize');

inject(function($injector) {
$document = $injector.get('$document');
Expand Down Expand Up @@ -437,6 +437,32 @@ describe('<md-select>', function() {
expect(label.text()).toBe($rootScope.selectedText);
});

it('should sanitize md-selected-html', function() {
$rootScope.selectedText = '<b>Hello World</b><script>window.mdSelectXss="YES"</script>';

var select = setupSelect(
'ng-model="someVal", ' +
'md-selected-html="selectedText"', null, true).find('md-select');
var label = select.find('md-select-value');

expect(label.text()).toBe('Hello World');

// The label is loaded into a span that is the first child of the '<md-select-value>`.
expect(label[0].childNodes[0].innerHTML).toBe('<b>Hello World</b>');
expect(window.mdSelectXss).toBeUndefined();
});

it('should always treat md-selected-text as text, not html', function() {
$rootScope.selectedText = '<b>Hello World</b>';

var select = setupSelect(
'ng-model="someVal", ' +
'md-selected-text="selectedText"', null, true).find('md-select');
var label = select.find('md-select-value');

expect(label.text()).toBe('<b>Hello World</b>');
});

it('supports rendering multiple', function() {
$rootScope.val = [1, 3];
var select = $compile('<md-input-container>' +
Expand Down Expand Up @@ -1379,3 +1405,42 @@ describe('<md-select>', function() {
}

});

describe('<md-select> without ngSanitize loaded', function() {
var $compile, pageScope;

beforeEach(module('material.components.select', 'material.components.input'));

beforeEach(inject(function($injector) {
$compile = $injector.get('$compile');
pageScope = $injector.get('$rootScope').$new();
}));

it('should throw an error when using md-selected-html without ngSanitize', function() {
var template =
'<md-select md-selected-html="myHtml" ng-model="selectedValue">' +
'<md-option>One</md-option>' +
'</md-select>';

var select = $compile(template)(pageScope);

expect(function() {
pageScope.myHtml = '<p>Barnacle Pete</p>';
pageScope.$apply();
}).toThrowError(/\$sce:unsafe/);
});


it('should throw an error if using md-selected-text and md-selected-html', function() {
var template =
'<md-select md-selected-text="myText" md-selected-html="myHtml" ng-model="selectedValue">' +
'<md-option>One</md-option>' +
'</md-select>';

var select = $compile(template)(pageScope);

expect(function() {
pageScope.$apply();
}).toThrowError('md-select cannot have both `md-selected-text` and `md-selected-html`');
});
});

0 comments on commit f7ecb4f

Please sign in to comment.