Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 0b51380

Browse files
maksimrgkalpak
authored andcommitted
feat($sanitize): support enhancing white-list
1 parent fb00991 commit 0b51380

File tree

2 files changed

+128
-12
lines changed

2 files changed

+128
-12
lines changed

src/ngSanitize/sanitize.js

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ var bind;
1616
var extend;
1717
var forEach;
1818
var isDefined;
19+
var isArray;
20+
var isObject;
1921
var lowercase;
2022
var noop;
2123
var nodeContains;
@@ -145,8 +147,10 @@ var htmlSanitizeWriter;
145147
*/
146148
function $SanitizeProvider() {
147149
var svgEnabled = false;
150+
var hasBeenInstantiated = false;
148151

149152
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
153+
hasBeenInstantiated = true;
150154
if (svgEnabled) {
151155
extend(validElements, svgElements);
152156
}
@@ -199,6 +203,56 @@ function $SanitizeProvider() {
199203
}
200204
};
201205

206+
207+
/**
208+
* @ngdoc method
209+
* @name $sanitizeProvider#addValidElements
210+
* @kind function
211+
*
212+
* @param {Array|Object} elements List of valid elements.
213+
*
214+
* Object properties:
215+
*
216+
* - `svgElements` – `{string[]=}` – An array of SVG elements' names.
217+
* - `htmlVoidElements` – `{string[]=}` – An array of void elements' names.
218+
* - `htmlElements` – `{string[]=}` – An array of html elements' names.
219+
*/
220+
this.addValidElements = function(elements) {
221+
if (hasBeenInstantiated) return this;
222+
223+
if (isArray(elements)) {
224+
addElementsTo(validElements, elements);
225+
return this;
226+
}
227+
228+
if (isObject(elements)) {
229+
addElementsTo(svgElements, elements['svgElements']);
230+
addElementsTo(voidElements, elements['htmlVoidElements']);
231+
addElementsTo(validElements, elements['htmlVoidElements']);
232+
addElementsTo(validElements, elements['htmlElements']);
233+
}
234+
235+
return this;
236+
};
237+
238+
239+
/**
240+
* @ngdoc method
241+
* @name $sanitizeProvider#addValidAttrs
242+
* @kind function
243+
*
244+
* @description
245+
* The added attributes will not be treated as URI attributes, which means their values will
246+
* not sanitized as URIs using the aHrefSanitizationWhitelist and imgSrcSanitizationWhitelist of {@link ng.$compileProvider $compileProvider}.
247+
*
248+
* @param {Array} attrs List of valid attributes
249+
*/
250+
this.addValidAttrs = function(attrs) {
251+
if (hasBeenInstantiated) return this;
252+
extend(validAttrs, arrayToMap(attrs, true));
253+
return this;
254+
};
255+
202256
//////////////////////////////////////////////////////////////////////////////////////////////////
203257
// Private stuff
204258
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -207,6 +261,8 @@ function $SanitizeProvider() {
207261
extend = angular.extend;
208262
forEach = angular.forEach;
209263
isDefined = angular.isDefined;
264+
isArray = angular.isArray;
265+
isObject = angular.isObject;
210266
lowercase = angular.$$lowercase;
211267
noop = angular.noop;
212268

@@ -230,36 +286,36 @@ function $SanitizeProvider() {
230286

231287
// Safe Void Elements - HTML5
232288
// http://dev.w3.org/html5/spec/Overview.html#void-elements
233-
var voidElements = toMap('area,br,col,hr,img,wbr');
289+
var voidElements = stringToMap('area,br,col,hr,img,wbr');
234290

235291
// Elements that you can, intentionally, leave open (and which close themselves)
236292
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
237-
var optionalEndTagBlockElements = toMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
238-
optionalEndTagInlineElements = toMap('rp,rt'),
293+
var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
294+
optionalEndTagInlineElements = stringToMap('rp,rt'),
239295
optionalEndTagElements = extend({},
240296
optionalEndTagInlineElements,
241297
optionalEndTagBlockElements);
242298

243299
// Safe Block Elements - HTML5
244-
var blockElements = extend({}, optionalEndTagBlockElements, toMap('address,article,' +
300+
var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' +
245301
'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
246302
'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));
247303

248304
// Inline Elements - HTML5
249-
var inlineElements = extend({}, optionalEndTagInlineElements, toMap('a,abbr,acronym,b,' +
305+
var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' +
250306
'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
251307
'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
252308

253309
// SVG Elements
254310
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
255311
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
256312
// They can potentially allow for arbitrary javascript to be executed. See #11290
257-
var svgElements = toMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
313+
var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
258314
'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
259315
'radialGradient,rect,stop,svg,switch,text,title,tspan');
260316

261317
// Blocked Elements (will be stripped)
262-
var blockedElements = toMap('script,style');
318+
var blockedElements = stringToMap('script,style');
263319

264320
var validElements = extend({},
265321
voidElements,
@@ -268,17 +324,17 @@ function $SanitizeProvider() {
268324
optionalEndTagElements);
269325

270326
//Attributes that have href and hence need to be sanitized
271-
var uriAttrs = toMap('background,cite,href,longdesc,src,xlink:href,xml:base');
327+
var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base');
272328

273-
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
329+
var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
274330
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
275331
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
276332
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
277333
'valign,value,vspace,width');
278334

279335
// SVG attributes (without "id" and "name" attributes)
280336
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
281-
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
337+
var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
282338
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
283339
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
284340
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
@@ -299,14 +355,24 @@ function $SanitizeProvider() {
299355
svgAttrs,
300356
htmlAttrs);
301357

302-
function toMap(str, lowercaseKeys) {
303-
var obj = {}, items = str.split(','), i;
358+
function stringToMap(str, lowercaseKeys) {
359+
return arrayToMap(str.split(','), lowercaseKeys);
360+
}
361+
362+
function arrayToMap(items, lowercaseKeys) {
363+
var obj = {}, i;
304364
for (i = 0; i < items.length; i++) {
305365
obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
306366
}
307367
return obj;
308368
}
309369

370+
function addElementsTo(elementsMap, newElements) {
371+
if (newElements && newElements.length) {
372+
extend(elementsMap, arrayToMap(newElements));
373+
}
374+
}
375+
310376
/**
311377
* Create an inert document that contains the dirty HTML that needs sanitizing
312378
* Depending upon browser support we use one of three strategies for doing this.

test/ngSanitize/sanitizeSpec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,56 @@ describe('HTML', function() {
293293
expect(doc).toEqual('<p><img src="x"></p>');
294294
}));
295295

296+
describe('Custom white-list support', function() {
297+
298+
var $sanitizeProvider;
299+
beforeEach(module(function(_$sanitizeProvider_) {
300+
$sanitizeProvider = _$sanitizeProvider_;
301+
302+
$sanitizeProvider.addValidElements(['foo']);
303+
$sanitizeProvider.addValidElements({
304+
htmlElements: ['foo-button', 'foo-video'],
305+
htmlVoidElements: ['foo-input'],
306+
svgElements: ['foo-svg']
307+
});
308+
$sanitizeProvider.addValidAttrs(['foo']);
309+
}));
310+
311+
it('should allow custom white-listed element', function() {
312+
expectHTML('<foo></foo>').toEqual('<foo></foo>');
313+
expectHTML('<foo-button></foo-button>').toEqual('<foo-button></foo-button>');
314+
expectHTML('<foo-video></foo-video>').toEqual('<foo-video></foo-video>');
315+
});
316+
317+
it('should allow custom white-listed void element', function() {
318+
expectHTML('<foo-input/>').toEqual('<foo-input>');
319+
});
320+
321+
it('should allow custom white-listed void element to be used with closing tag', function() {
322+
expectHTML('<foo-input></foo-input>').toEqual('<foo-input>');
323+
});
324+
325+
it('should allow custom white-listed attribute', function() {
326+
expectHTML('<foo-input foo="foo"/>').toEqual('<foo-input foo="foo">');
327+
});
328+
329+
it('should ignore custom white-listed SVG element if SVG disabled', function() {
330+
expectHTML('<foo-svg></foo-svg>').toEqual('');
331+
});
332+
333+
it('should not allow add custom element after service has been instantiated', inject(function($sanitize) {
334+
$sanitizeProvider.addValidElements(['bar']);
335+
expectHTML('<bar></bar>').toEqual('');
336+
}));
337+
});
338+
296339
describe('SVG support', function() {
297340

298341
beforeEach(module(function($sanitizeProvider) {
299342
$sanitizeProvider.enableSvg(true);
343+
$sanitizeProvider.addValidElements({
344+
svgElements: ['font-face-uri']
345+
});
300346
}));
301347

302348
it('should accept SVG tags', function() {
@@ -314,6 +360,10 @@ describe('HTML', function() {
314360

315361
});
316362

363+
it('should allow custom white-listed SVG element', function() {
364+
expectHTML('<font-face-uri></font-face-uri>').toEqual('<font-face-uri></font-face-uri>');
365+
});
366+
317367
it('should sanitize SVG xlink:href attribute values', function() {
318368
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
319369
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',

0 commit comments

Comments
 (0)