fix: do not allow to override cookie header (#35168)

Behavior before this PR regarding 'Cookie' header already varied between browsers:
- Chromium would not respect the 'Cookie' header if there was one with the same name in its cookie jar. If there was no corresponding cooke in the cookie jar, Chromium would apply one from the overrides.
- WebKit would always take one from the cookie jar.

To override cookies `addCookies` should be used instead.


See https://docs.google.com/document/d/1LXMSP4GVxFLYJxA6z4upKqwkgD-TnVCGeX-daS4VQjk/edit?usp=sharing for mode details.

Reference https://github.com/microsoft/playwright/issues/35154
This commit is contained in:
Yury Semikhatsky 2025-03-20 16:35:03 -07:00 committed by GitHub
parent b9ccc5b252
commit b83e0af11a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 125 additions and 9 deletions

View File

@ -106,6 +106,10 @@ The [`option: headers`] option applies to both the routed request and any redire
[`method: Route.continue`] will immediately send the request to the network, other matching handlers won't be invoked. Use [`method: Route.fallback`] If you want next matching handler in the chain to be invoked.
:::warning
The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [`method: BrowserContext.addCookies`].
:::
### option: Route.continue.url
* since: v1.8
- `url` <[string]>

View File

@ -20786,6 +20786,11 @@ export interface Route {
* request to the network, other matching handlers won't be invoked. Use
* [route.fallback([options])](https://playwright.dev/docs/api/class-route#route-fallback) If you want next matching
* handler in the chain to be invoked.
*
* **NOTE** The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored,
* and the cookie will be loaded from the browser's cookie store. To set custom cookies, use
* [browserContext.addCookies(cookies)](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies).
*
* @param options
*/
continue(options?: {

View File

@ -329,6 +329,8 @@ export class Route extends SdkObject {
if (oldUrl.protocol !== newUrl.protocol)
throw new Error('New URL must have same protocol as overridden URL');
}
if (overrides.headers)
overrides.headers = overrides.headers?.filter(header => header.name.toLowerCase() !== 'cookie');
this._request._setOverrides(overrides);
if (!overrides.isFallback)
this._request._context.emit(BrowserContext.Events.RequestContinued, this._request);

View File

@ -20786,6 +20786,11 @@ export interface Route {
* request to the network, other matching handlers won't be invoked. Use
* [route.fallback([options])](https://playwright.dev/docs/api/class-route#route-fallback) If you want next matching
* handler in the chain to be invoked.
*
* **NOTE** The `Cookie` header cannot be overridden using this method. If a value is provided, it will be ignored,
* and the cookie will be loaded from the browser's cookie store. To set custom cookies, use
* [browserContext.addCookies(cookies)](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies).
*
* @param options
*/
continue(options?: {

View File

@ -295,7 +295,7 @@ it('should record request overrides', async ({ contextFactory, server }, testInf
expect(request.url).toBe(server.EMPTY_PAGE);
expect(request.method).toBe('POST');
expect(request.headers).toContainEqual({ name: 'custom', value: 'value' });
expect(request.cookies).toContainEqual({ name: 'foo', value: 'bar' });
expect(request.cookies).toEqual([]);
expect(request.postData).toEqual({ 'mimeType': 'text/plain', 'params': [], 'text': 'Hi!' });
});
@ -508,7 +508,7 @@ it('should record failed request overrides', async ({ contextFactory, server },
expect(request.url).toBe(server.EMPTY_PAGE);
expect(request.method).toBe('POST');
expect(request.headers).toContainEqual({ name: 'custom', value: 'value' });
expect(request.cookies).toContainEqual({ name: 'foo', value: 'bar' });
expect(request.cookies).toEqual([]);
expect(request.postData).toEqual({ 'mimeType': 'text/plain', 'params': [], 'text': 'Hi!' });
});

View File

@ -452,6 +452,96 @@ it('should respect set-cookie in redirect response', {
expect.soft(await page.evaluate(() => document.cookie)).toBe('foo=bar');
});
it('continue should not propagate cookie override to redirects', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35168' },
]
}, async ({ page, server, browserName }) => {
it.fixme(browserName === 'firefox', 'We currently clear all headers during interception in firefox');
server.setRoute('/set-cookie', (request, response) => {
response.writeHead(200, { 'Set-Cookie': 'foo=bar;' });
response.end();
});
await page.goto(server.PREFIX + '/set-cookie');
expect(await page.evaluate(() => document.cookie)).toBe('foo=bar');
server.setRedirect('/redirect', server.PREFIX + '/empty.html');
await page.route('**/redirect', route => {
void route.continue({
headers: {
...route.request().headers(),
cookie: 'override'
}
});
});
const [serverRequest] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.PREFIX + '/redirect')
]);
expect(serverRequest.headers['cookie']).toBe('foo=bar');
});
it('continue should not override cookie', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35168' },
]
}, async ({ page, server, browserName }) => {
it.fixme(browserName === 'firefox', 'We currently clear all headers during interception in firefox');
server.setRoute('/set-cookie', (request, response) => {
response.writeHead(200, { 'Set-Cookie': 'foo=bar;' });
response.end();
});
await page.goto(server.PREFIX + '/set-cookie');
expect(await page.evaluate(() => document.cookie)).toBe('foo=bar');
await page.route('**', route => {
void route.continue({
headers: {
...route.request().headers(),
cookie: 'override',
custom: 'value'
}
});
});
const [serverRequest] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE)
]);
// Original cookie from the browser's cookie jar should be sent.
expect(serverRequest.headers['cookie']).toBe('foo=bar');
expect(serverRequest.headers['custom']).toBe('value');
});
it('redirect after continue should be able to delete cookie', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35168' },
]
}, async ({ page, server }) => {
server.setRoute('/set-cookie', (request, response) => {
response.writeHead(200, { 'Set-Cookie': 'foo=bar;' });
response.end();
});
await page.goto(server.PREFIX + '/set-cookie');
expect(await page.evaluate(() => document.cookie)).toBe('foo=bar');
server.setRoute('/delete-cookie', (request, response) => {
response.writeHead(200, { 'Set-Cookie': 'foo=bar; expires=Thu, 01 Jan 1970 00:00:00 GMT' });
response.end();
});
server.setRedirect('/redirect', '/delete-cookie');
await page.route('**/redirect', route => {
void route.continue({
headers: {
...route.request().headers(),
}
});
});
await page.goto(server.PREFIX + '/redirect');
const [serverRequest] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE)
]);
expect(serverRequest.headers['cookie']).toBeFalsy();
});
it('continue should propagate headers to redirects', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28758' },
@ -536,6 +626,7 @@ it('propagate headers same origin redirect', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35154' },
]
}, async ({ page, server }) => {
await page.goto(server.PREFIX + '/empty.html');
@ -547,7 +638,7 @@ it('propagate headers same origin redirect', {
'Access-Control-Allow-Origin': server.PREFIX,
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE',
'Access-Control-Allow-Headers': 'authorization,custom',
'Access-Control-Allow-Headers': 'authorization,cookie,custom',
});
response.end();
return;
@ -557,6 +648,7 @@ it('propagate headers same origin redirect', {
response.end('done');
});
await server.setRedirect('/redirect', '/something');
await page.evaluate(() => document.cookie = 'a=b');
const text = await page.evaluate(async url => {
const data = await fetch(url, {
headers: {
@ -570,6 +662,7 @@ it('propagate headers same origin redirect', {
expect(text).toBe('done');
const serverRequest = await serverRequestPromise;
expect.soft(serverRequest.headers['authorization']).toBe('credentials');
expect.soft(serverRequest.headers['cookie']).toBe('a=b');
expect.soft(serverRequest.headers['custom']).toBe('foo');
});
@ -620,6 +713,7 @@ it('propagate headers cross origin redirect', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35154' },
]
}, async ({ page, server, isAndroid }) => {
it.fixme(isAndroid, 'receives authorization:credentials header');
@ -633,7 +727,7 @@ it('propagate headers cross origin redirect', {
'Access-Control-Allow-Origin': server.PREFIX,
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE',
'Access-Control-Allow-Headers': 'authorization,custom',
'Access-Control-Allow-Headers': 'authorization,cookie,custom',
});
response.end();
return;
@ -649,6 +743,7 @@ it('propagate headers cross origin redirect', {
response.writeHead(301, { location: `${server.CROSS_PROCESS_PREFIX}/something` });
response.end();
});
await page.evaluate(() => document.cookie = 'a=b');
const text = await page.evaluate(async url => {
const data = await fetch(url, {
headers: {
@ -663,6 +758,7 @@ it('propagate headers cross origin redirect', {
const serverRequest = await serverRequestPromise;
// Authorization header not propagated to cross-origin redirect.
expect.soft(serverRequest.headers['authorization']).toBeFalsy();
expect.soft(serverRequest.headers['cookie']).toBeFalsy();
expect.soft(serverRequest.headers['custom']).toBe('foo');
});
@ -670,6 +766,7 @@ it('propagate headers cross origin redirect after interception', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/35154' },
]
}, async ({ page, server, browserName }) => {
await page.goto(server.PREFIX + '/empty.html');
@ -681,7 +778,7 @@ it('propagate headers cross origin redirect after interception', {
'Access-Control-Allow-Origin': server.PREFIX,
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE',
'Access-Control-Allow-Headers': 'authorization,custom',
'Access-Control-Allow-Headers': 'authorization,cookie,custom',
});
response.end();
return;
@ -697,6 +794,7 @@ it('propagate headers cross origin redirect after interception', {
response.writeHead(301, { location: `${server.CROSS_PROCESS_PREFIX}/something` });
response.end();
});
await page.evaluate(() => document.cookie = 'a=b');
await page.route('**/redirect', async route => {
await route.continue({
headers: {
@ -721,6 +819,7 @@ it('propagate headers cross origin redirect after interception', {
expect.soft(serverRequest.headers['authorization']).toBeFalsy();
else
expect.soft(serverRequest.headers['authorization']).toBe('credentials');
expect.soft(serverRequest.headers['cookie']).toBeFalsy();
expect.soft(serverRequest.headers['custom']).toBe('foo');
});

View File

@ -166,9 +166,9 @@ it('should properly return navigation response when URL has cookies', async ({ p
expect(response.status()).toBe(200);
});
it('should override cookie header', async ({ page, server, browserName }) => {
it('should not override cookie header', async ({ page, server, browserName }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/16773' });
it.fail(browserName !== 'firefox' && !browserName.includes('bidi'));
it.fixme(browserName === 'firefox', 'We currently clear all headers during interception in firefox');
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => document.cookie = 'original=value');
@ -184,8 +184,9 @@ it('should override cookie header', async ({ page, server, browserName }) => {
page.goto(server.EMPTY_PAGE),
]);
expect(cookieValueInRoute).toBe('original=value');
expect(serverReq.headers['cookie']).toBe('overridden=value');
if (browserName !== 'webkit')
expect.soft(cookieValueInRoute).toBe('original=value');
expect.soft(serverReq.headers['cookie']).toBe('original=value');
});
it('should show custom HTTP headers', async ({ page, server }) => {