mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
30e2938e04
## Overview Sets `resetModules: true` in the base Jest config, and deletes all the `jest.resetModule()` calls we don't need.
402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
* @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
|
|
*/
|
|
|
|
/* eslint-disable no-script-url */
|
|
|
|
'use strict';
|
|
|
|
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
|
|
|
|
let React;
|
|
let ReactDOMClient;
|
|
let ReactDOMServer;
|
|
let ReactTestUtils;
|
|
let act;
|
|
|
|
const EXPECTED_SAFE_URL =
|
|
"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')";
|
|
|
|
describe('ReactDOMServerIntegration - Untrusted URLs', () => {
|
|
// The `itRenders` helpers don't work with the gate pragma, so we have to do
|
|
// this instead.
|
|
if (gate(flags => flags.disableJavaScriptURLs)) {
|
|
it("empty test so Jest doesn't complain", () => {});
|
|
return;
|
|
}
|
|
|
|
function initModules() {
|
|
React = require('react');
|
|
ReactDOMClient = require('react-dom/client');
|
|
ReactDOMServer = require('react-dom/server');
|
|
ReactTestUtils = require('react-dom/test-utils');
|
|
act = require('internal-test-utils').act;
|
|
|
|
// Make them available to the helpers.
|
|
return {
|
|
ReactDOMClient,
|
|
ReactDOMServer,
|
|
ReactTestUtils,
|
|
};
|
|
}
|
|
|
|
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
|
|
|
|
beforeEach(() => {
|
|
resetModules();
|
|
});
|
|
|
|
itRenders('a http link with the word javascript in it', async render => {
|
|
const e = await render(
|
|
<a href="http://javascript:0/thisisfine">Click me</a>,
|
|
);
|
|
expect(e.tagName).toBe('A');
|
|
expect(e.href).toBe('http://javascript:0/thisisfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol href', async render => {
|
|
// Only the first one warns. The second warning is deduped.
|
|
const e = await render(
|
|
<div>
|
|
<a href="javascript:notfine">p0wned</a>
|
|
<a href="javascript:notfineagain">p0wned again</a>
|
|
</div>,
|
|
1,
|
|
);
|
|
expect(e.firstChild.href).toBe('javascript:notfine');
|
|
expect(e.lastChild.href).toBe('javascript:notfineagain');
|
|
});
|
|
|
|
itRenders('a javascript protocol with leading spaces', async render => {
|
|
const e = await render(
|
|
<a href={' \t \u0000\u001F\u0003javascript\n: notfine'}>p0wned</a>,
|
|
1,
|
|
);
|
|
// We use an approximate comparison here because JSDOM might not parse
|
|
// \u0000 in HTML properly.
|
|
expect(e.href).toContain('notfine');
|
|
});
|
|
|
|
itRenders(
|
|
'a javascript protocol with intermediate new lines and mixed casing',
|
|
async render => {
|
|
const e = await render(
|
|
<a href={'\t\r\n Jav\rasCr\r\niP\t\n\rt\n:notfine'}>p0wned</a>,
|
|
1,
|
|
);
|
|
expect(e.href).toBe('javascript:notfine');
|
|
},
|
|
);
|
|
|
|
itRenders('a javascript protocol area href', async render => {
|
|
const e = await render(
|
|
<map>
|
|
<area href="javascript:notfine" />
|
|
</map>,
|
|
1,
|
|
);
|
|
expect(e.firstChild.href).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol form action', async render => {
|
|
const e = await render(<form action="javascript:notfine">p0wned</form>, 1);
|
|
expect(e.action).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol input formAction', async render => {
|
|
const e = await render(
|
|
<input type="submit" formAction="javascript:notfine" />,
|
|
1,
|
|
);
|
|
expect(e.getAttribute('formAction')).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol button formAction', async render => {
|
|
const e = await render(
|
|
<button formAction="javascript:notfine">p0wned</button>,
|
|
1,
|
|
);
|
|
expect(e.getAttribute('formAction')).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol iframe src', async render => {
|
|
const e = await render(<iframe src="javascript:notfine" />, 1);
|
|
expect(e.src).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol frame src', async render => {
|
|
const e = await render(
|
|
<html>
|
|
<head />
|
|
<frameset>
|
|
<frame src="javascript:notfine" />
|
|
</frameset>
|
|
</html>,
|
|
1,
|
|
);
|
|
expect(e.lastChild.firstChild.src).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol in an SVG link', async render => {
|
|
const e = await render(
|
|
<svg>
|
|
<a href="javascript:notfine" />
|
|
</svg>,
|
|
1,
|
|
);
|
|
expect(e.firstChild.getAttribute('href')).toBe('javascript:notfine');
|
|
});
|
|
|
|
itRenders(
|
|
'a javascript protocol in an SVG link with a namespace',
|
|
async render => {
|
|
const e = await render(
|
|
<svg>
|
|
<a xlinkHref="javascript:notfine" />
|
|
</svg>,
|
|
1,
|
|
);
|
|
expect(
|
|
e.firstChild.getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
|
|
).toBe('javascript:notfine');
|
|
},
|
|
);
|
|
|
|
it('rejects a javascript protocol href if it is added during an update', async () => {
|
|
const container = document.createElement('div');
|
|
const root = ReactDOMClient.createRoot(container);
|
|
await act(async () => {
|
|
root.render(<a href="thisisfine">click me</a>);
|
|
});
|
|
await expect(async () => {
|
|
await act(() => {
|
|
root.render(<a href="javascript:notfine">click me</a>);
|
|
});
|
|
}).toErrorDev(
|
|
'Warning: A future version of React will block javascript: URLs as a security precaution. ' +
|
|
'Use event handlers instead if you can. If you need to generate unsafe HTML try using ' +
|
|
'dangerouslySetInnerHTML instead. React was passed "javascript:notfine".\n' +
|
|
' in a (at **)',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('ReactDOMServerIntegration - Untrusted URLs - disableJavaScriptURLs', () => {
|
|
// The `itRenders` helpers don't work with the gate pragma, so we have to do
|
|
// this instead.
|
|
if (gate(flags => !flags.disableJavaScriptURLs)) {
|
|
it("empty test so Jest doesn't complain", () => {});
|
|
return;
|
|
}
|
|
|
|
function initModules() {
|
|
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
|
ReactFeatureFlags.disableJavaScriptURLs = true;
|
|
|
|
React = require('react');
|
|
ReactDOMClient = require('react-dom/client');
|
|
ReactDOMServer = require('react-dom/server');
|
|
ReactTestUtils = require('react-dom/test-utils');
|
|
act = require('internal-test-utils').act;
|
|
|
|
// Make them available to the helpers.
|
|
return {
|
|
ReactDOMClient,
|
|
ReactDOMServer,
|
|
ReactTestUtils,
|
|
};
|
|
}
|
|
|
|
const {
|
|
resetModules,
|
|
itRenders,
|
|
clientRenderOnBadMarkup,
|
|
clientRenderOnServerString,
|
|
} = ReactDOMServerIntegrationUtils(initModules);
|
|
|
|
beforeEach(() => {
|
|
resetModules();
|
|
});
|
|
|
|
itRenders('a http link with the word javascript in it', async render => {
|
|
const e = await render(
|
|
<a href="http://javascript:0/thisisfine">Click me</a>,
|
|
);
|
|
expect(e.tagName).toBe('A');
|
|
expect(e.href).toBe('http://javascript:0/thisisfine');
|
|
});
|
|
|
|
itRenders('a javascript protocol href', async render => {
|
|
// Only the first one warns. The second warning is deduped.
|
|
const e = await render(
|
|
<div>
|
|
<a href="javascript:notfine">p0wned</a>
|
|
<a href="javascript:notfineagain">p0wned again</a>
|
|
</div>,
|
|
);
|
|
expect(e.firstChild.href).toBe(EXPECTED_SAFE_URL);
|
|
expect(e.lastChild.href).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol with leading spaces', async render => {
|
|
const e = await render(
|
|
<a href={' \t \u0000\u001F\u0003javascript\n: notfine'}>p0wned</a>,
|
|
);
|
|
// We use an approximate comparison here because JSDOM might not parse
|
|
// \u0000 in HTML properly.
|
|
expect(e.href).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders(
|
|
'a javascript protocol with intermediate new lines and mixed casing',
|
|
async render => {
|
|
const e = await render(
|
|
<a href={'\t\r\n Jav\rasCr\r\niP\t\n\rt\n:notfine'}>p0wned</a>,
|
|
);
|
|
expect(e.href).toBe(EXPECTED_SAFE_URL);
|
|
},
|
|
);
|
|
|
|
itRenders('a javascript protocol area href', async render => {
|
|
const e = await render(
|
|
<map>
|
|
<area href="javascript:notfine" />
|
|
</map>,
|
|
);
|
|
expect(e.firstChild.href).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol form action', async render => {
|
|
const e = await render(<form action="javascript:notfine">p0wned</form>);
|
|
expect(e.action).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol input formAction', async render => {
|
|
const e = await render(
|
|
<input type="submit" formAction="javascript:notfine" />,
|
|
);
|
|
expect(e.getAttribute('formAction')).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol button formAction', async render => {
|
|
const e = await render(
|
|
<button formAction="javascript:notfine">p0wned</button>,
|
|
);
|
|
expect(e.getAttribute('formAction')).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol iframe src', async render => {
|
|
const e = await render(<iframe src="javascript:notfine" />);
|
|
expect(e.src).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol frame src', async render => {
|
|
const e = await render(
|
|
<html>
|
|
<head />
|
|
<frameset>
|
|
<frame src="javascript:notfine" />
|
|
</frameset>
|
|
</html>,
|
|
);
|
|
expect(e.lastChild.firstChild.src).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('a javascript protocol in an SVG link', async render => {
|
|
const e = await render(
|
|
<svg>
|
|
<a href="javascript:notfine" />
|
|
</svg>,
|
|
);
|
|
expect(e.firstChild.getAttribute('href')).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders(
|
|
'a javascript protocol in an SVG link with a namespace',
|
|
async render => {
|
|
const e = await render(
|
|
<svg>
|
|
<a xlinkHref="javascript:notfine" />
|
|
</svg>,
|
|
);
|
|
expect(
|
|
e.firstChild.getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
|
|
).toBe(EXPECTED_SAFE_URL);
|
|
},
|
|
);
|
|
|
|
it('rejects a javascript protocol href if it is added during an update', async () => {
|
|
const container = document.createElement('div');
|
|
const root = ReactDOMClient.createRoot(container);
|
|
await act(() => {
|
|
root.render(<a href="http://thisisfine/">click me</a>);
|
|
});
|
|
expect(container.firstChild.href).toBe('http://thisisfine/');
|
|
await act(() => {
|
|
root.render(<a href="javascript:notfine">click me</a>);
|
|
});
|
|
expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
|
|
itRenders('only the first invocation of toString', async render => {
|
|
let expectedToStringCalls = 1;
|
|
if (render === clientRenderOnBadMarkup) {
|
|
// It gets called once on the server and once on the client
|
|
// which happens to share the same object in our test runner.
|
|
expectedToStringCalls = 2;
|
|
}
|
|
if (render === clientRenderOnServerString && __DEV__) {
|
|
// The hydration validation calls it one extra time.
|
|
// TODO: It would be good if we only called toString once for
|
|
// consistency but the code structure makes that hard right now.
|
|
expectedToStringCalls = 4;
|
|
} else if (__DEV__) {
|
|
// Checking for string coercion problems results in double the
|
|
// toString calls in DEV
|
|
expectedToStringCalls *= 2;
|
|
}
|
|
|
|
let toStringCalls = 0;
|
|
const firstIsSafe = {
|
|
toString() {
|
|
// This tries to avoid the validation by pretending to be safe
|
|
// the first times it is called and then becomes dangerous.
|
|
toStringCalls++;
|
|
if (toStringCalls <= expectedToStringCalls) {
|
|
return 'https://reactjs.org/';
|
|
}
|
|
return 'javascript:notfine';
|
|
},
|
|
};
|
|
|
|
const e = await render(<a href={firstIsSafe} />);
|
|
expect(toStringCalls).toBe(expectedToStringCalls);
|
|
expect(e.href).toBe('https://reactjs.org/');
|
|
});
|
|
|
|
it('rejects a javascript protocol href if it is added during an update twice', async () => {
|
|
const container = document.createElement('div');
|
|
const root = ReactDOMClient.createRoot(container);
|
|
await act(async () => {
|
|
root.render(<a href="http://thisisfine/">click me</a>);
|
|
});
|
|
expect(container.firstChild.href).toBe('http://thisisfine/');
|
|
await act(async () => {
|
|
root.render(<a href="javascript:notfine">click me</a>);
|
|
});
|
|
expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
|
|
// The second update ensures that a global flag hasn't been added to the regex
|
|
// which would fail to match the second time it is called.
|
|
await act(async () => {
|
|
root.render(<a href="javascript:notfine">click me</a>);
|
|
});
|
|
expect(container.firstChild.href).toBe(EXPECTED_SAFE_URL);
|
|
});
|
|
});
|