Skip to content

Commit 8fc47ef

Browse files
authored
Merge pull request #791 from plotly/html-encode-attributes
HTML-encode attributes in <tspan>s and <a>s
2 parents 5904af2 + 763485c commit 8fc47ef

File tree

2 files changed

+72
-4
lines changed

2 files changed

+72
-4
lines changed

src/lib/svg_text_utils.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,15 @@ util.plainText = function(_str) {
242242
return (_str || '').replace(STRIP_TAGS, ' ');
243243
};
244244

245+
function encodeForHTML(_str) {
246+
return (_str || '').replace(/&/g, '&amp;')
247+
.replace(/</g, '&lt;')
248+
.replace(/>/g, '&gt;')
249+
.replace(/"/g, '&quot;')
250+
.replace(/'/g, '&#x27;')
251+
.replace(/\//g, '&#x2F;');
252+
}
253+
245254
function convertToSVG(_str) {
246255
var htmlEntitiesDecoded = Plotly.util.html_entity_decode(_str);
247256
var result = htmlEntitiesDecoded
@@ -270,15 +279,14 @@ function convertToSVG(_str) {
270279
// remove quotes, leading '=', replace '&' with '&amp;'
271280
var href = extra.substr(4)
272281
.replace(/["']/g, '')
273-
.replace(/=/, '')
274-
.replace(/&/g, '&amp;');
282+
.replace(/=/, '');
275283

276284
// check protocol
277285
var dummyAnchor = document.createElement('a');
278286
dummyAnchor.href = href;
279287
if(PROTOCOLS.indexOf(dummyAnchor.protocol) === -1) return '<a>';
280288

281-
return '<a xlink:show="new" xlink:href="' + href + '">';
289+
return '<a xlink:show="new" xlink:href="' + encodeForHTML(href) + '">';
282290
}
283291
}
284292
else if(tag === 'br') return '<br>';
@@ -302,7 +310,7 @@ function convertToSVG(_str) {
302310
// most of the svg css users will care about is just like html,
303311
// but font color is different. Let our users ignore this.
304312
extraStyle = extraStyle[1].replace(/(^|;)\s*color:/, '$1 fill:');
305-
style = (style ? style + ';' : '') + extraStyle;
313+
style = (style ? style + ';' : '') + encodeForHTML(extraStyle);
306314
}
307315

308316
return tspanStart + (style ? ' style="' + style + '"' : '') + '>';

test/jasmine/tests/svg_text_utils_test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ describe('svg+text utils', function() {
2525
expect(a.attr('xlink:show')).toBe(href === null ? null : 'new');
2626
}
2727

28+
function assertTspanStyle(node, style) {
29+
var tspan = node.select('tspan');
30+
expect(tspan.attr('style')).toBe(style);
31+
}
32+
2833
function assertAnchorAttrs(node) {
2934
var a = node.select('a');
3035

@@ -75,6 +80,16 @@ describe('svg+text utils', function() {
7580
assertAnchorLink(node, null);
7681
});
7782

83+
it('whitelist relative hrefs (interpreted as http)', function() {
84+
var node = mockTextSVGElement(
85+
'<a href="/mylink">mylink</a>'
86+
);
87+
88+
expect(node.text()).toEqual('mylink');
89+
assertAnchorAttrs(node);
90+
assertAnchorLink(node, '/mylink');
91+
});
92+
7893
it('whitelist http hrefs', function() {
7994
var node = mockTextSVGElement(
8095
'<a href="http://bl.ocks.org/">bl.ocks.org</a>'
@@ -134,5 +149,50 @@ describe('svg+text utils', function() {
134149
assertAnchorLink(node, 'https://abc.com/myFeature.jsp?name=abc&pwd=def');
135150
});
136151
});
152+
153+
it('allow basic spans', function() {
154+
var node = mockTextSVGElement(
155+
'<span>text</span>'
156+
);
157+
158+
expect(node.text()).toEqual('text');
159+
assertTspanStyle(node, null);
160+
});
161+
162+
it('ignore unquoted styles in spans', function() {
163+
var node = mockTextSVGElement(
164+
'<span style=unquoted>text</span>'
165+
);
166+
167+
expect(node.text()).toEqual('text');
168+
assertTspanStyle(node, null);
169+
});
170+
171+
it('allow quoted styles in spans', function() {
172+
var node = mockTextSVGElement(
173+
'<span style="quoted: yeah;">text</span>'
174+
);
175+
176+
expect(node.text()).toEqual('text');
177+
assertTspanStyle(node, 'quoted: yeah;');
178+
});
179+
180+
it('ignore extra stuff after span styles', function() {
181+
var node = mockTextSVGElement(
182+
'<span style="quoted: yeah;"disallowed: indeed;">text</span>'
183+
);
184+
185+
expect(node.text()).toEqual('text');
186+
assertTspanStyle(node, 'quoted: yeah;');
187+
});
188+
189+
it('escapes HTML entities in span styles', function() {
190+
var node = mockTextSVGElement(
191+
'<span style="quoted: yeah&\';;">text</span>'
192+
);
193+
194+
expect(node.text()).toEqual('text');
195+
assertTspanStyle(node, 'quoted: yeah&\';;');
196+
});
137197
});
138198
});

0 commit comments

Comments
 (0)