fix(network): disallow intercepting redirects (#2617)

WebKit and Firefox are only able to continue redirects.
Firefox is faking it on the backend, so you can't even stall it.

Instead, we just do not fire routes for redirects on all browsers,
to avoid surprises.
This commit is contained in:
Dmitry Gozman 2020-06-18 17:15:47 -07:00 committed by GitHub
parent 2511cb4c56
commit d0336ea5c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 17 deletions

View file

@ -6,7 +6,7 @@
}, },
{ {
"name": "firefox", "name": "firefox",
"revision": "1112" "revision": "1113"
}, },
{ {
"name": "webkit", "name": "webkit",

View file

@ -191,6 +191,13 @@ export class CRNetworkManager {
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }); this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
return; return;
} }
let allowInterception = this._userRequestInterceptionEnabled;
if (redirectedFrom) {
allowInterception = false;
// We do not support intercepting redirects.
if (requestPausedEvent)
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
}
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document'; const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined; const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
if (isNavigationRequest) if (isNavigationRequest)
@ -199,7 +206,7 @@ export class CRNetworkManager {
client: this._client, client: this._client,
frame, frame,
documentId, documentId,
allowInterception: this._userRequestInterceptionEnabled, allowInterception,
requestWillBeSentEvent, requestWillBeSentEvent,
requestPausedEvent, requestPausedEvent,
redirectedFrom redirectedFrom

View file

@ -115,6 +115,7 @@ export class Request {
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined, constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
url: string, resourceType: string, method: string, postData: string | null, headers: Headers) { url: string, resourceType: string, method: string, postData: string | null, headers: Headers) {
assert(!url.startsWith('data:'), 'Data urls should not fire requests'); assert(!url.startsWith('data:'), 'Data urls should not fire requests');
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
this._routeDelegate = routeDelegate; this._routeDelegate = routeDelegate;
this._frame = frame; this._frame = frame;
this._redirectedFrom = redirectedFrom; this._redirectedFrom = redirectedFrom;

View file

@ -44,10 +44,12 @@ export class WKInterceptableRequest implements network.RouteDelegate {
readonly _requestId: string; readonly _requestId: string;
_interceptedCallback: () => void = () => {}; _interceptedCallback: () => void = () => {};
private _interceptedPromise: Promise<unknown>; private _interceptedPromise: Promise<unknown>;
readonly _allowInterception: boolean;
constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: network.Request | null, documentId: string | undefined) { constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: network.Request | null, documentId: string | undefined) {
this._session = session; this._session = session;
this._requestId = event.requestId; this._requestId = event.requestId;
this._allowInterception = allowInterception;
const resourceType = event.type ? event.type.toLowerCase() : (redirectedFrom ? redirectedFrom.resourceType() : 'other'); const resourceType = event.type ? event.type.toLowerCase() : (redirectedFrom ? redirectedFrom.resourceType() : 'other');
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url, this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url,
resourceType, event.request.method, event.request.postData || null, headersObject(event.request.headers)); resourceType, event.request.method, event.request.postData || null, headersObject(event.request.headers));

View file

@ -841,7 +841,9 @@ export class WKPage implements PageDelegate {
const documentId = isNavigationRequest ? event.loaderId : undefined; const documentId = isNavigationRequest ? event.loaderId : undefined;
if (isNavigationRequest) if (isNavigationRequest)
this._page._frameManager.frameUpdatedDocumentIdForNavigation(event.frameId, documentId!); this._page._frameManager.frameUpdatedDocumentIdForNavigation(event.frameId, documentId!);
const request = new WKInterceptableRequest(session, this._page._needsRequestInterception(), frame, event, redirectedFrom, documentId); // We do not support intercepting redirects.
const allowInterception = this._page._needsRequestInterception() && !redirectedFrom;
const request = new WKInterceptableRequest(session, allowInterception, frame, event, redirectedFrom, documentId);
this._requestIdToRequest.set(event.requestId, request); this._requestIdToRequest.set(event.requestId, request);
this._page._frameManager.requestStarted(request.request); this._page._frameManager.requestStarted(request.request);
} }
@ -856,8 +858,15 @@ export class WKPage implements PageDelegate {
_onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) { _onRequestIntercepted(event: Protocol.Network.requestInterceptedPayload) {
const request = this._requestIdToRequest.get(event.requestId); const request = this._requestIdToRequest.get(event.requestId);
if (request) if (!request)
return;
if (!request._allowInterception) {
// Intercepted, although we do not intend to allow interception.
// Just continue.
this._session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId });
} else {
request._interceptedCallback(); request._interceptedCallback();
}
} }
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) { _onResponseReceived(event: Protocol.Network.responseReceivedPayload) {

View file

@ -216,21 +216,26 @@ describe('Page.route', function() {
else else
expect(error.message).toContain('net::ERR_FAILED'); expect(error.message).toContain('net::ERR_FAILED');
}); });
it('should work with redirects', async({page, server}) => { it('should not work with redirects', async({page, server}) => {
const requests = []; const intercepted = [];
await page.route('**/*', route => { await page.route('**/*', route => {
route.continue(); route.continue();
requests.push(route.request()); intercepted.push(route.request());
}); });
server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html');
server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html'); server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html');
server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html'); server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html');
server.setRedirect('/non-existing-page-4.html', '/empty.html'); server.setRedirect('/non-existing-page-4.html', '/empty.html');
const response = await page.goto(server.PREFIX + '/non-existing-page.html'); const response = await page.goto(server.PREFIX + '/non-existing-page.html');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toContain('empty.html'); expect(response.url()).toContain('empty.html');
expect(requests.length).toBe(5);
expect(requests[2].resourceType()).toBe('document'); expect(intercepted.length).toBe(1);
expect(intercepted[0].resourceType()).toBe('document');
expect(intercepted[0].isNavigationRequest()).toBe(true);
expect(intercepted[0].url()).toContain('/non-existing-page.html');
const chain = []; const chain = [];
for (let r = response.request(); r; r = r.redirectedFrom()) { for (let r = response.request(); r; r = r.redirectedFrom()) {
chain.push(r); chain.push(r);
@ -246,10 +251,10 @@ describe('Page.route', function() {
expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null);
}); });
it('should work with redirects for subresources', async({page, server}) => { it('should work with redirects for subresources', async({page, server}) => {
const requests = []; const intercepted = [];
await page.route('**/*', route => { await page.route('**/*', route => {
route.continue(); route.continue();
requests.push(route.request()); intercepted.push(route.request());
}); });
server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/two-style.css', '/three-style.css');
@ -259,14 +264,16 @@ describe('Page.route', function() {
const response = await page.goto(server.PREFIX + '/one-style.html'); const response = await page.goto(server.PREFIX + '/one-style.html');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
expect(response.url()).toContain('one-style.html'); expect(response.url()).toContain('one-style.html');
expect(requests.length).toBe(5);
expect(requests[0].resourceType()).toBe('document');
let r = requests.find(r => r.url().includes('/four-style.css')); expect(intercepted.length).toBe(2);
for (const url of ['/four-style.css', '/three-style.css', '/two-style.css', '/one-style.css']) { expect(intercepted[0].resourceType()).toBe('document');
expect(intercepted[0].url()).toContain('one-style.html');
let r = intercepted[1];
for (const url of ['/one-style.css', '/two-style.css', '/three-style.css', '/four-style.css']) {
expect(r.resourceType()).toBe('stylesheet'); expect(r.resourceType()).toBe('stylesheet');
expect(r.url()).toContain(url); expect(r.url()).toContain(url);
r = r.redirectedFrom(); r = r.redirectedTo();
} }
expect(r).toBe(null); expect(r).toBe(null);
}); });
@ -602,7 +609,6 @@ describe('Interception vs isNavigationRequest', () => {
server.setRedirect('/rrredirect', '/frames/one-frame.html'); server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect'); await page.goto(server.PREFIX + '/rrredirect');
expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
expect(requests.get('frame.html').isNavigationRequest()).toBe(true); expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
expect(requests.get('script.js').isNavigationRequest()).toBe(false); expect(requests.get('script.js').isNavigationRequest()).toBe(false);
expect(requests.get('style.css').isNavigationRequest()).toBe(false); expect(requests.get('style.css').isNavigationRequest()).toBe(false);