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(']*)?/?>', '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'); + }); + }); +});