diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index f1b9368dc72..a253c3c6a0a 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -221,6 +221,8 @@ var TAG_STYLES = {
em: 'font-style:italic;font-weight:bold'
};
+var PROTOCOLS = ['http:', 'https:', 'mailto:'];
+
var STRIP_TAGS = new RegExp('?(' + Object.keys(TAG_STYLES).join('|') + ')( [^>]*)?/?>', 'g');
util.plainText = function(_str){
@@ -252,7 +254,14 @@ function convertToSVG(_str){
if(tag === 'a'){
if(close) return '';
else if(extra.substr(0,4).toLowerCase() !== 'href') return '';
- else return '';
+ else {
+ var dummyAnchor = document.createElement('a');
+ dummyAnchor.href = extra.substr(4).replace(/["'=]/g, '');
+
+ if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '';
+
+ return '';
+ }
}
else if(tag === 'br') return '
';
else if(close) {
diff --git a/test/jasmine/tests/svg_text_utils_test.js b/test/jasmine/tests/svg_text_utils_test.js
new file mode 100644
index 00000000000..4abb4692fd6
--- /dev/null
+++ b/test/jasmine/tests/svg_text_utils_test.js
@@ -0,0 +1,69 @@
+var d3 = require('d3');
+
+var util = require('@src/lib/svg_text_utils');
+
+
+describe('svg+text utils', function() {
+ 'use strict';
+
+ describe('convertToTspans', function() {
+
+ function mockTextSVGElement(txt) {
+ return d3.select('body')
+ .append('svg')
+ .attr('id', 'text')
+ .append('text')
+ .text(txt)
+ .call(util.convertToTspans);
+ }
+
+ afterEach(function() {
+ d3.select('#text').remove();
+ });
+
+ it('checks for XSS attack in href', function() {
+ var node = mockTextSVGElement(
+ 'XSS'
+ )
+
+ expect(node.text()).toEqual('XSS');
+ expect(node.select('a').attr('xlink:href')).toBe(null);
+ });
+
+ it('checks for XSS attack in href (with plenty of white spaces)', function() {
+ var node = mockTextSVGElement(
+ 'XSS'
+ )
+
+ expect(node.text()).toEqual('XSS');
+ expect(node.select('a').attr('xlink:href')).toBe(null);
+ });
+
+ it('whitelists http hrefs', function() {
+ var node = mockTextSVGElement(
+ 'bl.ocks.org'
+ )
+
+ expect(node.text()).toEqual('bl.ocks.org');
+ expect(node.select('a').attr('xlink:href')).toEqual('http://bl.ocks.org/');
+ });
+
+ it('whitelists https hrefs', function() {
+ var node = mockTextSVGElement(
+ 'plot.ly'
+ )
+
+ expect(node.text()).toEqual('plot.ly');
+ expect(node.select('a').attr('xlink:href')).toEqual('https://plot.ly');
+ });
+
+ it('whitelists mailto hrefs', function() {
+ var node = mockTextSVGElement(
+ 'support'
+ )
+
+ expect(node.text()).toEqual('support');
+ expect(node.select('a').attr('xlink:href')).toEqual('mailto:support@plot.ly');
+ });
+ });
+});