fix(route): remove cors option, compare origin (#13231)
This commit is contained in:
parent
a02384d2dd
commit
7d7fe3c618
|
|
@ -226,15 +226,6 @@ is resolved relative to the current working directory.
|
||||||
|
|
||||||
[APIResponse] to fulfill route's request with. Individual fields of the response (such as headers) can be overridden using fulfill options.
|
[APIResponse] to fulfill route's request with. Individual fields of the response (such as headers) can be overridden using fulfill options.
|
||||||
|
|
||||||
### option: Route.fulfill.cors
|
|
||||||
- `cors` <[CorsMode]<"allow"|"none">>
|
|
||||||
|
|
||||||
Wheb set to "allow" or omitted, the fulfilled response will have
|
|
||||||
["Access-Control-Allow-Origin"](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)
|
|
||||||
header set to request's origin. If the option is set to "none" then
|
|
||||||
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers won't be added to the response.
|
|
||||||
Note that all CORS headers configured via `headers` option will take precedence.
|
|
||||||
|
|
||||||
## method: Route.request
|
## method: Route.request
|
||||||
- returns: <[Request]>
|
- returns: <[Request]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
await this._raceWithPageClose(this._channel.abort({ errorCode }));
|
await this._raceWithPageClose(this._channel.abort({ errorCode }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, cors?: 'allow' | 'none', body?: string | Buffer, path?: string } = {}) {
|
async fulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
|
||||||
let fetchResponseUid;
|
let fetchResponseUid;
|
||||||
let { status: statusOption, headers: headersOption, body } = options;
|
let { status: statusOption, headers: headersOption, body } = options;
|
||||||
if (options.response) {
|
if (options.response) {
|
||||||
|
|
@ -282,7 +282,6 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
await this._raceWithPageClose(this._channel.fulfill({
|
await this._raceWithPageClose(this._channel.fulfill({
|
||||||
status: statusOption || 200,
|
status: statusOption || 200,
|
||||||
headers: headersObjectToArray(headers),
|
headers: headersObjectToArray(headers),
|
||||||
cors: options.cors,
|
|
||||||
body,
|
body,
|
||||||
isBase64,
|
isBase64,
|
||||||
fetchResponseUid
|
fetchResponseUid
|
||||||
|
|
|
||||||
|
|
@ -3139,7 +3139,6 @@ export type RouteContinueResult = void;
|
||||||
export type RouteFulfillParams = {
|
export type RouteFulfillParams = {
|
||||||
status?: number,
|
status?: number,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
cors?: 'allow' | 'none',
|
|
||||||
body?: string,
|
body?: string,
|
||||||
isBase64?: boolean,
|
isBase64?: boolean,
|
||||||
fetchResponseUid?: string,
|
fetchResponseUid?: string,
|
||||||
|
|
@ -3147,7 +3146,6 @@ export type RouteFulfillParams = {
|
||||||
export type RouteFulfillOptions = {
|
export type RouteFulfillOptions = {
|
||||||
status?: number,
|
status?: number,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
cors?: 'allow' | 'none',
|
|
||||||
body?: string,
|
body?: string,
|
||||||
isBase64?: boolean,
|
isBase64?: boolean,
|
||||||
fetchResponseUid?: string,
|
fetchResponseUid?: string,
|
||||||
|
|
|
||||||
|
|
@ -2454,11 +2454,6 @@ Route:
|
||||||
headers:
|
headers:
|
||||||
type: array?
|
type: array?
|
||||||
items: NameValue
|
items: NameValue
|
||||||
cors:
|
|
||||||
type: enum?
|
|
||||||
literals:
|
|
||||||
- allow
|
|
||||||
- none
|
|
||||||
body: string?
|
body: string?
|
||||||
isBase64: boolean?
|
isBase64: boolean?
|
||||||
fetchResponseUid: string?
|
fetchResponseUid: string?
|
||||||
|
|
|
||||||
|
|
@ -1171,7 +1171,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.RouteFulfillParams = tObject({
|
scheme.RouteFulfillParams = tObject({
|
||||||
status: tOptional(tNumber),
|
status: tOptional(tNumber),
|
||||||
headers: tOptional(tArray(tType('NameValue'))),
|
headers: tOptional(tArray(tType('NameValue'))),
|
||||||
cors: tOptional(tEnum(['allow', 'none'])),
|
|
||||||
body: tOptional(tString),
|
body: tOptional(tString),
|
||||||
isBase64: tOptional(tBoolean),
|
isBase64: tOptional(tBoolean),
|
||||||
fetchResponseUid: tOptional(tString),
|
fetchResponseUid: tOptional(tString),
|
||||||
|
|
|
||||||
|
|
@ -266,18 +266,7 @@ export class Route extends SdkObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const headers = [...(overrides.headers || [])];
|
const headers = [...(overrides.headers || [])];
|
||||||
if (overrides.cors !== 'none') {
|
this._maybeAddCorsHeaders(headers);
|
||||||
const corsHeader = headers.find(({ name }) => name === 'access-control-allow-origin');
|
|
||||||
// See https://github.com/microsoft/playwright/issues/12929
|
|
||||||
if (!corsHeader) {
|
|
||||||
const origin = this._request.headerValue('origin');
|
|
||||||
if (origin) {
|
|
||||||
headers.push({ name: 'access-control-allow-origin', value: origin });
|
|
||||||
headers.push({ name: 'access-control-allow-credentials', value: 'true' });
|
|
||||||
headers.push({ name: 'vary', value: 'Origin' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this._delegate.fulfill({
|
await this._delegate.fulfill({
|
||||||
status: overrides.status || 200,
|
status: overrides.status || 200,
|
||||||
headers,
|
headers,
|
||||||
|
|
@ -286,6 +275,24 @@ export class Route extends SdkObject {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See https://github.com/microsoft/playwright/issues/12929
|
||||||
|
private _maybeAddCorsHeaders(headers: NameValue[]) {
|
||||||
|
const origin = this._request.headerValue('origin');
|
||||||
|
if (!origin)
|
||||||
|
return;
|
||||||
|
const requestUrl = new URL(this._request.url());
|
||||||
|
if (!requestUrl.protocol.startsWith('http'))
|
||||||
|
return;
|
||||||
|
if (requestUrl.origin === origin.trim())
|
||||||
|
return;
|
||||||
|
const corsHeader = headers.find(({ name }) => name === 'access-control-allow-origin');
|
||||||
|
if (corsHeader)
|
||||||
|
return;
|
||||||
|
headers.push({ name: 'access-control-allow-origin', value: origin });
|
||||||
|
headers.push({ name: 'access-control-allow-credentials', value: 'true' });
|
||||||
|
headers.push({ name: 'vary', value: 'Origin' });
|
||||||
|
}
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides = {}) {
|
async continue(overrides: types.NormalizedContinueOverrides = {}) {
|
||||||
this._startHandling();
|
this._startHandling();
|
||||||
if (overrides.url) {
|
if (overrides.url) {
|
||||||
|
|
|
||||||
9
packages/playwright-core/types/types.d.ts
vendored
9
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -14728,15 +14728,6 @@ export interface Route {
|
||||||
*/
|
*/
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Wheb set to "allow" or omitted, the fulfilled response will have
|
|
||||||
* ["Access-Control-Allow-Origin"](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)
|
|
||||||
* header set to request's origin. If the option is set to "none" then
|
|
||||||
* [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers won't be added to the response. Note that all
|
|
||||||
* CORS headers configured via `headers` option will take precedence.
|
|
||||||
*/
|
|
||||||
cors?: "allow"|"none";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response headers. Header values will be converted to a string.
|
* Response headers. Header values will be converted to a string.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -516,10 +516,9 @@ it('should not fulfill with redirect status', async ({ page, server, browserName
|
||||||
it('should support cors with GET', async ({ page, server, browserName }) => {
|
it('should support cors with GET', async ({ page, server, browserName }) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.route('**/cars*', async (route, request) => {
|
await page.route('**/cars*', async (route, request) => {
|
||||||
const headers = request.url().endsWith('allow') ? { 'access-control-allow-origin': '*' } : {};
|
const headers = { 'access-control-allow-origin': request.url().endsWith('allow') ? '*' : 'none' };
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
cors: 'none',
|
|
||||||
headers,
|
headers,
|
||||||
status: 200,
|
status: 200,
|
||||||
body: JSON.stringify(['electric', 'gas']),
|
body: JSON.stringify(['electric', 'gas']),
|
||||||
|
|
@ -574,7 +573,31 @@ it('should add Access-Control-Allow-Origin by default when fulfill', async ({ pa
|
||||||
expect(await response.headerValue('Access-Control-Allow-Origin')).toBe(server.PREFIX);
|
expect(await response.headerValue('Access-Control-Allow-Origin')).toBe(server.PREFIX);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respect cors false', async ({ page, server, browserName }) => {
|
it('should allow null origin for about:blank', async ({ page, server, browserName }) => {
|
||||||
|
await page.route('**/something', async route => {
|
||||||
|
await route.fulfill({
|
||||||
|
contentType: 'text/plain',
|
||||||
|
status: 200,
|
||||||
|
body: 'done',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const [response, text] = await Promise.all([
|
||||||
|
page.waitForResponse(server.CROSS_PROCESS_PREFIX + '/something'),
|
||||||
|
page.evaluate(async url => {
|
||||||
|
const data = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'X-PINGOTHER': 'pingpong' }
|
||||||
|
});
|
||||||
|
return data.text();
|
||||||
|
}, server.CROSS_PROCESS_PREFIX + '/something')
|
||||||
|
]);
|
||||||
|
expect(text).toBe('done');
|
||||||
|
expect(await response.headerValue('Access-Control-Allow-Origin')).toBe('null');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect cors overrides', async ({ page, server, browserName }) => {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
server.setRoute('/something', (request, response) => {
|
server.setRoute('/something', (request, response) => {
|
||||||
if (request.method === 'OPTIONS') {
|
if (request.method === 'OPTIONS') {
|
||||||
response.writeHead(204, {
|
response.writeHead(204, {
|
||||||
|
|
@ -589,37 +612,13 @@ it('should respect cors false', async ({ page, server, browserName }) => {
|
||||||
response.writeHead(404, { 'Access-Control-Allow-Origin': '*' });
|
response.writeHead(404, { 'Access-Control-Allow-Origin': '*' });
|
||||||
response.end('NOT FOUND');
|
response.end('NOT FOUND');
|
||||||
});
|
});
|
||||||
// First check the browser will send preflight request when interception is OFF.
|
// Fetch request should fail when CORS header doesn't include the origin.
|
||||||
{
|
{
|
||||||
await page.route('**/something', async route => {
|
await page.route('**/something', async route => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
contentType: 'text/plain',
|
contentType: 'text/plain',
|
||||||
status: 200,
|
status: 200,
|
||||||
body: 'done',
|
headers: { 'Access-Control-Allow-Origin': 'http://non-existent' },
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const [response, text] = await Promise.all([
|
|
||||||
page.waitForResponse(server.CROSS_PROCESS_PREFIX + '/something'),
|
|
||||||
page.evaluate(async url => {
|
|
||||||
const data = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'X-PINGOTHER': 'pingpong' }
|
|
||||||
});
|
|
||||||
return data.text();
|
|
||||||
}, server.CROSS_PROCESS_PREFIX + '/something')
|
|
||||||
]);
|
|
||||||
expect(text).toBe('done');
|
|
||||||
expect(await response.headerValue('Access-Control-Allow-Origin')).toBe('null');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch request should when CORS headers are missing on the response.
|
|
||||||
{
|
|
||||||
await page.route('**/something', async route => {
|
|
||||||
await route.fulfill({
|
|
||||||
contentType: 'text/plain',
|
|
||||||
status: 200,
|
|
||||||
cors: 'none',
|
|
||||||
body: 'done',
|
body: 'done',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue