Skip to content

Commit f7ec9e1

Browse files
committed
Fix 245
1 parent ffaee29 commit f7ec9e1

File tree

2 files changed

+360
-5
lines changed

2 files changed

+360
-5
lines changed

src/partialHydration/__tests__/mountComponentsInHtml.spec.ts

Lines changed: 357 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,366 @@ describe('#mountComponentsInHtml', () => {
2424
expect(replaceSpecialCharacters('{"nh_count":15966,"classes":"mt-3"}')).toEqual(
2525
'{"nh_count":15966,"classes":"mt-3"}',
2626
);
27-
expect(replaceSpecialCharacters('&quot;&lt;&gt;&#39;&quot;\\n\\\\n\\"&amp;')).toEqual('"<>\'"\\n\\n"&');
27+
expect(replaceSpecialCharacters('&quot;&lt;&gt;&#39;&quot;\\n\\\\n\\"&amp;')).toEqual('"<>\'"\\n\\\\n\\"&');
2828
expect(replaceSpecialCharacters('abcd 1234 <&""&>')).toEqual('abcd 1234 <&""&>');
2929
});
3030

31+
it('#replaceSpecialCharacters and escapeHtml should return same result', () => {
32+
// eslint-disable-next-line global-require
33+
const { replaceSpecialCharacters } = require('../mountComponentsInHtml');
34+
// eslint-disable-next-line global-require
35+
const { escapeHtml } = require('../inlineSvelteComponent');
36+
const start = '{"prop":"This is a string with \\"escaped\\" quotes"}';
37+
const escaped = escapeHtml(start);
38+
const replaced = replaceSpecialCharacters(escaped);
39+
expect(start).toEqual(replaced);
40+
});
41+
42+
it('#replaceSpecialCharacters and escapeHtml should return same result. #245', () => {
43+
// eslint-disable-next-line global-require
44+
const { replaceSpecialCharacters } = require('../mountComponentsInHtml');
45+
// eslint-disable-next-line global-require
46+
const { escapeHtml } = require('../inlineSvelteComponent');
47+
48+
const naughtyObjsOrStrings = [
49+
{
50+
type: 'auditAdvisory',
51+
data: {
52+
resolution: { id: 566, path: 'hoek', dev: false, optional: false, bundled: false },
53+
advisory: {
54+
findings: [{ version: '2.16.3', paths: ['hoek'], dev: false, optional: false, bundled: false }],
55+
id: 566,
56+
created: '2018-04-20T21:25:58.421Z',
57+
updated: '2019-02-14T16:00:33.922Z',
58+
deleted: null,
59+
title: 'Prototype Pollution',
60+
found_by: { name: 'HoLyVieR' },
61+
reported_by: { name: 'HoLyVieR' },
62+
module_name: 'hoek',
63+
cves: [],
64+
vulnerable_versions: '<= 4.2.0 || >= 5.0.0 < 5.0.3',
65+
patched_versions: '> 4.2.0 < 5.0.0 || >= 5.0.3',
66+
overview:
67+
'Versions of `hoek` prior to 4.2.1 and 5.0.3 are vulnerable to prototype pollution.\n\nThe `merge` function, and the `applyToDefaults` and `applyToDefaultsWithShallow` functions which leverage `merge` behind the scenes, are vulnerable to a prototype pollution attack when provided an _unvalidated_ payload created from a JSON string containing the `__proto__` property.\n\nThis can be demonstrated like so:\n\n```javascript\nvar Hoek = require(\'hoek\');\nvar malicious_payload = \'{"__proto__":{"oops":"It works !"}}\';\n\nvar a = {};\nconsole.log("Before : " + a.oops);\nHoek.merge({}, JSON.parse(malicious_payload));\nconsole.log("After : " + a.oops);\n```\n\nThis type of attack can be used to overwrite existing properties causing a potential denial of service.',
68+
recommendation: 'Update to version 4.2.1, 5.0.3 or later.',
69+
references: '',
70+
access: 'public',
71+
severity: 'moderate',
72+
cwe: 'CWE-471',
73+
metadata: { module_type: '', exploitability: 5, affected_components: '' },
74+
url: 'https://npmjs.com/advisories/566',
75+
},
76+
},
77+
},
78+
'OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5',
79+
'OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5',
80+
'TmFO',
81+
'SW5maW5pdHk=',
82+
'LUluZmluaXR5',
83+
'SU5G',
84+
'MSNJTkY=',
85+
'LTEjSU5E',
86+
'MSNRTkFO',
87+
'MSNTTkFO',
88+
'MSNJTkQ=',
89+
'MHgw',
90+
'MHhmZmZmZmZmZg==',
91+
'MHhmZmZmZmZmZmZmZmZmZmZm',
92+
'MHhhYmFkMWRlYQ==',
93+
'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5',
94+
'MSwwMDAuMDA=',
95+
'MSAwMDAuMDA=',
96+
'MScwMDAuMDA=',
97+
'MSwwMDAsMDAwLjAw',
98+
'MSAwMDAgMDAwLjAw',
99+
'MScwMDAnMDAwLjAw',
100+
'MS4wMDAsMDA=',
101+
'MSAwMDAsMDA=',
102+
'MScwMDAsMDA=',
103+
'MS4wMDAuMDAwLDAw',
104+
'MSAwMDAgMDAwLDAw',
105+
'MScwMDAnMDAwLDAw',
106+
'MDEwMDA=',
107+
'MDg=',
108+
'MDk=',
109+
'Mi4yMjUwNzM4NTg1MDcyMDExZS0zMDg=',
110+
'LC4vOydbXS09',
111+
'PD4/OiJ7fXxfKw==',
112+
'IUAjJCVeJiooKWB+',
113+
'AQIDBAUGBwgODxAREhMUFRYXGBkaGxwdHh9/',
114+
'woDCgcKCwoPChMKGwofCiMKJworCi8KMwo3CjsKPwpDCkcKSwpPClMKVwpbCl8KYwpnCmsKbwpzC',
115+
'ncKewp8=',
116+
'CwwgwoXCoOGagOKAgOKAgeKAguKAg+KAhOKAheKAhuKAh+KAiOKAieKAiuKAi+KAqOKAqeKAr+KB',
117+
'n+OAgA==',
118+
'wq3YgNiB2ILYg9iE2IXYnNud3I/hoI7igIvigIzigI3igI7igI/igKrigKvigKzigK3igK7igaDi',
119+
'gaHigaLigaPigaTigabigafigajiganigarigavigaziga3iga7iga/vu7/vv7nvv7rvv7vwkYK9',
120+
'8JuyoPCbsqHwm7Ki8Juyo/CdhbPwnYW08J2FtfCdhbbwnYW38J2FuPCdhbnwnYW686CAgfOggKDz',
121+
'oICh86CAovOggKPzoICk86CApfOggKbzoICn86CAqPOggKnzoICq86CAq/OggKzzoICt86CArvOg',
122+
'gK/zoICw86CAsfOggLLzoICz86CAtPOggLXzoIC286CAt/OggLjzoIC586CAuvOggLvzoIC886CA',
123+
'vfOggL7zoIC/86CBgPOggYHzoIGC86CBg/OggYTzoIGF86CBhvOggYfzoIGI86CBifOggYrzoIGL',
124+
'86CBjPOggY3zoIGO86CBj/OggZDzoIGR86CBkvOggZPzoIGU86CBlfOggZbzoIGX86CBmPOggZnz',
125+
'oIGa86CBm/OggZzzoIGd86CBnvOggZ/zoIGg86CBofOggaLzoIGj86CBpPOggaXzoIGm86CBp/Og',
126+
'gajzoIGp86CBqvOggavzoIGs86CBrfOgga7zoIGv86CBsPOggbHzoIGy86CBs/OggbTzoIG186CB',
127+
'tvOggbfzoIG486CBufOggbrzoIG786CBvPOggb3zoIG+86CBvw==',
128+
'77u/',
129+
'77++',
130+
'zqniiYjDp+KImuKIq8ucwrXiiaTiiaXDtw==',
131+
'w6XDn+KIgsaSwqnLmeKIhsuawqzigKbDpg==',
132+
'xZPiiJHCtMKu4oCgwqXCqMuGw7jPgOKAnOKAmA==',
133+
'wqHihKLCo8Ki4oiewqfCtuKAosKqwrrigJPiiaA=',
134+
'wrjLm8OH4peKxLHLnMOCwq/LmMK/',
135+
'w4XDjcOOw4/LncOTw5Tvo7/DksOaw4bimIM=',
136+
'xZLigJ7CtOKAsMuHw4HCqMuGw5jiiI/igJ3igJk=',
137+
'YOKBhOKCrOKAueKAuu+sge+sguKAocKwwrfigJrigJTCsQ==',
138+
'4oWb4oWc4oWd4oWe',
139+
'undefined',
140+
'undef',
141+
'null',
142+
'NULL',
143+
'(null)',
144+
'nil',
145+
'NIL',
146+
'true',
147+
'false',
148+
'True',
149+
'False',
150+
'TRUE',
151+
'FALSE',
152+
'None',
153+
'hasOwnProperty',
154+
'\\',
155+
'\\\\',
156+
'0',
157+
'1',
158+
'1.00',
159+
'$1.00',
160+
'1/2',
161+
'1E2',
162+
'1E02',
163+
'1E+02',
164+
'-1',
165+
'-1.00',
166+
'-$1.00',
167+
'-1/2',
168+
'-1E2',
169+
'-1E02',
170+
'-1E+02',
171+
'1/0',
172+
'0/0',
173+
'-2147483648/-1',
174+
'-9223372036854775808/-1',
175+
'-0',
176+
'-0.0',
177+
'+0',
178+
'+0.0',
179+
'0.00',
180+
'0..0',
181+
'.',
182+
'0.0.0',
183+
'0,00',
184+
'0,,0',
185+
',',
186+
'0,0,0',
187+
'0.0/0',
188+
'1.0/0.0',
189+
'0.0/0.0',
190+
'1,0/0,0',
191+
'0,0/0,0',
192+
'--1',
193+
'-',
194+
'-.',
195+
'-,',
196+
'999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999',
197+
'NaN',
198+
'Infinity',
199+
'-Infinity',
200+
'INF',
201+
'1#INF',
202+
'-1#IND',
203+
'1#QNAN',
204+
'1#SNAN',
205+
'1#IND',
206+
'0x0',
207+
'0xffffffff',
208+
'0xffffffffffffffff',
209+
'0xabad1dea',
210+
'123456789012345678901234567890123456789',
211+
'1,000.00',
212+
'1 000.00',
213+
"1'000.00",
214+
'1,000,000.00',
215+
'1 000 000.00',
216+
"1'000'000.00",
217+
'1.000,00',
218+
'1 000,00',
219+
"1'000,00",
220+
'1.000.000,00',
221+
'1 000 000,00',
222+
"1'000'000,00",
223+
'01000',
224+
'08',
225+
'09',
226+
'2.2250738585072011e-308',
227+
",./;'[]\\-=",
228+
'<>?:"{}|_+',
229+
'\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f',
230+
'€‚ƒ„†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ',
231+
'The quick brown fox jumps over the lazy dog',
232+
'𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠',
233+
'𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌',
234+
'𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈',
235+
'𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰',
236+
'𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘',
237+
'𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐',
238+
'⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢',
239+
'<script>alert(123)</script>',
240+
'&lt;script&gt;alert(&#39;123&#39;);&lt;/script&gt;',
241+
'<img src=x onerror=alert(123) />',
242+
'<svg><script>123<1>alert(123)</script>',
243+
'"><script>alert(123)</script>',
244+
"'><script>alert(123)</script>",
245+
'><script>alert(123)</script>',
246+
'</script><script>alert(123)</script>',
247+
'< / script >< script >alert(123)< / script >',
248+
' onfocus=JaVaSCript:alert(123) autofocus',
249+
'" onfocus=JaVaSCript:alert(123) autofocus',
250+
"' onfocus=JaVaSCript:alert(123) autofocus",
251+
'<script>alert(123)</script>',
252+
'<sc<script>ript>alert(123)</sc</script>ript>',
253+
'--><script>alert(123)</script>',
254+
'";alert(123);t="',
255+
"';alert(123);t='",
256+
// eslint-disable-next-line no-script-url
257+
'JavaSCript:alert(123)',
258+
';alert(123);',
259+
'src=JaVaSCript:prompt(132)',
260+
'"><script>alert(123);</script x="',
261+
"'><script>alert(123);</script x='",
262+
'><script>alert(123);</script x=',
263+
'" autofocus onkeyup="javascript:alert(123)',
264+
"' autofocus onkeyup='javascript:alert(123)",
265+
'<script\\x20type="text/javascript">javascript:alert(1);</script>',
266+
'<script\\x3Etype="text/javascript">javascript:alert(1);</script>',
267+
'<script\\x0Dtype="text/javascript">javascript:alert(1);</script>',
268+
'<script\\x09type="text/javascript">javascript:alert(1);</script>',
269+
'<script\\x0Ctype="text/javascript">javascript:alert(1);</script>',
270+
'<script\\x2Ftype="text/javascript">javascript:alert(1);</script>',
271+
'<script\\x0Atype="text/javascript">javascript:alert(1);</script>',
272+
'ABC<div style="x\\x3Aexpression(javascript:alert(1)">DEF',
273+
'ABC<div style="x:expression\\x5C(javascript:alert(1)">DEF',
274+
'ABC<div style="x:expression\\x00(javascript:alert(1)">DEF',
275+
'ABC<div style="x:exp\\x00ression(javascript:alert(1)">DEF',
276+
'ABC<div style="x:exp\\x5Cression(javascript:alert(1)">DEF',
277+
'ABC<div style="x:\\x0Aexpression(javascript:alert(1)">DEF',
278+
'ABC<div style="x:\\x09expression(javascript:alert(1)">DEF',
279+
'ABC<div style="x:\\xE3\\x80\\x80expression(javascript:alert(1)">',
280+
'-',
281+
'--',
282+
'--version',
283+
'--help',
284+
'$USER',
285+
'/dev/null; touch /tmp/blns.fail ; echo',
286+
'$(touch /tmp/blns.fail)',
287+
'@{[system "touch /tmp/blns.fail"]}',
288+
'eval("puts \'hello world\'")',
289+
'System("ls -al /")',
290+
'Kernel.exec("ls -al /")',
291+
'Kernel.exit(1)',
292+
"%x('ls -al /')",
293+
'<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>',
294+
'$HOME',
295+
"$ENV{'HOME'}",
296+
'%d',
297+
'%s%s%s%s%s',
298+
'{0}',
299+
'%*.*s',
300+
'%@',
301+
'%n',
302+
'File:///',
303+
'../../../../../../../../../../../etc/passwd%00',
304+
'../../../../../../../../../../../etc/hosts',
305+
'() { 0; }; touch /tmp/blns.shellshock1.fail;',
306+
'() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }',
307+
"<<< %s(un='%s') = %u",
308+
'+++ATH0',
309+
'CON',
310+
'PRN',
311+
'AUX',
312+
'CLOCK$',
313+
'NUL',
314+
'A:',
315+
'ZZ:',
316+
'COM1',
317+
'LPT1',
318+
'LPT2',
319+
'LPT3',
320+
'COM2',
321+
'COM3',
322+
'COM4',
323+
'DCC SEND STARTKEYLOGGER 0 0 0',
324+
'Scunthorpe General Hospital',
325+
'Penistone Community Church',
326+
'Lightwater Country Park',
327+
'Jimmy Clitheroe',
328+
'Horniman Museum',
329+
'shitake mushrooms',
330+
'RomansInSussex.co.uk',
331+
'http://www.cum.qc.ca/',
332+
'Craig Cockburn, Software Specialist',
333+
'Linda Callahan',
334+
'Dr. Herman I. Libshitz',
335+
'magna cum laude',
336+
'Super Bowl XXX',
337+
'medieval erection of parapets',
338+
'evaluate',
339+
'mocha',
340+
'expression',
341+
'Arsenal canal',
342+
'classic',
343+
'Tyson Gay',
344+
'Dick Van Dyke',
345+
'basement',
346+
"If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.",
347+
'Roses are \u001b[0;31mred\u001b[0m, violets are \u001b[0;34mblue. Hope you enjoy terminal hue',
348+
'But now...\u001b[20Cfor my greatest trick...\u001b[8m',
349+
'The quic\b\b\b\b\b\bk brown fo\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007x... [Beeeep]',
350+
'Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗',
351+
'🏳0🌈️',
352+
'### No JSON flag' +
353+
'```' +
354+
`{
355+
"hello": "world" // This is a comment
356+
}` +
357+
'```' +
358+
'### `json` flag' +
359+
'```json' +
360+
`
361+
{
362+
"hello": "world" // This is a comment
363+
}
364+
` +
365+
'```' +
366+
'### `jsonc` flag' +
367+
'```jsonc' +
368+
`{
369+
"hello": "world" // This is a comment
370+
}` +
371+
'```' +
372+
'### `json5` flag' +
373+
'```json5' +
374+
`{
375+
"hello": "world" // This is a comment
376+
}` +
377+
'```',
378+
];
379+
380+
for (const start of naughtyObjsOrStrings) {
381+
const escaped = escapeHtml(JSON.stringify(start));
382+
const replaced = JSON.parse(replaceSpecialCharacters(escaped));
383+
expect(start).toEqual(replaced);
384+
}
385+
});
386+
31387
it('mounts a single component in HTML correctly', () => {
32388
hydrated = [];
33389
// eslint-disable-next-line global-require

src/partialHydration/mountComponentsInHtml.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import svelteComponent from '../utils/svelteComponent';
22

33
export const replaceSpecialCharacters = (str) =>
44
str
5-
.replace(/\\\\n/gim, '\\n')
65
.replace(/&quot;/gim, '"')
76
.replace(/&lt;/gim, '<')
87
.replace(/&gt;/gim, '>')
98
.replace(/&#39;/gim, "'")
10-
.replace(/\\"/gim, '"')
9+
.replace(/&#039;/gim, "'")
1110
.replace(/&amp;/gim, '&');
1211

1312
export default function mountComponentsInHtml({ page, html, hydrateOptions }): string {
@@ -25,12 +24,12 @@ export default function mountComponentsInHtml({ page, html, hydrateOptions }): s
2524
try {
2625
hydrateComponentProps = JSON.parse(replaceSpecialCharacters(match[3]));
2726
} catch (e) {
28-
throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[3]}`);
27+
throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${replaceSpecialCharacters(match[3])}`);
2928
}
3029
try {
3130
hydrateComponentOptions = JSON.parse(replaceSpecialCharacters(match[4]));
3231
} catch (e) {
33-
throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${match[4]}`);
32+
throw new Error(`Failed to JSON.parse props for ${hydrateComponentName} ${replaceSpecialCharacters(match[4])}`);
3433
}
3534

3635
if (hydrateOptions) {

0 commit comments

Comments
 (0)