OS-6: prevent XSS attacks
(i) add sanitize() function to utility function service.

Change-Id: I3f95e46b89f6067f74066e71ae2d736ee0b9ccc2
(cherry picked from commit 69b04c65d7d22d1725b20902cfde12bbf64ebfd4)
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 0d5d4cc..c9cef6d 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -437,6 +437,64 @@
     }
 
 
+    // -----------------------------------------------------------------
+    // 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', 'br'],
+        evillist = ['script', 'style', 'iframe'];
+
+    // Returns true if the tag is in the evil list, (and is not an end-tag)
+    function inEvilList(tag) {
+        return (evillist.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);
+
+        // completely obliterate evil tags and their contents...
+        evillist.forEach(function (tag) {
+            var re = new RegExp('<' + tag + '(.*?)>(.*?[\r\n])*?(.*?)(.*?[\r\n])*?<\/' + tag + '>', 'gim');
+            html = html.replace(re, '');
+        });
+
+        // filter out all but white-listed tags and end-tags
+        matches.forEach(function (tag) {
+            if (whitelist.indexOf(tag.name) === -1) {
+                html = html.replace(tag.full, '');
+                if (inEvilList(tag)) {
+                    $log.warn('Unsanitary HTML input -- ' + tag.full + ' detected!');
+                }
+            }
+        });
+
+        // TODO: consider encoding HTML entities, e.g. '&' -> '&amp;'
+
+        return html;
+    }
+
+
     angular.module('onosUtil')
         .factory('FnService',
         ['$window', '$location', '$log', function (_$window_, $loc, _$log_) {
@@ -476,7 +534,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 bc434ca..89a4b88 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', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup',
-            'classNames', 'extend'
+            'classNames', 'extend', 'sanitize'
         ])).toBeTruthy();
     });
 
@@ -445,5 +455,68 @@
     it('should return 2001', function () {
         expect(fs.parseBitRate('2,001.59 Gbps')).toBe(2001);
     });
+
+
+    // === 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 log a warning for < iframe > tags', function () {
+        spyOn($log, 'warn');
+        chkSan('Foo<iframe><body><h1>fake</h1></body></iframe>Bar', 'FooBar');
+        expect($log.warn).toHaveBeenCalledWith(
+            'Unsanitary HTML input -- <iframe> 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'
+        );
+    });
+
 });