19
let ReactDOMServer;
20
let ReactTestUtils;
21
22
-function runTests(itRenders, itRejectsRendering, expectToReject) {
22
+const EXPECTED_SAFE_URL =
23
+ "javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')";
24
+
25
+describe('ReactDOMServerIntegration - Untrusted URLs', () => {
26
+ // The `itRenders` helpers don't work with the gate pragma, so we have to do
27
+ // this instead.
28
+ if (gate(flags => flags.disableJavaScriptURLs)) {
29
+ it("empty test so Jest doesn't complain", () => {});
30
+ return;
31
+ }
32
+
33
+ function initModules() {
34
+ jest.resetModules();
35
+ React = require('react');
36
+ ReactDOM = require('react-dom');
37
+ ReactDOMServer = require('react-dom/server');
38
+ ReactTestUtils = require('react-dom/test-utils');
39
+
40
+ // Make them available to the helpers.
41
+ return {
42
+ ReactDOM,
43
+ ReactDOMServer,
44
+ ReactTestUtils,
45
+ };
46
+ }
47
+
48
+ const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
49
+
50
+ beforeEach(() => {
51
+ resetModules();
52
+ });
53
+
54
itRenders('a http link with the word javascript in it', async render => {
55
const e = await render(
56
<a href="http://javascript:0/thisisfine">Click me</a>,
59
expect(e.href).toBe('http://javascript:0/thisisfine');
60
});
61
31
- itRejectsRendering('a javascript protocol href', async render => {
62
+ itRenders('a javascript protocol href', async render => {
63
// Only the first one warns. The second warning is deduped.
64
const e = await render(
65
<div>
72
expect(e.lastChild.href).toBe('javascript:notfineagain');
73
});
74
44
- itRejectsRendering(
45
- 'a javascript protocol with leading spaces',
46
- async render => {
47
- const e = await render(
48
- <a href={' \t \u0000\u001F\u0003javascript\n: notfine'}>p0wned</a>,
49
- 1,
50
- );
51
- // We use an approximate comparison here because JSDOM might not parse
52
- // \u0000 in HTML properly.
53
- expect(e.href).toContain('notfine');
54
- },
55
- );
75
+ itRenders('a javascript protocol with leading spaces', async render => {
76
+ const e = await render(
77
+ <a href={' \t \u0000\u001F\u0003javascript\n: notfine'}>p0wned</a>,
78
+ 1,
79
+ );
80
+ // We use an approximate comparison here because JSDOM might not parse
81
+ // \u0000 in HTML properly.
82
+ expect(e.href).toContain('notfine');
83
+ });
84
57
- itRejectsRendering(
85
+ itRenders(
86
'a javascript protocol with intermediate new lines and mixed casing',
87
async render => {
88
const e = await render(
93
},
94
);
95
68
- itRejectsRendering('a javascript protocol area href', async render => {
96
+ itRenders('a javascript protocol area href', async render => {
97
const e = await render(
98
<map>
99
<area href="javascript:notfine" />
103
expect(e.firstChild.href).toBe('javascript:notfine');
104
});
105
78
- itRejectsRendering('a javascript protocol form action', async render => {
106
+ itRenders('a javascript protocol form action', async render => {
107
const e = await render(<form action="javascript:notfine">p0wned</form>, 1);
108
expect(e.action).toBe('javascript:notfine');
109
});
110
83
- itRejectsRendering(
84
- 'a javascript protocol button formAction',
85
- async render => {
86
- const e = await render(<input formAction="javascript:notfine" />, 1);
87
- expect(e.getAttribute('formAction')).toBe('javascript:notfine');
88
- },
89
- );
111
+ itRenders('a javascript protocol button formAction', async render => {
112
+ const e = await render(<input formAction="javascript:notfine" />, 1);
113
+ expect(e.getAttribute('formAction')).toBe('javascript:notfine');
114
+ });
115
91
- itRejectsRendering('a javascript protocol input formAction', async render => {
116
+ itRenders('a javascript protocol input formAction', async render => {
117
const e = await render(
118
<button formAction="javascript:notfine">p0wned</button>,
119
1,
121
expect(e.getAttribute('formAction')).toBe('javascript:notfine');
122
});
123
99
- itRejectsRendering('a javascript protocol iframe src', async render => {
124
+ itRenders('a javascript protocol iframe src', async render => {
125
const e = await render(<iframe src="javascript:notfine" />, 1);
126
expect(e.src).toBe('javascript:notfine');
127
});
128
104
- itRejectsRendering('a javascript protocol frame src', async render => {
129
+ itRenders('a javascript protocol frame src', async render => {
130
const e = await render(
131
<html>
132
<head />
139
expect(e.lastChild.firstChild.src).toBe('javascript:notfine');
140
});
141
117
- itRejectsRendering('a javascript protocol in an SVG link', async render => {
142
+ itRenders('a javascript protocol in an SVG link', async render => {
143
const e = await render(
144
<svg>
145
<a href="javascript:notfine" />
149
expect(e.firstChild.getAttribute('href')).toBe('javascript:notfine');
150
});
151
127
- itRejectsRendering(
152
+ itRenders(
153
'a javascript protocol in an SVG link with a namespace',
154
async render => {
155
const e = await render(
167
it('rejects a javascript protocol href if it is added during an update', () => {
168
const container = document.createElement('div');
169
ReactDOM.render(<a href="thisisfine">click me</a>, container);
145
- expectToReject(() => {
170
+ expect(() => {
171
ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
147
- });
148
- });
149
-}
150
-
151
-describe('ReactDOMServerIntegration - Untrusted URLs', () => {
152
- // The `itRenders` helpers don't work with the gate pragma, so we have to do
153
- // this instead.
154
- if (gate(flags => flags.disableJavaScriptURLs)) {
155
- it("empty test so Jest doesn't complain", () => {});
156
- return;
157
- }
158
-
159
- function initModules() {
160
- jest.resetModules();
161
- React = require('react');
162
- ReactDOM = require('react-dom');
163
- ReactDOMServer = require('react-dom/server');
164
- ReactTestUtils = require('react-dom/test-utils');
165
-
166
- // Make them available to the helpers.
167
- return {
168
- ReactDOM,
169
- ReactDOMServer,
170
- ReactTestUtils,
171
- };
172
- }
173
-
174
- const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
175
-
176
- beforeEach(() => {
177
- resetModules();
178
- });
179
-
180
- runTests(itRenders, itRenders, fn =>
181
- expect(fn).toErrorDev(
172
+ }).toErrorDev(
173
'Warning: A future version of React will block javascript: URLs as a security precaution. ' +
174
'Use event handlers instead if you can. If you need to generate unsafe HTML try using ' +
175
'dangerouslySetInnerHTML instead. React was passed "javascript:notfine".\n' +
176
' in a (at **)',
186
- ),
187
- );
177
+ );
178
+ });
179
});
180
181
describe('ReactDOMServerIntegration - Untrusted URLs - disableJavaScriptURLs', () => {
207
const {
208
resetModules,
209
itRenders,
219
- itThrowsWhenRendering,
210
clientRenderOnBadMarkup,
211
clientRenderOnServerString,
212
} = ReactDOMServerIntegrationUtils(initModules);
213
224
- const expectToReject = fn => {
225
- let msg;
226
- try {
227
- fn();
228
- } catch (x) {
229
- msg = x.message;
230
- }
231
- expect(msg).toContain(
232
- 'React has blocked a javascript: URL as a security precaution.',
233
- );
234
- };
235
-
214
beforeEach(() => {
215
resetModules();
216
});
217
240
- runTests(
241
- itRenders,
242
- (message, test) =>
243
- itThrowsWhenRendering(message, test, 'blocked a javascript: URL'),
244
- expectToReject,
218
+ itRenders('a http link with the word javascript in it', async render => {
219
+ const e = await render(
220
+ <a href="http://javascript:0/thisisfine">Click me</a>,
221
+ );
222
+ expect(e.tagName).toBe('A');
223
+ expect(e.href).toBe('http://javascript:0/thisisfine');
224
+ });
225
+
226
+ itRenders('a javascript protocol href', async render => {
227
+ // Only the first one warns. The second warning is deduped.
228
+ const e = await render(
229
+ <div>
230
+ <a href="javascript:notfine">p0wned</a>
231
+ <a href="javascript:notfineagain">p0wned again</a>
232
+ </div>,
233
+ );
234
+ expect(e.firstChild.href).toBe(EXPECTED_SAFE_URL);
235
+ expect(e.lastChild.href).toBe(EXPECTED_SAFE_URL);
236
+ });
237
+
238
+ itRenders('a javascript protocol with leading spaces', async render => {
239
+ const e = await render(
240
+ <a href={' \t \u0000\u001F\u0003javascript\n: notfine'}>p0wned</a>,
241
+ );
242
+ // We use an approximate comparison here because JSDOM might not parse
243
+ // \u0000 in HTML properly.
244
+ expect(e.href).toBe(EXPECTED_SAFE_URL);
245
+ });
246
+
247
+ itRenders(
248
+ 'a javascript protocol with intermediate new lines and mixed casing',
249
+ async render => {
250
+ const e = await render(
251
+ <a href={'\t\r\n Jav\rasCr\r\niP\t\n\rt\n:notfine'}>p0wned</a>,
252
+ );
253
+ expect(e.href).toBe(EXPECTED_SAFE_URL);
254
+ },
255
);
256
257
+ itRenders('a javascript protocol area href', async render => {
258
+ const e = await render(
259
+ <map>
260
+ <area href="javascript:notfine" />
261
+ </map>,
262
+ );
263
+ expect(e.firstChild.href).toBe(EXPECTED_SAFE_URL);
264
+ });
265
+
266
+ itRenders('a javascript protocol form action', async render => {
267
+ const e = await render(<form action="javascript:notfine">p0wned</form>);
268
+ expect(e.action).toBe(EXPECTED_SAFE_URL);
269
+ });
270
+
271
+ itRenders('a javascript protocol button formAction', async render => {
272
+ const e = await render(<input formAction="javascript:notfine" />);
273
+ expect(e.getAttribute('formAction')).toBe(EXPECTED_SAFE_URL);
274
+ });
275
+
276
+ itRenders('a javascript protocol input formAction', async render => {
277
+ const e = await render(
278
+ <button formAction="javascript:notfine">p0wned</button>,
279
+ );
280
+ expect(e.getAttribute('formAction')).toBe(EXPECTED_SAFE_URL);
281
+ });
282
+
283
+ itRenders('a javascript protocol iframe src', async render => {
284
+ const e = await render(<iframe src="javascript:notfine" />);
285
+ expect(e.src).toBe(EXPECTED_SAFE_URL);
286
+ });
287
+
288
+ itRenders('a javascript protocol frame src', async render => {
289
+ const e = await render(
290
+ <html>
291
+ <head />
292
+ <frameset>
293
+ <frame src="javascript:notfine" />
294
+ </frameset>
295
+ </html>,
296
+ );
297
+ expect(e.lastChild.firstChild.src).toBe(EXPECTED_SAFE_URL);
298
+ });
299
+
300
+ itRenders('a javascript protocol in an SVG link', async render => {
301
+ const e = await render(
302
+ <svg>
303
+ <a href="javascript:notfine" />
304
+ </svg>,
305
+ );
306
+ expect(e.firstChild.getAttribute('href')).toBe(EXPECTED_SAFE_URL);
307
+ });
308
+
309
+ itRenders(
310
+ 'a javascript protocol in an SVG link with a namespace',
311
+ async render => {
312
+ const e = await render(
313
+ <svg>
314
+ <a xlinkHref="javascript:notfine" />
315
+ </svg>,
316
+ );
317
+ expect(
318
+ e.firstChild.getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
319
+ ).toBe(EXPECTED_SAFE_URL);
320
+ },
321
+ );
322
+
323
+ it('rejects a javascript protocol href if it is added during an update', () => {
324
+ const container = document.createElement('div');
325
+ ReactDOM.render(<a href="http://thisisfine/">click me</a>, container);
326
+ expect(container.firstChild.href).toBe('http://thisisfine/');
327
+ ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
328
+ expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
329
+ });
330
+
331
itRenders('only the first invocation of toString', async render => {
332
let expectedToStringCalls = 1;
333
if (render === clientRenderOnBadMarkup) {
339
// The hydration validation calls it one extra time.
340
// TODO: It would be good if we only called toString once for
341
// consistency but the code structure makes that hard right now.
258
- expectedToStringCalls = 2;
259
- }
260
- if (__DEV__) {
342
+ expectedToStringCalls = 5;
343
+ } else if (__DEV__) {
344
// Checking for string coercion problems results in double the
345
// toString calls in DEV
346
expectedToStringCalls *= 2;
366
367
it('rejects a javascript protocol href if it is added during an update twice', () => {
368
const container = document.createElement('div');
286
- ReactDOM.render(<a href="thisisfine">click me</a>, container);
287
- expectToReject(() => {
288
- ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
289
- });
369
+ ReactDOM.render(<a href="http://thisisfine/">click me</a>, container);
370
+ expect(container.firstChild.href).toBe('http://thisisfine/');
371
+ ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
372
+ expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
373
// The second update ensures that a global flag hasn't been added to the regex
374
// which would fail to match the second time it is called.
292
- expectToReject(() => {
293
- ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
294
- });
375
+ ReactDOM.render(<a href="javascript:notfine">click me</a>, container);
376
+ expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
377
});
378
});