Skip to content

Commit

Permalink
fix(cors): allow intercepting cors requests on chromium (#2643)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Jun 20, 2020
1 parent 2251f9b commit eac7dab
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 3 deletions.
2 changes: 1 addition & 1 deletion browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
{
"name": "webkit",
"revision": "1290"
"revision": "1292"
}
]
}
24 changes: 22 additions & 2 deletions src/chromium/crNetworkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,28 @@ export class CRNetworkManager {
}

if (!frame) {
if (requestPausedEvent)
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
if (requestPausedEvent) {
// CORS options request is generated by the network stack, it is not associated with the frame id.
// If URL matches interception pattern, accept it, assuming that this was intended when setting route.
if (requestPausedEvent.request.method === 'OPTIONS' && this._page._isRouted(requestPausedEvent.request.url)) {
const requestHeaders = requestPausedEvent.request.headers;
const responseHeaders: Protocol.Fetch.HeaderEntry[] = [
{ name: 'Access-Control-Allow-Origin', value: requestHeaders['Access-Control-Allow-Methods'] || '*' },
{ name: 'Access-Control-Allow-Methods', value: requestHeaders['Access-Control-Request-Method'] || 'GET, POST, OPTIONS, DELETE' }
];
if (requestHeaders['Access-Control-Request-Headers'])
responseHeaders.push({ name: 'Access-Control-Allow-Headers', value: requestHeaders['Access-Control-Request-Headers'] });
this._client._sendMayFail('Fetch.fulfillRequest', {
requestId: requestPausedEvent.requestId,
responseCode: 204,
responsePhrase: network.STATUS_TEXTS['204'],
responseHeaders,
body: '',
});
} else {
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
}
}
return;
}
let allowInterception = this._userRequestInterceptionEnabled;
Expand Down
12 changes: 12 additions & 0 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,18 @@ export class Page extends EventEmitter {
route.continue();
}

_isRouted(requestURL: string): boolean {
for (const { url } of this._routes) {
if (helper.urlMatches(requestURL, url))
return true;
}
for (const { url } of this._browserContext._routes) {
if (helper.urlMatches(requestURL, url))
return true;
}
return false;
}

async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
return this._screenshotter.screenshotPage(options);
}
Expand Down
89 changes: 89 additions & 0 deletions test/interception.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,95 @@ describe('Page.route', function() {
}, server.PREFIX + '/redirect_this');
expect(text).toBe('');
});

it('should support cors with GET', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/cars*', async (route, request) => {
const headers = request.url().endsWith('allow') ? { 'access-control-allow-origin': '*' } : {};
await route.fulfill({
contentType: 'application/json',
headers,
status: 200,
body: JSON.stringify(['electric', 'gas']),
});
});
{
// Should succeed
const resp = await page.evaluate(async () => {
const response = await fetch('https://example.com/cars?allow', { mode: 'cors' });
return response.json();
});
expect(resp).toEqual(['electric', 'gas']);
}
{
// Should be rejected
const error = await page.evaluate(async () => {
const response = await fetch('https://example.com/cars?reject', { mode: 'cors' });
return response.json();
}).catch(e => e);
expect(error.message).toContain('failed');
}
});

it('should support cors with POST', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/cars', async (route) => {
await route.fulfill({
contentType: 'application/json',
headers: { 'Access-Control-Allow-Origin': '*' },
status: 200,
body: JSON.stringify(['electric', 'gas']),
});
});
const resp = await page.evaluate(async () => {
const response = await fetch('https://example.com/cars', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
mode: 'cors',
body: JSON.stringify({ 'number': 1 })
});
return response.json();
});
expect(resp).toEqual(['electric', 'gas']);
});

it('should support cors for different methods', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/cars', async (route, request) => {
await route.fulfill({
contentType: 'application/json',
headers: { 'Access-Control-Allow-Origin': '*' },
status: 200,
body: JSON.stringify([request.method(), 'electric', 'gas']),
});
});
// First POST
{
const resp = await page.evaluate(async () => {
const response = await fetch('https://example.com/cars', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
mode: 'cors',
body: JSON.stringify({ 'number': 1 })
});
return response.json();
});
expect(resp).toEqual(['POST', 'electric', 'gas']);
}
// Then DELETE
{
const resp = await page.evaluate(async () => {
const response = await fetch('https://example.com/cars', {
method: 'DELETE',
headers: {},
mode: 'cors',
body: ''
});
return response.json();
});
expect(resp).toEqual(['DELETE', 'electric', 'gas']);
}
});
});

describe('Request.continue', function() {
Expand Down

0 comments on commit eac7dab

Please sign in to comment.