fix(fetch): handle negative max-age and expires attributes (#24311)
Fixes #24221
This commit is contained in:
parent
b2965158d3
commit
59d5198d17
|
@ -39,6 +39,7 @@ import { ProgressController } from './progress';
|
|||
import { Tracing } from './trace/recorder/tracing';
|
||||
import type * as types from './types';
|
||||
import type { HeadersArray, ProxySettings } from './types';
|
||||
import { kMaxCookieExpiresDateInSeconds } from './network';
|
||||
|
||||
type FetchRequestOptions = {
|
||||
userAgent: string;
|
||||
|
@ -606,13 +607,25 @@ function parseCookie(header: string): channels.NetworkCookie | null {
|
|||
switch (name.toLowerCase()) {
|
||||
case 'expires':
|
||||
const expiresMs = (+new Date(value));
|
||||
if (isFinite(expiresMs))
|
||||
cookie.expires = expiresMs / 1000;
|
||||
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1
|
||||
if (isFinite(expiresMs)) {
|
||||
if (expiresMs <= 0)
|
||||
cookie.expires = 0;
|
||||
else
|
||||
cookie.expires = Math.min(expiresMs / 1000, kMaxCookieExpiresDateInSeconds);
|
||||
}
|
||||
break;
|
||||
case 'max-age':
|
||||
const maxAgeSec = parseInt(value, 10);
|
||||
if (isFinite(maxAgeSec))
|
||||
cookie.expires = Date.now() / 1000 + maxAgeSec;
|
||||
if (isFinite(maxAgeSec)) {
|
||||
// From https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2
|
||||
// If delta-seconds is less than or equal to zero (0), let expiry-time
|
||||
// be the earliest representable date and time.
|
||||
if (maxAgeSec <= 0)
|
||||
cookie.expires = 0;
|
||||
else
|
||||
cookie.expires = Math.min(Date.now() / 1000 + maxAgeSec, kMaxCookieExpiresDateInSeconds);
|
||||
}
|
||||
break;
|
||||
case 'domain':
|
||||
cookie.domain = value.toLocaleLowerCase() || '';
|
||||
|
|
|
@ -283,7 +283,7 @@ export class FFBrowserContext extends BrowserContext {
|
|||
async addCookies(cookies: channels.SetNetworkCookie[]) {
|
||||
const cc = network.rewriteCookies(cookies).map(c => ({
|
||||
...c,
|
||||
expires: c.expires && c.expires !== -1 ? c.expires : undefined,
|
||||
expires: c.expires === -1 ? undefined : c.expires,
|
||||
}));
|
||||
await this._browser._connection.send('Browser.setCookies', { browserContextId: this._browserContextId, cookies: cc });
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export function filterCookies(cookies: channels.NetworkCookie[], urls: string[])
|
|||
// Rollover to 5-digit year:
|
||||
// 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC)
|
||||
// 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC)
|
||||
const kMaxCookieExpiresDateInSeconds = 253402300799;
|
||||
export const kMaxCookieExpiresDateInSeconds = 253402300799;
|
||||
|
||||
export function rewriteCookies(cookies: channels.SetNetworkCookie[]): channels.SetNetworkCookie[] {
|
||||
return cookies.map(c => {
|
||||
|
|
|
@ -270,6 +270,43 @@ it('should not lose body while handling Set-Cookie header', async ({ context, se
|
|||
expect(await response.text()).toBe('text content');
|
||||
});
|
||||
|
||||
it('should remove cookie with negative max-age', async ({ page, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=v; max-age=100000', `b=v; max-age=100000`, 'c=v']);
|
||||
res.end();
|
||||
});
|
||||
server.setRoute('/removecookie.html', (req, res) => {
|
||||
const maxAge = -2 * Date.now();
|
||||
res.setHeader('Set-Cookie', [`a=v; max-age=${maxAge}`, `b=v; max-age=-1`]);
|
||||
res.end();
|
||||
});
|
||||
await page.request.get(`${server.PREFIX}/setcookie.html`);
|
||||
await page.request.get(`${server.PREFIX}/removecookie.html`);
|
||||
const [serverRequest] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
page.request.get(server.EMPTY_PAGE)
|
||||
]);
|
||||
expect(serverRequest.headers.cookie).toBe('c=v');
|
||||
});
|
||||
|
||||
it('should remove cookie with expires far in the past', async ({ page, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=v; max-age=1000000']);
|
||||
res.end();
|
||||
});
|
||||
server.setRoute('/removecookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', [`a=v; expires=Wed, 01 Jan 1000 00:00:00 GMT`]);
|
||||
res.end();
|
||||
});
|
||||
await page.request.get(`${server.PREFIX}/setcookie.html`);
|
||||
await page.request.get(`${server.PREFIX}/removecookie.html`);
|
||||
const [serverRequest] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
page.request.get(server.EMPTY_PAGE)
|
||||
]);
|
||||
expect(serverRequest.headers.cookie).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should handle cookies on redirects', async ({ context, server, browserName, isWindows }) => {
|
||||
server.setRoute('/redirect1', (req, res) => {
|
||||
res.setHeader('Set-Cookie', 'r1=v1;SameSite=Lax');
|
||||
|
|
|
@ -168,6 +168,43 @@ it('should remove expired cookies', async ({ request, server }) => {
|
|||
expect(serverRequest.headers.cookie).toBe('a=v');
|
||||
});
|
||||
|
||||
it('should remove cookie with negative max-age', async ({ request, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=v; max-age=100000', `b=v; max-age=100000`, 'c=v']);
|
||||
res.end();
|
||||
});
|
||||
server.setRoute('/removecookie.html', (req, res) => {
|
||||
const maxAge = -2 * Date.now();
|
||||
res.setHeader('Set-Cookie', [`a=v; max-age=${maxAge}`, `b=v; max-age=-1`]);
|
||||
res.end();
|
||||
});
|
||||
await request.get(`${server.PREFIX}/setcookie.html`);
|
||||
await request.get(`${server.PREFIX}/removecookie.html`);
|
||||
const [serverRequest] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
request.get(server.EMPTY_PAGE)
|
||||
]);
|
||||
expect(serverRequest.headers.cookie).toBe('c=v');
|
||||
});
|
||||
|
||||
it('should remove cookie with expires far in the past', async ({ request, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=v; max-age=1000000']);
|
||||
res.end();
|
||||
});
|
||||
server.setRoute('/removecookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', [`a=v; expires=1 Jan 1000 00:00:00 +0000 (UTC)`]);
|
||||
res.end();
|
||||
});
|
||||
await request.get(`${server.PREFIX}/setcookie.html`);
|
||||
await request.get(`${server.PREFIX}/removecookie.html`);
|
||||
const [serverRequest] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
request.get(server.EMPTY_PAGE)
|
||||
]);
|
||||
expect(serverRequest.headers.cookie).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should store cookie from Set-Cookie header even if it contains equal signs', async ({ request, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11612' });
|
||||
|
||||
|
|
Loading…
Reference in New Issue