fix(fetch): handle negative max-age and expires attributes (#24311)

Fixes #24221
This commit is contained in:
Yury Semikhatsky 2023-07-20 15:42:52 -07:00 committed by GitHub
parent b2965158d3
commit 59d5198d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 6 deletions

View File

@ -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() || '';

View File

@ -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 });
}

View File

@ -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 => {

View File

@ -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');

View File

@ -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' });