Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions packages/engine.io-client/lib/globals.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,22 @@ export function parse(setCookieString: string): Cookie {
const key = subParts[0].trim();
const value = subParts[1].trim();
switch (key) {
case "Expires":
cookie.expires = new Date(value);
case "Expires": {
const expires = new Date(value);
// an unparseable date must not turn a deletion into a cookie that is kept forever
cookie.expires = isNaN(expires.getTime()) ? new Date(0) : expires;
break;
case "Max-Age":
const expiration = new Date();
expiration.setUTCSeconds(
expiration.getUTCSeconds() + parseInt(value, 10),
);
cookie.expires = expiration;
}
case "Max-Age": {
const delta = parseInt(value, 10);
// RFC 6265, section 5.2.2: a delta-seconds of zero or less means the cookie expires
// immediately, and an unparseable one must not be kept either
cookie.expires =
isNaN(delta) || delta <= 0
? new Date(0)
: new Date(Date.now() + delta * 1000);
break;
}
default:
// ignore other keys
}
Expand All @@ -85,7 +91,9 @@ export class CookieJar {
get cookies(): IterableIterator<[string, Cookie]> {
const now = Date.now();
this._cookies.forEach((cookie, name) => {
if (cookie.expires?.getTime() < now) {
// a cookie is expired as soon as its expiry time is reached, so Max-Age=0 (a deletion
// request) must not stay alive until the clock advances past it
if (cookie.expires !== undefined && cookie.expires.getTime() <= now) {
this._cookies.delete(name);
}
});
Expand Down
41 changes: 41 additions & 0 deletions packages/engine.io-client/test/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { Socket } = require("../");
const { repeat } = require("./util");
const expect = require("expect.js");
const { parse } = require("../build/cjs/globals.node.js");
const { CookieJar } = require("../build/cjs/globals.node.js");

describe("node.js", () => {
describe("autoRef option", () => {
Expand Down Expand Up @@ -140,3 +141,43 @@ describe("cookie parsing", () => {
});
});
});

describe("cookie expiry", () => {
// RFC 6265, 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."
it("should treat Max-Age=0 as an expired cookie", () => {
const jar = new CookieJar();
jar.parseCookies(["foo=bar; Max-Age=0"]);

expect([...jar.cookies]).to.eql([]);
});

it("should treat a negative Max-Age as an expired cookie", () => {
const jar = new CookieJar();
jar.parseCookies(["foo=bar; Max-Age=-1"]);

expect([...jar.cookies]).to.eql([]);
});

it("should not keep a cookie whose Max-Age cannot be parsed", () => {
// a malformed attribute must not turn a deletion request into a session cookie
const jar = new CookieJar();
jar.parseCookies(["foo=bar; Max-Age=abc"]);

expect([...jar.cookies]).to.eql([]);
});

it("should not keep a cookie whose Expires date cannot be parsed", () => {
const jar = new CookieJar();
jar.parseCookies(["foo=bar; Expires=not-a-date"]);

expect([...jar.cookies]).to.eql([]);
});

it("should keep a cookie with a Max-Age in the future", () => {
const jar = new CookieJar();
jar.parseCookies(["foo=bar; Max-Age=1000"]);

expect([...jar.cookies].map(([name]) => name)).to.eql(["foo"]);
});
});
Loading