Skip to content

Commit a5607e4

Browse files
committed
Merge branch 'master' into cut-size-in-half
2 parents 0574085 + 1a30900 commit a5607e4

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "preact-render-to-string",
33
"amdName": "preactRenderToString",
4-
"version": "3.7.0",
4+
"version": "3.7.2",
55
"description": "Render JSX to an HTML string, with support for Preact components.",
66
"main": "dist/index.js",
77
"umd:main": "dist/index.js",

src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ export default function renderToString(vnode, context, opts, inner, isSvgMode) {
113113
let name = attrs[i],
114114
v = attributes[name];
115115
if (name==='children') continue;
116+
117+
if (name.match(/[\s\n\/='"\0<>]/)) continue;
118+
116119
if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
117120

118121
if (name==='className') {
@@ -156,6 +159,7 @@ export default function renderToString(vnode, context, opts, inner, isSvgMode) {
156159
else if (pretty && ~s.indexOf('\n')) s += '\n';
157160

158161
s = `<${nodeName}${s}>`;
162+
if (String(nodeName).match(/[\s\n\/='"\0<>]/)) throw s;
159163

160164
if (VOID_ELEMENTS.indexOf(nodeName)>-1) {
161165
s = s.replace(/>$/, ' />');

test/render.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,45 @@ describe('render', () => {
5656
expect(render(<div foo={0} />)).to.equal(`<div foo="0"></div>`);
5757
});
5858

59+
describe('attribute name sanitization', () => {
60+
it('should omit attributes with invalid names', () => {
61+
let rendered = render(h('div', {
62+
'<a': '1',
63+
'a>': '1',
64+
'foo"bar': '1',
65+
'"hello"': '1'
66+
}));
67+
expect(rendered).to.equal(`<div></div>`);
68+
});
69+
70+
it('should mitigate attribute name injection', () => {
71+
let rendered = render(h('div', {
72+
'></div><script>alert("hi")</script>': '',
73+
'foo onclick': 'javascript:alert()',
74+
a: 'b'
75+
}));
76+
expect(rendered).to.equal(`<div a="b"></div>`);
77+
});
78+
79+
it('should allow emoji attribute names', () => {
80+
let rendered = render(h('div', {
81+
'a;b': '1',
82+
'a🧙‍b': '1'
83+
}));
84+
expect(rendered).to.equal(`<div a;b="1" a🧙‍b="1"></div>`);
85+
});
86+
});
87+
88+
it('should throw for invalid nodeName values', () => {
89+
expect(() => render(h('div'))).not.to.throw();
90+
expect(() => render(h('x-💩'))).not.to.throw();
91+
expect(() => render(h('a b'))).to.throw(/<a b>/);
92+
expect(() => render(h('a\0b'))).to.throw(/<a\0b>/);
93+
expect(() => render(h('a>'))).to.throw(/<a>>/);
94+
expect(() => render(h('<'))).to.throw(/<<>/);
95+
expect(() => render(h('"'))).to.throw(/<">/);
96+
});
97+
5998
it('should collapse collapsible attributes', () => {
6099
let rendered = render(<div class="" style="" foo={true} bar />),
61100
expected = `<div class style foo bar></div>`;

0 commit comments

Comments
 (0)