Web UI: add sanitize() function to fn.js library.
Change-Id: I2d8fedf737dfaa86362b83edab57967888414088
(cherry picked from commit 0fe05d6c80d17c7dd19528ad63914767934416f0)
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index e3b9600..9706478 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -430,6 +430,61 @@
return child;
}
+ // -----------------------------------------------------------------
+ // The next section deals with sanitizing external strings destined
+ // to be loaded via a .html() function call.
+
+ var matcher = /<\/?([a-zA-Z0-9]+)*(.*?)\/?>/igm,
+ whitelist = ['b', 'i', 'p', 'em', 'strong'],
+ warnlist = ['script', 'style'];
+
+ // Returns true if the tag is in the warn list, (and is not an end-tag)
+ function inWarnList(tag) {
+ return (warnlist.indexOf(tag.name) !== -1 && tag.full.indexOf('/') === -1);
+ }
+
+ function analyze(html) {
+ html = String(html) || '';
+
+ var matches = [],
+ match;
+
+ // extract all tags
+ while ((match = matcher.exec(html)) !== null) {
+ matches.push({
+ full: match[0],
+ name: match[1]
+ // NOTE: ignoring attributes {match[2].split(' ')} for now
+ });
+ }
+
+ return matches;
+ }
+
+ function sanitize(html) {
+ html = String(html) || '';
+
+ var matches = analyze(html);
+
+ // do not allow script tags or style tags
+ html = html.replace(/<script(.*?)>(.*?[\r\n])*?(.*?)(.*?[\r\n])*?<\/script>/gim, '');
+ html = html.replace(/<style(.*?)>(.*?[\r\n])*?(.*?)(.*?[\r\n])*?<\/style>/gim, '');
+
+ // filter out all but whitelisted tag types
+ matches.forEach(function (tag) {
+ if (whitelist.indexOf(tag.name) === -1) {
+ html = html.replace(tag.full, '');
+ if (inWarnList(tag)) {
+ $log.warn('Unsanitary HTML input -- ' + tag.full + ' detected!');
+ }
+ }
+ });
+
+ // TODO: consider encoding HTML entities, e.g. '&' -> '&'
+
+ return html;
+ }
+
angular.module('onosUtil')
.factory('FnService',
@@ -469,7 +524,8 @@
removeFromTrie: removeFromTrie,
trieLookup: trieLookup,
classNames: classNames,
- extend: extend
+ extend: extend,
+ sanitize: sanitize
};
}]);
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
index 6f999a6..a7bda70 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -19,6 +19,7 @@
*/
describe('factory: fw/util/fn.js', function() {
var $window,
+ $log,
fs,
someFunction = function () {},
someArray = [1, 2, 3],
@@ -38,13 +39,22 @@
}
};
+ var mockLog = {
+ debug: function () {},
+ info: function () {},
+ warn: function () {},
+ error: function () {}
+ };
+
beforeEach(function () {
module(function ($provide) {
$provide.value('$window', mockWindow);
+ $provide.value('$log', mockLog);
});
});
- beforeEach(inject(function (_$window_, FnService) {
+ beforeEach(inject(function (_$log_, _$window_, FnService) {
+ $log = _$log_;
$window = _$window_;
fs = FnService;
}));
@@ -217,7 +227,7 @@
'debugOn', 'debug',
'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap',
'eecode', 'noPx', 'noPxStyle', 'endsWith', 'addToTrie', 'removeFromTrie', 'trieLookup',
- 'classNames', 'extend'
+ 'classNames', 'extend', 'sanitize'
])).toBeTruthy();
});
@@ -425,5 +435,57 @@
expect(fs.endsWith("barfood", "foo")).toBe(false);
});
+ // === Tests for sanitize()
+ function chkSan(u, s) {
+ expect(fs.sanitize(u)).toEqual(s);
+ }
+ function chkGood(g) {
+ chkSan(g, g)
+ }
+ it('should return foo', function () {
+ chkGood('foo');
+ });
+ it('should retain < b > tags', function () {
+ chkGood('foo <b>bar</b> baz');
+ });
+ it('should retain < i > tags', function () {
+ chkGood('foo <i>bar</i> baz');
+ });
+ it('should retain < p > tags', function () {
+ chkGood('foo <p>bar</p> baz');
+ });
+ it('should retain < em > tags', function () {
+ chkGood('foo <em>bar</em> baz');
+ });
+ it('should retain < strong > tags', function () {
+ chkGood('foo <strong>bar</strong> baz');
+ });
+
+ it('should reject < a > tags', function () {
+ chkSan('test <a href="hah">something</a> this', 'test something this');
+ });
+
+ it('should log a warning for < script > tags', function () {
+ spyOn($log, 'warn');
+ chkSan('<script>alert("foo");</script>', '');
+ expect($log.warn).toHaveBeenCalledWith(
+ 'Unsanitary HTML input -- <script> detected!'
+ );
+ });
+ it('should log a warning for < style > tags', function () {
+ spyOn($log, 'warn');
+ chkSan('<style> h1 {color:red;} </style>', '');
+ expect($log.warn).toHaveBeenCalledWith(
+ 'Unsanitary HTML input -- <style> detected!'
+ );
+ });
+
+ it('should completely strip < script >, remove < a >, retain < i >', function () {
+ chkSan(
+ 'Hey <i>this</i> is <script>alert("foo");</script> <a href="meh">cool</a>',
+ 'Hey <i>this</i> is cool'
+ );
+ });
+
});