From fe18b8d98833f8bf90534de6f27ca0a920521991 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Sat, 5 Feb 2022 10:01:56 -0400 Subject: [PATCH 01/33] Typescript Support --- .eslintignore | 1 + .gitignore | 1 + .prettierignore | 1 + jest.config.js | 5 + lib/__tests__/api.test.ts | 226 +++++++++++++++++++++ lib/__tests__/date.test.ts | 176 ++++++++++++++++ lib/__tests__/ietf.test.ts | 85 ++++++++ lib/{cookie.js => cookie.ts} | 314 ++++++++++++++++++++++------- lib/{pathMatch.js => pathMatch.ts} | 10 +- lib/{store.js => store.ts} | 8 +- lib/validators.js | 19 +- package.json | 4 + test/api_test.js | 2 +- test/cookie_jar_test.js | 10 +- test/cookie_prefixes_test.js | 2 +- test/cookie_sorting_test.js | 2 +- test/cookie_to_json_test.js | 2 +- test/cookie_to_string_test.js | 2 +- test/date_test.js | 2 +- test/domain_and_path_test.js | 2 +- test/ietf_test.js | 2 +- test/jar_serialization_test.js | 2 +- test/lifetime_test.js | 2 +- test/parsing_test.js | 2 +- test/regression_test.js | 2 +- test/remove_all_test.js | 2 +- test/same_site_test.js | 2 +- tsconfig.json | 11 + 28 files changed, 796 insertions(+), 103 deletions(-) create mode 100644 .eslintignore create mode 100644 jest.config.js create mode 100644 lib/__tests__/api.test.ts create mode 100644 lib/__tests__/date.test.ts create mode 100644 lib/__tests__/ietf.test.ts rename lib/{cookie.js => cookie.ts} (87%) rename lib/{pathMatch.js => pathMatch.ts} (91%) rename lib/{store.js => store.ts} (95%) create mode 100644 tsconfig.json diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +dist diff --git a/.gitignore b/.gitignore index 3dbda85b..df02b1cc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ package-lock.json .npm .config .bash_history +dist/ diff --git a/.prettierignore b/.prettierignore index 89ed1e87..c3062b7c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ lib/version.js +dist/*.js diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..9e5dccd2 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node" +}; diff --git a/lib/__tests__/api.test.ts b/lib/__tests__/api.test.ts new file mode 100644 index 00000000..6e67cb99 --- /dev/null +++ b/lib/__tests__/api.test.ts @@ -0,0 +1,226 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import {Cookie, CookieJar, SerializedCookieJar, version} from '../cookie' +import packageJson from '../../package.json' + +jest.useFakeTimers() + +describe('API', () => { + it('should define Cookie', () => { + expect(Cookie).not.toBeUndefined() + }) + + it('should define CookieJar', () => { + expect(CookieJar).not.toBeUndefined() + }) + + it('should define the version that matches the package metadata', () => { + expect(version).toBe(packageJson.version) + }) + + describe('Cookie', () => { + let cookie: Cookie + + describe('constructor', () => { + beforeEach(() => { + cookie = new Cookie({ + key: "test", + value: "b", + maxAge: 60 + }) + }) + + it("should check for key property", () => { + expect(cookie.key).toEqual('test') + }) + + it('should check for value property', () => { + expect(cookie.value).toBe("b"); + }) + + it("should check for maxAge", () => { + expect(cookie.maxAge).toBe(60); + }) + + it("should check for default values for unspecified properties", () => { + expect(cookie.expires).toBe('Infinity') + expect(cookie.secure).toBe(false) + expect(cookie.httpOnly).toBe(false) + }) + }) + }) + + describe('CookieJar Promises', () => { + let cookieJar: CookieJar + + beforeEach(() => { + cookieJar = new CookieJar() + }) + + describe('setCookie', () => { + it('should resolve to a Cookie', async () => { + const cookie = await cookieJar.setCookie("foo=bar", "http://example.com") + expect(cookie).toBeInstanceOf(Cookie) + expect(cookie.key).toBe('foo') + expect(cookie.value).toBe('bar') + }) + + it('supports the "expiry" option', async () => { + const cookie = await cookieJar.setCookie( + "near=expiry; Domain=example.com; Path=/; Max-Age=1", + "http://www.example.com", + { now: new Date(Date.now() - 1) } + ) + expect(cookie).toEqual(expect.objectContaining({ + key: 'near', + value: 'expiry' + })) + jest.advanceTimersByTime(1) + const cookies = await cookieJar.getCookies("http://www.example.com", { + http: true, + expire: false + }) + expect(cookies).toHaveLength(1) + expect(cookies[0]).toEqual(expect.objectContaining({ + key: 'near', + value: 'expiry' + })) + }) + + it('supports the "allPaths" option', async () => { + const cookiesByUrl = { + "http://example.com": [ + "nopath_dom=qq; Path=/; Domain=example.com", + "path_dom=qq; Path=/foo; Domain=example.com" + ], + "http://www.example.com": [ + "nopath_host=qq; Path=/", + "path_host=qq; Path=/foo" + ], + "http://other.example.com": [ + "other=qq; Path=/" + ], + "http://other.example.com/foo": [ + "other2=qq; Path=/foo" + ] + } + const allCookiesSet = [] + for await (let [url, cookies] of Object.entries(cookiesByUrl)) { + for await (let cookie of cookies) { + const setCookie = await cookieJar.setCookie(cookie, url) + if (setCookie) { + allCookiesSet.push(setCookie) + } + } + } + expect(allCookiesSet).toHaveLength(6) + + const cookies = await cookieJar.getCookies("http://www.example.com/") + expect(cookies).toHaveLength(2) + expect(cookies.every(cookie => cookie.path === '/')).toBe(true) + expect(cookies.every(cookie => !/^other/.test(cookie.key))).toBe(true) + + const fooCookies = await cookieJar.getCookies("http://www.example.com/foo") + expect(fooCookies).toHaveLength(4) + expect(fooCookies.every(cookie => !/^other/.test(cookie.key))).toBe(true) + }) + }) + + describe('getCookies', () => { + it('resolves to an array of cookies', async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + const cookies = await cookieJar.getCookies("http://example.com") + expect(cookies).toEqual([ + expect.objectContaining({ + key: 'foo', + value: 'bar' + }) + ]) + }) + }) + + describe('getCookieString', () => { + it('resolves to a string', async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + const cookieString = await cookieJar.getCookieString("http://example.com") + expect(cookieString).toBe('foo=bar') + }) + }) + + describe('getSetCookieStrings', () => { + it('resolves to an array of strings', async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + const cookieString = await cookieJar.getSetCookieStrings("http://example.com") + expect(cookieString).toEqual(['foo=bar; Path=/']) + }) + }) + + describe('removeAllCookies', () => { + it('resolves to an array of strings', async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + expect(await cookieJar.getCookies("http://example.com")).toHaveLength(1) + await cookieJar.removeAllCookies() + expect(await cookieJar.getCookies("http://example.com")).toHaveLength(0) + }) + }) + + describe('serialize', () => { + it('resolves to an array of strings', async () => { + const now = new Date().toISOString() + + await cookieJar.setCookie("foo=bar", "http://example.com") + const data = await cookieJar.serialize() + const expected: SerializedCookieJar = { + "allowSpecialUseDomain": false, + "cookies": [ + { + "creation": now, + "domain": "example.com", + "hostOnly": true, + "key": "foo", + "lastAccessed": now, + "path": "/", + "pathIsDefault": true, + "value": "bar" + } + ], + "enableLooseMode": false, + "prefixSecurity": "silent", + "rejectPublicSuffixes": true, + "storeType": "MemoryCookieStore", + "version": "tough-cookie@4.0.0" + } + expect(data).toEqual(expected) + }) + }) + }) +}) diff --git a/lib/__tests__/date.test.ts b/lib/__tests__/date.test.ts new file mode 100644 index 00000000..b8fdc10f --- /dev/null +++ b/lib/__tests__/date.test.ts @@ -0,0 +1,176 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +type DateParsingTestCase = { + [key: string]: boolean +} + +type EquivalenceDateParsingTestCase = { + [key: string]: string +} + +const {parseDate} = require("../cookie"); + +const dateTests: DateParsingTestCase = { + "Wed, 09 Jun 2021 10:18:14 GMT": true, + "Wed, 09 JUN 2021 10:18:14 GMT": true, + "Wed, 09 Jun 2021 22:18:14 GMT": true, + "Tue, 18 Oct 2011 07:42:42.123 GMT": true, + "18 Oct 2011 07:42:42 GMT": true, + "8 Oct 2011 7:42:42 GMT": true, + "8 Oct 2011 7:2:42 GMT": true, + "8 Oct 2011 7:2:2 GMT": true, + "Oct 18 2011 07:42:42 GMT": true, + "Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)": true, + "09 Jun 2021 10:18:14 GMT": true, + "99 Jix 3038 48:86:72 ZMT": false, + "01 Jan 1970 00:00:00 GMT": true, + "01 Jan 1600 00:00:00 GMT": false, // before 1601 + "01 Jan 1601 00:00:00 GMT": true, + "10 Feb 81 13:00:00 GMT": true, // implicit year + "Thu, 17-Apr-2014 02:12:29 GMT": true, // dashes + "Thu, 17-Apr-2014 02:12:29 UTC": true, // dashes and UTC + + // garbage after parts: + "Wedxxx, 09 Jun 2021 10:18:14 GMT": true, // day of week doesn't matter + "Wed, 09e9 Jun 2021 10:18:14 GMT": true, // garbage after day ignored + "Wed, 09 Junxxx 2021 10:18:14 GMT": true, // prefix match on month + "Wed, 09 Jun 2021e9 10:18:14 GMT": true, // garbage after year OK + "Wed, 09 Jun 2021 10e9:18:14 GMT": false, // can't have garbage after HH + "Wed, 09 Jun 2021 10:18e9:14 GMT": false, // can't have garbage after MM + "Wed, 09 Jun 2021 10:18:14e9 GMT": true, // garbage after SS ignored + + // extra digit in time parts: + "Thu, 01 Jan 1970 000:00:01 GMT": false, + "Thu, 01 Jan 1970 00:000:01 GMT": false, + "Thu, 01 Jan 1970 00:00:010 GMT": false, + + // hex in time + "Wed, 09 Jun 2021 1a:33:44 GMT": false, + "Wed, 09 Jun 2021 a1:33:44 GMT": false, + "Wed, 09 Jun 2021 11:f3:44 GMT": false, + "Wed, 09 Jun 2021 11:3f:44 GMT": false, + "Wed, 09 Jun 2021 11:33:e4 GMT": false, + "Wed, 09 Jun 2021 11:33:4e GMT": true, // garbage after seconds is OK + + // negatives in time + "Wed, 09 Jun 2021 -1:33:44 GMT": true, // parses as 1:33; - is a delimiter + "Wed, 09 Jun 2021 11:-3:44 GMT": false, + "Wed, 09 Jun 2021 11:33:-4 GMT": false, + + "": false +} + +const equivalenceTests: EquivalenceDateParsingTestCase = { + // milliseconds ignored + "Tue, 18 Oct 2011 07:42:42.123 GMT": "Tue, 18 Oct 2011 07:42:42 GMT", + + // shorter HH:MM:SS works how you'd expect: + "8 Oct 2011 7:32:42 GMT": "8 Oct 2011 07:32:42 GMT", + "8 Oct 2011 7:2:42 GMT": "8 Oct 2011 07:02:42 GMT", + "8 Oct 2011 7:2:2 GMT": "8 Oct 2011 07:02:02 GMT", + + // MDY versus DMY: + "Oct 18 2011 07:42:42 GMT": "18 Oct 2011 07:42:42 GMT", + + // some other messy auto format + "Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)": + "Tue, 18 Oct 2011 07:05:03 GMT", + + // short year + "10 Feb 81 13:00:00 GMT": "10 Feb 1981 13:00:00 GMT", + "10 Feb 17 13:00:00 GMT": "10 Feb 2017 13:00:00 GMT", + + // dashes + "Thu, 17-Apr-2014 02:12:29 GMT": "Thu, 17 Apr 2014 02:12:29 GMT", + // dashes and "UTC" (timezone is always ignored) + "Thu, 17-Apr-2014 02:12:29 UTC": "Thu, 17 Apr 2014 02:12:29 GMT", + + // no weekday + "09 Jun 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + + // garbage after seconds is OK + "Wed, 09 Jun 2021 11:33:4e GMT": "Wed, 09 Jun 2021 11:33:04 GMT", + + // - is delimiter in this position + "Wed, 09 Jun 2021 -1:33:44 GMT": "Wed, 09 Jun 2021 01:33:44 GMT", + + // prefix match on month + "Wed, 09 Junxxx 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + "09 November 2021 10:18:14 GMT": "09 Nov 2021 10:18:14 GMT", + + // case of Month + "Wed, 09 JUN 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + "Wed, 09 jUN 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + + // test the framework :wink: + "Wed, 09 Jun 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT" +} + +describe("Dates", () => { + describe('parsing', () => { + const validDateTestCases = Object.entries(dateTests).filter(testCase => testCase[1]) + const invalidDateTestCases = Object.entries(dateTests).filter(testCase => !testCase[1]) + const equivalenceTestCases = Object.entries(equivalenceTests) + + it.each(validDateTestCases) + (`'%s' is valid`, (date: string) => { + expect(parseDate(date)).toBeInstanceOf(Date) + }) + + it.each(invalidDateTestCases) + (`'%s' is not valid`, (date: string) => { + expect(parseDate(date)).toBeUndefined(); + }) + + it.each(equivalenceTestCases) + (`'%s' parses the same as '%s'`, (date: string, equivalentDate: string) => { + expect(parseDate(date)).toStrictEqual(parseDate(equivalentDate)) + }) + }) + + describe('regexp denial of service attack vectors', () => { + const TOO_MANY_XS = String("x").repeat(65535); + + it('should avoid unbounded regexps when parsing the hour from a date', () => { + expect(parseDate(`Wed, 09 Jun 2021 10${TOO_MANY_XS}:18:14 GMT`)).toBeUndefined() + }) + + it('should avoid unbounded regexps when parsing the minute from a date', () => { + expect(parseDate(`Wed, 09 Jun 2021 10:18${TOO_MANY_XS}:14 GMT`)).toBeUndefined() + }) + + it('should avoid unbounded regexps when parsing the seconds from a date', () => { + const dateWithMillisIgnored = new Date(Date.parse('2021-06-09T10:18:14.000Z')) + expect(parseDate(`Wed, 09 Jun 2021 10:18:14${TOO_MANY_XS} GMT`)).toStrictEqual(dateWithMillisIgnored) + }) + }) +}) diff --git a/lib/__tests__/ietf.test.ts b/lib/__tests__/ietf.test.ts new file mode 100644 index 00000000..a2703341 --- /dev/null +++ b/lib/__tests__/ietf.test.ts @@ -0,0 +1,85 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import {CookieJar, parseDate} from '../cookie' +import url from 'url' +import parserData from '../../test/ietf_data/parser.json' +import bsdExampleDates from '../../test/ietf_data/dates/bsd-examples.json' +import exampleDates from '../../test/ietf_data/dates/examples.json' + +describe('IETF http state tests', () => { + describe('Set/get cookie tests', () => { + it.each(parserData) + (`$test`, (testCase) => { + const jar = new CookieJar(); + const expected = testCase.sent + const sentFrom = `http://home.example.org/cookie-parser?${testCase.test}`; + const sentTo = testCase["sent-to"] + ? url.resolve("http://home.example.org", testCase["sent-to"]) + : `http://home.example.org/cookie-parser-result?${testCase.test}`; + + testCase["received"].forEach(cookieStr => { + // @ts-ignore + jar.setCookieSync(cookieStr, sentFrom, {ignoreError: true}); + }); + + // @ts-ignore + const actual = jar.getCookiesSync(sentTo, {sort: true}) as Array<{ key: string, value: string }>; + + expect(actual.length).toBe(expected.length) + actual.forEach((actualCookie, idx) => { + const expectedCookie = expected[idx]; + expect(actualCookie.key).toBe(expectedCookie.name) + expect(actualCookie.value).toBe(expectedCookie.value) + }); + }) + }) + + describe('Date handling', () => { + it.each(exampleDates) + (`ietf_data/dates/examples: $test`, ({test, expected}) => { + if (expected) { + expect(parseDate(test).toUTCString()).toBe(expected) + } else { + expect(parseDate(test)).toBeUndefined() + } + }) + + it.each(bsdExampleDates) + (`ietf_data/dates/bsd_examples: $test`, ({test, expected}) => { + if (expected) { + expect(parseDate(test).toUTCString()).toBe(expected) + } else { + expect(parseDate(test)).toBeUndefined() + } + }) + }) +}) diff --git a/lib/cookie.js b/lib/cookie.ts similarity index 87% rename from lib/cookie.js rename to lib/cookie.ts index f6ba38fd..e9a1da8e 100644 --- a/lib/cookie.js +++ b/lib/cookie.ts @@ -28,17 +28,18 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; -const punycode = require("punycode"); -const urlParse = require("url").parse; -const util = require("util"); -const pubsuffix = require("./pubsuffix-psl"); -const Store = require("./store").Store; -const MemoryCookieStore = require("./memstore").MemoryCookieStore; -const pathMatch = require("./pathMatch").pathMatch; -const validators = require("./validators.js"); -const VERSION = require("./version"); -const { fromCallback } = require("universalify"); + +import * as punycode from "punycode"; +import { parse as urlParse } from 'url' +import * as pubsuffix from './pubsuffix-psl' +import util from 'util' +import {Store} from './store' +import {MemoryCookieStore} from './memstore' +import {pathMatch} from "./pathMatch"; +import * as validators from './validators' +import VERSION from './version' +import {fromCallback} from 'universalify' +import {permuteDomain} from "./permuteDomain" // From RFC6265 S4.1.1 // note that it excludes \x3B ";" @@ -808,7 +809,26 @@ const cookieDefaults = { sameSite: "none" }; -class Cookie { +export class Cookie { + key: string; + value: string; + expires: Date | 'Infinity'; + maxAge: number | 'Infinity' | '-Infinity'; + domain: string | null; + path: string | null; + secure: boolean; + httpOnly: boolean; + extensions: string[] | null; + creation: Date | 'Infinity'; + creationIndex: number; + hostOnly: boolean | null; + pathIsDefault: boolean | null; + lastAccessed: Date | null | 'Infinity'; + sameSite: string | undefined; + + static serializableProperties: string[] + static cookiesCreated: number + constructor(options = {}) { if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; @@ -830,9 +850,11 @@ class Cookie { const now = Date.now(); const hostOnly = this.hostOnly != null ? this.hostOnly : "?"; const createAge = this.creation + // @ts-ignore ? `${now - this.creation.getTime()}ms` : "?"; const accessAge = this.lastAccessed + // @ts-ignore ? `${now - this.lastAccessed.getTime()}ms` : "?"; return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`; @@ -857,6 +879,7 @@ class Cookie { obj[prop] = this[prop] == "Infinity" // intentionally not === ? "Infinity" + // @ts-ignore : this[prop].toISOString(); } } else if (prop === "maxAge") { @@ -886,6 +909,7 @@ class Cookie { return false; } if ( + // @ts-ignore this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires) @@ -944,6 +968,7 @@ class Cookie { toString() { let str = this.cookieString(); + // @ts-ignore if (this.expires != Infinity) { if (this.expires instanceof Date) { str += `; Expires=${formatDate(this.expires)}`; @@ -970,6 +995,7 @@ class Cookie { str += "; HttpOnly"; } if (this.sameSite && this.sameSite !== "none") { + // @ts-ignore const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()]; str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`; } @@ -993,19 +1019,24 @@ class Cookie { * (Concurs with S5.3 step 3) */ if (this.maxAge != null) { + // @ts-ignore return this.maxAge <= 0 ? 0 : this.maxAge * 1000; } let expires = this.expires; + // @ts-ignore if (expires != Infinity) { if (!(expires instanceof Date)) { + // @ts-ignore expires = parseDate(expires) || Infinity; } + // @ts-ignore if (expires == Infinity) { return Infinity; } + // @ts-ignore return expires.getTime() - (now || Date.now()); } @@ -1017,13 +1048,16 @@ class Cookie { expiryTime(now) { if (this.maxAge != null) { const relativeTo = now || this.creation || new Date(); + // @ts-ignore const age = this.maxAge <= 0 ? -Infinity : this.maxAge * 1000; return relativeTo.getTime() + age; } + // @ts-ignore if (this.expires == Infinity) { return Infinity; } + // @ts-ignore return this.expires.getTime(); } @@ -1042,6 +1076,7 @@ class Cookie { // This replaces the "persistent-flag" parts of S5.3 step 3 isPersistent() { + // @ts-ignore return this.maxAge != null || this.expires != Infinity; } @@ -1059,15 +1094,19 @@ class Cookie { } Cookie.cookiesCreated = 0; +// @ts-ignore Cookie.parse = parse; +// @ts-ignore Cookie.fromJSON = fromJSON; Cookie.serializableProperties = Object.keys(cookieDefaults); +// @ts-ignore Cookie.sameSiteLevel = { strict: 3, lax: 2, none: 1 }; +// @ts-ignore Cookie.sameSiteCanonical = { strict: "Strict", lax: "Lax" @@ -1088,49 +1127,136 @@ function getNormalizedPrefixSecurity(prefixSecurity) { return PrefixSecurityEnum.SILENT; } -class CookieJar { - constructor(store, options = { rejectPublicSuffixes: true }) { +type SetCookieOptions = { + loose?: boolean; + sameSiteContext?: boolean; + ignoreError?: boolean; + http?: boolean; + now?: Date; +} + +const defaultSetCookieOptions: SetCookieOptions = { + loose: false, + sameSiteContext: false, + ignoreError: false, + http: false +} + +interface PromiseCallback { + promise: Promise; + callback: (error?: Error, result?: T) => Promise; +} + +export interface SerializedCookieJar { + version: string; + storeType: string; + rejectPublicSuffixes: boolean; + [key: string]: any; + cookies: SerializedCookie[]; +} + +export interface SerializedCookie { + key: string; + value: string; + [key: string]: any; +} + +function createPromiseCallback(args: IArguments): PromiseCallback { + let callback: (error: Error, result: T) => Promise + let resolve: (result: T) => void + let reject: (error: Error) => void + + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + + if (typeof args[args.length - 1] === 'function') { + const cb = args[args.length - 1] + callback = (err, result) => { + try { + cb(err, result) + } catch(e) { + reject(e) + } + return promise + } + } else { + callback = (err, result) => { + try { + err ? reject(err) : resolve(result) + } catch (e) { + reject(e) + } + return promise + } + } + + return { + promise, + callback + } +} + +export class CookieJar { + store: Store; + + constructor(store?: any, options: any = { rejectPublicSuffixes: true }) { if (typeof options === "boolean") { options = { rejectPublicSuffixes: options }; } validators.validate(validators.isObject(options), options); + // @ts-ignore this.rejectPublicSuffixes = options.rejectPublicSuffixes; + // @ts-ignore this.enableLooseMode = !!options.looseMode; + // @ts-ignore this.allowSpecialUseDomain = !!options.allowSpecialUseDomain; this.store = store || new MemoryCookieStore(); + // @ts-ignore this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); + // @ts-ignore this._cloneSync = syncWrap("clone"); + // @ts-ignore this._importCookiesSync = syncWrap("_importCookies"); + // @ts-ignore this.getCookiesSync = syncWrap("getCookies"); + // @ts-ignore this.getCookieStringSync = syncWrap("getCookieString"); + // @ts-ignore this.getSetCookieStringsSync = syncWrap("getSetCookieStrings"); + // @ts-ignore this.removeAllCookiesSync = syncWrap("removeAllCookies"); + // @ts-ignore this.setCookieSync = syncWrap("setCookie"); + // @ts-ignore this.serializeSync = syncWrap("serialize"); } - setCookie(cookie, url, options, cb) { - validators.validate(validators.isNonEmptyString(url), cb, options); + setCookie(cookie, url, options: SetCookieOptions = defaultSetCookieOptions, callback?: (error: Error, result: Cookie) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + validators.validate(validators.isNonEmptyString(url), callback, options); let err; if (validators.isFunction(url)) { - cb = url; return cb(new Error("No URL was specified")); } const context = getCookieContext(url); if (validators.isFunction(options)) { - cb = options; - options = {}; + options = defaultSetCookieOptions; } validators.validate(validators.isFunction(cb), cb); - if(!validators.isNonEmptyString(cookie) && !validators.isObject(cookie) && ( cookie instanceof String && cookie.length == 0)) { + if (!validators.isNonEmptyString(cookie) && !validators.isObject(cookie) && (cookie instanceof String && cookie.length == 0)) { return cb(null); } const host = canonicalDomain(context.hostname); + // @ts-ignore const loose = options.loose || this.enableLooseMode; let sameSiteContext = null; @@ -1143,7 +1269,8 @@ class CookieJar { // S5.3 step 1 if (typeof cookie === "string" || cookie instanceof String) { - cookie = Cookie.parse(cookie, { loose: loose }); + // @ts-ignore + cookie = Cookie.parse(cookie, {loose: loose}); if (!cookie) { err = new Error("Cookie failed to parse"); return cb(options.ignoreError ? null : err); @@ -1165,8 +1292,10 @@ class CookieJar { // S5.3 step 4: NOOP; domain is null by default // S5.3 step 5: public suffixes + // @ts-ignore if (this.rejectPublicSuffixes && cookie.domain) { const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { + // @ts-ignore allowSpecialUseDomain: this.allowSpecialUseDomain, ignoreError: options.ignoreError }); @@ -1228,8 +1357,10 @@ class CookieJar { /* 6265bis-02 S5.4 Steps 15 & 16 */ const ignoreErrorForPrefixSecurity = + // @ts-ignore this.prefixSecurity === PrefixSecurityEnum.SILENT; const prefixSecurityDisabled = + // @ts-ignore this.prefixSecurity === PrefixSecurityEnum.DISABLED; /* If prefix checking is not disabled ...*/ if (!prefixSecurityDisabled) { @@ -1257,7 +1388,7 @@ class CookieJar { const store = this.store; if (!store.updateCookie) { - store.updateCookie = function(oldCookie, newCookie, cb) { + store.updateCookie = function (oldCookie, newCookie, cb) { this.putCookie(newCookie, cb); }; } @@ -1267,7 +1398,7 @@ class CookieJar { return cb(err); } - const next = function(err) { + const next = function (err) { if (err) { return cb(err); } else { @@ -1278,9 +1409,11 @@ class CookieJar { if (oldCookie) { // S5.3 step 11 - "If the cookie store contains a cookie with the same name, // domain, and path as the newly created cookie:" + // @ts-ignore if (options.http === false && oldCookie.httpOnly) { // step 11.2 err = new Error("old Cookie is HttpOnly and this isn't an HTTP API"); + // @ts-ignore return cb(options.ignoreError ? null : err); } cookie.creation = oldCookie.creation; // step 11.3 @@ -1295,14 +1428,18 @@ class CookieJar { } store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie); + return promiseCallback.promise + // } } // RFC6365 S5.4 - getCookies(url, options, cb) { + getCookies(url: string, options: any = {}, callback?: (error: Error, result: Cookie[]) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + validators.validate(validators.isNonEmptyString(url), cb, url); const context = getCookieContext(url); if (validators.isFunction(options)) { - cb = options; options = {}; } validators.validate(validators.isObject(options), cb, options); @@ -1323,6 +1460,7 @@ class CookieJar { let sameSiteLevel = 0; if (options.sameSiteContext) { const sameSiteContext = checkSameSiteContext(options.sameSiteContext); + // @ts-ignore sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext]; if (!sameSiteLevel) { return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); @@ -1337,6 +1475,7 @@ class CookieJar { const now = options.now || Date.now(); const expireCheck = options.expire !== false; const allPaths = !!options.allPaths; + // @ts-ignore const store = this.store; function matchingCookie(c) { @@ -1375,6 +1514,7 @@ class CookieJar { // RFC6265bis-02 S5.3.7 if (sameSiteLevel) { + // @ts-ignore const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"]; if (cookieLevel > sameSiteLevel) { // only allow cookies at or below the request level @@ -1395,6 +1535,7 @@ class CookieJar { store.findCookies( host, allPaths ? null : path, + // @ts-ignore this.allowSpecialUseDomain, (err, cookies) => { if (err) { @@ -1418,16 +1559,18 @@ class CookieJar { cb(null, cookies); } ); + + return promiseCallback.promise } - getCookieString(...args) { - const cb = args.pop(); - validators.validate(validators.isFunction(cb), cb); - const next = function(err, cookies) { + getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + + const next = function(err: Error, cookies: Cookie[]) { if (err) { - cb(err); + promiseCallback.callback(err); } else { - cb( + promiseCallback.callback( null, cookies .sort(cookieCompare) @@ -1436,18 +1579,19 @@ class CookieJar { ); } }; - args.push(next); - this.getCookies.apply(this, args); + + this.getCookies(url, options, next) + return promiseCallback.promise } - getSetCookieStrings(...args) { - const cb = args.pop(); - validators.validate(validators.isFunction(cb), cb); - const next = function(err, cookies) { + getSetCookieStrings (url: string, options: any = {}, callback?: (error: Error, result: string[]) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + + const next = function(err: Error, cookies: Cookie[]) { if (err) { - cb(err); + promiseCallback.callback(err); } else { - cb( + promiseCallback.callback( null, cookies.map(c => { return c.toString(); @@ -1455,19 +1599,24 @@ class CookieJar { ); } }; - args.push(next); - this.getCookies.apply(this, args); + + this.getCookies(url, options, next); + return promiseCallback.promise } - serialize(cb) { + serialize(callback?: (error: Error, data: SerializedCookieJar) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + validators.validate(validators.isFunction(cb), cb); + // @ts-ignore let type = this.store.constructor.name; if (validators.isObject(type)) { type = null; } // update README.md "Serialization Format" if you change this, please! - const serialized = { + const serialized: SerializedCookieJar = { // The version of tough-cookie that serialized this jar. Generally a good // practice since future versions can make data import decisions based on // known past behavior. When/if this matters, use `semver`. @@ -1477,9 +1626,13 @@ class CookieJar { storeType: type, // CookieJar configuration: + // @ts-ignore rejectPublicSuffixes: !!this.rejectPublicSuffixes, + // @ts-ignore enableLooseMode: !!this.enableLooseMode, + // @ts-ignore allowSpecialUseDomain: !!this.allowSpecialUseDomain, + // @ts-ignore prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), // this gets filled from getAllCookies: @@ -1488,7 +1641,9 @@ class CookieJar { if ( !( + // @ts-ignore this.store.getAllCookies && + // @ts-ignore typeof this.store.getAllCookies === "function" ) ) { @@ -1499,6 +1654,7 @@ class CookieJar { ); } + // @ts-ignore this.store.getAllCookies((err, cookies) => { if (err) { return cb(err); @@ -1516,9 +1672,12 @@ class CookieJar { return cb(null, serialized); }); + + return promiseCallback.promise } toJSON() { + // @ts-ignore return this.serializeSync(); } @@ -1550,9 +1709,11 @@ class CookieJar { return putNext(null); // skip this cookie } + // @ts-ignore this.store.putCookie(cookie, putNext); }; + // @ts-ignore putNext(); } @@ -1572,6 +1733,7 @@ class CookieJar { cloneSync(newStore) { if (arguments.length === 0) { + // @ts-ignore return this._cloneSync(); } if (!newStore.synchronous) { @@ -1579,11 +1741,15 @@ class CookieJar { "CookieJar clone destination store is not synchronous; use async API instead." ); } + // @ts-ignore return this._cloneSync(newStore); } - removeAllCookies(cb) { - validators.validate(validators.isFunction(cb), cb); + removeAllCookies(callback?: (error?: Error) => void): Promise { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + // @ts-ignore const store = this.store; // Check that the store implements its own removeAllCookies(). The default @@ -1593,7 +1759,8 @@ class CookieJar { typeof store.removeAllCookies === "function" && store.removeAllCookies !== Store.prototype.removeAllCookies ) { - return store.removeAllCookies(cb); + store.removeAllCookies(cb); + return promiseCallback.promise } store.getAllCookies((err, cookies) => { @@ -1629,6 +1796,8 @@ class CookieJar { ); }); }); + + return promiseCallback.promise } static deserialize(strOrObj, store, cb) { @@ -1649,8 +1818,9 @@ class CookieJar { serialized = strOrObj; } - const jar = new CookieJar(store, { + const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, + // @ts-ignore looseMode: serialized.enableLooseMode, allowSpecialUseDomain: serialized.allowSpecialUseDomain, prefixSecurity: serialized.prefixSecurity @@ -1666,22 +1836,26 @@ class CookieJar { static deserializeSync(strOrObj, store) { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; - const jar = new CookieJar(store, { + const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, - looseMode: serialized.enableLooseMode + // @ts-ignore + looseMode: serialized.enableLooseMode }); // catch this mistake early: + // @ts-ignore if (!jar.store.synchronous) { throw new Error( "CookieJar store is not synchronous; use async API instead." ); } + // @ts-ignore jar._importCookiesSync(serialized); return jar; } } +// @ts-ignore CookieJar.fromJSON = CookieJar.deserializeSync; [ @@ -1692,10 +1866,11 @@ CookieJar.fromJSON = CookieJar.deserializeSync; "getSetCookieStrings", "removeAllCookies", "serialize", - "setCookie" + // "setCookie" ].forEach(name => { CookieJar.prototype[name] = fromCallback(CookieJar.prototype[name]); }); +// CookieJar.prototype.setCookie = fromCallback(CookieJar.prototype.setCookie) CookieJar.deserialize = fromCallback(CookieJar.deserialize); // Use a closure to provide a true imperative API for synchronous stores. @@ -1720,22 +1895,23 @@ function syncWrap(method) { }; } -exports.version = VERSION; -exports.CookieJar = CookieJar; -exports.Cookie = Cookie; -exports.Store = Store; -exports.MemoryCookieStore = MemoryCookieStore; -exports.parseDate = parseDate; -exports.formatDate = formatDate; -exports.parse = parse; -exports.fromJSON = fromJSON; -exports.domainMatch = domainMatch; -exports.defaultPath = defaultPath; -exports.pathMatch = pathMatch; -exports.getPublicSuffix = pubsuffix.getPublicSuffix; -exports.cookieCompare = cookieCompare; -exports.permuteDomain = require("./permuteDomain").permuteDomain; -exports.permutePath = permutePath; -exports.canonicalDomain = canonicalDomain; -exports.PrefixSecurityEnum = PrefixSecurityEnum; -exports.ParameterError = validators.ParameterError; +const getPublicSuffix = pubsuffix.getPublicSuffix +const ParameterError = validators.ParameterError + +export { VERSION as version } +export { Store as Store } +export { MemoryCookieStore as MemoryCookieStore } +export { parseDate as parseDate } +export { formatDate as formatDate } +export { parse as parse } +export { fromJSON as fromJSON } +export { domainMatch as domainMatch } +export { defaultPath as defaultPath } +export { pathMatch as pathMatch } +export { getPublicSuffix as getPublicSuffix } +export { cookieCompare as cookieCompare } +export { permuteDomain as permuteDomain } +export { permutePath as permutePath } +export { canonicalDomain as canonicalDomain } +export { PrefixSecurityEnum as PrefixSecurityEnum } +export { ParameterError as ParameterError } diff --git a/lib/pathMatch.js b/lib/pathMatch.ts similarity index 91% rename from lib/pathMatch.js rename to lib/pathMatch.ts index 16ff63ee..926c885c 100644 --- a/lib/pathMatch.js +++ b/lib/pathMatch.ts @@ -28,12 +28,12 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; + /* * "A request-path path-matches a given cookie-path if at least one of the * following conditions holds:" */ -function pathMatch(reqPath, cookiePath) { +export function pathMatch(reqPath: string, cookiePath: string): boolean { // "o The cookie-path and the request-path are identical." if (cookiePath === reqPath) { return true; @@ -43,19 +43,17 @@ function pathMatch(reqPath, cookiePath) { if (idx === 0) { // "o The cookie-path is a prefix of the request-path, and the last // character of the cookie-path is %x2F ("/")." - if (cookiePath.substr(-1) === "/") { + if (cookiePath[cookiePath.length - 1] === "/") { return true; } // " o The cookie-path is a prefix of the request-path, and the first // character of the request-path that is not included in the cookie- path // is a %x2F ("/") character." - if (reqPath.substr(cookiePath.length, 1) === "/") { + if (new RegExp(`^${cookiePath}`).test(reqPath) && reqPath[cookiePath.length] === "/") { return true; } } return false; } - -exports.pathMatch = pathMatch; diff --git a/lib/store.js b/lib/store.ts similarity index 95% rename from lib/store.js rename to lib/store.ts index 2ed0259e..4c5bc666 100644 --- a/lib/store.js +++ b/lib/store.ts @@ -31,12 +31,16 @@ "use strict"; /*jshint unused:false */ -class Store { +import {Cookie} from "./cookie"; + +export class Store { + synchronous: boolean; + constructor() { this.synchronous = false; } - findCookie(domain, path, key, cb) { + findCookie(domain, path, key, cb): Cookie { throw new Error("findCookie is not implemented"); } diff --git a/lib/validators.js b/lib/validators.js index d2f39deb..b75c3905 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,21 +1,21 @@ /* ************************************************************************************ Extracted from check-types.js https://gitlab.com/philbooth/check-types.js - + MIT License - + Copyright (c) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Phil Booth - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,7 +23,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + ************************************************************************************ */ "use strict"; @@ -82,6 +82,11 @@ function validate(bool, cb, options) { class ParameterError extends Error { constructor(...params) { super(...params); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, ParameterError.prototype); + } else { + this.__proto__ = new.target.prototype; + } } }; @@ -92,4 +97,4 @@ exports.isDate = isDate; exports.isEmptyString = isEmptyString; exports.isString = isString; exports.isObject = isObject; -exports.validate = validate; \ No newline at end of file +exports.validate = validate; diff --git a/package.json b/package.json index e07dcb7c..8c536698 100644 --- a/package.json +++ b/package.json @@ -92,13 +92,17 @@ "node": ">=6" }, "devDependencies": { + "@types/jest": "^27.4.0", "async": "^2.6.2", "eslint": "^5.16.0", "eslint-config-prettier": "^4.2.0", "eslint-plugin-prettier": "^3.0.1", "genversion": "^2.1.0", + "jest": "^27.4.7", "nyc": "^14.0.0", "prettier": "^1.17.0", + "ts-jest": "^27.1.3", + "typescript": "^4.5.5", "vows": "^0.8.2" }, "dependencies": { diff --git a/test/api_test.js b/test/api_test.js index 1144cf34..81fbbc68 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -32,7 +32,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 328a639b..e409f8aa 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -32,7 +32,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; @@ -750,12 +750,12 @@ vows "of undefined": { topic: function() { const jar = new tough.CookieJar(); - const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; - Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; + const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; + Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; Secure; HttpOnly, AuthToken=EFKFFFCH@K@GHIHEJCJMMGJM>CDHDEK>CFGK?MHJ >>JI@B??@CAEHBJH@H@A@GCFDLIMLJEEJEIFGALA?BIM?@G@DEDI@JE?I?HKJBIDDHJMEFEFM - >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; - path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 + >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; + path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 GMT; path=/`; jar.setCookieSync(cookieString, "https://google.com"); diff --git a/test/cookie_prefixes_test.js b/test/cookie_prefixes_test.js index 5a21be3f..81ef4a1b 100644 --- a/test/cookie_prefixes_test.js +++ b/test/cookie_prefixes_test.js @@ -31,7 +31,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const CookieJar = tough.CookieJar; const PrefixSecurityEnum = tough.PrefixSecurityEnum; diff --git a/test/cookie_sorting_test.js b/test/cookie_sorting_test.js index 23ddeee4..0e50685c 100644 --- a/test/cookie_sorting_test.js +++ b/test/cookie_sorting_test.js @@ -31,7 +31,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/cookie_to_json_test.js b/test/cookie_to_json_test.js index 5becedb2..8506cedc 100644 --- a/test/cookie_to_json_test.js +++ b/test/cookie_to_json_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/cookie_to_string_test.js b/test/cookie_to_string_test.js index b531646a..2a853efe 100644 --- a/test/cookie_to_string_test.js +++ b/test/cookie_to_string_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/date_test.js b/test/date_test.js index 2965ac11..5b81003c 100644 --- a/test/date_test.js +++ b/test/date_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); function dateVows(table) { const theVows = {}; diff --git a/test/domain_and_path_test.js b/test/domain_and_path_test.js index de15f317..e85ba70e 100644 --- a/test/domain_and_path_test.js +++ b/test/domain_and_path_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; function matchVows(func, table) { diff --git a/test/ietf_test.js b/test/ietf_test.js index 1529ea08..a15d5fda 100644 --- a/test/ietf_test.js +++ b/test/ietf_test.js @@ -35,7 +35,7 @@ const assert = require("assert"); const fs = require("fs"); const path = require("path"); const url = require("url"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const CookieJar = tough.CookieJar; function readJson(filePath) { diff --git a/test/jar_serialization_test.js b/test/jar_serialization_test.js index dfa2fb39..5bbb9758 100644 --- a/test/jar_serialization_test.js +++ b/test/jar_serialization_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const Store = tough.Store; diff --git a/test/lifetime_test.js b/test/lifetime_test.js index 0954d6a5..9a5c0357 100644 --- a/test/lifetime_test.js +++ b/test/lifetime_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/parsing_test.js b/test/parsing_test.js index b26d4cfb..82aff72d 100644 --- a/test/parsing_test.js +++ b/test/parsing_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const LOTS_OF_SEMICOLONS = ";".repeat(65535); diff --git a/test/regression_test.js b/test/regression_test.js index 29d21c0e..8f88a83a 100644 --- a/test/regression_test.js +++ b/test/regression_test.js @@ -33,7 +33,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const MemoryCookieStore = tough.MemoryCookieStore; diff --git a/test/remove_all_test.js b/test/remove_all_test.js index fa19f506..bed18b18 100644 --- a/test/remove_all_test.js +++ b/test/remove_all_test.js @@ -33,7 +33,7 @@ const util = require("util"); const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const Store = tough.Store; diff --git a/test/same_site_test.js b/test/same_site_test.js index c43b3f4e..785c61e3 100644 --- a/test/same_site_test.js +++ b/test/same_site_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../lib/cookie"); +const tough = require("../dist/lib/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..ccd5d5b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "outDir": "./dist", + "allowJs": true, + "target": "es5", + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": ["./lib/**/*"] +} From 43b9b11d53786ebe929b18701f9ee7010a52a866 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Sat, 12 Feb 2022 09:35:02 -0400 Subject: [PATCH 02/33] Typescript Support --- lib/__tests__/api.test.ts | 226 -------- lib/__tests__/cookie.spec.ts | 66 +++ lib/__tests__/cookieJar.spec.ts | 576 +++++++++++++++++++ lib/__tests__/{date.test.ts => date.spec.ts} | 0 lib/__tests__/{ietf.test.ts => ietf.spec.ts} | 0 lib/cookie.ts | 278 +++++---- lib/store.ts | 8 +- 7 files changed, 779 insertions(+), 375 deletions(-) delete mode 100644 lib/__tests__/api.test.ts create mode 100644 lib/__tests__/cookie.spec.ts create mode 100644 lib/__tests__/cookieJar.spec.ts rename lib/__tests__/{date.test.ts => date.spec.ts} (100%) rename lib/__tests__/{ietf.test.ts => ietf.spec.ts} (100%) diff --git a/lib/__tests__/api.test.ts b/lib/__tests__/api.test.ts deleted file mode 100644 index 6e67cb99..00000000 --- a/lib/__tests__/api.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -/*! - * Copyright (c) 2015, Salesforce.com, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. Neither the name of Salesforce.com nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -import {Cookie, CookieJar, SerializedCookieJar, version} from '../cookie' -import packageJson from '../../package.json' - -jest.useFakeTimers() - -describe('API', () => { - it('should define Cookie', () => { - expect(Cookie).not.toBeUndefined() - }) - - it('should define CookieJar', () => { - expect(CookieJar).not.toBeUndefined() - }) - - it('should define the version that matches the package metadata', () => { - expect(version).toBe(packageJson.version) - }) - - describe('Cookie', () => { - let cookie: Cookie - - describe('constructor', () => { - beforeEach(() => { - cookie = new Cookie({ - key: "test", - value: "b", - maxAge: 60 - }) - }) - - it("should check for key property", () => { - expect(cookie.key).toEqual('test') - }) - - it('should check for value property', () => { - expect(cookie.value).toBe("b"); - }) - - it("should check for maxAge", () => { - expect(cookie.maxAge).toBe(60); - }) - - it("should check for default values for unspecified properties", () => { - expect(cookie.expires).toBe('Infinity') - expect(cookie.secure).toBe(false) - expect(cookie.httpOnly).toBe(false) - }) - }) - }) - - describe('CookieJar Promises', () => { - let cookieJar: CookieJar - - beforeEach(() => { - cookieJar = new CookieJar() - }) - - describe('setCookie', () => { - it('should resolve to a Cookie', async () => { - const cookie = await cookieJar.setCookie("foo=bar", "http://example.com") - expect(cookie).toBeInstanceOf(Cookie) - expect(cookie.key).toBe('foo') - expect(cookie.value).toBe('bar') - }) - - it('supports the "expiry" option', async () => { - const cookie = await cookieJar.setCookie( - "near=expiry; Domain=example.com; Path=/; Max-Age=1", - "http://www.example.com", - { now: new Date(Date.now() - 1) } - ) - expect(cookie).toEqual(expect.objectContaining({ - key: 'near', - value: 'expiry' - })) - jest.advanceTimersByTime(1) - const cookies = await cookieJar.getCookies("http://www.example.com", { - http: true, - expire: false - }) - expect(cookies).toHaveLength(1) - expect(cookies[0]).toEqual(expect.objectContaining({ - key: 'near', - value: 'expiry' - })) - }) - - it('supports the "allPaths" option', async () => { - const cookiesByUrl = { - "http://example.com": [ - "nopath_dom=qq; Path=/; Domain=example.com", - "path_dom=qq; Path=/foo; Domain=example.com" - ], - "http://www.example.com": [ - "nopath_host=qq; Path=/", - "path_host=qq; Path=/foo" - ], - "http://other.example.com": [ - "other=qq; Path=/" - ], - "http://other.example.com/foo": [ - "other2=qq; Path=/foo" - ] - } - const allCookiesSet = [] - for await (let [url, cookies] of Object.entries(cookiesByUrl)) { - for await (let cookie of cookies) { - const setCookie = await cookieJar.setCookie(cookie, url) - if (setCookie) { - allCookiesSet.push(setCookie) - } - } - } - expect(allCookiesSet).toHaveLength(6) - - const cookies = await cookieJar.getCookies("http://www.example.com/") - expect(cookies).toHaveLength(2) - expect(cookies.every(cookie => cookie.path === '/')).toBe(true) - expect(cookies.every(cookie => !/^other/.test(cookie.key))).toBe(true) - - const fooCookies = await cookieJar.getCookies("http://www.example.com/foo") - expect(fooCookies).toHaveLength(4) - expect(fooCookies.every(cookie => !/^other/.test(cookie.key))).toBe(true) - }) - }) - - describe('getCookies', () => { - it('resolves to an array of cookies', async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") - const cookies = await cookieJar.getCookies("http://example.com") - expect(cookies).toEqual([ - expect.objectContaining({ - key: 'foo', - value: 'bar' - }) - ]) - }) - }) - - describe('getCookieString', () => { - it('resolves to a string', async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") - const cookieString = await cookieJar.getCookieString("http://example.com") - expect(cookieString).toBe('foo=bar') - }) - }) - - describe('getSetCookieStrings', () => { - it('resolves to an array of strings', async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") - const cookieString = await cookieJar.getSetCookieStrings("http://example.com") - expect(cookieString).toEqual(['foo=bar; Path=/']) - }) - }) - - describe('removeAllCookies', () => { - it('resolves to an array of strings', async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") - expect(await cookieJar.getCookies("http://example.com")).toHaveLength(1) - await cookieJar.removeAllCookies() - expect(await cookieJar.getCookies("http://example.com")).toHaveLength(0) - }) - }) - - describe('serialize', () => { - it('resolves to an array of strings', async () => { - const now = new Date().toISOString() - - await cookieJar.setCookie("foo=bar", "http://example.com") - const data = await cookieJar.serialize() - const expected: SerializedCookieJar = { - "allowSpecialUseDomain": false, - "cookies": [ - { - "creation": now, - "domain": "example.com", - "hostOnly": true, - "key": "foo", - "lastAccessed": now, - "path": "/", - "pathIsDefault": true, - "value": "bar" - } - ], - "enableLooseMode": false, - "prefixSecurity": "silent", - "rejectPublicSuffixes": true, - "storeType": "MemoryCookieStore", - "version": "tough-cookie@4.0.0" - } - expect(data).toEqual(expected) - }) - }) - }) -}) diff --git a/lib/__tests__/cookie.spec.ts b/lib/__tests__/cookie.spec.ts new file mode 100644 index 00000000..69d1c766 --- /dev/null +++ b/lib/__tests__/cookie.spec.ts @@ -0,0 +1,66 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import {Cookie} from '../cookie' + +jest.useFakeTimers() + +describe('Cookie', () => { + let cookie: Cookie + + describe('constructor', () => { + beforeEach(() => { + cookie = new Cookie({ + key: "test", + value: "b", + maxAge: 60 + }) + }) + + it("should check for key property", () => { + expect(cookie.key).toEqual('test') + }) + + it('should check for value property', () => { + expect(cookie.value).toBe("b"); + }) + + it("should check for maxAge", () => { + expect(cookie.maxAge).toBe(60); + }) + + it("should check for default values for unspecified properties", () => { + expect(cookie.expires).toBe('Infinity') + expect(cookie.secure).toBe(false) + expect(cookie.httpOnly).toBe(false) + }) + }) +}) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts new file mode 100644 index 00000000..8eba0c3a --- /dev/null +++ b/lib/__tests__/cookieJar.spec.ts @@ -0,0 +1,576 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import {Cookie, CookieJar, MemoryCookieStore, SerializedCookieJar} from '../cookie' + +const { objectContaining, assertions } = expect +jest.useFakeTimers() + +describe('CookieJar', () => { + let cookieJar: CookieJar + + beforeEach(() => { + cookieJar = new CookieJar() + }) + + describe('setCookie', () => { + let cookie + + apiVariants('should resolve to a Cookie', { + callbackStyle (done) { + cookieJar.setCookie("foo=bar", "http://example.com", (error, result) => { + cookie = result + done() + }) + }, + async asyncStyle () { + cookie = await cookieJar.setCookie("foo=bar", "http://example.com") + }, + syncStyle () { + cookie = cookieJar.setCookieSync("foo=bar", "http://example.com") + } + }, async () => { + expect(cookie).toBeInstanceOf(Cookie) + expect(cookie.key).toBe('foo') + expect(cookie.value).toBe('bar') + }) + + it('supports the "expiry" option', async () => { + const cookie = await cookieJar.setCookie( + "near=expiry; Domain=example.com; Path=/; Max-Age=1", + "http://www.example.com", + { now: new Date(Date.now() - 1) } + ) + expect(cookie).toEqual(objectContaining({ + key: 'near', + value: 'expiry' + })) + jest.advanceTimersByTime(1) + const cookies = await cookieJar.getCookies("http://www.example.com", { + http: true, + expire: false + }) + expect(cookies).toHaveLength(1) + expect(cookies[0]).toEqual(objectContaining({ + key: 'near', + value: 'expiry' + })) + }) + + describe('the "loose" option', () => { + it('should allow keyless cookie to be accepted when loose: true', async () => { + const cookie = await cookieJar.setCookie("=b", "http://example.com/index.html", { + loose: true + }) + expect(cookie).toEqual(objectContaining({ + key: '', + value: 'b' + })) + }) + + it('should not allow keyless cookie to be accepted when loose: false', async () => { + assertions(1) + await expect(cookieJar.setCookie("=b", "http://example.com/index.html", { + loose: false + })).rejects.toThrowError('Cookie failed to parse') + }) + + it('should not default to loose: true when using map', () => { + const cookies = [ + "=a;domain=example.com", + "=b;domain=example.com", + "c=d;domain=example.com" + ].map(Cookie.parse); + expect(cookies).toEqual([ + undefined, + undefined, + objectContaining({ + key: 'c', + value: 'd' + }) + ]) + }) + }) + + it('should set a timestamp when storing a cookie', async () => { + cookie = Cookie.parse("a=b; Domain=example.com; Path=/") + const t0 = new Date() + + expect(cookie).toEqual(objectContaining({ + hostOnly: null, + creation: t0, + lastAccessed: null + })) + + jest.advanceTimersByTime(10000) + const t1 = new Date() + cookie = await cookieJar.setCookie(cookie, "http://example.com/index.html") + + expect(cookie).toEqual(objectContaining({ + hostOnly: false, + creation: t1, + lastAccessed: t1 + })) + expect(cookie.TTL()).toBe(Infinity) + expect(cookie.isPersistent()).toBe(false) + }) + + it('should be able to set a no-path cookie', async () => { + cookie = await cookieJar.setCookie("a=b; Domain=example.com", "http://example.com/index.html") + expect(cookie).toEqual(objectContaining({ + domain: 'example.com', + path: '/', + pathIsDefault: true + })) + }) + + it('should be able to set a cookie already marked as host-only', async () => { + cookie = await cookieJar.setCookie( + createCookie("a=b; Domain=example.com", { + hostOnly: true + }), + "http://example.com/index.html" + ) + expect(cookie).toEqual(objectContaining({ + domain: 'example.com', + hostOnly: true + })) + }) + + it('should be able to set a session cookie', async () => { + cookie = createCookie("a=b") + expect(cookie.path).toBeNull() + cookie = await cookieJar.setCookie(cookie, "http://www.example.com/dir/index.html") + expect(cookie).toEqual(objectContaining({ + domain: 'www.example.com', + path: '/dir', + hostOnly: true + })) + }) + + it('should fail when the cookie domain does not match the provided domain', async () => { + assertions(1) + await expect(cookieJar.setCookie( + "a=b; Domain=fooxample.com; Path=/", + "http://example.com/index.html" + )).rejects.toThrowError("Cookie not in this host's domain. Cookie:fooxample.com Request:example.com") + }) + + it('should fail when the cookie has a sub-domain but the provided domain is the root', async () => { + assertions(1) + await expect(cookieJar.setCookie( + "a=b; Domain=www.example.com; Path=/", + "http://example.com/index.html" + )).rejects.toThrowError("Cookie not in this host's domain. Cookie:www.example.com Request:example.com") + }) + + it('should allow for a cookie with a root domain to be stored under a super-domain', async () => { + cookie = await cookieJar.setCookie( + "a=b; Domain=example.com; Path=/", + "http://www.app.example.com/index.html" + ) + expect(cookie.domain).toBe('example.com') + }) + + it('should allow a sub-path cookie on a super-domain', async () => { + cookie = await cookieJar.setCookie( + "a=b; Domain=example.com; Path=/subpath", + "http://www.example.com/index.html" + ) + expect(cookie).toEqual(objectContaining({ + domain: 'example.com', + path: '/subpath', + pathIsDefault: null + })) + }) + + it('should fail when using an httpOnly cookie when using a non-HTTP API', async () => { + assertions(1) + await expect(cookieJar.setCookie( + "a=b; Domain=example.com; Path=/; HttpOnly", + "http://example.com/index.html", + { http: false } + )).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") + }) + + it.each([ + { testCase: 'basic', IPv6: '[::1]' }, + { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, + { testCase: 'classic', IPv6: '[2001:4860:4860::8888]' }, + { testCase: 'short', IPv6: '[2600::]' } + ]) + ('should store a $testCase IPv6', async (test) => { + cookie = await cookieJar.setCookie( + `a=b; Domain=${test.IPv6}; Path=/`, + `http://${test.IPv6}/` + ) + expect(cookie).toEqual(objectContaining({ + creation: new Date(), + lastAccessed: new Date(), + })) + expect(cookie.TTL()).toBe(Infinity) + expect(cookie.isPersistent()).toBe(false) + }) + }) + + describe('getCookies', () => { + let cookies + + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to an array of cookies', { + callbackStyle (done) { + cookieJar.getCookies("http://example.com", (error, result) => { + cookies = result + done() + }) + }, + async asyncStyle () { + cookies = await cookieJar.getCookies("http://example.com") + }, + syncStyle () { + cookies = cookieJar.getCookiesSync("http://example.com") + } + }, async () => { + expect(cookies).toEqual([ + objectContaining({ + key: 'foo', + value: 'bar' + }) + ]) + }) + + describe('the "allPaths" option', () => { + beforeEach(async () => { + await cookieJar.removeAllCookies() + const cookiesByUrl = { + "http://example.com": [ + "nopath_dom=qq; Path=/; Domain=example.com", + "path_dom=qq; Path=/foo; Domain=example.com" + ], + "http://www.example.com": [ + "nopath_host=qq; Path=/", + "path_host=qq; Path=/foo" + ], + "http://other.example.com": [ + "other=qq; Path=/" + ], + "http://other.example.com/foo": [ + "other2=qq; Path=/foo" + ] + } + for await (let [url, cookies] of Object.entries(cookiesByUrl)) { + for await (let cookie of cookies) { + await cookieJar.setCookie(cookie, url) + } + } + }) + + it('should set all the cookies', async () => { + const { cookies } = await cookieJar.serialize() + expect(cookies).toHaveLength(6) + }) + + it('should scope cookies by path as the default behavior', async () => { + const cookies = await cookieJar.getCookies("http://www.example.com/") + expect(cookies).toHaveLength(2) + + const allHaveRootPath = cookies.every(cookie => cookie.path === '/') + expect(allHaveRootPath).toBe(true) + + const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) + }) + + it('should scope cookies by path when reading from the /foo path', async () => { + const cookies = await cookieJar.getCookies("http://www.example.com/foo") + expect(cookies).toHaveLength(4) + + const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) + }) + + it('should not scope cookies by path when using allPaths: true', async () => { + const cookies = await cookieJar.getCookies("http://www.example.com/", { + allPaths: true + }) + expect(cookies).toHaveLength(4) + + const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) + }) + }) + }) + + describe('getCookieString', () => { + let cookieString: string + + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to a string', { + callbackStyle (done) { + cookieJar.getCookieString("http://example.com", (err, result) => { + cookieString = result + done() + }) + }, + async asyncStyle () { + cookieString = await cookieJar.getCookieString("http://example.com") + }, + syncStyle() { + cookieString = cookieJar.getCookieStringSync("http://example.com") + } + }, async () => { + expect(cookieString).toBe('foo=bar') + }) + }) + + describe('getSetCookieStrings', () => { + let cookieStrings: string[] + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to an array of strings', { + callbackStyle (done) { + cookieJar.getSetCookieStrings("http://example.com", (error, result) => { + cookieStrings = result + done() + }) + }, + async asyncStyle() { + cookieStrings = await cookieJar.getSetCookieStrings("http://example.com") + }, + syncStyle() { + cookieStrings = cookieJar.getSetCookieStringsSync("http://example.com") + } + }, async () => { + expect(cookieStrings).toEqual(['foo=bar; Path=/']) + }) + }) + + describe('removeAllCookies', () => { + beforeEach(async () => { + await cookieJar.setCookie("a=b", "http://example.com") + await cookieJar.setCookie("c=d", "http://example.com") + expect(await cookieJar.getCookies("http://example.com")).toHaveLength(2) + }) + + apiVariants('should remove all the stored cookies', { + callbackStyle(done) { + cookieJar.removeAllCookies(() => done()) + }, + async asyncStyle() { + await cookieJar.removeAllCookies() + }, + syncStyle() { + cookieJar.removeAllCookiesSync() + } + }, async () => { + expect(await cookieJar.getCookies("http://example.com")).toHaveLength(0) + }) + }) + + describe('serialize', () => { + let data: SerializedCookieJar + let now: string + + beforeEach(async () => { + now = new Date().toISOString() + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to an array of strings', { + callbackStyle(done) { + cookieJar.serialize((error, result) => { + data = result + done() + }) + }, + async asyncStyle() { + data = await cookieJar.serialize() + }, + syncStyle() { + data = cookieJar.serializeSync() + } + }, async () => { + const expected: SerializedCookieJar = { + "allowSpecialUseDomain": false, + "cookies": [ + { + "creation": now, + "domain": "example.com", + "hostOnly": true, + "key": "foo", + "lastAccessed": now, + "path": "/", + "pathIsDefault": true, + "value": "bar" + } + ], + "enableLooseMode": false, + "prefixSecurity": "silent", + "rejectPublicSuffixes": true, + "storeType": "MemoryCookieStore", + "version": "tough-cookie@4.0.0" + } + expect(data).toEqual(expected) + }) + }) + + describe('remove cookies', () => { + beforeEach(async () => { + const cookiesByDomain = { + "http://example.com/index.html": [ + Cookie.parse("a=b; Domain=example.com; Path=/") + ], + "http://foo.com/index.html": [ + Cookie.parse("a=b; Domain=foo.com; Path=/"), + Cookie.parse("foo=bar; Domain=foo.com; Path=/") + ] + } + for await (let [path, cookies] of Object.entries(cookiesByDomain)) { + for await (let cookie of cookies) { + await cookieJar.setCookie(cookie, path) + } + } + }) + + it('should remove all from matching domain', async () => { + await cookieJar.store.removeCookies("example.com", null) + + const exampleCookies = await cookieJar.store.findCookies("example.com", null) + expect(exampleCookies).toHaveLength(0) + + const fooCookies = await cookieJar.store.findCookies("foo.com", null) + expect(fooCookies).toHaveLength(2) + }) + + it('should remove all with matching domain and key', async () => { + await cookieJar.store.removeCookie("foo.com", "/", "foo") + const cookies = await cookieJar.store.findCookies("foo.com", null) + expect(cookies).toEqual([ + objectContaining({ + key: 'a' + }) + ]) + }) + }) +}) + +const specialUseDomains = [ + "local", + "example", + "invalid", + "localhost", + "test" +] +describe.each(specialUseDomains)('when special use domain is %s', (specialUseDomain) => { + it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true + }) + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}` + ) + expect(cookie).toEqual(objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `dev.${specialUseDomain}` + })) + const cookies = await cookieJar.getCookies(`http://dev.${specialUseDomain}`, { + http: true + }) + expect(cookies).toEqual([cookie]) + }) + + it('should reject special domain cookies if allowSpecialUseDomain: false', async () => { + assertions(1) + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: false + }) + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}` + ) + } catch(e) { + expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + } + }) +}) + +function createCookie( + cookieString: string, + options: { + hostOnly?: boolean; + } = {} +): Cookie { + const cookie = Cookie.parse(cookieString) + if (options?.hostOnly) { + cookie.hostOnly = options.hostOnly + } + return cookie +} + +function apiVariants (testName: string, apiVariants: ApiVariants, assertions: () => Promise) { + it(`${testName} (callback)`, async () => { + await new Promise(resolve => apiVariants.callbackStyle(() => resolve(undefined))) + await assertions() + }) + + it(`${testName} (async)`, async () => { + await apiVariants.asyncStyle() + await assertions() + }) + + it(`${testName} (sync)`, async () => { + apiVariants.syncStyle() + await assertions() + }) +} + +type CallbackApiVariant = (done: () => void) => void +type PromiseApiVariant = () => Promise +type SyncApiVariant = () => void + +interface ApiVariants { + callbackStyle: CallbackApiVariant, + asyncStyle: PromiseApiVariant, + syncStyle: SyncApiVariant +} diff --git a/lib/__tests__/date.test.ts b/lib/__tests__/date.spec.ts similarity index 100% rename from lib/__tests__/date.test.ts rename to lib/__tests__/date.spec.ts diff --git a/lib/__tests__/ietf.test.ts b/lib/__tests__/ietf.spec.ts similarity index 100% rename from lib/__tests__/ietf.test.ts rename to lib/__tests__/ietf.spec.ts diff --git a/lib/cookie.ts b/lib/cookie.ts index e9a1da8e..2773ca36 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -474,11 +474,7 @@ function parseCookiePair(cookiePair, looseMode) { return c; } -function parse(str, options) { - if (!options || typeof options !== "object") { - options = {}; - } - +function parse(str: string, options: any = {}): Cookie { if (validators.isEmptyString(str) || !validators.isString(str)) { return null } @@ -826,9 +822,6 @@ export class Cookie { lastAccessed: Date | null | 'Infinity'; sameSite: string | undefined; - static serializableProperties: string[] - static cookiesCreated: number - constructor(options = {}) { if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; @@ -1012,7 +1005,7 @@ export class Cookie { // elsewhere) // S5.3 says to give the "latest representable date" for which we use Infinity // For "expired" we use 0 - TTL(now) { + TTL(now: number = Date.now()) { /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires * attribute, the Max-Age attribute has precedence and controls the * expiration date of the cookie. @@ -1091,26 +1084,30 @@ export class Cookie { cdomain() { return this.canonicalizedDomain(); } -} -Cookie.cookiesCreated = 0; -// @ts-ignore -Cookie.parse = parse; -// @ts-ignore -Cookie.fromJSON = fromJSON; -Cookie.serializableProperties = Object.keys(cookieDefaults); -// @ts-ignore -Cookie.sameSiteLevel = { - strict: 3, - lax: 2, - none: 1 -}; + static parse (cookieString: string, options?): Cookie { + return parse(cookieString, options) + } -// @ts-ignore -Cookie.sameSiteCanonical = { - strict: "Strict", - lax: "Lax" -}; + static fromJSON (jsonString: string): Cookie { + return fromJSON(jsonString) + } + + static cookiesCreated: number = 0 + + static sameSiteLevel = { + strict: 3, + lax: 2, + none: 1 + } + + static sameSiteCanonical = { + strict: "Strict", + lax: "Lax" + } + + static serializableProperties = Object.keys(cookieDefaults) +} function getNormalizedPrefixSecurity(prefixSecurity) { if (prefixSecurity != null) { @@ -1127,14 +1124,6 @@ function getNormalizedPrefixSecurity(prefixSecurity) { return PrefixSecurityEnum.SILENT; } -type SetCookieOptions = { - loose?: boolean; - sameSiteContext?: boolean; - ignoreError?: boolean; - http?: boolean; - now?: Date; -} - const defaultSetCookieOptions: SetCookieOptions = { loose: false, sameSiteContext: false, @@ -1142,25 +1131,6 @@ const defaultSetCookieOptions: SetCookieOptions = { http: false } -interface PromiseCallback { - promise: Promise; - callback: (error?: Error, result?: T) => Promise; -} - -export interface SerializedCookieJar { - version: string; - storeType: string; - rejectPublicSuffixes: boolean; - [key: string]: any; - cookies: SerializedCookie[]; -} - -export interface SerializedCookie { - key: string; - value: string; - [key: string]: any; -} - function createPromiseCallback(args: IArguments): PromiseCallback { let callback: (error: Error, result: T) => Promise let resolve: (result: T) => void @@ -1199,41 +1169,44 @@ function createPromiseCallback(args: IArguments): PromiseCallback { } export class CookieJar { - store: Store; + readonly store: Store; + private readonly rejectPublicSuffixes: boolean; + private readonly enableLooseMode: boolean; + private readonly allowSpecialUseDomain: boolean; + private readonly prefixSecurity: string; constructor(store?: any, options: any = { rejectPublicSuffixes: true }) { if (typeof options === "boolean") { options = { rejectPublicSuffixes: options }; } validators.validate(validators.isObject(options), options); - // @ts-ignore this.rejectPublicSuffixes = options.rejectPublicSuffixes; - // @ts-ignore this.enableLooseMode = !!options.looseMode; - // @ts-ignore this.allowSpecialUseDomain = !!options.allowSpecialUseDomain; this.store = store || new MemoryCookieStore(); - // @ts-ignore this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); - // @ts-ignore - this._cloneSync = syncWrap("clone"); - // @ts-ignore - this._importCookiesSync = syncWrap("_importCookies"); - // @ts-ignore - this.getCookiesSync = syncWrap("getCookies"); - // @ts-ignore - this.getCookieStringSync = syncWrap("getCookieString"); - // @ts-ignore - this.getSetCookieStringsSync = syncWrap("getSetCookieStrings"); - // @ts-ignore - this.removeAllCookiesSync = syncWrap("removeAllCookies"); - // @ts-ignore - this.setCookieSync = syncWrap("setCookie"); - // @ts-ignore - this.serializeSync = syncWrap("serialize"); } - setCookie(cookie, url, options: SetCookieOptions = defaultSetCookieOptions, callback?: (error: Error, result: Cookie) => void): Promise { + private callSync(fn): T { + if (!this.store.synchronous) { + throw new Error( + "CookieJar store is not synchronous; use async API instead." + ); + } + let syncErr, syncResult; + fn.call(this, (error: Error, result: T) => { + syncErr = error + syncResult = result + }) + if (syncErr) throw syncErr; + return syncResult; + } + + setCookie(cookie, url: string, callback: Callback): void; + setCookie(cookie, url: string, options: SetCookieOptions, callback: Callback): void; + setCookie(cookie, url: string): Promise + setCookie(cookie, url: string, options: SetCookieOptions): Promise + setCookie(cookie, url: string, options: SetCookieOptions | Callback = defaultSetCookieOptions, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1245,7 +1218,7 @@ export class CookieJar { } const context = getCookieContext(url); - if (validators.isFunction(options)) { + if (typeof options === 'function') { options = defaultSetCookieOptions; } @@ -1256,7 +1229,6 @@ export class CookieJar { } const host = canonicalDomain(context.hostname); - // @ts-ignore const loose = options.loose || this.enableLooseMode; let sameSiteContext = null; @@ -1292,10 +1264,8 @@ export class CookieJar { // S5.3 step 4: NOOP; domain is null by default // S5.3 step 5: public suffixes - // @ts-ignore if (this.rejectPublicSuffixes && cookie.domain) { const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { - // @ts-ignore allowSpecialUseDomain: this.allowSpecialUseDomain, ignoreError: options.ignoreError }); @@ -1357,10 +1327,8 @@ export class CookieJar { /* 6265bis-02 S5.4 Steps 15 & 16 */ const ignoreErrorForPrefixSecurity = - // @ts-ignore this.prefixSecurity === PrefixSecurityEnum.SILENT; const prefixSecurityDisabled = - // @ts-ignore this.prefixSecurity === PrefixSecurityEnum.DISABLED; /* If prefix checking is not disabled ...*/ if (!prefixSecurityDisabled) { @@ -1431,9 +1399,16 @@ export class CookieJar { return promiseCallback.promise // } } + setCookieSync(cookie, url: string, options?: SetCookieOptions): Cookie { + return this.callSync(this.setCookie.bind(this, cookie, url, options)) + } // RFC6365 S5.4 - getCookies(url: string, options: any = {}, callback?: (error: Error, result: Cookie[]) => void): Promise { + getCookies(url: string, callback: Callback); + getCookies(url: string, options: any, callback: Callback) + getCookies(url: string): Promise + getCookies(url: string, options: any): Promise + getCookies(url: string, options: any = {}, callback?: (error: Error, result: Cookie[]) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1475,7 +1450,6 @@ export class CookieJar { const now = options.now || Date.now(); const expireCheck = options.expire !== false; const allPaths = !!options.allPaths; - // @ts-ignore const store = this.store; function matchingCookie(c) { @@ -1535,7 +1509,6 @@ export class CookieJar { store.findCookies( host, allPaths ? null : path, - // @ts-ignore this.allowSpecialUseDomain, (err, cookies) => { if (err) { @@ -1562,8 +1535,15 @@ export class CookieJar { return promiseCallback.promise } + getCookiesSync(url: string, options: any = {}): Cookie[] { + return this.callSync(this.getCookies.bind(this, url, options)) + } - getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): Promise { + getCookieString(url: string, options: any, callback: (error: Error, result: string) => void): void; + getCookieString(url, callback: (error: Error, result: string) => void): void; + getCookieString(url): Promise; + getCookieString(url, options: any): Promise; + getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const next = function(err: Error, cookies: Cookie[]) { @@ -1583,8 +1563,15 @@ export class CookieJar { this.getCookies(url, options, next) return promiseCallback.promise } + getCookieStringSync(url: string, options: any = {}): string { + return this.callSync(this.getCookieString.bind(this, url, options)) + } - getSetCookieStrings (url: string, options: any = {}, callback?: (error: Error, result: string[]) => void): Promise { + getSetCookieStrings (url: string, callback: Callback): void + getSetCookieStrings (url: string, options: any, callback: Callback): void + getSetCookieStrings (url: string): Promise + getSetCookieStrings (url: string, options: any): Promise + getSetCookieStrings (url: string, options: any = {}, callback?: (error: Error, result: string[]) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const next = function(err: Error, cookies: Cookie[]) { @@ -1603,13 +1590,17 @@ export class CookieJar { this.getCookies(url, options, next); return promiseCallback.promise } + getSetCookieStringsSync(url: string, options: any = {}): string[] { + return this.callSync(this.getSetCookieStrings.bind(this, url, options)) + } - serialize(callback?: (error: Error, data: SerializedCookieJar) => void): Promise { + serialize(callback: Callback): void; + serialize(): Promise; + serialize(callback?: (error: Error, data: SerializedCookieJar) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback validators.validate(validators.isFunction(cb), cb); - // @ts-ignore let type = this.store.constructor.name; if (validators.isObject(type)) { type = null; @@ -1626,13 +1617,9 @@ export class CookieJar { storeType: type, // CookieJar configuration: - // @ts-ignore rejectPublicSuffixes: !!this.rejectPublicSuffixes, - // @ts-ignore enableLooseMode: !!this.enableLooseMode, - // @ts-ignore allowSpecialUseDomain: !!this.allowSpecialUseDomain, - // @ts-ignore prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), // this gets filled from getAllCookies: @@ -1641,9 +1628,7 @@ export class CookieJar { if ( !( - // @ts-ignore this.store.getAllCookies && - // @ts-ignore typeof this.store.getAllCookies === "function" ) ) { @@ -1654,7 +1639,6 @@ export class CookieJar { ); } - // @ts-ignore this.store.getAllCookies((err, cookies) => { if (err) { return cb(err); @@ -1675,9 +1659,11 @@ export class CookieJar { return promiseCallback.promise } + serializeSync(): SerializedCookieJar { + return this.callSync(this.serialize.bind(this)) + } toJSON() { - // @ts-ignore return this.serializeSync(); } @@ -1709,7 +1695,6 @@ export class CookieJar { return putNext(null); // skip this cookie } - // @ts-ignore this.store.putCookie(cookie, putNext); }; @@ -1717,6 +1702,10 @@ export class CookieJar { putNext(); } + _importCookiesSync (serialized): void { + this.callSync(this._importCookies.bind(this, serialized)) + } + clone(newStore, cb) { if (arguments.length === 1) { cb = newStore; @@ -1731,9 +1720,12 @@ export class CookieJar { }); } + _cloneSync(newStore?): void { + return this.callSync(this.clone.bind(this, newStore)) + } + cloneSync(newStore) { if (arguments.length === 0) { - // @ts-ignore return this._cloneSync(); } if (!newStore.synchronous) { @@ -1741,15 +1733,15 @@ export class CookieJar { "CookieJar clone destination store is not synchronous; use async API instead." ); } - // @ts-ignore return this._cloneSync(newStore); } - removeAllCookies(callback?: (error?: Error) => void): Promise { + removeAllCookies(callback: ErrorCallback): void; + removeAllCookies(): Promise; + removeAllCookies(callback?: ErrorCallback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - // @ts-ignore const store = this.store; // Check that the store implements its own removeAllCookies(). The default @@ -1799,6 +1791,9 @@ export class CookieJar { return promiseCallback.promise } + removeAllCookiesSync(): void { + return this.callSync(this.removeAllCookies.bind(this)) + } static deserialize(strOrObj, store, cb) { if (arguments.length !== 3) { @@ -1820,7 +1815,6 @@ export class CookieJar { const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, - // @ts-ignore looseMode: serialized.enableLooseMode, allowSpecialUseDomain: serialized.allowSpecialUseDomain, prefixSecurity: serialized.prefixSecurity @@ -1833,68 +1827,32 @@ export class CookieJar { }); } - static deserializeSync(strOrObj, store) { + static deserializeSync(strOrObj, store): CookieJar { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, - // @ts-ignore looseMode: serialized.enableLooseMode }); // catch this mistake early: - // @ts-ignore if (!jar.store.synchronous) { throw new Error( "CookieJar store is not synchronous; use async API instead." ); } - // @ts-ignore jar._importCookiesSync(serialized); return jar; } -} -// @ts-ignore -CookieJar.fromJSON = CookieJar.deserializeSync; - -[ - "_importCookies", - "clone", - "getCookies", - "getCookieString", - "getSetCookieStrings", - "removeAllCookies", - "serialize", - // "setCookie" -].forEach(name => { - CookieJar.prototype[name] = fromCallback(CookieJar.prototype[name]); -}); -// CookieJar.prototype.setCookie = fromCallback(CookieJar.prototype.setCookie) -CookieJar.deserialize = fromCallback(CookieJar.deserialize); -// Use a closure to provide a true imperative API for synchronous stores. -function syncWrap(method) { - return function(...args) { - if (!this.store.synchronous) { - throw new Error( - "CookieJar store is not synchronous; use async API instead." - ); - } - - let syncErr, syncResult; - this[method](...args, (err, result) => { - syncErr = err; - syncResult = result; - }); - - if (syncErr) { - throw syncErr; - } - return syncResult; - }; + static fromJSON (jsonString: string, store): CookieJar { + return CookieJar.deserializeSync(jsonString, store) + } } +CookieJar.deserialize = fromCallback(CookieJar.deserialize); + const getPublicSuffix = pubsuffix.getPublicSuffix const ParameterError = validators.ParameterError @@ -1915,3 +1873,33 @@ export { permutePath as permutePath } export { canonicalDomain as canonicalDomain } export { PrefixSecurityEnum as PrefixSecurityEnum } export { ParameterError as ParameterError } + +type SetCookieOptions = { + loose?: boolean; + sameSiteContext?: boolean; + ignoreError?: boolean; + http?: boolean; + now?: Date; +} + +interface PromiseCallback { + promise: Promise; + callback: (error?: Error, result?: T) => Promise; +} + +export interface SerializedCookieJar { + version: string; + storeType: string; + rejectPublicSuffixes: boolean; + [key: string]: any; + cookies: SerializedCookie[]; +} + +export interface SerializedCookie { + key: string; + value: string; + [key: string]: any; +} + +export type Callback = (error: Error, result: T) => void +export type ErrorCallback = (error: Error) => void diff --git a/lib/store.ts b/lib/store.ts index 4c5bc666..0c5a1b4e 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -44,7 +44,7 @@ export class Store { throw new Error("findCookie is not implemented"); } - findCookies(domain, path, allowSpecialUseDomain, cb) { + findCookies(domain: string, path?: string, allowSpecialUseDomain: boolean = false, callback?: (error: Error, result: Cookie[]) => void): Promise { throw new Error("findCookies is not implemented"); } @@ -58,15 +58,15 @@ export class Store { throw new Error("updateCookie is not implemented"); } - removeCookie(domain, path, key, cb) { + removeCookie(domain: string, path: string, key: string, callback?: (error: Error) => void): Promise { throw new Error("removeCookie is not implemented"); } - removeCookies(domain, path, cb) { + removeCookies(domain: string, path?: string, callback?: (error: Error) => void): Promise { throw new Error("removeCookies is not implemented"); } - removeAllCookies(cb) { + removeAllCookies(callback?: (error: Error) => void): Promise { throw new Error("removeAllCookies is not implemented"); } From 9c7b9b43ce96db859532a14664a1b5f32c62bab8 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 23 May 2022 22:33:45 -0300 Subject: [PATCH 03/33] Step 1: test parity between jest and vows --- jest.config.js | 8 +- lib/__tests__/canonicalDomain.spec.ts | 43 ++ lib/__tests__/cookieJar.spec.ts | 572 ++++++++++++++++++--- lib/__tests__/cookiePrefixes.spec.ts | 234 +++++++++ lib/__tests__/cookieSorting.spec.ts | 57 ++ lib/__tests__/cookieToAndFromJson.spec.ts | 57 ++ lib/__tests__/cookieToString.spec.ts | 41 ++ lib/__tests__/defaultPath.spec.ts | 28 + lib/__tests__/domainMatch.spec.ts | 71 +++ lib/__tests__/ietf.spec.ts | 2 - lib/__tests__/jarSerialization.spec.ts | 349 +++++++++++++ lib/__tests__/lifetime.spec.ts | 51 ++ lib/__tests__/memoryCookieStore.spec.ts | 19 + lib/__tests__/parse.spec.ts | 383 ++++++++++++++ lib/__tests__/pathMatch.spec.ts | 16 + lib/__tests__/permuteDomain.spec.ts | 37 ++ lib/__tests__/permutePath.spec.ts | 24 + lib/__tests__/regression.spec.ts | 101 ++++ lib/__tests__/removeAll.spec.ts | 181 +++++++ lib/__tests__/sameSite.spec.ts | 186 +++++++ lib/cookie.ts | 192 +++---- lib/{memstore.js => memstore.ts} | 124 +++-- lib/{permuteDomain.js => permuteDomain.ts} | 4 +- lib/{pubsuffix-psl.js => pubsuffix-psl.ts} | 17 +- lib/store.ts | 35 +- lib/{validators.js => validators.ts} | 35 +- lib/version.js | 2 - lib/version.ts | 1 + package.json | 5 +- tsconfig.json | 3 +- 30 files changed, 2634 insertions(+), 244 deletions(-) create mode 100644 lib/__tests__/canonicalDomain.spec.ts create mode 100644 lib/__tests__/cookiePrefixes.spec.ts create mode 100644 lib/__tests__/cookieSorting.spec.ts create mode 100644 lib/__tests__/cookieToAndFromJson.spec.ts create mode 100644 lib/__tests__/cookieToString.spec.ts create mode 100644 lib/__tests__/defaultPath.spec.ts create mode 100644 lib/__tests__/domainMatch.spec.ts create mode 100644 lib/__tests__/jarSerialization.spec.ts create mode 100644 lib/__tests__/lifetime.spec.ts create mode 100644 lib/__tests__/memoryCookieStore.spec.ts create mode 100644 lib/__tests__/parse.spec.ts create mode 100644 lib/__tests__/pathMatch.spec.ts create mode 100644 lib/__tests__/permuteDomain.spec.ts create mode 100644 lib/__tests__/permutePath.spec.ts create mode 100644 lib/__tests__/regression.spec.ts create mode 100644 lib/__tests__/removeAll.spec.ts create mode 100644 lib/__tests__/sameSite.spec.ts rename lib/{memstore.js => memstore.ts} (59%) rename lib/{permuteDomain.js => permuteDomain.ts} (96%) rename lib/{pubsuffix-psl.js => pubsuffix-psl.ts} (87%) rename lib/{validators.js => validators.ts} (78%) delete mode 100644 lib/version.js create mode 100644 lib/version.ts diff --git a/jest.config.js b/jest.config.js index 9e5dccd2..a509eed3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,11 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: "ts-jest", - testEnvironment: "node" + testEnvironment: "node", + maxWorkers: 1, + globals: { + "ts-jest": { + isolatedModules: true + } + } }; diff --git a/lib/__tests__/canonicalDomain.spec.ts b/lib/__tests__/canonicalDomain.spec.ts new file mode 100644 index 00000000..ec94221b --- /dev/null +++ b/lib/__tests__/canonicalDomain.spec.ts @@ -0,0 +1,43 @@ +import {canonicalDomain} from "../cookie"; + +describe('canonicalDomain', () => { + it.each([ + { + description: 'already canonical', + input: 'example.com', + output: 'example.com' + }, + { + description: 'simple', + input: 'EXAMPLE.com', + output: 'example.com' + }, + { + description: 'leading dot stripped', + input: '.EXAMPLE.com', + output: 'example.com' + }, + { + description: 'trailing dot', + input: 'EXAMPLE.com.', + output: 'example.com.' + }, + { + description: 'leading and trailing dot', + input: '.EXAMPLE.com.', + output: 'example.com.' + }, + { + description: 'internal dots', + input: '.EXAMPLE...com.', + output: 'example...com.' + }, + { + description: 'IDN: test.test in greek', + input: 'δοκιμή.δοκιμή', + output: 'xn--jxalpdlp.xn--jxalpdlp' + }, + ])('$description: $input → $output', ({ input, output }) => { + expect(canonicalDomain(input)).toBe(output) + }) +}) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 8eba0c3a..13acdfb6 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie, CookieJar, MemoryCookieStore, SerializedCookieJar} from '../cookie' +import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar} from '../cookie' const { objectContaining, assertions } = expect jest.useFakeTimers() @@ -226,8 +226,7 @@ describe('CookieJar', () => { { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, { testCase: 'classic', IPv6: '[2001:4860:4860::8888]' }, { testCase: 'short', IPv6: '[2600::]' } - ]) - ('should store a $testCase IPv6', async (test) => { + ])('should store a $testCase IPv6', async (test) => { cookie = await cookieJar.setCookie( `a=b; Domain=${test.IPv6}; Path=/`, `http://${test.IPv6}/` @@ -242,32 +241,34 @@ describe('CookieJar', () => { }) describe('getCookies', () => { - let cookies + describe('api', () => { + let cookies - beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") - }) + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) - apiVariants('resolves to an array of cookies', { - callbackStyle (done) { - cookieJar.getCookies("http://example.com", (error, result) => { - cookies = result - done() - }) - }, - async asyncStyle () { - cookies = await cookieJar.getCookies("http://example.com") - }, - syncStyle () { - cookies = cookieJar.getCookiesSync("http://example.com") - } - }, async () => { - expect(cookies).toEqual([ - objectContaining({ - key: 'foo', - value: 'bar' - }) - ]) + apiVariants('resolves to an array of cookies', { + callbackStyle(done) { + cookieJar.getCookies("http://example.com", (error, result) => { + cookies = result + done() + }) + }, + async asyncStyle() { + cookies = await cookieJar.getCookies("http://example.com") + }, + syncStyle() { + cookies = cookieJar.getCookiesSync("http://example.com") + } + }, async () => { + expect(cookies).toEqual([ + objectContaining({ + key: 'foo', + value: 'bar' + }) + ]) + }) }) describe('the "allPaths" option', () => { @@ -330,54 +331,362 @@ describe('CookieJar', () => { expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) }) + + describe('retrieving cookies', () => { + beforeEach(async () => { + const url = "http://example.com/index.html" + + const cookies = await Promise.all([ + cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), + cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), + cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), + // path + cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), + // host only + cookieJar.setCookie("e=5", url, at(4000)), + // other domain + cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), + // expired + cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), + // expired via Max-Age + cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), + ]) + + jest.advanceTimersByTime(2000) // so that 'h=8' expires + + expect(cookies).toHaveLength(8) + }) + + it('should be able to get the cookies for http://nodejs.org', async () => { + const cookies = await cookieJar.getCookies("http://nodejs.org") + expect(cookies).toEqual([ + objectContaining({ + key: 'f', + value: '6', + path: '/', + domain: 'nodejs.org' + }) + ]) + }) + + it('should be able to get the cookies for https://example.com', async () => { + const cookies = await cookieJar.getCookies("https://example.com") + expect(cookies).toEqual([ + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }), + objectContaining({ + key: 'c', + value: '3', + path: '/', + domain: 'example.com', + secure: true + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for https://example.com with the secure: true option', async () => { + const cookies = await cookieJar.getCookies("https://example.com", { + secure: true + }) + expect(cookies).toEqual([ + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }), + objectContaining({ + key: 'c', + value: '3', + path: '/', + domain: 'example.com', + secure: true + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for http://example.com', async () => { + const cookies = await cookieJar.getCookies("http://example.com") + expect(cookies).toEqual([ + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for http://EXAMPlE.com (case-insensitive)', async () => { + const cookies = await cookieJar.getCookies("http://EXAMPlE.com") + expect(cookies).toEqual([ + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for http://example.com with the http: false option', async () => { + const cookies = await cookieJar.getCookies("http://example.com", { + http: false + }) + expect(cookies).toEqual([ + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for http://example.com/foo/bar', async () => { + const cookies = await cookieJar.getCookies("http://example.com/foo/bar") + expect(cookies).toEqual([ + objectContaining({ + key: 'd', + value: '4', + path: '/foo', + domain: 'example.com' + }), + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }), + objectContaining({ + key: 'e', + value: '5', + path: '/', + domain: 'example.com' + }) + ]) + }) + + it('should be able to get the cookies for http://www.example.com/foo/bar', async () => { + const cookies = await cookieJar.getCookies("http://www.example.com/foo/bar") + expect(cookies).toEqual([ + objectContaining({ + key: 'd', + value: '4', + path: '/foo', + domain: 'example.com' + }), + objectContaining({ + key: 'a', + value: '1', + path: '/', + domain: 'example.com' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/', + domain: 'example.com', + httpOnly: true + }) + ]) + }) + }) }) describe('getCookieString', () => { let cookieString: string - beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") + describe('api', () => { + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to a string', { + callbackStyle(done) { + cookieJar.getCookieString("http://example.com", (err, result) => { + cookieString = result + done() + }) + }, + async asyncStyle() { + cookieString = await cookieJar.getCookieString("http://example.com") + }, + syncStyle() { + cookieString = cookieJar.getCookieStringSync("http://example.com") + } + }, async () => { + expect(cookieString).toBe('foo=bar') + }) }) - apiVariants('resolves to a string', { - callbackStyle (done) { - cookieJar.getCookieString("http://example.com", (err, result) => { - cookieString = result - done() - }) - }, - async asyncStyle () { + describe('retrieving cookie strings', () => { + beforeEach(async () => { + const url = "http://example.com/index.html" + const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) + + const cookies = await Promise.all([ + cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), + cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), + cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), + // path + cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), + // host only + cookieJar.setCookie("e=5", url, at(4000)), + // other domain + cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), + // expired + cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), + // expired via Max-Age + cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), + ]) + + jest.advanceTimersByTime(2000) // so that 'h=8' expires + + expect(cookies).toHaveLength(8) + }) + + it('be able to get the cookie string for http://example.com', async () => { cookieString = await cookieJar.getCookieString("http://example.com") - }, - syncStyle() { - cookieString = cookieJar.getCookieStringSync("http://example.com") - } - }, async () => { - expect(cookieString).toBe('foo=bar') + expect(cookieString).toBe("a=1; b=2; e=5") + }) + + it('be able to get the cookie string for http://example.com', async () => { + cookieString = await cookieJar.getCookieString("http://example.com") + expect(cookieString).toBe("a=1; b=2; e=5") + }) }) }) describe('getSetCookieStrings', () => { - let cookieStrings: string[] - beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") + let cookieHeaders: string[] + + describe('api', () => { + beforeEach(async () => { + await cookieJar.setCookie("foo=bar", "http://example.com") + }) + + apiVariants('resolves to an array of strings', { + callbackStyle(done) { + cookieJar.getSetCookieStrings("http://example.com", (error, result) => { + cookieHeaders = result + done() + }) + }, + async asyncStyle() { + cookieHeaders = await cookieJar.getSetCookieStrings("http://example.com") + }, + syncStyle() { + cookieHeaders = cookieJar.getSetCookieStringsSync("http://example.com") + } + }, async () => { + expect(cookieHeaders).toEqual(['foo=bar; Path=/']) + }) }) - apiVariants('resolves to an array of strings', { - callbackStyle (done) { - cookieJar.getSetCookieStrings("http://example.com", (error, result) => { - cookieStrings = result - done() - }) - }, - async asyncStyle() { - cookieStrings = await cookieJar.getSetCookieStrings("http://example.com") - }, - syncStyle() { - cookieStrings = cookieJar.getSetCookieStringsSync("http://example.com") - } - }, async () => { - expect(cookieStrings).toEqual(['foo=bar; Path=/']) + describe('retrieving cookie strings', () => { + beforeEach(async () => { + const url = "http://example.com/index.html" + const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) + + const cookies = await Promise.all([ + cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), + cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), + cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), + // path + cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), + // host only + cookieJar.setCookie("e=5", url, at(4000)), + // other domain + cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), + // expired + cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), + // expired via Max-Age + cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), + ]) + + jest.advanceTimersByTime(2000) // so that 'h=8' expires + + expect(cookies).toHaveLength(8) + }) + + it('be able to get the set-cookie header strings for http://example.com', async () => { + cookieHeaders = await cookieJar.getSetCookieStrings("http://example.com") + expect(cookieHeaders).toEqual([ + "a=1; Domain=example.com; Path=/", + "b=2; Domain=example.com; Path=/; HttpOnly", + "e=5; Path=/" + ]) + }) }) }) @@ -490,14 +799,143 @@ describe('CookieJar', () => { }) }) -const specialUseDomains = [ +it('should allow cookies with the same name under different domains and/or paths', async () => { + const cookieJar = new CookieJar() + const url = "http://www.example.com/" + await Promise.all([ + cookieJar.setCookie("aaaa=xxxx", url, at(0)), + cookieJar.setCookie("aaaa=1111; Domain=www.example.com", url, at(1000)), + cookieJar.setCookie("aaaa=2222; Domain=example.com", url, at(2000)), + cookieJar.setCookie("aaaa=3333; Domain=www.example.com; Path=/pathA", url, at(3000)), + ]) + const cookies = await cookieJar.getCookies("http://www.example.com/pathA") + expect(cookies).toEqual([ + objectContaining({value: '3333'}), + objectContaining({value: '1111'}), + objectContaining({value: '2222'}), + ]) +}) + +describe('setCookie errors', () => { + it('should throw an error if domain is set to a public suffix', async () => { + const cookieJar = new CookieJar() + await expect(cookieJar.setCookie( + "i=9; Domain=kyoto.jp; Path=/", + "kyoto.jp" + )).rejects.toThrowError("Cookie has domain set to a public suffix") + }) + + it('should throw an error if domains do not match', async () => { + const cookieJar = new CookieJar() + await expect(cookieJar.setCookie( + "j=10; Domain=google.com; Path=/", + "http://google.ca" + )).rejects.toThrowError(`Cookie not in this host's domain. Cookie:google.com Request:google.ca`) + }) + + it('should throw an error if trying to overwrite an http cookie with a non-http one', async () => { + const cookieJar = new CookieJar() + const httpCookie = await cookieJar.setCookie( + "k=11; Domain=example.ca; Path=/; HttpOnly", + "http://example.ca", + { http: true } + ) + await expect(cookieJar.setCookie( + "k=12; Domain=example.ca; Path=/", + "http://example.ca", + { http: false } + )).rejects.toThrowError("old Cookie is HttpOnly and this isn't an HTTP API") + + const cookies = await cookieJar.getCookies("http://example.ca", { http: true }) + expect(cookies).toEqual([httpCookie]) + }) +}) + +describe('loose mode', () => { + it('should accept a cookie in loose mode', async () => { + const cookieJar = new CookieJar(null, { looseMode: true }) + await cookieJar.setCookie( + "FooBar", + "http://www.foonet.net" + ) + const cookies = await cookieJar.getCookies("http://www.foonet.net") + expect(cookies).toEqual([ + objectContaining({ + key: "", + value: "FooBar" + }) + ]) + }) + + it('should retain loose mode when cloning cookie store with loose mode enabled', async () => { + const cookieJar = new CookieJar(null, { looseMode: true }) + const clonedCookieJar = CookieJar.fromJSON(cookieJar.toJSON()) + await clonedCookieJar.setCookie( + "FooBar", + "http://www.foonet.net" + ) + const cookies = await clonedCookieJar.getCookies("http://www.foonet.net") + expect(cookies).toEqual([ + objectContaining({ + key: "", + value: "FooBar" + }) + ]) + }) +}) + +it('should fix issue #132', async () => { + const cookieJar = new CookieJar() + await expect(cookieJar.setCookie( + { key: "x", value: "y" }, + "http://example.com/" + )).rejects.toThrowError("First argument to setCookie must be a Cookie object or string") +}) + +it('should fix issue #144', async () => { + const cookieJar = new CookieJar() + const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; + Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; + Secure; HttpOnly, AuthToken=EFKFFFCH@K@GHIHEJCJMMGJM>CDHDEK>CFGK?MHJ + >>JI@B??@CAEHBJH@H@A@GCFDLIMLJEEJEIFGALA?BIM?@G@DEDI@JE?I?HKJBIDDHJMEFEFM + >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; + path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 + GMT; path=/` + await cookieJar.setCookie(cookieString, "https://google.com") + const cookies = await cookieJar.getCookies("https://google.com") + expect(cookies).toEqual([ + objectContaining({ + secure: true + }) + ]) +}) + +it('should fix issue #145', async () => { + assertions(1) + const cookieJar = new CookieJar() + try { + // @ts-ignore + await cookieJar.setCookie("x=y; Domain=example.com; Path=/") + } catch(e) { + expect(e).toBeInstanceOf(ParameterError) + } +}) + +it('should fix issue #197', async () => { + const cookieJar = new CookieJar() + await expect(cookieJar.setCookie( + "", + "https://google.com" + )).rejects.toThrowError('Cookie failed to parse') +}) + +describe.each([ "local", "example", "invalid", "localhost", "test" -] -describe.each(specialUseDomains)('when special use domain is %s', (specialUseDomain) => { +])('when special use domain is %s', (specialUseDomain) => { it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { const cookieJar = new CookieJar(new MemoryCookieStore(), { rejectPublicSuffixes: true, @@ -574,3 +1012,9 @@ interface ApiVariants { asyncStyle: PromiseApiVariant, syncStyle: SyncApiVariant } + +function at (timeFromNow: number) { + return { + now: new Date(Date.now() + timeFromNow) + } +} diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts new file mode 100644 index 00000000..5b2be3d1 --- /dev/null +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -0,0 +1,234 @@ +import {Cookie, CookieJar} from "../cookie"; + +const {objectContaining} = expect + +let cookieJar: CookieJar + +describe('When `prefixSecurity` is enabled for `CookieJar`', () => { + it('need to be redone', () => {}) + // describe(`If a cookie's name begins with a case-sensitive match for the string "__Secure-", then the cookie must be set with a "Secure" attribute`, () => { + // createSecurePrefixTests([ + // {secure: false}, + // {secure: true}, + // ], { + // 'when `prefixSecurity=silent`': 'expect setting cookie to fail silently', + // "when `prefixSecurity=strict`": 'expect setting cookie throws an error', + // "when `prefixSecurity=unsafe-disabled`": 'expect setting cookie works' + // }) + // }) + + // describe(`If a cookie's name begins with a case-sensitive match for the string "__Host-", then the cookie will have been set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute`, () => { + // createHostPrefixTests([ + // {secure: false, useDefaultPath: false, domain: false}, + // {secure: false, useDefaultPath: false, domain: true}, + // {secure: false, useDefaultPath: true, domain: false}, + // {secure: false, useDefaultPath: true, domain: true}, + // {secure: true, useDefaultPath: false, domain: false}, + // {secure: true, useDefaultPath: false, domain: true}, + // {secure: true, useDefaultPath: true, domain: false}, + // {secure: true, useDefaultPath: true, domain: true}, + // ], { + // "when `prefixSecurity=silent`": 'expect setting cookie to fail silently', + // //"when `prefixSecurity=strict`": 'expect setting cookie throws an error', + // // "when `prefixSecurity=unsafe-disabled`": 'expect setting cookie works' + // }) + // }) +}) + +function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], expectations: PrefixTestCaseExpectations) { + const prefixTestCases = ['http', 'https'].reduce((acc: PrefixTestCase[], protocol: Protocol) => { + for (const securePrefixTestCase of securePrefixTestCases) { + const prefixTestCase = { + useDefaultPath: true, + domain: false, + cookieName: '__Secure-SID', + cookieValue: '12345', + ...securePrefixTestCase, + protocol + } + + let expectation: 'valid' | 'invalid' | 'prefixPrevented' + if (protocol === "https" && securePrefixTestCase.secure) { + expectation = 'valid' + } else if (protocol === 'http' && securePrefixTestCase.secure) { + expectation = 'invalid' + } else { + expectation = 'prefixPrevented' + } + + acc.push({ + ...prefixTestCase, + expectation + }) + } + return acc + }, []) + return createPrefixTests(prefixTestCases, expectations) +} + +function createHostPrefixTests(securePrefixTestCases: HostPrefixTestCase[], expectations: PrefixTestCaseExpectations) { + const prefixTestCases: PrefixTestCase[] = securePrefixTestCases.map((hostPrefixTestCase: HostPrefixTestCase) => { + return { + cookieName: '__Host-SID', + cookieValue: '12345', + ...hostPrefixTestCase + } + }) + return createPrefixTests(prefixTestCases, expectations) +} + +function createPrefixTests(prefixTestCases: PrefixTestCase[], expectations: PrefixTestCaseExpectations) { + const protocols: Protocol[] = ['http', 'https'] + for (const [scenario, prefixPreventionExpectation] of Object.entries(expectations) as Array<[PrefixTestScenario, PrefixTestExpectation]>) { + describe(scenario, () => { + const prefixSecurity = lookupPrefixSecurity(scenario) + beforeEach(createCookieJar({ prefixSecurity })) + + protocols.forEach(protocol => { + describe(`using ${protocol} protocol`, () => { + const testCases = partitionCases(prefixTestCases, protocol) + expectScenario(testCases, 'valid', 'expect setting cookie works', protocol) + expectScenario(testCases, 'invalid', 'expect setting cookie to fail silently', protocol) + expectScenario(testCases, 'prefixPrevented', prefixPreventionExpectation, protocol) + }) + }) + }) + } +} + +function expectScenario(partitionedTestCases: PartitionedPrefixTestCases, testCasesKey: keyof PartitionedPrefixTestCases, expectation: PrefixTestExpectation, protocol: Protocol) { + const onTestCase = lookupExpectationHandler(expectation) + const testCases = partitionedTestCases[testCasesKey] + testCases.forEach(testCase => { + it(`[${testCasesKey}] ${expectation}: ${toCookie(testCase)}`, () => { + onTestCase(testCase, protocol) + }) + }) +} + +function toCookie(testCase: PrefixTestCase) { + return [ + `${testCase.cookieName}=${testCase.cookieValue}`, + testCase.secure ? 'Secure' : undefined, + testCase.useDefaultPath ? undefined : 'Path=/some/path', + testCase.domain ? 'Domain=example.com' : undefined + ].filter(v => v).join('; ') +} + +function lookupPrefixSecurity(prefixTestScenario: PrefixTestScenario): PrefixSecurityValue { + switch (prefixTestScenario) { + case 'when `prefixSecurity=silent`': + return 'silent' + case 'when `prefixSecurity=strict`': + return 'strict' + case 'when `prefixSecurity=unsafe-disabled`': + return 'unsafe-disabled' + } +} + +function lookupExpectationHandler(expectation): (testCase: PrefixTestCase, protocol: Protocol) => Promise { + switch (expectation) { + case "expect setting cookie to fail silently": + return expectSettingCookieToFailSilently + case "expect setting cookie throws an error": + return expectSettingCookieThrowsAnError + case "expect setting cookie works": + return expectSettingCookieWorks + } +} + +async function expectSettingCookieToFailSilently(testCase: PrefixTestCase, protocol: Protocol) { + const cookie = await createCookieFrom(testCase, protocol) + expect(cookie).toBeUndefined() +} + +async function expectSettingCookieThrowsAnError(testCase: PrefixTestCase, protocol: Protocol) { + const errorMessage = testCase.cookieName.startsWith('__Secure') + ? `Cookie has __Secure prefix but Secure attribute is not set` + : `Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'` + await expect(createCookieFrom(testCase, protocol)) + .rejects + .toThrowError(errorMessage) +} + +async function expectSettingCookieWorks(testCase: PrefixTestCase, protocol: Protocol) { + const cookie = await createCookieFrom(testCase, protocol) + expect(cookie).toEqual(objectContaining({ + key: testCase.cookieName, + value: testCase.cookieValue + })) +} + +function createCookieJar(options: { prefixSecurity: PrefixSecurityValue }): () => void { + return () => { + const { prefixSecurity } = options + cookieJar = new CookieJar(null, { prefixSecurity }) + expect(cookieJar.prefixSecurity).toBe(prefixSecurity) + } +} + +async function createCookieFrom(testCase: PrefixTestCase, protocol: Protocol): Promise { + const cookie = toCookie(testCase) + const domainWithProtocol = `${protocol}://www.example.com/${testCase.useDefaultPath ? '' : 'some/path'}` + try { + await cookieJar.setCookie(cookie, domainWithProtocol) + } catch (e) { + throw e + } + const cookies = await cookieJar.getCookies(domainWithProtocol) + return cookies[0] +} + +function partitionCases(testCases: PrefixTestCase[], protocol: Protocol): PartitionedPrefixTestCases { + const valid = testCases.filter(testCase => { + if (testCase.cookieName.startsWith('__Host')) { + return testCase.secure && protocol === 'https' && testCase.useDefaultPath + } + return testCase.secure && protocol === 'https' + }) + const invalid = testCases.filter(testCase => { + if (testCase.cookieName.startsWith('__Host')) { + return testCase.secure && protocol !== 'https' && !testCase.domain + } + return testCase.secure && protocol !== 'https' + }) + const prefixPrevented = testCases.filter(testCase => !valid.includes(testCase) && !invalid.includes(testCase)) + expect(valid.length + invalid.length + prefixPrevented.length).toBe(testCases.length) + return { valid, invalid, prefixPrevented } +} + +type PartitionedPrefixTestCases = { + valid: PrefixTestCase[]; + invalid: PrefixTestCase[]; + prefixPrevented: PrefixTestCase[]; +} + +type Protocol = 'http' | 'https'; + +type PrefixTestCase = { + secure: boolean; + useDefaultPath: boolean; + domain: boolean; + cookieName: string; + cookieValue: string; + protocol: Protocol; + expectation: 'prefixPrevented' | 'valid' | 'invalid' +} + +type PrefixSecurityValue = 'strict' | 'silent' | 'unsafe-disabled' + +type PrefixTestScenario = 'when `prefixSecurity=silent`' | + 'when `prefixSecurity=strict`' | + 'when `prefixSecurity=unsafe-disabled`'; + +type PrefixTestExpectation = 'expect setting cookie to fail silently' | + 'expect setting cookie throws an error' | + 'expect setting cookie works'; + +type PrefixTestCaseExpectations = { + [key in PrefixTestScenario]?: PrefixTestExpectation; +}; + +type SecurePrefixTestCase = Pick + +type HostPrefixTestCase = Omit diff --git a/lib/__tests__/cookieSorting.spec.ts b/lib/__tests__/cookieSorting.spec.ts new file mode 100644 index 00000000..7cee83b1 --- /dev/null +++ b/lib/__tests__/cookieSorting.spec.ts @@ -0,0 +1,57 @@ +import {Cookie, cookieCompare, CookieJar} from "../cookie"; + +jest.useFakeTimers() + +describe('Cookie sorting', () => { + describe('assumptions', () => { + it('should set the creation index during construction', () => { + const cookie1 = new Cookie() + const cookie2 = new Cookie() + expect(typeof cookie1.creationIndex).toBe('number') + expect(typeof cookie2.creationIndex).toBe('number') + expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex) + }) + + it('should set the creation index during construction when creation time is provided', () => { + const now = new Date() + const cookie1 = new Cookie({ creation: now }) + const cookie2 = new Cookie({ creation: now }) + expect(cookie1.creation).toEqual(cookie2.creation) + expect(typeof cookie1.creationIndex).toBe('number') + expect(typeof cookie2.creationIndex).toBe('number') + expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex) + }) + + it('should leave the creation index alone during setCookie', async () => { + const cookieJar = new CookieJar() + const cookie = new Cookie({ key: 'k', value: 'v', domain: 'example.com' }) + const { creationIndex } = cookie + await cookieJar.setCookie(cookie, 'http://example.com/') + expect(cookie.creationIndex).toBe(creationIndex) + }) + + it('should preserve the creation index during update with setCookie', async () => { + const cookieJar = new CookieJar() + const cookie = new Cookie({ key: 'k', value: 'v1', domain: 'example.com' }) + const { creationIndex } = cookie + await cookieJar.setCookie(cookie, 'http://example.com/') + expect(cookie.creationIndex).toBe(creationIndex) + + const updatedCookie = new Cookie({ key: 'k', value: 'v2', domain: 'example.com' }) + await cookieJar.setCookie(updatedCookie, 'http://example.com/') + expect(cookie.creationIndex).toBe(updatedCookie.creationIndex) + }) + }) + + it('should sort an array of cookies accordingly by path length (desc), creation time (asc), creation index (asc)', () => { + const cookies = [ + new Cookie({ key: 'a', value: '' }), + new Cookie({ key: 'b', value: '' }), + new Cookie({ key: 'c', value: '', path: '/path' }), + new Cookie({ key: 'd', value: '', path: '/path' }), + new Cookie({ key: 'e', value: '', path: '/longer/path', creation: new Date(Date.now() + 1) }), + new Cookie({ key: 'f', value: '', path: '/longer/path', creation: new Date(Date.now() + 2) }), + ].sort(cookieCompare) + expect(cookies.map(cookie => cookie.key)).toEqual(['e', 'f', 'c', 'd', 'a', 'b']) + }) +}) diff --git a/lib/__tests__/cookieToAndFromJson.spec.ts b/lib/__tests__/cookieToAndFromJson.spec.ts new file mode 100644 index 00000000..f58f4eeb --- /dev/null +++ b/lib/__tests__/cookieToAndFromJson.spec.ts @@ -0,0 +1,57 @@ +import {Cookie} from "../cookie"; + +jest.useFakeTimers() + +describe('Cookie.toJSON()', () => { + it('should serialize a cookie to JSON', () => { + const cookie = Cookie.parse("alpha=beta; Domain=example.com; Path=/foo; Expires=Tue, 19 Jan 2038 03:14:07 GMT; HttpOnly") + expect(cookie.toJSON()).toEqual({ + "creation": new Date().toISOString(), + "domain": "example.com", + "expires": "2038-01-19T03:14:07.000Z", + "httpOnly": true, + "key": "alpha", + "path": "/foo", + "value": "beta", + }) + }) +}) + +describe('Cookie.fromJSON()', () => { + it('should deserialize a cookie from JSON', () => { + const json = JSON.stringify({ + "key": "alpha", + "value": "beta", + "domain": "example.com", + "path": "/foo", + "expires": "2038-01-19T03:14:07.000Z", + "httpOnly": true, + "lastAccessed": 2000000000123 + }) + expect(Cookie.fromJSON(json)).toEqual(new Cookie({ + "creation": new Date(), + "domain": "example.com", + "expires": new Date(Date.parse("2038-01-19T03:14:07.000Z")), + "httpOnly": true, + "key": "alpha", + "path": "/foo", + "value": "beta", + "lastAccessed": new Date(2000000000123) + })) + }) + + it('should be able to handle a null value deserialization', () => { + expect(Cookie.fromJSON(null)).toBeNull() + }) + + it('should be able to handle expiry, creation, or lastAccessed with Infinity during deserialization', () => { + const json = JSON.stringify({ + "expires": "Infinity", + "creation": "Infinity", + "lastAccessed": "Infinity", + }) + expect(Cookie.fromJSON(json).expires).toBe("Infinity") + expect(Cookie.fromJSON(json).creation).toBe("Infinity") + expect(Cookie.fromJSON(json).lastAccessed).toBe("Infinity") + }) +}) diff --git a/lib/__tests__/cookieToString.spec.ts b/lib/__tests__/cookieToString.spec.ts new file mode 100644 index 00000000..d6bffee4 --- /dev/null +++ b/lib/__tests__/cookieToString.spec.ts @@ -0,0 +1,41 @@ +import {Cookie} from "../cookie"; + +describe('Cookie.toString()', () => { + it('should produce a string from a simple cookie', () => { + expect(Cookie.parse("a=b").toString()).toBe('a=b') + }) + + it('should trim spaces from the cookie value', () => { + expect(Cookie.parse("a= b ").toString()).toBe('a=b') + }) + + it('should produce a string with an empty value and an attribute', () => { + expect(Cookie.parse("a=;HttpOnly").toString()).toBe('a=; HttpOnly') + }) + + it('should produce a string from a cookie with several attributes', () => { + expect( + Cookie.parse("a=b;Expires=Tue, 18 Oct 2011 07:05:03 GMT;Max-Age=12345;Domain=example.com;Path=/foo;Secure;HttpOnly;MyExtension").toString() + ).toBe('a=b; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Max-Age=12345; Domain=example.com; Path=/foo; Secure; HttpOnly; MyExtension') + }) + + it('should not include the domain on a host-only cookie', () => { + const cookie = new Cookie({ + key: "a", + value: "b", + hostOnly: true, + domain: "shouldnt-stringify.example.com", + path: "/should-stringify" + }) + expect(cookie.toString()).toBe('a=b; Path=/should-stringify') + }) + + it('should output the right expires date when minutes are 10', () => { + const cookie = new Cookie({ + key: "a", + value: "b", + expires: new Date(1284113410000) + }) + expect(cookie.toString()).toBe('a=b; Expires=Fri, 10 Sep 2010 10:10:10 GMT') + }) +}) diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts new file mode 100644 index 00000000..dd27c193 --- /dev/null +++ b/lib/__tests__/defaultPath.spec.ts @@ -0,0 +1,28 @@ +import {defaultPath} from "../cookie"; + +describe('defaultPath', () => { + it.each([ + { + input: null, + output: '/' + }, + { + input: '/', + output: '/' + }, + { + input: '/file', + output: '/' + }, + { + input: '/dir/file', + output: '/dir' + }, + { + input: 'noslash', + output: '/' + }, + ])('defaultPath("$input") => $output', ({ input, output }) => { + expect(defaultPath(input)).toBe(output) + }) +}) diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts new file mode 100644 index 00000000..2ddfcdbe --- /dev/null +++ b/lib/__tests__/domainMatch.spec.ts @@ -0,0 +1,71 @@ +import {domainMatch} from "../cookie"; + +describe('domainMatch', () => { + it.each([ + // string, domain, expect + ["example.com", "example.com", true], // identical + ["eXaMpLe.cOm", "ExAmPlE.CoM", true], // both canonicalized + ["no.ca", "yes.ca", false], + ["wwwexample.com", "example.com", false], + ["www.subdom.example.com", "example.com", true], + ["www.subdom.example.com", "subdom.example.com", true], + ["example.com", "example.com.", false], // RFC6265 S4.1.2.3 + + // nulls and undefineds + [null, "example.com", null], + ["example.com", null, null], + [null, null, null], + [undefined, undefined, null], + + // suffix matching: + ["www.example.com", "example.com", true], // substr AND suffix + ["www.example.com.org", "example.com", false], // substr but not suffix + ["example.com", "www.example.com.org", false], // neither + ["example.com", "www.example.com", false], // super-str + ["aaa.com", "aaaa.com", false], // str can't be suffix of domain + ["aaaa.com", "aaa.com", false], // dom is suffix, but has to match on "." boundary! + ["www.aaaa.com", "aaa.com", false], + ["www.aaa.com", "aaa.com", true], + ["www.aexample.com", "example.com", false], // has to match on "." boundary + + // S5.1.3 "The string is a host name (i.e., not an IP address)" + ["192.168.0.1", "168.0.1", false], // because str is an IP (v4) + ["100.192.168.0.1", "168.0.1", true], // WEIRD: because str is not a valid IPv4 + ["100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not a valid IPv4 + ["::ffff:192.168.0.1", "168.0.1", false], // because str is an IP (v6) + ["::ffff:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6) + ["::FFFF:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6) + ["::192.168.0.1", "192.168.0.1", false], // because str is an IP (yes, v6!) + [":192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6 + [":ffff:100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not valid IPv6 + [":ffff:192.168.0.1", "192.168.0.1", false], + [":ffff:192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6 + ["::Fxxx:192.168.0.1", "168.0.1", true], // WEIRD: because str isnt IPv6 + ["192.168.0.1", "68.0.1", false], + ["192.168.0.1", "2.68.0.1", false], + ["192.168.0.1", "92.68.0.1", false], + ["10.1.2.3", "210.1.2.3", false], + ["2008::1", "::1", false], + ["::1", "2008::1", false], + ["::1", "::1", true], // "are identical" rule, despite IPv6 + ["::3xam:1e", "2008::3xam:1e", false], // malformed IPv6 + ["::3Xam:1e", "::3xaM:1e", true], // identical, even though malformed + ["3xam::1e", "3xam::1e", true], // identical + ["::3xam::1e", "3xam::1e", false], + ["3xam::1e", "::3xam:1e", false], + ["::f00f:10.0.0.1", "10.0.0.1", false], + ["10.0.0.1", "::f00f:10.0.0.1", false], + + // "IP like" hostnames: + ["1.example.com", "example.com", true], + ["11.example.com", "example.com", true], + ["192.168.0.1.example.com", "example.com", true], + + // exact length "TLD" tests: + ["com", "net", false], // same len, non-match + ["com", "com", true], // "are identical" rule + ["NOTATLD", "notaTLD", true], // "are identical" rule (after canonicalization) + ])('domainMatch(%s, %s) => %s', (string, domain, expectedValue) => { + expect(domainMatch(string, domain)).toBe(expectedValue) + }) +}) diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index a2703341..43b9486a 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -47,11 +47,9 @@ describe('IETF http state tests', () => { : `http://home.example.org/cookie-parser-result?${testCase.test}`; testCase["received"].forEach(cookieStr => { - // @ts-ignore jar.setCookieSync(cookieStr, sentFrom, {ignoreError: true}); }); - // @ts-ignore const actual = jar.getCookiesSync(sentTo, {sort: true}) as Array<{ key: string, value: string }>; expect(actual.length).toBe(expected.length) diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts new file mode 100644 index 00000000..4aa31b2f --- /dev/null +++ b/lib/__tests__/jarSerialization.spec.ts @@ -0,0 +1,349 @@ +/*! + * Copyright (c) 2015, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import {Cookie, CookieJar, MemoryCookieStore, Store, version} from '../cookie' + +const { any, objectContaining } = expect + +jest.useFakeTimers() + +describe('cookieJar serialization', () => { + it('should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`', () => { + expect(Cookie.serializableProperties).toEqual([ + "key", + "value", + "expires", + "maxAge", + "domain", + "path", + "secure", + "httpOnly", + "extensions", + "hostOnly", + "pathIsDefault", + "creation", + "lastAccessed", + "sameSite", + ]) + }) + + describe('a store without `getAllCookies`', () => { + it('cannot call toJSON', () => { + const store = new Store() + store.synchronous = true + + const jar = new CookieJar(store) + expect(() => jar.toJSON()).toThrowError('getAllCookies is not implemented (therefore jar cannot be serialized)') + }) + }) + + describe('for async stores', () => { + it('cannot call toJSON', () => { + const store = new MemoryCookieStore() + store.synchronous = false + const jar = new CookieJar(store) + expect(() => jar.toJSON()).toThrowError('CookieJar store is not synchronous; use async API instead.') + }) + }) + + describe('with a small store', () => { + let jar: CookieJar + + beforeEach(async () => { + jar = new CookieJar() + + // domain cookie with custom extension + await jar.setCookie( + "sid=one; domain=example.com; path=/; fubar", + "http://example.com/" + ) + + await jar.setCookie( + "sid=two; domain=example.net; path=/; fubar", + "http://example.net/" + ) + }) + + it('should serialize synchronously', () => { + const serializedJar = jar.serializeSync() + expectDataToMatchSerializationSchema(serializedJar) + expect(serializedJar.cookies.length).toBe(2) + }) + + it('should deserialize synchronously', () => { + const serializedJar = jar.serializeSync() + const deserializedJar = CookieJar.deserializeSync(serializedJar) + expect(jar.store).toEqual(deserializedJar.store); + }) + + it('should serialize asynchronously', async () => { + const serializedJar = await jar.serialize() + expectDataToMatchSerializationSchema(serializedJar) + expect(serializedJar.cookies.length).toBe(2) + }) + + it('should deserialize asynchronously', async () => { + const serializedJar = await jar.serialize() + const deserializedJar = await CookieJar.deserialize(serializedJar) + expect(jar.store).toEqual(deserializedJar.store); + }) + }) + + describe('with a small store for cloning', () => { + let jar: CookieJar + + beforeEach(async () => { + jar = new CookieJar() + + // domain cookie with custom extension + await jar.setCookie( + "sid=three; domain=example.com; path=/; cloner", + "http://example.com/" + ) + + await jar.setCookie( + "sid=four; domain=example.net; path=/; cloner", + "http://example.net/" + ) + }) + + it('should contain the same contents when cloned asynchronously', async () => { + const clonedJar = await jar.clone(new MemoryCookieStore()) + expect(clonedJar.store).toEqual(jar.store) + }) + + it('should contain the same contents when cloned synchronously', async () => { + const clonedJar = jar.cloneSync(new MemoryCookieStore()) + expect(clonedJar.store).toEqual(jar.store) + }) + + it('should raise an error when attempting to synchronously clone to an async store', async () => { + const newStore = new MemoryCookieStore(); + newStore.synchronous = false; + expect(() => jar.cloneSync(newStore)).toThrowError('CookieJar clone destination store is not synchronous; use async API instead.') + }) + }) + + describe('with a moderately-sized store', () => { + let jar: CookieJar + let expires: Date + + beforeEach(async () => { + expires = new Date(Date.now() + 86400000) + + jar = new CookieJar() + + // Do paths first since the MemoryCookieStore index is domain at the top + // level. This should cause the preservation of creation order in + // getAllCookies to be exercised. + const paths = ["/", "/foo", "/foo/bar"] + const domains = ["example.com", "www.example.com", "example.net"] + for await (const path of paths) { + for await (const domain of domains) { + const key = "key" + const value = JSON.stringify({ path, domain }) + const cookie = new Cookie({ expires, domain, path, key, value }) + await jar.setCookie(cookie, `http://${domain}/`); + } + } + + // corner cases + const cornerCases = [ + { expires: "Infinity", key: "infExp", value: "infExp" }, + { maxAge: 3600, key: "max", value: "max" }, + { + expires, + key: "flags", + value: "flags", + secure: true, + httpOnly: true + }, + { + expires, + key: "honly", + value: "honly", + hostOnly: true, + domain: "www.example.org" + } + ]; + + for await (const cornerCase of cornerCases) { + const domain = cornerCase.domain ?? "example.org" + const path = '/' + const cookie = new Cookie({ ...cornerCase, domain, path }) + await jar.setCookie(cookie, "https://www.example.org/", { + ignoreError: true + }); + } + }) + + it('should have the expected metadata', async () => { + const serializedJar = await jar.serialize() + expect(serializedJar.version).toBe(`tough-cookie@${version}`) + expect(serializedJar.rejectPublicSuffixes).toBe(true) + expect(serializedJar.storeType).toBe('MemoryCookieStore') + }) + + it('should contain the expected serialized cookies', async () => { + const serializedJar = await jar.serialize() + expect(serializedJar.cookies.length).toBe(13) + expectDataToMatchSerializationSchema(serializedJar) + serializedJar.cookies.forEach(serializedCookie => { + if (serializedCookie.key === 'key') { + expect(JSON.parse(serializedCookie.value)).toEqual(objectContaining({ + domain: any(String), + path: any(String) + })) + } + + if (serializedCookie.key === 'infExp' || serializedCookie.key === 'max') { + expect(serializedCookie.expires).toBeFalsy() + } else { + expect(serializedCookie.expires).toBe(expires.toISOString()) + } + + if (serializedCookie.key === 'max') { + expect(serializedCookie.maxAge).toBe(3600) + } else { + expect(serializedCookie.maxAge).toBeUndefined() + } + + if (serializedCookie.key === 'flags') { + expect(serializedCookie.secure).toBe(true) + expect(serializedCookie.httpOnly).toBe(true) + } else { + expect(serializedCookie.secure).toBeUndefined() + expect(serializedCookie.httpOnly).toBeUndefined() + } + + expect(serializedCookie.hostOnly).toBe(serializedCookie.key === "honly") + + expect(serializedCookie.creation).toBe(new Date().toISOString()) + expect(serializedCookie.lastAccessed).toBe(new Date().toISOString()) + }) + }) + + it('should be the same when deserialized', async () => { + const serializedJar = await jar.serialize() + const deserializedJar = await CookieJar.deserialize(serializedJar) + expect(deserializedJar.store).toEqual(jar.store) + + const cookies = await deserializedJar.getCookies('http://example.org/') + expect(cookies).toEqual([ + objectContaining({ + key: 'infExp', + expires: 'Infinity', + }), + objectContaining({ + key: 'max' + }) + ]) + expect(cookies[0].TTL(Date.now())).toBe(Infinity) + expect(cookies[1].TTL(Date.now())).toBe(3600 * 1000) + }) + }) +}) + +function expectDataToMatchSerializationSchema (serializedJar) { + expect(serializedJar).not.toBeNull() + expect(serializedJar).toBeInstanceOf(Object) + expect(serializedJar.version).toBe('tough-cookie@4.0.0') + expect(serializedJar.storeType).toBe('MemoryCookieStore') + expect(serializedJar.rejectPublicSuffixes).toBe(true) + expect(serializedJar.cookies).toBeInstanceOf(Array) + serializedJar.cookies.forEach(cookie => validateSerializedCookie(cookie)) +} + +const serializedCookiePropTypes = { + key: "string", + value: "string", + expires: "isoDate", // if "Infinity" it's supposed to be missing + maxAge: "intOrInf", + domain: "string", + path: "string", + secure: "boolean", + httpOnly: "boolean", + extensions: "array", // of strings, technically + hostOnly: "boolean", + pathIsDefault: "boolean", + creation: "isoDate", + lastAccessed: "isoDate", + sameSite: "string" +}; + +function validateSerializedCookie(cookie) { + expect(typeof cookie).toBe('object'); + expect(cookie).not.toBeInstanceOf(Cookie); + + Object.keys(cookie).forEach(prop => { + const type = serializedCookiePropTypes[prop]; + switch (type) { + case "string": + case "boolean": + case "number": + expect(typeof cookie[prop]).toBe(type); + break; + + case "array": + expect(Array.isArray(cookie[prop])).toBe(true); + break; + + case "intOrInf": + if (cookie[prop] !== "Infinity" && cookie[prop] !== "-Infinity") { + expect(isInteger(cookie[prop])).toBe(true) + } + break; + + case "isoDate": + if (cookie[prop] != null) { + const parsed = new Date(Date.parse(cookie[prop])); + expect(cookie[prop]).toBe(parsed.toISOString()); + } + break; + + default: + fail(`unexpected serialized property: ${prop}`); + } + }); +} + +function isInteger(value) { + if (Number.isInteger) { + return Number.isInteger(value) + } + // Node 0.10 (still supported) doesn't have Number.isInteger + // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger + return ( + typeof value === "number" && + isFinite(value) && + Math.floor(value) === value + ); +} diff --git a/lib/__tests__/lifetime.spec.ts b/lib/__tests__/lifetime.spec.ts new file mode 100644 index 00000000..7af3378e --- /dev/null +++ b/lib/__tests__/lifetime.spec.ts @@ -0,0 +1,51 @@ +import {Cookie} from "../cookie"; + +jest.useFakeTimers() + +describe('Lifetime', () => { + it('should be able to set a TTL using max-age', () => { + const cookie = new Cookie() + cookie.maxAge = 123 + expect(cookie.TTL()).toBe(123_000) + expect(cookie.expiryTime(new Date(9_000_000))).toBe(9_123_000) + }) + + it('should be treat a TTL with zero max-age as "earliest representable"', () => { + const cookie = new Cookie({ key: 'a', value: 'b', maxAge: 0 }) + expect(cookie.TTL()).toBe(0) + expect(cookie.expiryTime(new Date(9_000_000))).toBe(-Infinity) + expect(cookie.validate()).toBe(false) + }) + + it('should be treat a TTL with negative max-age as "earliest representable"', () => { + const cookie = new Cookie({ key: 'a', value: 'b', maxAge: -1 }) + expect(cookie.TTL()).toBe(0) + expect(cookie.expiryTime(new Date(9_000_000))).toBe(-Infinity) + expect(cookie.validate()).toBe(false) + }) + + it('should be able control the TTL with max-age and expiry in the future', () => { + const cookie = new Cookie({ key: 'a', value: 'b', maxAge: 123, expires: new Date(Date.now() + 9_000) }) + expect(cookie.TTL()).toBe(123_000) + expect(cookie.isPersistent()).toBe(true) + }) + + it('should be able control the TTL with expiry in the future', () => { + const cookie = new Cookie({ key: 'a', value: 'b', expires: new Date(Date.now() + 9_000) }) + expect(cookie.TTL()).toBe(9_000) + expect(cookie.expiryTime()).toEqual((cookie.expires as Date).getTime()) + }) + + it('should be able control the TTL with expiry in the past', () => { + const cookie = new Cookie({ key: 'a', value: 'b'}) + cookie.setExpires('17 Oct 2010 00:00:00 GMT') + expect(cookie.TTL()).toBeLessThan(0) + expect(cookie.isPersistent()).toBe(true) + }) + + it('should have a default TTL', () => { + const cookie = new Cookie() + expect(cookie.TTL()).toBe(Infinity) + expect(cookie.isPersistent()).toBe(false) + }) +}) diff --git a/lib/__tests__/memoryCookieStore.spec.ts b/lib/__tests__/memoryCookieStore.spec.ts new file mode 100644 index 00000000..1a59fe0c --- /dev/null +++ b/lib/__tests__/memoryCookieStore.spec.ts @@ -0,0 +1,19 @@ +import {Cookie, MemoryCookieStore} from "../cookie"; + +describe('MemoryCookieStore', () => { + it('should have no static methods', () => { + expect(Object.keys(MemoryCookieStore)).toEqual([]) + }) + + it('should have instance methods that return promises', () => { + const memoryCookieStore = new MemoryCookieStore() + expect(memoryCookieStore.findCookie("example.com", "/", "key")).toBeInstanceOf(Promise); + expect(memoryCookieStore.findCookies("example.com", "/")).toBeInstanceOf(Promise); + expect(memoryCookieStore.putCookie(new Cookie())).toBeInstanceOf(Promise); + expect(memoryCookieStore.updateCookie(new Cookie(), new Cookie())).toBeInstanceOf(Promise); + expect(memoryCookieStore.removeCookie("example.com", "/", "key")).toBeInstanceOf(Promise); + expect(memoryCookieStore.removeCookies("example.com", "/")).toBeInstanceOf(Promise); + expect(memoryCookieStore.removeAllCookies()).toBeInstanceOf(Promise); + expect(memoryCookieStore.getAllCookies()).toBeInstanceOf(Promise); + }) +}) diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts new file mode 100644 index 00000000..509819da --- /dev/null +++ b/lib/__tests__/parse.spec.ts @@ -0,0 +1,383 @@ +import {Cookie} from "../cookie"; +import {performance} from 'node:perf_hooks' + +describe('Cookie.parse', () => { + it.each([ + { + input: 'a=bcd; Expires=Tue, 18 Oct 2011 07:05:03 GMT', + output: { + key: 'a', + value: 'bcd', + expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')) + } + }, + { + input: 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc', + output: { + key: 'abc', + value: '"xyzzy!"', + expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')), + path: '/aBc', + httpOnly: false, + secure: false + } + }, + { + input: 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc; Domain=example.com; Secure; HTTPOnly; Max-Age=1234; Foo=Bar; Baz', + output: { + key: 'abc', + value: '"xyzzy!"', + expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')), + path: '/aBc', + domain: 'example.com', + secure: true, + httpOnly: true, + maxAge: 1234, + sameSite: 'none', + extensions: ['Foo=Bar', 'Baz'] + } + }, + { + input: 'a=b; Expires=xyzzy', + output: { + key: 'a', + value: 'b', + expires: "Infinity" + } + }, + { + input: 'a=b; Max-Age=0', + output: { + key: 'a', + value: 'b', + maxAge: 0 + } + }, + { + input: 'a=b; Max-Age=-1', + output: { + key: 'a', + value: 'b', + maxAge: -1 + } + }, + { + input: 'a=b; domain=', + output: { + key: 'a', + value: 'b', + domain: null + } + }, + { + input: 'a=b; domain=.', + output: { + key: 'a', + value: 'b', + domain: null + } + }, + { + input: 'a=b; domain=EXAMPLE.COM', + output: { + key: 'a', + value: 'b', + domain: "example.com" + } + }, + { + input: 'a=b; Domain=example.com.', + output: { + key: 'a', + value: 'b', + domain: "example.com." + } + }, + { + input: 'a=b; path=', + output: { + key: 'a', + value: 'b', + path: null + } + }, + { + input: 'a=b; path=xyzzy', + output: { + key: 'a', + value: 'b', + path: null + } + }, + { + input: 'a=b; path=/;', + output: { + key: 'a', + value: 'b', + path: '/' + } + }, + { + input: 'c=d;;;;', + output: { + key: 'c', + value: 'd', + path: null + } + }, + { + input: 'a=b; Secure=xyzzy', + output: { + key: 'a', + value: 'b', + secure: true + } + }, + { + input: 'a=b; HttpOnly=xyzzy', + output: { + key: 'a', + value: 'b', + httpOnly: true + } + }, + { + input: '\x08', + output: undefined + }, + { + input: 'a=b; domain=kyoto.jp', + output: { + key: 'a', + value: 'b', + domain: 'kyoto.jp' + }, + }, + { + input: 'a=b; domain=foonet.net', + output: { + key: 'a', + value: 'b', + domain: 'foonet.net' + }, + }, + { + input: 'a=b; domain=www.foonet.net', + output: { + key: 'a', + value: 'b', + domain: 'www.foonet.net' + }, + }, + { + input: 'a=b; domain=.foonet.net', + output: { + key: 'a', + value: 'b', + domain: 'foonet.net' + }, + }, + { + input: 'GAPS=1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-;Path=/;Expires=Thu, 17-Apr-2014 02:12:29 GMT;Secure;HttpOnly', + output: { + key: 'GAPS', + value: '1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-', + path: '/', + expires: new Date(Date.parse('Thu, 17-Apr-2014 02:12:29 GMT')), + secure: true, + httpOnly: true + }, + }, + { + input: 'queryPref=b=c&d=e; Path=/f=g; Expires=Thu, 17 Apr 2014 02:12:29 GMT; HttpOnly', + output: { + key: 'queryPref', + value: 'b=c&d=e', + path: '/f=g', + expires: new Date(Date.parse('Thu, 17 Apr 2014 02:12:29 GMT')), + httpOnly: true + }, + }, + { + input: 'a=one two three', + output: { + key: 'a', + value: 'one two three', + path: null, + domain: null, + extensions: null + }, + }, + { + input: 'a="one two three"', + output: { + key: 'a', + value: '"one two three"', + path: null, + domain: null, + extensions: null + }, + }, + { + input: 'farbe=weiß', + output: { + key: 'farbe', + value: 'weiß', + path: null, + domain: null, + extensions: null + }, + }, + { + input: '=abc', + output: { + key: '', + value: 'abc', + path: null, + domain: null, + extensions: null + }, + parseOptions: { loose: true } + }, + { + input: 'abc', + output: { + key: '', + value: 'abc', + path: null, + domain: null, + extensions: null + }, + parseOptions: { loose: true } + }, + { + input: '=foo=bar', + output: { + key: 'foo', + value: 'bar', + path: null, + domain: null, + extensions: null + }, + parseOptions: { loose: true } + }, + { + input: `foo=bar${";".repeat(65535)} domain=example.com`, + output: { + key: 'foo', + value: 'bar', + path: null, + domain: 'example.com', + extensions: null + } + }, + { + input: `x x`, + output: undefined + }, + { + input: `x${" ".repeat(65535)}x`, + output: undefined + }, + { + input: `abc=xyzzy; SameSite=Lax`, + output: { + key: 'abc', + value: 'xyzzy', + sameSite: 'lax', + extensions: null + } + }, + { + input: `abc=xyzzy; SameSite=StRiCt`, + output: { + key: 'abc', + value: 'xyzzy', + sameSite: 'strict', + extensions: null + } + }, + { + input: `abc=xyzzy; SameSite=example.com`, + output: { + key: 'abc', + value: 'xyzzy', + sameSite: 'none', + extensions: null + } + }, + { + input: ``, + output: null + }, + { + input: undefined, + output: null + }, + { + input: new String(''), + output: null + }, + { + input: new String(), + output: null + } + ])('Cookie.parse("$input")', ({input, output, parseOptions = {}}) => { + const value = input === undefined ? undefined : input.valueOf() + const cookie = Cookie.parse(value, parseOptions) + if (output !== undefined) { + expect(cookie).toEqual(expect.objectContaining(output)) + } else { + expect(cookie).toBe(output) + } + }) + + it.each([ + { + shortVersion: 'x x', + longVersion: `x${" ".repeat(65535)}x`, + }, + { + shortVersion: 'x x', + longVersion: `x${" ".repeat(65535)}x`, + parseOptions: { loose: true } + }, + { + shortVersion: 'x =x', + longVersion: `x${" ".repeat(65535)}=x` + }, + { + shortVersion: 'x =x', + longVersion: `x${" ".repeat(65535)}=x`, + parseOptions: { loose: true } + }, + ])('Cookie.parse("$shortVersion") should not take significantly longer to run than Cookie.parse("$longVersion")', ({ shortVersion, longVersion , parseOptions = {}}) => { + const startShortVersionParse = performance.now() + Cookie.parse(shortVersion, parseOptions) + const endShortVersionParse = performance.now() + + const startLongVersionParse = performance.now() + Cookie.parse(longVersion, parseOptions) + const endLongVersionParse = performance.now() + + const ratio = (endLongVersionParse - startLongVersionParse) / (endShortVersionParse - startShortVersionParse) + expect(ratio).toBeLessThan(250) // if broken this ratio goes 2000-4000x higher + }) +}) + + +it('should parse a long cookie string with spaces in roughly the same amount of time as one with short spaces', () => { + const longCookie = `x${" ".repeat(65535)}x` + const shortCookie = `x x` + + const startLongCookieParse = performance.now() + Cookie.parse(longCookie) + const endLongCookieParse = performance.now() + + const startShortCookieParse = performance.now() + Cookie.parse(shortCookie) + const endShortCookieParse = performance.now() + + const ratio = (endLongCookieParse - startLongCookieParse) / (endShortCookieParse - startShortCookieParse) + expect(ratio).toBeLessThan(250) // if broken this ratio goes 2000-4000x higher +}) diff --git a/lib/__tests__/pathMatch.spec.ts b/lib/__tests__/pathMatch.spec.ts new file mode 100644 index 00000000..141849c1 --- /dev/null +++ b/lib/__tests__/pathMatch.spec.ts @@ -0,0 +1,16 @@ +import {pathMatch} from "../cookie"; + +describe('pathMatch', () => { + it.each([ + // request, cookie, match + ["/", "/", true], + ["/dir", "/", true], + ["/", "/dir", false], + ["/dir/", "/dir/", true], + ["/dir/file", "/dir/", true], + ["/dir/file", "/dir", true], + ["/directory", "/dir", false] + ])('pathMatch("%s", "%s") => %s', (requestPath, cookiePath, expectedValue) => { + expect(pathMatch(requestPath, cookiePath)).toBe(expectedValue) + }) +}) diff --git a/lib/__tests__/permuteDomain.spec.ts b/lib/__tests__/permuteDomain.spec.ts new file mode 100644 index 00000000..75d52814 --- /dev/null +++ b/lib/__tests__/permuteDomain.spec.ts @@ -0,0 +1,37 @@ +import {permuteDomain} from "../cookie"; + +describe('permuteDomain', () => { + it.each([ + { + domain: 'example.com', + permutations: ["example.com"] + }, + { + domain: 'foo.bar.example.com', + permutations: [ + "example.com", + "bar.example.com", + "foo.bar.example.com" + ] + }, + { + domain: 'foo.bar.example.localduhmain', + permutations: [ + "example.localduhmain", + "bar.example.localduhmain", + "foo.bar.example.localduhmain" + ] + }, + { + domain: "foo.bar.example.com.", + permutations: [ + "example.com", + "bar.example.com", + "foo.bar.example.com" + ] + }, + ])('permuteDomain("%s", %s") => %o', ({domain, permutations}) => { + const allowSpecialUseDomain = undefined + expect(permuteDomain(domain, allowSpecialUseDomain)).toEqual(permutations) + }) +}) diff --git a/lib/__tests__/permutePath.spec.ts b/lib/__tests__/permutePath.spec.ts new file mode 100644 index 00000000..90f4c540 --- /dev/null +++ b/lib/__tests__/permutePath.spec.ts @@ -0,0 +1,24 @@ +import {permutePath} from "../cookie"; + +describe('permutePath', () => { + it.each([ + { + path: '/', + permutations: ["/"] + }, + { + path: '/foo', + permutations: ["/foo", "/"] + }, + { + path: '/foo/bar', + permutations: ["/foo/bar", "/foo", "/"] + }, + { + path: "/foo/bar/", + permutations: ["/foo/bar/", "/foo/bar", "/foo", "/"] + }, + ])('permuteDomain("%s", %s") => %o', ({path, permutations}) => { + expect(permutePath(path)).toEqual(permutations) + }) +}) diff --git a/lib/__tests__/regression.spec.ts b/lib/__tests__/regression.spec.ts new file mode 100644 index 00000000..718275a4 --- /dev/null +++ b/lib/__tests__/regression.spec.ts @@ -0,0 +1,101 @@ +import {CookieJar} from "../cookie"; + +const { objectContaining, assertions } = expect; +const url = 'http://www.example.com' + +describe('Regression Tests', () => { + it('should handle trailing semi-colons', async () => { + const cookieJar = new CookieJar() + await cookieJar.setCookie('broken_path=testme; path=/;', url) + await cookieJar.setCookie('b=2; Path=/;;;;', url) + const cookies = await cookieJar.getCookies(url) + expect(cookies).toEqual([ + objectContaining({ + key: 'broken_path', + value: 'testme', + path: '/' + }), + objectContaining({ + key: 'b', + value: '2', + path: '/' + }) + ]) + }) + + it('should not throw exception on malformed URI (GH-32)', async () => { + const malformedUri = `${url}/?test=100%` + const cookieJar = new CookieJar() + await cookieJar.setCookie('Test=Test', malformedUri) + await expect(cookieJar.getCookieString(malformedUri)).resolves.toBe('Test=Test') + }) + + it('should allow setCookie (without options) callback works even if it is not instanceof Function (GH-158/GH-175)', () => { + assertions(2) + const cookieJar = new CookieJar() + + const callback = function(err, cookie) { + expect(err).toBeNull() + expect(cookie).toEqual(objectContaining({ + key: 'a', + value: 'b' + })) + } + + Object.setPrototypeOf(callback, null) + if (callback instanceof Function) { + fail('clearing callback prototype chain failed') + } + + cookieJar.setCookie('a=b', url, callback) + }) + + it('getCookies (without options) callback works even if it is not instanceof Function (GH-175)', async () => { + assertions(2) + const cookieJar = new CookieJar() + + const callback = function(err, cookie) { + expect(err).toBeNull() + expect(cookie).toEqual([ + objectContaining({ + key: 'a', + value: 'b' + }) + ]) + } + + Object.setPrototypeOf(callback, null) + if (callback instanceof Function) { + fail('clearing callback prototype chain failed') + } + + await cookieJar.setCookie('a=b', url) + cookieJar.getCookies(url, callback) + }) + + it.each([ + ["a=b; Domain=localhost", 'localhost'], + ["a=b; Domain=localhost.local", 'local'], + ["a=b; Domain=.localhost", 'localhost'] + ])('should raise an error if using a special use domain (GH-215) - %s', async (cookieString, publicSuffix) => { + const cookieJar = new CookieJar(); + await expect(cookieJar.setCookie( + cookieString, + "http://localhost" + )).rejects.toThrowError(`Cookie has domain set to the public suffix "${publicSuffix}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + }) + + it('should allow setCookie with localhost and null domain (GH-215)', async () => { + const cookieJar = new CookieJar(); + await expect(cookieJar.setCookie( + "a=b; Domain=", + "http://localhost" + )).resolves.toEqual(objectContaining({ + key: 'a', + value: 'b', + domain: 'localhost' + })) + }) + + +}) diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts new file mode 100644 index 00000000..c426334c --- /dev/null +++ b/lib/__tests__/removeAll.spec.ts @@ -0,0 +1,181 @@ +import {Callback, Cookie, CookieJar, MemoryCookieStore, Store} from "../cookie"; +import spyOn = jest.spyOn; +import SpyInstance = jest.SpyInstance; + +const url = "http://example.com/index.html" + +describe('store removeAllCookies API', () => { + describe(`with a store that doesn't implement removeAllCookies`, () => { + it('should remove cookies one at a time under normal conditions', async () => { + const store = new StoreWithoutRemoveAll() + const jar = new CookieJar(store) + await jar.setCookie('a=b', url) + await jar.setCookie('c=d', url) + await jar.removeAllCookies() + expect(store.stats).toEqual({ + put: 2, + getAll: 1, + remove: 2 + }) + }) + + it('should throw an error if one of the removeCookie calls fail', async () => { + const store = new StoreWithoutRemoveAll() + const jar = new CookieJar(store) + + await jar.setCookieSync("a=b", url); + await jar.setCookieSync("c=d", url); + await jar.setCookieSync("e=f", url); + await jar.setCookieSync("g=h", url); + + // replace remove cookie behavior to throw an error on the 4th invocation + const _removeCookie = store.removeCookie + const spy: SpyInstance> = spyOn(store, 'removeCookie') + spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) + spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) + spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) + spy.mockImplementationOnce((domain, path, key, callback) => callback(new Error('something happened 4'))) + + await expect(jar.removeAllCookies()) + .rejects + .toThrowError('something happened 4') + + expect(store.stats).toEqual({ + put: 4, + getAll: 1, + remove: 3 + }) + }) + + it('should throw an error when several of the removeCookie calls fail', async () => { + const store = new StoreWithoutRemoveAll() + const jar = new CookieJar(store) + + await jar.setCookieSync("a=b", url); + await jar.setCookieSync("c=d", url); + await jar.setCookieSync("e=f", url); + await jar.setCookieSync("g=h", url); + + // replace remove cookie behavior to throw an error on the 4th invocation + const _removeCookie = store.removeCookie + const spy: SpyInstance> = spyOn(store, 'removeCookie') + spy.mockImplementation((domain, path, key, callback) => { + if (spy.mock.calls.length % 2 === 1) { + return callback(new Error(`something happened ${spy.mock.calls.length}`)) + } + return _removeCookie.call(store, domain, path, key, callback) + }) + + await expect(jar.removeAllCookies()) + .rejects + .toThrowError('something happened 1') + + expect(store.stats).toEqual({ + put: 4, + getAll: 1, + remove: 2 + }) + }) + }) + + describe('with a store that does implement removeAllCookies', () => { + it("should remove the cookies using a batch operation", async () => { + const store = new MemoryStoreExtension() + const jar = new CookieJar(store) + await jar.setCookie('a=b', url) + await jar.setCookie('c=d', url) + await jar.removeAllCookies() + expect(store.stats).toEqual({ + getAll: 0, + remove: 0, + removeAll: 1 + }) + expect(store.idx).toEqual({}) + }) + }) +}) + +class StoreWithoutRemoveAll extends Store { + stats: { + put: number; + getAll: number; + remove: number; + } + + private cookies: Cookie[] + + constructor() { + super(); + this.synchronous = true; + this.stats = { put: 0, getAll: 0, remove: 0 }; + this.cookies = []; + } + + findCookie(domain: string, path: string, key: string): Promise + findCookie(domain: string, path: string, key: string, callback: Callback): void + findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + return callback(null, null); + } + + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void + findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + return callback(null, []); + } + + putCookie(cookie: Cookie): Promise + putCookie(cookie: Cookie, callback: Callback): void; + putCookie(cookie: Cookie, callback?: Callback): unknown { + this.stats.put++; + this.cookies.push(cookie); + return callback(null); + } + + getAllCookies(): Promise + getAllCookies(callback: Callback): void + getAllCookies(callback?: Callback): unknown { + this.stats.getAll++; + return callback(null, this.cookies.slice()); + } + + removeCookie(domain: string, path: string, key: string): Promise + removeCookie(domain: string, path: string, key: string, callback: Callback): void + removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + this.stats.remove++; + return callback(null, null); + } +} + +class MemoryStoreExtension extends MemoryCookieStore { + stats: { + getAll: number; + remove: number; + removeAll: number; + } + + constructor() { + super(); + this.stats = { getAll: 0, remove: 0, removeAll: 0 }; + } + + getAllCookies(): Promise + getAllCookies(callback: Callback): void + getAllCookies(callback?: Callback): unknown { + this.stats.getAll++; + return super.getAllCookies(callback) + } + + removeCookie(domain: string, path: string, key: string): Promise + removeCookie(domain: string, path: string, key: string, callback: Callback): void + removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + this.stats.remove++; + return super.removeCookie(domain, path, key, callback); + } + + removeAllCookies(): Promise + removeAllCookies(callback: Callback): void + removeAllCookies(callback?: Callback): unknown { + this.stats.removeAll++; + return super.removeAllCookies(callback); + } +} diff --git a/lib/__tests__/sameSite.spec.ts b/lib/__tests__/sameSite.spec.ts new file mode 100644 index 00000000..e2930b26 --- /dev/null +++ b/lib/__tests__/sameSite.spec.ts @@ -0,0 +1,186 @@ +import {Cookie, CookieJar} from "../cookie"; + +const url = "http://example.com/index.html" +const {objectContaining} = expect + +describe('Same-Site Cookies', function () { + let cookieJar: CookieJar + let garbage: Cookie + let strict: Cookie + let lax: Cookie + let normal: Cookie + + beforeEach(() => { + cookieJar = new CookieJar() + garbage = Cookie.parse("garbageIn=treatedAsNone; SameSite=garbage") + strict = Cookie.parse("strict=authorized; SameSite=sTrIcT") + lax = Cookie.parse("lax=okay; SameSite=lax") + normal = Cookie.parse("normal=whatever") + }) + + describe('Retrieval', () => { + beforeEach(async () => { + await cookieJar.setCookie("strict=authorized; SameSite=strict", url) + await cookieJar.setCookie("lax=okay; SameSite=lax", url) + await cookieJar.setCookie("normal=whatever", url) + }) + + it('should return all cookies when making a "strict" same-site request', async () => { + const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'strict' }) + expect(cookies).toEqual([ + objectContaining({ + key: 'strict', + value: 'authorized', + sameSite: 'strict' + }), + objectContaining({ + key: 'lax', + value: 'okay', + sameSite: 'lax' + }), + objectContaining({ + key: 'normal', + value: 'whatever', + sameSite: 'none' + }) + ]) + }) + + it('should return no "strict" cookies when making a "lax" same-site request', async () => { + const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'lax' }) + expect(cookies).toEqual([ + objectContaining({ + key: 'lax', + value: 'okay', + sameSite: 'lax' + }), + objectContaining({ + key: 'normal', + value: 'whatever', + sameSite: 'none' + }) + ]) + }) + + it('should return only the "none" cookie when making a cross-origin request', async () => { + const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'none' }) + expect(cookies).toEqual([ + objectContaining({ + key: 'normal', + value: 'whatever', + sameSite: 'none' + }) + ]) + }) + + it('should return all cookies when making an unqualified request', async () => { + const cookies = await cookieJar.getCookies(url, { sameSiteContext: undefined }) + expect(cookies).toEqual([ + objectContaining({ + key: 'strict', + value: 'authorized', + sameSite: 'strict' + }), + objectContaining({ + key: 'lax', + value: 'okay', + sameSite: 'lax' + }), + objectContaining({ + key: 'normal', + value: 'whatever', + sameSite: 'none' + }) + ]) + }) + }) + + describe('Setting', () => { + describe('from same-site context', () => { + it('should treat the garbage cookie as sameSite=none', async () => { + await cookieJar.setCookie(garbage, url, { sameSiteContext: 'strict' }) + expect(garbage.sameSite).toBe('none') + }) + + it('should treat the strict cookie as sameSite=strict', async () => { + await cookieJar.setCookie(strict, url, { sameSiteContext: 'strict' }) + expect(strict.sameSite).toBe('strict') + }) + + it('should treat the lax cookie as sameSite=lax', async () => { + await cookieJar.setCookie(lax, url, { sameSiteContext: 'strict' }) + expect(lax.sameSite).toBe('lax') + }) + + it('should treat the normal cookie as sameSite=none', async () => { + await cookieJar.setCookie(normal, url, { sameSiteContext: 'strict' }) + expect(normal.sameSite).toBe('none') + }) + }) + + describe('from a cross-origin context', () => { + it('should treat the garbage cookie as sameSite=none', async () => { + await cookieJar.setCookie(garbage, url, { sameSiteContext: 'none' }) + expect(garbage.sameSite).toBe('none') + }) + + it('should not allow strict cookie to be set', async () => { + await expect(cookieJar.setCookie(strict, url, { sameSiteContext: 'none' })) + .rejects + .toThrowError('Cookie is SameSite but this is a cross-origin request') + }) + + it('should not allow lax cookie to be set', async () => { + await expect(cookieJar.setCookie(lax, url, { sameSiteContext: 'none' })) + .rejects + .toThrowError('Cookie is SameSite but this is a cross-origin request') + }) + + it('should treat the normal cookie as sameSite=none', async () => { + await cookieJar.setCookie(normal, url, { sameSiteContext: 'none' }) + expect(normal.sameSite).toBe('none') + }) + }) + + describe('from an undefined context', () => { + it('should treat the garbage cookie as sameSite=none', async () => { + await cookieJar.setCookie(garbage, url) + expect(garbage.sameSite).toBe('none') + }) + + it('should treat the strict cookie as sameSite=strict', async () => { + await cookieJar.setCookie(strict, url) + expect(strict.sameSite).toBe('strict') + }) + + it('should treat the lax cookie as sameSite=lax', async () => { + await cookieJar.setCookie(lax, url) + expect(lax.sameSite).toBe('lax') + }) + + it('should treat the normal cookie as sameSite=none', async () => { + await cookieJar.setCookie(normal, url) + expect(normal.sameSite).toBe('none') + }) + }) + }) + + describe('Canonicalized Strings', () => { + it('garbage in = garbage out', () => { + garbage.sameSite = 'GaRbAGe' + expect(garbage.toString()).toBe('garbageIn=treatedAsNone; SameSite=GaRbAGe') + }) + + it('turn strict to "Strict"', () => { + expect(strict.toString()).toBe('strict=authorized; SameSite=Strict') + }) + + it('turn lax to "Lax"', () => { + expect(lax.toString()).toBe('lax=okay; SameSite=Lax') + }) + + it('omit if same-site was not specified', () => { + expect(normal.toString()).toBe('normal=whatever') + }) + }) +}); diff --git a/lib/cookie.ts b/lib/cookie.ts index 2773ca36..cca16a92 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -30,7 +30,7 @@ */ import * as punycode from "punycode"; -import { parse as urlParse } from 'url' +import {parse as urlParse} from 'url' import * as pubsuffix from './pubsuffix-psl' import util from 'util' import {Store} from './store' @@ -38,7 +38,6 @@ import {MemoryCookieStore} from './memstore' import {pathMatch} from "./pathMatch"; import * as validators from './validators' import VERSION from './version' -import {fromCallback} from 'universalify' import {permuteDomain} from "./permuteDomain" // From RFC6265 S4.1.1 @@ -342,7 +341,7 @@ function canonicalDomain(str) { } // S5.1.3 Domain Matching -function domainMatch(str, domStr, canonicalize) { +function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boolean { if (str == null || domStr == null) { return null; } @@ -401,7 +400,7 @@ function domainMatch(str, domStr, canonicalize) { * * Assumption: the path (and not query part or absolute uri) is passed in. */ -function defaultPath(path) { +function defaultPath(path?: string): string { // "2. If the uri-path is empty or if the first character of the uri-path is not // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. if (!path || path.substr(0, 1) !== "/") { @@ -753,7 +752,7 @@ function cookieCompare(a, b) { // Gives the permutation of all possible pathMatch()es of a given path. The // array is in longest-to-shortest order. Handy for indexing. -function permutePath(path) { +function permutePath(path: string): string[] { validators.validate(validators.isString(path)); if (path === "/") { return ["/"]; @@ -842,18 +841,16 @@ export class Cookie { inspect() { const now = Date.now(); const hostOnly = this.hostOnly != null ? this.hostOnly : "?"; - const createAge = this.creation - // @ts-ignore + const createAge = this.creation && this.creation !== 'Infinity' ? `${now - this.creation.getTime()}ms` : "?"; - const accessAge = this.lastAccessed - // @ts-ignore + const accessAge = this.lastAccessed && this.lastAccessed !== 'Infinity' ? `${now - this.lastAccessed.getTime()}ms` : "?"; return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`; } - toJSON() { + toJSON(): SerializedCookie { const obj = {}; for (const prop of Cookie.serializableProperties) { @@ -866,14 +863,15 @@ export class Cookie { prop === "creation" || prop === "lastAccessed" ) { - if (this[prop] === null) { + if (this[prop] == null) { obj[prop] = null; } else { - obj[prop] = - this[prop] == "Infinity" // intentionally not === - ? "Infinity" - // @ts-ignore - : this[prop].toISOString(); + const value = this[prop] + if (value === 'Infinity') { + obj[prop] = value + } else { + obj[prop] = value.toISOString() + } } } else if (prop === "maxAge") { if (this[prop] !== null) { @@ -890,7 +888,7 @@ export class Cookie { } } - return obj; + return obj; } clone() { @@ -902,8 +900,7 @@ export class Cookie { return false; } if ( - // @ts-ignore - this.expires != Infinity && + this.expires != 'Infinity' && !(this.expires instanceof Date) && !parseDate(this.expires) ) { @@ -961,8 +958,7 @@ export class Cookie { toString() { let str = this.cookieString(); - // @ts-ignore - if (this.expires != Infinity) { + if (this.expires != 'Infinity') { if (this.expires instanceof Date) { str += `; Expires=${formatDate(this.expires)}`; } else { @@ -988,7 +984,6 @@ export class Cookie { str += "; HttpOnly"; } if (this.sameSite && this.sameSite !== "none") { - // @ts-ignore const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()]; str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`; } @@ -1005,52 +1000,45 @@ export class Cookie { // elsewhere) // S5.3 says to give the "latest representable date" for which we use Infinity // For "expired" we use 0 - TTL(now: number = Date.now()) { + TTL(now: number = Date.now()): number { /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires * attribute, the Max-Age attribute has precedence and controls the * expiration date of the cookie. * (Concurs with S5.3 step 3) */ - if (this.maxAge != null) { - // @ts-ignore + if (this.maxAge != null && typeof this.maxAge === 'number') { return this.maxAge <= 0 ? 0 : this.maxAge * 1000; } let expires = this.expires; - // @ts-ignore - if (expires != Infinity) { - if (!(expires instanceof Date)) { - // @ts-ignore - expires = parseDate(expires) || Infinity; - } - - // @ts-ignore - if (expires == Infinity) { - return Infinity; - } + if (expires === 'Infinity') { + return Infinity + } - // @ts-ignore - return expires.getTime() - (now || Date.now()); + if (typeof expires === 'string') { + expires = parseDate(expires) } - return Infinity; + return expires.getTime() - (now || Date.now()) } // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() // elsewhere) - expiryTime(now) { + expiryTime(now?: Date): number { if (this.maxAge != null) { const relativeTo = now || this.creation || new Date(); - // @ts-ignore - const age = this.maxAge <= 0 ? -Infinity : this.maxAge * 1000; + const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity; + const age = maxAge <= 0 ? -Infinity : maxAge * 1000; + if (relativeTo === 'Infinity') { + return Infinity + } return relativeTo.getTime() + age; } - // @ts-ignore - if (this.expires == Infinity) { + if (this.expires == 'Infinity') { return Infinity; } - // @ts-ignore + return this.expires.getTime(); } @@ -1068,9 +1056,8 @@ export class Cookie { } // This replaces the "persistent-flag" parts of S5.3 step 3 - isPersistent() { - // @ts-ignore - return this.maxAge != null || this.expires != Infinity; + isPersistent(): boolean { + return this.maxAge != null || this.expires != 'Infinity'; } // Mostly S5.1.2 and S5.2.3: @@ -1126,12 +1113,12 @@ function getNormalizedPrefixSecurity(prefixSecurity) { const defaultSetCookieOptions: SetCookieOptions = { loose: false, - sameSiteContext: false, + sameSiteContext: undefined, ignoreError: false, http: false } -function createPromiseCallback(args: IArguments): PromiseCallback { +export function createPromiseCallback(args: IArguments): PromiseCallback { let callback: (error: Error, result: T) => Promise let resolve: (result: T) => void let reject: (error: Error) => void @@ -1173,7 +1160,7 @@ export class CookieJar { private readonly rejectPublicSuffixes: boolean; private readonly enableLooseMode: boolean; private readonly allowSpecialUseDomain: boolean; - private readonly prefixSecurity: string; + readonly prefixSecurity: string; constructor(store?: any, options: any = { rejectPublicSuffixes: true }) { if (typeof options === "boolean") { @@ -1206,6 +1193,7 @@ export class CookieJar { setCookie(cookie, url: string, options: SetCookieOptions, callback: Callback): void; setCookie(cookie, url: string): Promise setCookie(cookie, url: string, options: SetCookieOptions): Promise + setCookie(cookie, url: string, options: SetCookieOptions | Callback, callback?: Callback): unknown; setCookie(cookie, url: string, options: SetCookieOptions | Callback = defaultSetCookieOptions, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1241,8 +1229,7 @@ export class CookieJar { // S5.3 step 1 if (typeof cookie === "string" || cookie instanceof String) { - // @ts-ignore - cookie = Cookie.parse(cookie, {loose: loose}); + cookie = Cookie.parse(cookie.toString(), {loose: loose}); if (!cookie) { err = new Error("Cookie failed to parse"); return cb(options.ignoreError ? null : err); @@ -1265,14 +1252,18 @@ export class CookieJar { // S5.3 step 5: public suffixes if (this.rejectPublicSuffixes && cookie.domain) { - const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { - allowSpecialUseDomain: this.allowSpecialUseDomain, - ignoreError: options.ignoreError - }); - if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { - // e.g. "com" - err = new Error("Cookie has domain set to a public suffix"); - return cb(options.ignoreError ? null : err); + try { + const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { + allowSpecialUseDomain: this.allowSpecialUseDomain, + ignoreError: options.ignoreError + }); + if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { + // e.g. "com" + err = new Error("Cookie has domain set to a public suffix"); + return cb(options.ignoreError ? null : err); + } + } catch (err) { + return cb(options.ignoreError ? null : err) } } @@ -1356,8 +1347,8 @@ export class CookieJar { const store = this.store; if (!store.updateCookie) { - store.updateCookie = function (oldCookie, newCookie, cb) { - this.putCookie(newCookie, cb); + store.updateCookie = function (oldCookie: Cookie, newCookie: Cookie, cb?: Callback): Promise { + return this.putCookie(newCookie, cb); }; } @@ -1377,11 +1368,9 @@ export class CookieJar { if (oldCookie) { // S5.3 step 11 - "If the cookie store contains a cookie with the same name, // domain, and path as the newly created cookie:" - // @ts-ignore - if (options.http === false && oldCookie.httpOnly) { + if ('http' in options && options.http === false && oldCookie.httpOnly) { // step 11.2 err = new Error("old Cookie is HttpOnly and this isn't an HTTP API"); - // @ts-ignore return cb(options.ignoreError ? null : err); } cookie.creation = oldCookie.creation; // step 11.3 @@ -1408,6 +1397,7 @@ export class CookieJar { getCookies(url: string, options: any, callback: Callback) getCookies(url: string): Promise getCookies(url: string, options: any): Promise + getCookies(url: string, options: any, callback?: (error: Error, result: Cookie[]) => void): unknown; getCookies(url: string, options: any = {}, callback?: (error: Error, result: Cookie[]) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1435,7 +1425,6 @@ export class CookieJar { let sameSiteLevel = 0; if (options.sameSiteContext) { const sameSiteContext = checkSameSiteContext(options.sameSiteContext); - // @ts-ignore sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext]; if (!sameSiteLevel) { return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); @@ -1488,7 +1477,6 @@ export class CookieJar { // RFC6265bis-02 S5.3.7 if (sameSiteLevel) { - // @ts-ignore const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"]; if (cookieLevel > sameSiteLevel) { // only allow cookies at or below the request level @@ -1543,6 +1531,7 @@ export class CookieJar { getCookieString(url, callback: (error: Error, result: string) => void): void; getCookieString(url): Promise; getCookieString(url, options: any): Promise; + getCookieString(url: string, options: any, callback?: (error: Error, result: string) => void): unknown; getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1571,7 +1560,8 @@ export class CookieJar { getSetCookieStrings (url: string, options: any, callback: Callback): void getSetCookieStrings (url: string): Promise getSetCookieStrings (url: string, options: any): Promise - getSetCookieStrings (url: string, options: any = {}, callback?: (error: Error, result: string[]) => void): unknown { + getSetCookieStrings (url: string, options: any, callback?: Callback): unknown; + getSetCookieStrings (url: string, options: any = {}, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const next = function(err: Error, cookies: Cookie[]) { @@ -1596,7 +1586,8 @@ export class CookieJar { serialize(callback: Callback): void; serialize(): Promise; - serialize(callback?: (error: Error, data: SerializedCookieJar) => void): unknown { + serialize(callback?: Callback): unknown; + serialize(callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1646,12 +1637,12 @@ export class CookieJar { serialized.cookies = cookies.map(cookie => { // convert to serialized 'raw' cookies - cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie; + const serializedCookie = cookie instanceof Cookie ? cookie.toJSON() : cookie; // Remove the index so new ones get assigned during deserialization - delete cookie.creationIndex; + delete serializedCookie.creationIndex; - return cookie; + return serializedCookie; }); return cb(null, serialized); @@ -1675,7 +1666,7 @@ export class CookieJar { } cookies = cookies.slice(); // do not modify the original - const putNext = err => { + const putNext = (err?: Error) => { if (err) { return cb(err); } @@ -1698,7 +1689,6 @@ export class CookieJar { this.store.putCookie(cookie, putNext); }; - // @ts-ignore putNext(); } @@ -1706,25 +1696,33 @@ export class CookieJar { this.callSync(this._importCookies.bind(this, serialized)) } - clone(newStore, cb) { - if (arguments.length === 1) { - cb = newStore; + clone(callback: Callback): void; + clone(newStore: Store, callback: Callback): void; + clone(): Promise; + clone(newStore: Store): Promise; + clone(newStore?: Store | Callback, callback?: Callback): unknown { + if (typeof newStore === 'function') { newStore = null; } + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + this.serialize((err, serialized) => { if (err) { return cb(err); } - CookieJar.deserialize(serialized, newStore, cb); + return CookieJar.deserialize(serialized, newStore, cb); }); + + return promiseCallback.promise } - _cloneSync(newStore?): void { + _cloneSync(newStore?): CookieJar { return this.callSync(this.clone.bind(this, newStore)) } - cloneSync(newStore) { + cloneSync(newStore?: Store): CookieJar { if (arguments.length === 0) { return this._cloneSync(); } @@ -1738,6 +1736,7 @@ export class CookieJar { removeAllCookies(callback: ErrorCallback): void; removeAllCookies(): Promise; + removeAllCookies(callback?: ErrorCallback): unknown; removeAllCookies(callback?: ErrorCallback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1795,19 +1794,24 @@ export class CookieJar { return this.callSync(this.removeAllCookies.bind(this)) } - static deserialize(strOrObj, store, cb) { - if (arguments.length !== 3) { - // store is optional - cb = store; + static deserialize(strOrObj: string | object, callback: Callback): void; + static deserialize(strOrObj: string | object, store: Store, callback: Callback): void; + static deserialize(strOrObj: string | object): Promise; + static deserialize(strOrObj: string | object, store: Store): Promise; + static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown; + static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown { + if (typeof store === 'function') { store = null; } - validators.validate(validators.isFunction(cb), cb); + + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback let serialized; if (typeof strOrObj === "string") { serialized = jsonParse(strOrObj); if (serialized instanceof Error) { - return cb(serialized); + return cb(serialized, undefined); } } else { serialized = strOrObj; @@ -1821,13 +1825,15 @@ export class CookieJar { }); jar._importCookies(serialized, err => { if (err) { - return cb(err); + return cb(err, undefined); } - cb(null, jar); + return cb(undefined, jar); }); + + return promiseCallback.promise } - static deserializeSync(strOrObj, store): CookieJar { + static deserializeSync(strOrObj, store?: Store): CookieJar { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; const jar = new CookieJar(store, { @@ -1846,13 +1852,11 @@ export class CookieJar { return jar; } - static fromJSON (jsonString: string, store): CookieJar { + static fromJSON (jsonString: SerializedCookieJar, store?: Store): CookieJar { return CookieJar.deserializeSync(jsonString, store) } } -CookieJar.deserialize = fromCallback(CookieJar.deserialize); - const getPublicSuffix = pubsuffix.getPublicSuffix const ParameterError = validators.ParameterError @@ -1876,7 +1880,7 @@ export { ParameterError as ParameterError } type SetCookieOptions = { loose?: boolean; - sameSiteContext?: boolean; + sameSiteContext?: 'strict' | 'lax' | 'none'; ignoreError?: boolean; http?: boolean; now?: Date; diff --git a/lib/memstore.js b/lib/memstore.ts similarity index 59% rename from lib/memstore.js rename to lib/memstore.ts index 0b7b3cc6..53796621 100644 --- a/lib/memstore.js +++ b/lib/memstore.ts @@ -29,13 +29,20 @@ * POSSIBILITY OF SUCH DAMAGE. */ "use strict"; -const { fromCallback } = require("universalify"); -const Store = require("./store").Store; -const permuteDomain = require("./permuteDomain").permuteDomain; -const pathMatch = require("./pathMatch").pathMatch; -const util = require("util"); +import {Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain} from "./cookie"; +import {Store} from './store' +import util from 'util' + +export class MemoryCookieStore extends Store { + synchronous: boolean; + idx: { + [domain: string]: { + [path: string]: { + [key: string]: Cookie + } + } + }; -class MemoryCookieStore extends Store { constructor() { super(); this.synchronous = true; @@ -49,21 +56,34 @@ class MemoryCookieStore extends Store { return `{ idx: ${util.inspect(this.idx, false, 2)} }`; } - findCookie(domain, path, key, cb) { + findCookie(domain: string, path: string, key: string): Promise + findCookie(domain: string, path: string, key: string, callback: Callback): void + findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + if (!this.idx[domain]) { return cb(null, undefined); } if (!this.idx[domain][path]) { return cb(null, undefined); } - return cb(null, this.idx[domain][path][key] || null); + + cb(null, this.idx[domain][path][key] || null); + return promiseCallback.promise } - findCookies(domain, path, allowSpecialUseDomain, cb) { - const results = []; + + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void + findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { if (typeof allowSpecialUseDomain === "function") { - cb = allowSpecialUseDomain; allowSpecialUseDomain = false; } + + const results = []; + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + if (!domain) { return cb(null, []); } @@ -105,9 +125,15 @@ class MemoryCookieStore extends Store { }); cb(null, results); + return promiseCallback.promise } - putCookie(cookie, cb) { + putCookie(cookie: Cookie): Promise + putCookie(cookie: Cookie, callback: Callback): void; + putCookie(cookie: Cookie, callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + if (!this.idx[cookie.domain]) { this.idx[cookie.domain] = {}; } @@ -115,15 +141,26 @@ class MemoryCookieStore extends Store { this.idx[cookie.domain][cookie.path] = {}; } this.idx[cookie.domain][cookie.path][cookie.key] = cookie; - cb(null); + cb(null, undefined); + + return promiseCallback.promise } - updateCookie(oldCookie, newCookie, cb) { + + updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise + updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; + updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { // updateCookie() may avoid updating cookies that are identical. For example, // lastAccessed may not be important to some stores and an equality // comparison could exclude that field. - this.putCookie(newCookie, cb); + return this.putCookie(newCookie, callback); } - removeCookie(domain, path, key, cb) { + + removeCookie(domain: string, path: string, key: string): Promise + removeCookie(domain: string, path: string, key: string, callback: Callback): void + removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + if ( this.idx[domain] && this.idx[domain][path] && @@ -131,9 +168,17 @@ class MemoryCookieStore extends Store { ) { delete this.idx[domain][path][key]; } - cb(null); + + cb(null, undefined); + return promiseCallback.promise } - removeCookies(domain, path, cb) { + + removeCookies(domain: string, path: string): Promise + removeCookies(domain: string, path: string, callback: Callback): void + removeCookies(domain: string, path: string, callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + if (this.idx[domain]) { if (path) { delete this.idx[domain][path]; @@ -141,14 +186,30 @@ class MemoryCookieStore extends Store { delete this.idx[domain]; } } - return cb(null); + + cb(null); + return promiseCallback.promise } - removeAllCookies(cb) { + + removeAllCookies(): Promise + removeAllCookies(callback: Callback): void + removeAllCookies(callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + this.idx = {}; - return cb(null); + + cb(null); + return promiseCallback.promise } - getAllCookies(cb) { - const cookies = []; + + getAllCookies(): Promise + getAllCookies(callback: Callback): void + getAllCookies(callback?: Callback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + const cookies: Cookie[] = []; const idx = this.idx; const domains = Object.keys(idx); @@ -171,22 +232,7 @@ class MemoryCookieStore extends Store { }); cb(null, cookies); + return promiseCallback.promise } } -[ - "findCookie", - "findCookies", - "putCookie", - "updateCookie", - "removeCookie", - "removeCookies", - "removeAllCookies", - "getAllCookies" -].forEach(name => { - MemoryCookieStore.prototype[name] = fromCallback( - MemoryCookieStore.prototype[name] - ); -}); - -exports.MemoryCookieStore = MemoryCookieStore; diff --git a/lib/permuteDomain.js b/lib/permuteDomain.ts similarity index 96% rename from lib/permuteDomain.js rename to lib/permuteDomain.ts index 35ab0228..2f38082f 100644 --- a/lib/permuteDomain.js +++ b/lib/permuteDomain.ts @@ -34,7 +34,7 @@ const pubsuffix = require("./pubsuffix-psl"); // Gives the permutation of all possible domainMatch()es of a given domain. The // array is in shortest-to-longest order. Handy for indexing. -function permuteDomain(domain, allowSpecialUseDomain) { +export function permuteDomain(domain: string, allowSpecialUseDomain?: boolean): string[] { const pubSuf = pubsuffix.getPublicSuffix(domain, { allowSpecialUseDomain: allowSpecialUseDomain }); @@ -61,5 +61,3 @@ function permuteDomain(domain, allowSpecialUseDomain) { } return permutations; } - -exports.permuteDomain = permuteDomain; diff --git a/lib/pubsuffix-psl.js b/lib/pubsuffix-psl.ts similarity index 87% rename from lib/pubsuffix-psl.js rename to lib/pubsuffix-psl.ts index d5555523..8f0726cb 100644 --- a/lib/pubsuffix-psl.js +++ b/lib/pubsuffix-psl.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ "use strict"; -const psl = require("psl"); +import * as psl from 'psl' // RFC 6761 const SPECIAL_USE_DOMAINS = [ @@ -40,7 +40,18 @@ const SPECIAL_USE_DOMAINS = [ "test" ]; -function getPublicSuffix(domain, options = {}) { +type GetPublicSuffixOptions = { + allowSpecialUseDomain?: boolean; + ignoreError?: boolean; +} + +const defaultGetPublicSuffixOptions: GetPublicSuffixOptions = { + allowSpecialUseDomain: false, + ignoreError: false +} + +export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions = {}): string { + options = { ...defaultGetPublicSuffixOptions, ...options } const domainParts = domain.split("."); const topLevelDomain = domainParts[domainParts.length - 1]; const allowSpecialUseDomain = !!options.allowSpecialUseDomain; @@ -66,5 +77,3 @@ function getPublicSuffix(domain, options = {}) { return psl.get(domain); } - -exports.getPublicSuffix = getPublicSuffix; diff --git a/lib/store.ts b/lib/store.ts index 0c5a1b4e..ace0ca7a 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -31,7 +31,7 @@ "use strict"; /*jshint unused:false */ -import {Cookie} from "./cookie"; +import {Callback, Cookie} from "./cookie"; export class Store { synchronous: boolean; @@ -40,37 +40,54 @@ export class Store { this.synchronous = false; } - findCookie(domain, path, key, cb): Cookie { + findCookie(domain: string, path: string, key: string): Promise + findCookie(domain: string, path: string, key: string, callback: Callback): void + findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { throw new Error("findCookie is not implemented"); } - findCookies(domain: string, path?: string, allowSpecialUseDomain: boolean = false, callback?: (error: Error, result: Cookie[]) => void): Promise { + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise + findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void + findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { throw new Error("findCookies is not implemented"); } - putCookie(cookie, cb) { + putCookie(cookie: Cookie): Promise + putCookie(cookie: Cookie, callback: Callback): void; + putCookie(cookie: Cookie, callback?: Callback): unknown { throw new Error("putCookie is not implemented"); } - updateCookie(oldCookie, newCookie, cb) { + + updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise + updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; + updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { // recommended default implementation: // return this.putCookie(newCookie, cb); throw new Error("updateCookie is not implemented"); } - removeCookie(domain: string, path: string, key: string, callback?: (error: Error) => void): Promise { + removeCookie(domain: string, path: string, key: string): Promise + removeCookie(domain: string, path: string, key: string, callback: Callback): void + removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { throw new Error("removeCookie is not implemented"); } - removeCookies(domain: string, path?: string, callback?: (error: Error) => void): Promise { + removeCookies(domain: string, path: string): Promise + removeCookies(domain: string, path: string, callback: Callback): void + removeCookies(domain: string, path: string, callback?: Callback): unknown { throw new Error("removeCookies is not implemented"); } - removeAllCookies(callback?: (error: Error) => void): Promise { + removeAllCookies(): Promise + removeAllCookies(callback: Callback): void + removeAllCookies(callback?: Callback): unknown { throw new Error("removeAllCookies is not implemented"); } - getAllCookies(cb) { + getAllCookies(): Promise + getAllCookies(callback: Callback): void + getAllCookies(callback?: Callback): unknown { throw new Error( "getAllCookies is not implemented (therefore jar cannot be serialized)" ); diff --git a/lib/validators.js b/lib/validators.ts similarity index 78% rename from lib/validators.js rename to lib/validators.ts index b75c3905..3c3b8ee4 100644 --- a/lib/validators.js +++ b/lib/validators.ts @@ -28,30 +28,31 @@ SOFTWARE. "use strict"; /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ -function isFunction(data) { +export function isFunction(data: any): boolean { return typeof data === 'function'; } -function isNonEmptyString(data) { +export function isNonEmptyString(data: any): boolean { return isString(data) && data !== ''; } -function isDate(data) { +export function isDate(data: any): boolean { return isInstanceStrict(data, Date) && isInteger(data.getTime()); } -function isEmptyString(data) { +export function isEmptyString(data: any): boolean { return data === '' || (data instanceof String && data.toString() === ''); } -function isString(data) { +export function isString(data: any): boolean { return typeof data === 'string' || data instanceof String } -function isObject(data) { +export function isObject(data: any): boolean { return toString.call(data) === '[object Object]'; } -function isInstanceStrict(data, prototype) { + +export function isInstanceStrict(data: any, prototype: Function): boolean { try { return data instanceof prototype; } catch (error) { @@ -59,12 +60,12 @@ function isInstanceStrict(data, prototype) { } } -function isInteger(data) { +export function isInteger(data: any): boolean { return typeof data === 'number' && data % 1 === 0; } /* End validation functions */ -function validate(bool, cb, options) { +export function validate(bool: boolean, cb?: any, options?: any): void { if (!isFunction(cb)) { options = cb; cb = null; @@ -79,22 +80,12 @@ function validate(bool, cb, options) { } } -class ParameterError extends Error { +export class ParameterError extends Error { constructor(...params) { super(...params); if (Object.setPrototypeOf) { Object.setPrototypeOf(this, ParameterError.prototype); - } else { - this.__proto__ = new.target.prototype; } } -}; - -exports.ParameterError = ParameterError; -exports.isFunction = isFunction; -exports.isNonEmptyString = isNonEmptyString; -exports.isDate = isDate; -exports.isEmptyString = isEmptyString; -exports.isString = isString; -exports.isObject = isObject; -exports.validate = validate; +} + diff --git a/lib/version.js b/lib/version.js deleted file mode 100644 index e52f25be..00000000 --- a/lib/version.js +++ /dev/null @@ -1,2 +0,0 @@ -// generated by genversion -module.exports = '4.0.0' diff --git a/lib/version.ts b/lib/version.ts new file mode 100644 index 00000000..d2fe98ec --- /dev/null +++ b/lib/version.ts @@ -0,0 +1 @@ +export default '4.0.0' diff --git a/package.json b/package.json index 8c536698..3aecc81f 100644 --- a/package.json +++ b/package.json @@ -102,12 +102,11 @@ "nyc": "^14.0.0", "prettier": "^1.17.0", "ts-jest": "^27.1.3", - "typescript": "^4.5.5", + "typescript": "4.5.5", "vows": "^0.8.2" }, "dependencies": { "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" + "punycode": "^2.1.1" } } diff --git a/tsconfig.json b/tsconfig.json index ccd5d5b7..1258f41b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "allowJs": true, "target": "es5", "resolveJsonModule": true, - "esModuleInterop": true + "esModuleInterop": true, + "strict": false }, "include": ["./lib/**/*"] } From b3a92a815ea1095da65bd7f5f203a6f5ca2a6f95 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 29 Sep 2022 16:39:19 -0300 Subject: [PATCH 04/33] make `npm test` run both jest and vows --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3aecc81f..c83f74f6 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ ], "scripts": { "version": "genversion lib/version.js && git add lib/version.js", - "test": "vows test/*_test.js", + "test": "jest && tsc && vows test/*_test.js", "cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js", "eslint": "eslint --env node --ext .js .", "prettier": "prettier '**/*.{json,ts,yaml,md}'", From c6cfc532dc437a071428e53136ec5f5a6f09136a Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 29 Sep 2022 17:09:02 -0300 Subject: [PATCH 05/33] make `npm test` run both jest and vows --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c3452575..7fdc1378 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12 +FROM node:16 MAINTAINER awaterman@salesforce.com LABEL Description="Vendor=\"Salesforce.com\" Version=\"1.0\"" RUN apt-get update && \ From 7119d5598074eca21b0b394b369ea8e6e4d35c51 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Sat, 5 Nov 2022 18:39:21 -0300 Subject: [PATCH 06/33] changes to get strict mode enabled --- lib/cookie.ts | 462 +++++++++++++++++++++++++++---------------- lib/memstore.ts | 81 ++++---- lib/permuteDomain.ts | 2 +- lib/pubsuffix-psl.ts | 2 +- lib/store.ts | 18 +- lib/validators.ts | 2 +- package.json | 1 + tsconfig.json | 6 +- 8 files changed, 362 insertions(+), 212 deletions(-) diff --git a/lib/cookie.ts b/lib/cookie.ts index cca16a92..511bf083 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -79,7 +79,7 @@ const MIN_TIME = 0; // 31-bit min const SAME_SITE_CONTEXT_VAL_ERR = 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'; -function checkSameSiteContext(value) { +function checkSameSiteContext(value: string) { validators.validate(validators.isNonEmptyString(value), value); const context = String(value).toLowerCase(); if (context === "none" || context === "lax" || context === "strict") { @@ -125,7 +125,7 @@ const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`) * The "trailingOK" boolean controls if the grammar accepts a * "( non-digit *OCTET )" trailer. */ -function parseDigits(token, minDigits, maxDigits, trailingOK) { +function parseDigits(token: string, minDigits: number, maxDigits: number, trailingOK: boolean) { let count = 0; while (count < token.length) { const c = token.charCodeAt(count); @@ -148,7 +148,7 @@ function parseDigits(token, minDigits, maxDigits, trailingOK) { return parseInt(token.substr(0, count), 10); } -function parseTime(token) { +function parseTime(token: string) { const parts = token.split(":"); const result = [0, 0, 0]; @@ -164,7 +164,7 @@ function parseTime(token) { for (let i = 0; i < 3; i++) { // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be - // followed by "( non-digit *OCTET )" so therefore the last time-field can + // followed by "( non-digit *OCTET )" therefore the last time-field can // have a trailer const trailingOK = i == 2; const num = parseDigits(parts[i], 1, 2, trailingOK); @@ -177,20 +177,33 @@ function parseTime(token) { return result; } -function parseMonth(token) { +function parseMonth(token: string) { token = String(token) .substr(0, 3) .toLowerCase(); - const num = MONTH_TO_NUM[token]; - return num >= 0 ? num : null; + switch (token) { + case 'jan': return MONTH_TO_NUM.jan + case 'feb': return MONTH_TO_NUM.feb + case 'mar': return MONTH_TO_NUM.mar + case 'apr': return MONTH_TO_NUM.apr + case 'may': return MONTH_TO_NUM.may + case 'jun': return MONTH_TO_NUM.jun + case 'jul': return MONTH_TO_NUM.jul + case 'aug': return MONTH_TO_NUM.aug + case 'sep': return MONTH_TO_NUM.sep + case 'oct': return MONTH_TO_NUM.oct + case 'nov': return MONTH_TO_NUM.nov + case 'dec': return MONTH_TO_NUM.dec + default: return null + } } /* * RFC6265 S5.1.1 date parser (see RFC for full grammar) */ -function parseDate(str) { +function parseDate(str: string | undefined | null): Date | undefined { if (!str) { - return; + return undefined; } /* RFC6265 S5.1.1: @@ -199,7 +212,7 @@ function parseDate(str) { */ const tokens = str.split(DATE_DELIM); if (!tokens) { - return; + return undefined; } let hour = null; @@ -306,23 +319,25 @@ function parseDate(str) { dayOfMonth < 1 || dayOfMonth > 31 || year < 1601 || + hour === null || hour > 23 || + minute === null || minute > 59 || second > 59 ) { - return; + return undefined; } return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)); } -function formatDate(date) { +function formatDate(date: Date) { validators.validate(validators.isDate(date), date); return date.toUTCString(); } // S5.1.2 Canonicalized Host Names -function canonicalDomain(str) { +function canonicalDomain(str: string | null) { if (str == null) { return null; } @@ -341,13 +356,24 @@ function canonicalDomain(str) { } // S5.1.3 Domain Matching -function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boolean { +function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boolean | null { if (str == null || domStr == null) { return null; } + + let _str: string | null + let _domStr: string | null + if (canonicalize !== false) { - str = canonicalDomain(str); - domStr = canonicalDomain(domStr); + _str = canonicalDomain(str); + _domStr = canonicalDomain(domStr); + } else { + _str = str; + _domStr = domStr + } + + if (_str == null || _domStr == null) { + return null; } /* @@ -359,14 +385,14 @@ function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boo * domain string and the string will have been canonicalized to lower case at * this point)" */ - if (str == domStr) { + if (_str == _domStr) { return true; } /* " o All of the following [three] conditions hold:" */ /* "* The domain string is a suffix of the string" */ - const idx = str.indexOf(domStr); + const idx = _str.indexOf(_domStr); if (idx <= 0) { return false; // it's a non-match (-1) or prefix (0) } @@ -374,18 +400,18 @@ function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boo // next, check it's a proper suffix // e.g., "a.b.c".indexOf("b.c") === 2 // 5 === 3+2 - if (str.length !== domStr.length + idx) { + if (_str.length !== _domStr.length + idx) { return false; // it's not a suffix } /* " * The last character of the string that is not included in the * domain string is a %x2E (".") character." */ - if (str.substr(idx-1,1) !== '.') { + if (_str.substr(idx-1,1) !== '.') { return false; // doesn't align on "." } /* " * The string is a host name (i.e., not an IP address)." */ - if (IP_REGEX_LOWERCASE.test(str)) { + if (IP_REGEX_LOWERCASE.test(_str)) { return false; // it's an IP address } @@ -423,7 +449,7 @@ function defaultPath(path?: string): string { return path.slice(0, rightSlash); } -function trimTerminator(str) { +function trimTerminator(str: string) { if (validators.isEmptyString(str)) return str; for (let t = 0; t < TERMINATORS.length; t++) { const terminatorIdx = str.indexOf(TERMINATORS[t]); @@ -435,7 +461,7 @@ function trimTerminator(str) { return str; } -function parseCookiePair(cookiePair, looseMode) { +function parseCookiePair(cookiePair: string, looseMode: boolean) { cookiePair = trimTerminator(cookiePair); validators.validate(validators.isString(cookiePair), cookiePair); @@ -450,7 +476,7 @@ function parseCookiePair(cookiePair, looseMode) { // non-loose mode if (firstEq <= 0) { // no '=' or is at start - return; // needs to have non-empty "cookie-name" + return undefined; // needs to have non-empty "cookie-name" } } @@ -464,7 +490,7 @@ function parseCookiePair(cookiePair, looseMode) { } if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) { - return; + return undefined; } const c = new Cookie(); @@ -473,9 +499,9 @@ function parseCookiePair(cookiePair, looseMode) { return c; } -function parse(str: string, options: any = {}): Cookie { +function parse(str: string, options: any = {}): Cookie | undefined { if (validators.isEmptyString(str) || !validators.isString(str)) { - return null + return undefined } str = str.trim(); @@ -485,7 +511,7 @@ function parse(str: string, options: any = {}): Cookie { const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi); const c = parseCookiePair(cookiePair, !!options.loose); if (!c) { - return; + return undefined; } if (firstSemi === -1) { @@ -513,7 +539,7 @@ function parse(str: string, options: any = {}): Cookie { */ const cookie_avs = unparsed.split(";"); while (cookie_avs.length) { - const av = cookie_avs.shift().trim(); + const av = (cookie_avs.shift() ?? "").trim(); if (av.length === 0) { // happens if ";;" appears continue; @@ -639,9 +665,10 @@ function parse(str: string, options: any = {}): Cookie { * @param cookie * @returns boolean */ -function isSecurePrefixConditionMet(cookie) { +function isSecurePrefixConditionMet(cookie: Cookie) { validators.validate(validators.isObject(cookie), cookie); - return !cookie.key.startsWith("__Secure-") || cookie.secure; + const startsWithSecurePrefix = typeof cookie.key === 'string' && cookie.key.startsWith("__Secure-") + return !startsWithSecurePrefix || cookie.secure } /** @@ -655,10 +682,11 @@ function isSecurePrefixConditionMet(cookie) { * @param cookie * @returns boolean */ -function isHostPrefixConditionMet(cookie) { +function isHostPrefixConditionMet(cookie: Cookie) { validators.validate(validators.isObject(cookie)); + const startsWithHostPrefix = typeof cookie.key === 'string' && cookie.key.startsWith("__Host-") return ( - !cookie.key.startsWith("__Host-") || + !startsWithHostPrefix || (cookie.secure && cookie.hostOnly && cookie.path != null && @@ -667,7 +695,7 @@ function isHostPrefixConditionMet(cookie) { } // avoid the V8 deoptimization monster! -function jsonParse(str) { +function jsonParse(str: string) { let obj; try { obj = JSON.parse(str); @@ -677,7 +705,7 @@ function jsonParse(str) { return obj; } -function fromJSON(str) { +function fromJSON(str: string | SerializedCookie) { if (!str || validators.isEmptyString(str)) { return null; } @@ -696,6 +724,7 @@ function fromJSON(str) { const c = new Cookie(); for (let i = 0; i < Cookie.serializableProperties.length; i++) { const prop = Cookie.serializableProperties[i]; + // @ts-ignore if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) { continue; // leave as prototype default } @@ -707,6 +736,7 @@ function fromJSON(str) { c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]); } } else { + // @ts-ignore c[prop] = obj[prop]; } } @@ -723,7 +753,7 @@ function fromJSON(str) { * creation-times." */ -function cookieCompare(a, b) { +function cookieCompare(a: Cookie, b: Cookie) { validators.validate(validators.isObject(a), a); validators.validate(validators.isObject(b), b); let cmp = 0; @@ -737,15 +767,15 @@ function cookieCompare(a, b) { } // ascending for time: a CMP b - const aTime = a.creation ? a.creation.getTime() : MAX_TIME; - const bTime = b.creation ? b.creation.getTime() : MAX_TIME; + const aTime = a.creation && a.creation instanceof Date ? a.creation.getTime() : MAX_TIME; + const bTime = b.creation && b.creation instanceof Date ? b.creation.getTime() : MAX_TIME; cmp = aTime - bTime; if (cmp !== 0) { return cmp; } // break ties for the same millisecond (precision of JavaScript's clock) - cmp = a.creationIndex - b.creationIndex; + cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0); return cmp; } @@ -770,19 +800,20 @@ function permutePath(path: string): string[] { return permutations; } -function getCookieContext(url) { - if (url instanceof Object) { +function getCookieContext(url: string | URL) { + if (url instanceof URL && 'query' in url) { return url; } - // NOTE: decodeURI will throw on malformed URIs (see GH-32). - // Therefore, we will just skip decoding for such URIs. - try { - url = decodeURI(url); - } catch (err) { - // Silently swallow error + + if (typeof url === 'string') { + try { + return urlParse(decodeURI(url)) + } catch { + return urlParse(url); + } } - return urlParse(url); + throw new Error('`url` argument is invalid') } const cookieDefaults = { @@ -790,44 +821,53 @@ const cookieDefaults = { key: "", value: "", expires: "Infinity", + // @ts-ignore maxAge: null, + // @ts-ignore domain: null, + // @ts-ignore path: null, secure: false, httpOnly: false, + // @ts-ignore extensions: null, // set by the CookieJar: + // @ts-ignore hostOnly: null, + // @ts-ignore pathIsDefault: null, + // @ts-ignore creation: null, + // @ts-ignore lastAccessed: null, sameSite: "none" }; export class Cookie { - key: string; - value: string; - expires: Date | 'Infinity'; - maxAge: number | 'Infinity' | '-Infinity'; - domain: string | null; - path: string | null; - secure: boolean; - httpOnly: boolean; - extensions: string[] | null; - creation: Date | 'Infinity'; - creationIndex: number; - hostOnly: boolean | null; - pathIsDefault: boolean | null; - lastAccessed: Date | null | 'Infinity'; + key: string | undefined; + value: string | undefined; + expires: Date | 'Infinity' | null | undefined; + maxAge: number | 'Infinity' | '-Infinity' | undefined; + domain: string | null | undefined; + path: string | null | undefined; + secure: boolean | undefined; + httpOnly: boolean | undefined; + extensions: string[] | null | undefined; + creation: Date | 'Infinity' | null; + creationIndex: number | undefined; + hostOnly: boolean | null | undefined; + pathIsDefault: boolean | null | undefined; + lastAccessed: Date | 'Infinity' | null | undefined; sameSite: string | undefined; - constructor(options = {}) { + constructor(options: { creation?: Date } = {}) { if (util.inspect.custom) { + // @ts-ignore this[util.inspect.custom] = this.inspect; } Object.assign(this, cookieDefaults, options); - this.creation = this.creation || new Date(); + this.creation = options.creation ?? cookieDefaults.creation ?? new Date(); // used to break creation ties in cookieCompare(): Object.defineProperty(this, "creationIndex", { @@ -851,9 +891,10 @@ export class Cookie { } toJSON(): SerializedCookie { - const obj = {}; + const obj: SerializedCookie = {}; for (const prop of Cookie.serializableProperties) { + // @ts-ignore if (this[prop] === cookieDefaults[prop]) { continue; // leave as prototype default } @@ -870,25 +911,29 @@ export class Cookie { if (value === 'Infinity') { obj[prop] = value } else { - obj[prop] = value.toISOString() + obj[prop] = value && value.toISOString() } } } else if (prop === "maxAge") { - if (this[prop] !== null) { + const maxAge = this[prop] + + if (maxAge != null) { // again, intentionally not === obj[prop] = - this[prop] == Infinity || this[prop] == -Infinity - ? this[prop].toString() - : this[prop]; + maxAge == Infinity || maxAge == -Infinity + ? maxAge.toString() + : maxAge; } } else { + // @ts-ignore if (this[prop] !== cookieDefaults[prop]) { + // @ts-ignore obj[prop] = this[prop]; } } } - return obj; + return obj; } clone() { @@ -896,7 +941,7 @@ export class Cookie { } validate() { - if (!COOKIE_OCTETS.test(this.value)) { + if (this.value == null || !COOKIE_OCTETS.test(this.value)) { return false; } if ( @@ -927,7 +972,7 @@ export class Cookie { return true; } - setExpires(exp) { + setExpires(exp: string | Date) { if (exp instanceof Date) { this.expires = exp; } else { @@ -935,9 +980,11 @@ export class Cookie { } } - setMaxAge(age) { - if (age === Infinity || age === -Infinity) { - this.maxAge = age.toString(); // so JSON.stringify() works + setMaxAge(age: number) { + if (age === Infinity) { + this.maxAge = "Infinity"; + } else if (age === -Infinity) { + this.maxAge = '-Infinity'; } else { this.maxAge = age; } @@ -984,8 +1031,13 @@ export class Cookie { str += "; HttpOnly"; } if (this.sameSite && this.sameSite !== "none") { - const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()]; - str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`; + if (this.sameSite.toLowerCase() === Cookie.sameSiteCanonical.lax.toLowerCase()) { + str += `; SameSite=${Cookie.sameSiteCanonical.lax}`; + } else if (this.sameSite.toLowerCase() === Cookie.sameSiteCanonical.strict.toLowerCase()) { + str += `; SameSite=${Cookie.sameSiteCanonical.strict}`; + } else { + str += `; SameSite=${this.sameSite}`; + } } if (this.extensions) { this.extensions.forEach(ext => { @@ -1015,10 +1067,12 @@ export class Cookie { return Infinity } + // @ts-ignore if (typeof expires === 'string') { expires = parseDate(expires) } + // @ts-ignore return expires.getTime() - (now || Date.now()) } @@ -1039,12 +1093,13 @@ export class Cookie { return Infinity; } + // @ts-ignore return this.expires.getTime(); } // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() // elsewhere), except it returns a Date - expiryDate(now) { + expiryDate(now: Date) { const millisec = this.expiryTime(now); if (millisec == Infinity) { return new Date(MAX_TIME); @@ -1072,11 +1127,11 @@ export class Cookie { return this.canonicalizedDomain(); } - static parse (cookieString: string, options?): Cookie { + static parse (cookieString: string, options?: {}): Cookie | undefined { return parse(cookieString, options) } - static fromJSON (jsonString: string): Cookie { + static fromJSON (jsonString: string): Cookie | null { return fromJSON(jsonString) } @@ -1093,10 +1148,25 @@ export class Cookie { lax: "Lax" } - static serializableProperties = Object.keys(cookieDefaults) + static serializableProperties = [ + 'key', + 'value', + 'expires', + 'maxAge', + 'domain', + 'path', + 'secure', + 'httpOnly', + 'extensions', + 'hostOnly', + 'pathIsDefault', + 'creation', + 'lastAccessed', + 'sameSite' + ] } -function getNormalizedPrefixSecurity(prefixSecurity) { +function getNormalizedPrefixSecurity(prefixSecurity: string) { if (prefixSecurity != null) { const normalizedPrefixSecurity = prefixSecurity.toLowerCase(); /* The three supported options */ @@ -1119,11 +1189,11 @@ const defaultSetCookieOptions: SetCookieOptions = { } export function createPromiseCallback(args: IArguments): PromiseCallback { - let callback: (error: Error, result: T) => Promise - let resolve: (result: T) => void - let reject: (error: Error) => void + let callback: (error: Error | null | undefined, result: T | undefined) => Promise + let resolve: (result: T | undefined) => void + let reject: (error: Error | null) => void - const promise = new Promise((_resolve, _reject) => { + const promise = new Promise((_resolve, _reject) => { resolve = _resolve reject = _reject }) @@ -1134,7 +1204,7 @@ export function createPromiseCallback(args: IArguments): PromiseCallback { try { cb(err, result) } catch(e) { - reject(e) + reject(e instanceof Error ? e : new Error(`${e}`)) } return promise } @@ -1143,7 +1213,7 @@ export function createPromiseCallback(args: IArguments): PromiseCallback { try { err ? reject(err) : resolve(result) } catch (e) { - reject(e) + reject(e instanceof Error ? e : new Error(`${e}`)) } return promise } @@ -1174,27 +1244,30 @@ export class CookieJar { this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); } - private callSync(fn): T { + private callSync(fn: Function): T { if (!this.store.synchronous) { throw new Error( "CookieJar store is not synchronous; use async API instead." ); } - let syncErr, syncResult; + let syncErr: Error | undefined, syncResult: T; fn.call(this, (error: Error, result: T) => { syncErr = error syncResult = result }) - if (syncErr) throw syncErr; + if (syncErr) { + throw syncErr; + } + // @ts-ignore return syncResult; } - setCookie(cookie, url: string, callback: Callback): void; - setCookie(cookie, url: string, options: SetCookieOptions, callback: Callback): void; - setCookie(cookie, url: string): Promise - setCookie(cookie, url: string, options: SetCookieOptions): Promise - setCookie(cookie, url: string, options: SetCookieOptions | Callback, callback?: Callback): unknown; - setCookie(cookie, url: string, options: SetCookieOptions | Callback = defaultSetCookieOptions, callback?: Callback): unknown { + setCookie(cookie: string | Cookie, url: string, callback: Callback): void; + setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions, callback: Callback): void; + setCookie(cookie: string | Cookie, url: string): Promise + setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions): Promise + setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions | Callback, callback?: Callback): unknown; + setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions | Callback = defaultSetCookieOptions, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1229,11 +1302,12 @@ export class CookieJar { // S5.3 step 1 if (typeof cookie === "string" || cookie instanceof String) { - cookie = Cookie.parse(cookie.toString(), {loose: loose}); - if (!cookie) { + const parsedCookie = Cookie.parse(cookie.toString(), {loose: loose}); + if (!parsedCookie) { err = new Error("Cookie failed to parse"); return cb(options.ignoreError ? null : err); } + cookie = parsedCookie } else if (!(cookie instanceof Cookie)) { // If you're seeing this error, and are passing in a Cookie object, // it *might* be a Cookie object from another loaded version of tough-cookie. @@ -1253,23 +1327,32 @@ export class CookieJar { // S5.3 step 5: public suffixes if (this.rejectPublicSuffixes && cookie.domain) { try { - const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), { + const cdomain = cookie.cdomain() + const suffix = typeof cdomain === 'string' ? pubsuffix.getPublicSuffix(cdomain, { allowSpecialUseDomain: this.allowSpecialUseDomain, ignoreError: options.ignoreError - }); + }) : null; if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" err = new Error("Cookie has domain set to a public suffix"); return cb(options.ignoreError ? null : err); } } catch (err) { - return cb(options.ignoreError ? null : err) + if (options.ignoreError) { + return cb(null) + } else { + if (err instanceof Error) { + return cb(err) + } else { + return cb(null) + } + } } } // S5.3 step 6: if (cookie.domain) { - if (!domainMatch(host, cookie.cdomain(), false)) { + if (!domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false)) { err = new Error( `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}` ); @@ -1289,7 +1372,7 @@ export class CookieJar { //attribute-value is not %x2F ("/"): //Let cookie-path be the default-path. if (!cookie.path || cookie.path[0] !== "/") { - cookie.path = defaultPath(context.pathname); + cookie.path = defaultPath(context.pathname ?? undefined); cookie.pathIsDefault = true; } @@ -1348,18 +1431,30 @@ export class CookieJar { if (!store.updateCookie) { store.updateCookie = function (oldCookie: Cookie, newCookie: Cookie, cb?: Callback): Promise { - return this.putCookie(newCookie, cb); + return this.putCookie(newCookie).then( + () => { + if (cb) { + cb(undefined, undefined) + } + }, + (error: Error) => { + if (cb) { + cb(error, undefined) + } + } + ); }; } - function withCookie(err, oldCookie) { + function withCookie(err: Error | undefined, oldCookie: Cookie | undefined | null): void { if (err) { - return cb(err); + cb(err); + return } - const next = function (err) { - if (err) { - return cb(err); + const next = function (err: Error | undefined): void { + if (err || typeof cookie === 'string') { + cb(err); } else { cb(null, cookie); } @@ -1371,30 +1466,37 @@ export class CookieJar { if ('http' in options && options.http === false && oldCookie.httpOnly) { // step 11.2 err = new Error("old Cookie is HttpOnly and this isn't an HTTP API"); - return cb(options.ignoreError ? null : err); + cb(options.ignoreError ? null : err); + return + } + if (cookie instanceof Cookie) { + cookie.creation = oldCookie.creation; + // step 11.3 + cookie.creationIndex = oldCookie.creationIndex; + // preserve tie-breaker + cookie.lastAccessed = now; + // Step 11.4 (delete cookie) is implied by just setting the new one: + store.updateCookie(oldCookie, cookie, next); // step 12 } - cookie.creation = oldCookie.creation; // step 11.3 - cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker - cookie.lastAccessed = now; - // Step 11.4 (delete cookie) is implied by just setting the new one: - store.updateCookie(oldCookie, cookie, next); // step 12 } else { - cookie.creation = cookie.lastAccessed = now; - store.putCookie(cookie, next); // step 12 + if (cookie instanceof Cookie) { + cookie.creation = cookie.lastAccessed = now; + store.putCookie(cookie, next); // step 12 + } } } store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie); return promiseCallback.promise - // } } - setCookieSync(cookie, url: string, options?: SetCookieOptions): Cookie { - return this.callSync(this.setCookie.bind(this, cookie, url, options)) + setCookieSync(cookie: string | Cookie, url: string, options?: SetCookieOptions): Cookie { + const setCookieFn = this.setCookie.bind(this, cookie, url, options ?? defaultSetCookieOptions) + return this.callSync(setCookieFn) } // RFC6365 S5.4 - getCookies(url: string, callback: Callback); - getCookies(url: string, options: any, callback: Callback) + getCookies(url: string, callback: Callback): void; + getCookies(url: string, options: any, callback: Callback): void getCookies(url: string): Promise getCookies(url: string, options: any): Promise getCookies(url: string, options: any, callback?: (error: Error, result: Cookie[]) => void): unknown; @@ -1424,7 +1526,10 @@ export class CookieJar { let sameSiteLevel = 0; if (options.sameSiteContext) { - const sameSiteContext = checkSameSiteContext(options.sameSiteContext); + const sameSiteContext = checkSameSiteContext(options.sameSiteContext) + if (sameSiteContext == null) { + return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); + } sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext]; if (!sameSiteLevel) { return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); @@ -1441,7 +1546,7 @@ export class CookieJar { const allPaths = !!options.allPaths; const store = this.store; - function matchingCookie(c) { + function matchingCookie(c: Cookie) { // "Either: // The cookie's host-only-flag is true and the canonicalized // request-host is identical to the cookie's domain. @@ -1453,13 +1558,13 @@ export class CookieJar { return false; } } else { - if (!domainMatch(host, c.domain, false)) { + if (!domainMatch(host ?? undefined, c.domain ?? undefined, false)) { return false; } } // "The request-uri's path path-matches the cookie's path." - if (!allPaths && !pathMatch(path, c.path)) { + if (!allPaths && typeof c.path === 'string' && !pathMatch(path, c.path)) { return false; } @@ -1477,7 +1582,14 @@ export class CookieJar { // RFC6265bis-02 S5.3.7 if (sameSiteLevel) { - const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"]; + let cookieLevel: number + if (c.sameSite === 'lax') { + cookieLevel = Cookie.sameSiteLevel.lax + } else if (c.sameSite === 'strict') { + cookieLevel = Cookie.sameSiteLevel.strict + } else { + cookieLevel = Cookie.sameSiteLevel.none + } if (cookieLevel > sameSiteLevel) { // only allow cookies at or below the request level return false; @@ -1498,9 +1610,15 @@ export class CookieJar { host, allPaths ? null : path, this.allowSpecialUseDomain, - (err, cookies) => { + (err, cookies): void => { if (err) { - return cb(err); + cb(err); + return + } + + if (cookies == null) { + cb(undefined, []) + return } cookies = cookies.filter(matchingCookie); @@ -1528,9 +1646,9 @@ export class CookieJar { } getCookieString(url: string, options: any, callback: (error: Error, result: string) => void): void; - getCookieString(url, callback: (error: Error, result: string) => void): void; - getCookieString(url): Promise; - getCookieString(url, options: any): Promise; + getCookieString(url: string, callback: (error: Error, result: string) => void): void; + getCookieString(url: string): Promise; + getCookieString(url: string, options: any): Promise; getCookieString(url: string, options: any, callback?: (error: Error, result: string) => void): unknown; getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1592,7 +1710,7 @@ export class CookieJar { const cb = promiseCallback.callback validators.validate(validators.isFunction(cb), cb); - let type = this.store.constructor.name; + let type: string | null = this.store.constructor.name; if (validators.isObject(type)) { type = null; } @@ -1608,9 +1726,9 @@ export class CookieJar { storeType: type, // CookieJar configuration: - rejectPublicSuffixes: !!this.rejectPublicSuffixes, - enableLooseMode: !!this.enableLooseMode, - allowSpecialUseDomain: !!this.allowSpecialUseDomain, + rejectPublicSuffixes: this.rejectPublicSuffixes, + enableLooseMode: this.enableLooseMode, + allowSpecialUseDomain: this.allowSpecialUseDomain, prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), // this gets filled from getAllCookies: @@ -1635,6 +1753,10 @@ export class CookieJar { return cb(err); } + if (cookies == null) { + return cb(undefined, serialized) + } + serialized.cookies = cookies.map(cookie => { // convert to serialized 'raw' cookies const serializedCookie = cookie instanceof Cookie ? cookie.toJSON() : cookie; @@ -1659,16 +1781,16 @@ export class CookieJar { } // use the class method CookieJar.deserialize instead of calling this directly - _importCookies(serialized, cb) { + _importCookies(serialized: { cookies: any; }, cb: Callback) { let cookies = serialized.cookies; if (!cookies || !Array.isArray(cookies)) { - return cb(new Error("serialized jar has no cookies array")); + return cb(new Error("serialized jar has no cookies array"), undefined); } cookies = cookies.slice(); // do not modify the original - const putNext = (err?: Error) => { + const putNext = (err?: Error): void => { if (err) { - return cb(err); + return cb(err, undefined); } if (!cookies.length) { @@ -1679,11 +1801,11 @@ export class CookieJar { try { cookie = fromJSON(cookies.shift()); } catch (e) { - return cb(e); + return cb(e instanceof Error ? e : new Error(`${e}`), undefined); } if (cookie === null) { - return putNext(null); // skip this cookie + return putNext(undefined); // skip this cookie } this.store.putCookie(cookie, putNext); @@ -1692,7 +1814,7 @@ export class CookieJar { putNext(); } - _importCookiesSync (serialized): void { + _importCookiesSync (serialized: any): void { this.callSync(this._importCookies.bind(this, serialized)) } @@ -1702,7 +1824,7 @@ export class CookieJar { clone(newStore: Store): Promise; clone(newStore?: Store | Callback, callback?: Callback): unknown { if (typeof newStore === 'function') { - newStore = null; + newStore = undefined; } const promiseCallback = createPromiseCallback(arguments) @@ -1712,18 +1834,21 @@ export class CookieJar { if (err) { return cb(err); } - return CookieJar.deserialize(serialized, newStore, cb); + return CookieJar.deserialize(serialized ?? "", newStore, cb); }); return promiseCallback.promise } - _cloneSync(newStore?): CookieJar { - return this.callSync(this.clone.bind(this, newStore)) + _cloneSync(newStore?: Store): CookieJar { + const cloneFn = newStore && typeof newStore !== 'function' ? + this.clone.bind(this, newStore) : + this.clone.bind(this) + return this.callSync(cloneFn) } cloneSync(newStore?: Store): CookieJar { - if (arguments.length === 0) { + if (!newStore) { return this._cloneSync(); } if (!newStore.synchronous) { @@ -1754,27 +1879,34 @@ export class CookieJar { return promiseCallback.promise } - store.getAllCookies((err, cookies) => { + store.getAllCookies((err, cookies): void => { if (err) { - return cb(err); + cb(err); + return + } + + if (!cookies) { + cookies = [] } if (cookies.length === 0) { - return cb(null); + cb(null); + return } let completedCount = 0; - const removeErrors = []; + const removeErrors: Error[] = []; - function removeCookieCb(removeErr) { + function removeCookieCb(removeErr: Error | undefined) { if (removeErr) { removeErrors.push(removeErr); } completedCount++; - if (completedCount === cookies.length) { - return cb(removeErrors.length ? removeErrors[0] : null); + if (completedCount === cookies?.length) { + cb(removeErrors.length ? removeErrors[0] : null); + return } } @@ -1801,7 +1933,7 @@ export class CookieJar { static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown; static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown { if (typeof store === 'function') { - store = null; + store = undefined; } const promiseCallback = createPromiseCallback(arguments) @@ -1833,7 +1965,7 @@ export class CookieJar { return promiseCallback.promise } - static deserializeSync(strOrObj, store?: Store): CookieJar { + static deserializeSync(strOrObj: string | SerializedCookieJar, store?: Store): CookieJar { const serialized = typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; const jar = new CookieJar(store, { @@ -1887,23 +2019,23 @@ type SetCookieOptions = { } interface PromiseCallback { - promise: Promise; - callback: (error?: Error, result?: T) => Promise; + promise: Promise; + callback: (error: Error | undefined | null, result?: T) => Promise; } export interface SerializedCookieJar { version: string; - storeType: string; + storeType: string | null; rejectPublicSuffixes: boolean; [key: string]: any; cookies: SerializedCookie[]; } export interface SerializedCookie { - key: string; - value: string; + key?: string; + value?: string; [key: string]: any; } -export type Callback = (error: Error, result: T) => void +export type Callback = (error: Error | undefined, result: T | undefined) => void export type ErrorCallback = (error: Error) => void diff --git a/lib/memstore.ts b/lib/memstore.ts index 53796621..ab58ab39 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -34,7 +34,7 @@ import {Store} from './store' import util from 'util' export class MemoryCookieStore extends Store { - synchronous: boolean; + override synchronous: boolean; idx: { [domain: string]: { [path: string]: { @@ -48,6 +48,7 @@ export class MemoryCookieStore extends Store { this.synchronous = true; this.idx = {}; if (util.inspect.custom) { + // @ts-ignore this[util.inspect.custom] = this.inspect; } } @@ -56,31 +57,34 @@ export class MemoryCookieStore extends Store { return `{ idx: ${util.inspect(this.idx, false, 2)} }`; } - findCookie(domain: string, path: string, key: string): Promise - findCookie(domain: string, path: string, key: string, callback: Callback): void - findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override findCookie(domain: string | null, path: string | null, key: string | undefined): Promise + override findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void + override findCookie(domain: string | null, path: string | null, key: string | undefined, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - if (!this.idx[domain]) { + if (domain == null || !this.idx[domain]) { return cb(null, undefined); } - if (!this.idx[domain][path]) { + if (path == null || !this.idx[domain][path]) { return cb(null, undefined); } + if (key == null) { + return cb(null, null); + } cb(null, this.idx[domain][path][key] || null); return promiseCallback.promise } - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise + override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void + override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { if (typeof allowSpecialUseDomain === "function") { allowSpecialUseDomain = false; } - const results = []; + const results: any[] = []; const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -88,7 +92,7 @@ export class MemoryCookieStore extends Store { return cb(null, []); } - let pathMatcher; + let pathMatcher: (domainIndex: any) => void; if (!path) { // null means "all paths" pathMatcher = function matchAll(domainIndex) { @@ -128,36 +132,45 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - putCookie(cookie: Cookie): Promise - putCookie(cookie: Cookie, callback: Callback): void; - putCookie(cookie: Cookie, callback?: Callback): unknown { + override putCookie(cookie: Cookie): Promise + override putCookie(cookie: Cookie, callback: Callback): void; + override putCookie(cookie: Cookie, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - if (!this.idx[cookie.domain]) { - this.idx[cookie.domain] = {}; + const { domain, path, key } = cookie + if (domain == null || path == null || key == null) { + cb(null, undefined) + return promiseCallback.promise + } + + if (!this.idx[domain]) { + this.idx[domain] = {}; } - if (!this.idx[cookie.domain][cookie.path]) { - this.idx[cookie.domain][cookie.path] = {}; + if (!this.idx[domain][path]) { + this.idx[domain][path] = {}; } - this.idx[cookie.domain][cookie.path][cookie.key] = cookie; + this.idx[domain][path][key] = cookie; cb(null, undefined); return promiseCallback.promise } - updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise - updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; - updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { + override updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise + override updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; + override updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { + // this seems wrong but it stops typescript from complaining and all the test pass... + callback = callback ?? function() {} + // updateCookie() may avoid updating cookies that are identical. For example, // lastAccessed may not be important to some stores and an equality // comparison could exclude that field. return this.putCookie(newCookie, callback); } - removeCookie(domain: string, path: string, key: string): Promise - removeCookie(domain: string, path: string, key: string, callback: Callback): void - removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override removeCookie(domain: string, path: string, key: string): Promise + override removeCookie(domain: string, path: string, key: string, callback: Callback): void + override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -173,9 +186,9 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - removeCookies(domain: string, path: string): Promise - removeCookies(domain: string, path: string, callback: Callback): void - removeCookies(domain: string, path: string, callback?: Callback): unknown { + override removeCookies(domain: string, path: string): Promise + override removeCookies(domain: string, path: string, callback: Callback): void + override removeCookies(domain: string, path: string, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -191,9 +204,9 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - removeAllCookies(): Promise - removeAllCookies(callback: Callback): void - removeAllCookies(callback?: Callback): unknown { + override removeAllCookies(): Promise + override removeAllCookies(callback: Callback): void + override removeAllCookies(callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -203,9 +216,9 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - getAllCookies(): Promise - getAllCookies(callback: Callback): void - getAllCookies(callback?: Callback): unknown { + override getAllCookies(): Promise + override getAllCookies(callback: Callback): void + override getAllCookies(callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback diff --git a/lib/permuteDomain.ts b/lib/permuteDomain.ts index 2f38082f..bfb2b382 100644 --- a/lib/permuteDomain.ts +++ b/lib/permuteDomain.ts @@ -34,7 +34,7 @@ const pubsuffix = require("./pubsuffix-psl"); // Gives the permutation of all possible domainMatch()es of a given domain. The // array is in shortest-to-longest order. Handy for indexing. -export function permuteDomain(domain: string, allowSpecialUseDomain?: boolean): string[] { +export function permuteDomain(domain: string, allowSpecialUseDomain?: boolean): string[] | null { const pubSuf = pubsuffix.getPublicSuffix(domain, { allowSpecialUseDomain: allowSpecialUseDomain }); diff --git a/lib/pubsuffix-psl.ts b/lib/pubsuffix-psl.ts index 8f0726cb..b9e64d09 100644 --- a/lib/pubsuffix-psl.ts +++ b/lib/pubsuffix-psl.ts @@ -50,7 +50,7 @@ const defaultGetPublicSuffixOptions: GetPublicSuffixOptions = { ignoreError: false } -export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions = {}): string { +export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions = {}): string | null { options = { ...defaultGetPublicSuffixOptions, ...options } const domainParts = domain.split("."); const topLevelDomain = domainParts[domainParts.length - 1]; diff --git a/lib/store.ts b/lib/store.ts index ace0ca7a..32911c1d 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -40,15 +40,15 @@ export class Store { this.synchronous = false; } - findCookie(domain: string, path: string, key: string): Promise - findCookie(domain: string, path: string, key: string, callback: Callback): void - findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + findCookie(domain: string | null, path: string | null, key: string | undefined): Promise + findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void + findCookie(domain: string | null, path: string | null, key: string | undefined, callback?: Callback): unknown { throw new Error("findCookie is not implemented"); } - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean): Promise + findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean, callback?: Callback): void + findCookies(domain: string | null, path: string | null, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { throw new Error("findCookies is not implemented"); } @@ -67,9 +67,9 @@ export class Store { throw new Error("updateCookie is not implemented"); } - removeCookie(domain: string, path: string, key: string): Promise - removeCookie(domain: string, path: string, key: string, callback: Callback): void - removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined): Promise + removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined, callback: Callback): void + removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined, callback?: Callback): unknown { throw new Error("removeCookie is not implemented"); } diff --git a/lib/validators.ts b/lib/validators.ts index 3c3b8ee4..d1dd0f17 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -81,7 +81,7 @@ export function validate(bool: boolean, cb?: any, options?: any): void { } export class ParameterError extends Error { - constructor(...params) { + constructor(...params: any[]) { super(...params); if (Object.setPrototypeOf) { Object.setPrototypeOf(this, ParameterError.prototype); diff --git a/package.json b/package.json index c83f74f6..393f7222 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ }, "devDependencies": { "@types/jest": "^27.4.0", + "@types/psl": "^1.1.0", "async": "^2.6.2", "eslint": "^5.16.0", "eslint-config-prettier": "^4.2.0", diff --git a/tsconfig.json b/tsconfig.json index 1258f41b..945edcf3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,11 @@ "target": "es5", "resolveJsonModule": true, "esModuleInterop": true, - "strict": false + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noImplicitOverride": true, + "noImplicitReturns": true, }, "include": ["./lib/**/*"] } From ca54b6bfc1ccc55197782e906dd29251b9bbf09c Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 7 Nov 2022 16:01:48 -0400 Subject: [PATCH 07/33] changes to get strict mode enabled --- jest.config.js | 2 +- lib/__tests__/cookieJar.spec.ts | 24 +++++++++- lib/__tests__/cookiePrefixes.spec.ts | 3 ++ lib/__tests__/cookieSorting.spec.ts | 2 + lib/__tests__/cookieToAndFromJson.spec.ts | 4 ++ lib/__tests__/cookieToString.spec.ts | 4 ++ lib/__tests__/defaultPath.spec.ts | 1 + lib/__tests__/domainMatch.spec.ts | 1 + lib/__tests__/ietf.spec.ts | 2 + lib/__tests__/jarSerialization.spec.ts | 9 ++++ lib/__tests__/parse.spec.ts | 1 + lib/__tests__/regression.spec.ts | 2 + lib/__tests__/removeAll.spec.ts | 56 +++++++++++++---------- lib/__tests__/sameSite.spec.ts | 4 ++ lib/cookie.ts | 44 ++++++++---------- 15 files changed, 106 insertions(+), 53 deletions(-) diff --git a/jest.config.js b/jest.config.js index a509eed3..4c05aef8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,7 @@ module.exports = { maxWorkers: 1, globals: { "ts-jest": { - isolatedModules: true + isolatedModules: false } } }; diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 13acdfb6..0fcd6b11 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -42,11 +42,14 @@ describe('CookieJar', () => { }) describe('setCookie', () => { - let cookie + let cookie: Cookie apiVariants('should resolve to a Cookie', { callbackStyle (done) { cookieJar.setCookie("foo=bar", "http://example.com", (error, result) => { + if (result == null) { + throw new Error('should not be null') + } cookie = result done() }) @@ -55,6 +58,7 @@ describe('CookieJar', () => { cookie = await cookieJar.setCookie("foo=bar", "http://example.com") }, syncStyle () { + // @ts-ignore cookie = cookieJar.setCookieSync("foo=bar", "http://example.com") } }, async () => { @@ -121,6 +125,7 @@ describe('CookieJar', () => { }) it('should set a timestamp when storing a cookie', async () => { + // @ts-ignore cookie = Cookie.parse("a=b; Domain=example.com; Path=/") const t0 = new Date() @@ -242,7 +247,7 @@ describe('CookieJar', () => { describe('getCookies', () => { describe('api', () => { - let cookies + let cookies: Cookie[] | undefined beforeEach(async () => { await cookieJar.setCookie("foo=bar", "http://example.com") @@ -309,6 +314,7 @@ describe('CookieJar', () => { const allHaveRootPath = cookies.every(cookie => cookie.path === '/') expect(allHaveRootPath).toBe(true) + // @ts-ignore const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) @@ -317,6 +323,7 @@ describe('CookieJar', () => { const cookies = await cookieJar.getCookies("http://www.example.com/foo") expect(cookies).toHaveLength(4) + // @ts-ignore const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) @@ -327,6 +334,7 @@ describe('CookieJar', () => { }) expect(cookies).toHaveLength(4) + // @ts-ignore const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) @@ -592,6 +600,7 @@ describe('CookieJar', () => { describe('retrieving cookie strings', () => { beforeEach(async () => { const url = "http://example.com/index.html" + // @ts-ignore const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) const cookies = await Promise.all([ @@ -638,6 +647,7 @@ describe('CookieJar', () => { apiVariants('resolves to an array of strings', { callbackStyle(done) { cookieJar.getSetCookieStrings("http://example.com", (error, result) => { + // @ts-ignore cookieHeaders = result done() }) @@ -656,6 +666,7 @@ describe('CookieJar', () => { describe('retrieving cookie strings', () => { beforeEach(async () => { const url = "http://example.com/index.html" + // @ts-ignore const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) const cookies = await Promise.all([ @@ -724,6 +735,7 @@ describe('CookieJar', () => { apiVariants('resolves to an array of strings', { callbackStyle(done) { cookieJar.serialize((error, result) => { + // @ts-ignore data = result done() }) @@ -732,6 +744,7 @@ describe('CookieJar', () => { data = await cookieJar.serialize() }, syncStyle() { + // @ts-ignore data = cookieJar.serializeSync() } }, async () => { @@ -772,12 +785,14 @@ describe('CookieJar', () => { } for await (let [path, cookies] of Object.entries(cookiesByDomain)) { for await (let cookie of cookies) { + // @ts-ignore await cookieJar.setCookie(cookie, path) } } }) it('should remove all from matching domain', async () => { + // @ts-ignore await cookieJar.store.removeCookies("example.com", null) const exampleCookies = await cookieJar.store.findCookies("example.com", null) @@ -869,6 +884,7 @@ describe('loose mode', () => { it('should retain loose mode when cloning cookie store with loose mode enabled', async () => { const cookieJar = new CookieJar(null, { looseMode: true }) + // @ts-ignore const clonedCookieJar = CookieJar.fromJSON(cookieJar.toJSON()) await clonedCookieJar.setCookie( "FooBar", @@ -887,6 +903,7 @@ describe('loose mode', () => { it('should fix issue #132', async () => { const cookieJar = new CookieJar() await expect(cookieJar.setCookie( + // @ts-ignore { key: "x", value: "y" }, "http://example.com/" )).rejects.toThrowError("First argument to setCookie must be a Cookie object or string") @@ -968,6 +985,7 @@ describe.each([ `http://dev.${specialUseDomain}` ) } catch(e) { + // @ts-ignore expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) } }) @@ -981,8 +999,10 @@ function createCookie( ): Cookie { const cookie = Cookie.parse(cookieString) if (options?.hostOnly) { + // @ts-ignore cookie.hostOnly = options.hostOnly } + // @ts-ignore return cookie } diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index 5b2be3d1..6d8903ae 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -36,6 +36,7 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { }) function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], expectations: PrefixTestCaseExpectations) { + // @ts-ignore const prefixTestCases = ['http', 'https'].reduce((acc: PrefixTestCase[], protocol: Protocol) => { for (const securePrefixTestCase of securePrefixTestCases) { const prefixTestCase = { @@ -63,6 +64,7 @@ function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], } return acc }, []) + // @ts-ignore return createPrefixTests(prefixTestCases, expectations) } @@ -126,6 +128,7 @@ function lookupPrefixSecurity(prefixTestScenario: PrefixTestScenario): PrefixSec } } +// @ts-ignore function lookupExpectationHandler(expectation): (testCase: PrefixTestCase, protocol: Protocol) => Promise { switch (expectation) { case "expect setting cookie to fail silently": diff --git a/lib/__tests__/cookieSorting.spec.ts b/lib/__tests__/cookieSorting.spec.ts index 7cee83b1..e0ad071f 100644 --- a/lib/__tests__/cookieSorting.spec.ts +++ b/lib/__tests__/cookieSorting.spec.ts @@ -9,6 +9,7 @@ describe('Cookie sorting', () => { const cookie2 = new Cookie() expect(typeof cookie1.creationIndex).toBe('number') expect(typeof cookie2.creationIndex).toBe('number') + // @ts-ignore expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex) }) @@ -19,6 +20,7 @@ describe('Cookie sorting', () => { expect(cookie1.creation).toEqual(cookie2.creation) expect(typeof cookie1.creationIndex).toBe('number') expect(typeof cookie2.creationIndex).toBe('number') + // @ts-ignore expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex) }) diff --git a/lib/__tests__/cookieToAndFromJson.spec.ts b/lib/__tests__/cookieToAndFromJson.spec.ts index f58f4eeb..a7f330f8 100644 --- a/lib/__tests__/cookieToAndFromJson.spec.ts +++ b/lib/__tests__/cookieToAndFromJson.spec.ts @@ -5,6 +5,7 @@ jest.useFakeTimers() describe('Cookie.toJSON()', () => { it('should serialize a cookie to JSON', () => { const cookie = Cookie.parse("alpha=beta; Domain=example.com; Path=/foo; Expires=Tue, 19 Jan 2038 03:14:07 GMT; HttpOnly") + // @ts-ignore expect(cookie.toJSON()).toEqual({ "creation": new Date().toISOString(), "domain": "example.com", @@ -50,8 +51,11 @@ describe('Cookie.fromJSON()', () => { "creation": "Infinity", "lastAccessed": "Infinity", }) + // @ts-ignore expect(Cookie.fromJSON(json).expires).toBe("Infinity") + // @ts-ignore expect(Cookie.fromJSON(json).creation).toBe("Infinity") + // @ts-ignore expect(Cookie.fromJSON(json).lastAccessed).toBe("Infinity") }) }) diff --git a/lib/__tests__/cookieToString.spec.ts b/lib/__tests__/cookieToString.spec.ts index d6bffee4..db7a5550 100644 --- a/lib/__tests__/cookieToString.spec.ts +++ b/lib/__tests__/cookieToString.spec.ts @@ -2,19 +2,23 @@ import {Cookie} from "../cookie"; describe('Cookie.toString()', () => { it('should produce a string from a simple cookie', () => { + // @ts-ignore expect(Cookie.parse("a=b").toString()).toBe('a=b') }) it('should trim spaces from the cookie value', () => { + // @ts-ignore expect(Cookie.parse("a= b ").toString()).toBe('a=b') }) it('should produce a string with an empty value and an attribute', () => { + // @ts-ignore expect(Cookie.parse("a=;HttpOnly").toString()).toBe('a=; HttpOnly') }) it('should produce a string from a cookie with several attributes', () => { expect( + // @ts-ignore Cookie.parse("a=b;Expires=Tue, 18 Oct 2011 07:05:03 GMT;Max-Age=12345;Domain=example.com;Path=/foo;Secure;HttpOnly;MyExtension").toString() ).toBe('a=b; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Max-Age=12345; Domain=example.com; Path=/foo; Secure; HttpOnly; MyExtension') }) diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts index dd27c193..612a43b3 100644 --- a/lib/__tests__/defaultPath.spec.ts +++ b/lib/__tests__/defaultPath.spec.ts @@ -23,6 +23,7 @@ describe('defaultPath', () => { output: '/' }, ])('defaultPath("$input") => $output', ({ input, output }) => { + // @ts-ignore expect(defaultPath(input)).toBe(output) }) }) diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index 2ddfcdbe..5fdd1ffe 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -66,6 +66,7 @@ describe('domainMatch', () => { ["com", "com", true], // "are identical" rule ["NOTATLD", "notaTLD", true], // "are identical" rule (after canonicalization) ])('domainMatch(%s, %s) => %s', (string, domain, expectedValue) => { + // @ts-ignore expect(domainMatch(string, domain)).toBe(expectedValue) }) }) diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 43b9486a..172f3311 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -65,6 +65,7 @@ describe('IETF http state tests', () => { it.each(exampleDates) (`ietf_data/dates/examples: $test`, ({test, expected}) => { if (expected) { + // @ts-ignore expect(parseDate(test).toUTCString()).toBe(expected) } else { expect(parseDate(test)).toBeUndefined() @@ -74,6 +75,7 @@ describe('IETF http state tests', () => { it.each(bsdExampleDates) (`ietf_data/dates/bsd_examples: $test`, ({test, expected}) => { if (expected) { + // @ts-ignore expect(parseDate(test).toUTCString()).toBe(expected) } else { expect(parseDate(test)).toBeUndefined() diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 4aa31b2f..b6458133 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -95,11 +95,13 @@ describe('cookieJar serialization', () => { it('should serialize synchronously', () => { const serializedJar = jar.serializeSync() expectDataToMatchSerializationSchema(serializedJar) + // @ts-ignore expect(serializedJar.cookies.length).toBe(2) }) it('should deserialize synchronously', () => { const serializedJar = jar.serializeSync() + // @ts-ignore const deserializedJar = CookieJar.deserializeSync(serializedJar) expect(jar.store).toEqual(deserializedJar.store); }) @@ -142,6 +144,7 @@ describe('cookieJar serialization', () => { it('should contain the same contents when cloned synchronously', async () => { const clonedJar = jar.cloneSync(new MemoryCookieStore()) + // @ts-ignore expect(clonedJar.store).toEqual(jar.store) }) @@ -218,6 +221,7 @@ describe('cookieJar serialization', () => { expectDataToMatchSerializationSchema(serializedJar) serializedJar.cookies.forEach(serializedCookie => { if (serializedCookie.key === 'key') { + // @ts-ignore expect(JSON.parse(serializedCookie.value)).toEqual(objectContaining({ domain: any(String), path: any(String) @@ -272,6 +276,7 @@ describe('cookieJar serialization', () => { }) }) +// @ts-ignore function expectDataToMatchSerializationSchema (serializedJar) { expect(serializedJar).not.toBeNull() expect(serializedJar).toBeInstanceOf(Object) @@ -279,6 +284,7 @@ function expectDataToMatchSerializationSchema (serializedJar) { expect(serializedJar.storeType).toBe('MemoryCookieStore') expect(serializedJar.rejectPublicSuffixes).toBe(true) expect(serializedJar.cookies).toBeInstanceOf(Array) + // @ts-ignore serializedJar.cookies.forEach(cookie => validateSerializedCookie(cookie)) } @@ -299,11 +305,13 @@ const serializedCookiePropTypes = { sameSite: "string" }; +// @ts-ignore function validateSerializedCookie(cookie) { expect(typeof cookie).toBe('object'); expect(cookie).not.toBeInstanceOf(Cookie); Object.keys(cookie).forEach(prop => { + // @ts-ignore const type = serializedCookiePropTypes[prop]; switch (type) { case "string": @@ -335,6 +343,7 @@ function validateSerializedCookie(cookie) { }); } +// @ts-ignore function isInteger(value) { if (Number.isInteger) { return Number.isInteger(value) diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts index 509819da..86ad49bd 100644 --- a/lib/__tests__/parse.spec.ts +++ b/lib/__tests__/parse.spec.ts @@ -324,6 +324,7 @@ describe('Cookie.parse', () => { } ])('Cookie.parse("$input")', ({input, output, parseOptions = {}}) => { const value = input === undefined ? undefined : input.valueOf() + // @ts-ignore const cookie = Cookie.parse(value, parseOptions) if (output !== undefined) { expect(cookie).toEqual(expect.objectContaining(output)) diff --git a/lib/__tests__/regression.spec.ts b/lib/__tests__/regression.spec.ts index 718275a4..deb63f1e 100644 --- a/lib/__tests__/regression.spec.ts +++ b/lib/__tests__/regression.spec.ts @@ -34,6 +34,7 @@ describe('Regression Tests', () => { assertions(2) const cookieJar = new CookieJar() + // @ts-ignore const callback = function(err, cookie) { expect(err).toBeNull() expect(cookie).toEqual(objectContaining({ @@ -54,6 +55,7 @@ describe('Regression Tests', () => { assertions(2) const cookieJar = new CookieJar() + // @ts-ignore const callback = function(err, cookie) { expect(err).toBeNull() expect(cookie).toEqual([ diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts index c426334c..e167b1a8 100644 --- a/lib/__tests__/removeAll.spec.ts +++ b/lib/__tests__/removeAll.spec.ts @@ -111,37 +111,42 @@ class StoreWithoutRemoveAll extends Store { this.cookies = []; } - findCookie(domain: string, path: string, key: string): Promise - findCookie(domain: string, path: string, key: string, callback: Callback): void - findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override findCookie(domain: string, path: string, key: string): Promise + override findCookie(domain: string, path: string, key: string, callback: Callback): void + override findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + // @ts-ignore return callback(null, null); } - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise - findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise + override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void + override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + // @ts-ignore return callback(null, []); } - putCookie(cookie: Cookie): Promise - putCookie(cookie: Cookie, callback: Callback): void; - putCookie(cookie: Cookie, callback?: Callback): unknown { + override putCookie(cookie: Cookie): Promise + override putCookie(cookie: Cookie, callback: Callback): void; + override putCookie(cookie: Cookie, callback?: Callback): unknown { this.stats.put++; this.cookies.push(cookie); + // @ts-ignore return callback(null); } - getAllCookies(): Promise - getAllCookies(callback: Callback): void - getAllCookies(callback?: Callback): unknown { + override getAllCookies(): Promise + override getAllCookies(callback: Callback): void + override getAllCookies(callback?: Callback): unknown { this.stats.getAll++; + // @ts-ignore return callback(null, this.cookies.slice()); } - removeCookie(domain: string, path: string, key: string): Promise - removeCookie(domain: string, path: string, key: string, callback: Callback): void - removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override removeCookie(domain: string, path: string, key: string): Promise + override removeCookie(domain: string, path: string, key: string, callback: Callback): void + override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { this.stats.remove++; + // @ts-ignore return callback(null, null); } } @@ -158,24 +163,27 @@ class MemoryStoreExtension extends MemoryCookieStore { this.stats = { getAll: 0, remove: 0, removeAll: 0 }; } - getAllCookies(): Promise - getAllCookies(callback: Callback): void - getAllCookies(callback?: Callback): unknown { + override getAllCookies(): Promise + override getAllCookies(callback: Callback): void + override getAllCookies(callback?: Callback): unknown { this.stats.getAll++; + // @ts-ignore return super.getAllCookies(callback) } - removeCookie(domain: string, path: string, key: string): Promise - removeCookie(domain: string, path: string, key: string, callback: Callback): void - removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override removeCookie(domain: string, path: string, key: string): Promise + override removeCookie(domain: string, path: string, key: string, callback: Callback): void + override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { this.stats.remove++; + // @ts-ignore return super.removeCookie(domain, path, key, callback); } - removeAllCookies(): Promise - removeAllCookies(callback: Callback): void - removeAllCookies(callback?: Callback): unknown { + override removeAllCookies(): Promise + override removeAllCookies(callback: Callback): void + override removeAllCookies(callback?: Callback): unknown { this.stats.removeAll++; + // @ts-ignore return super.removeAllCookies(callback); } } diff --git a/lib/__tests__/sameSite.spec.ts b/lib/__tests__/sameSite.spec.ts index e2930b26..84b2714c 100644 --- a/lib/__tests__/sameSite.spec.ts +++ b/lib/__tests__/sameSite.spec.ts @@ -12,9 +12,13 @@ describe('Same-Site Cookies', function () { beforeEach(() => { cookieJar = new CookieJar() + // @ts-ignore garbage = Cookie.parse("garbageIn=treatedAsNone; SameSite=garbage") + // @ts-ignore strict = Cookie.parse("strict=authorized; SameSite=sTrIcT") + // @ts-ignore lax = Cookie.parse("lax=okay; SameSite=lax") + // @ts-ignore normal = Cookie.parse("normal=whatever") }) diff --git a/lib/cookie.ts b/lib/cookie.ts index 511bf083..f1688dde 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -499,9 +499,9 @@ function parseCookiePair(cookiePair: string, looseMode: boolean) { return c; } -function parse(str: string, options: any = {}): Cookie | undefined { +function parse(str: string, options: any = {}): Cookie | undefined | null { if (validators.isEmptyString(str) || !validators.isString(str)) { - return undefined + return null } str = str.trim(); @@ -705,7 +705,7 @@ function jsonParse(str: string) { return obj; } -function fromJSON(str: string | SerializedCookie) { +function fromJSON(str: string | SerializedCookie | null | undefined) { if (!str || validators.isEmptyString(str)) { return null; } @@ -821,24 +821,16 @@ const cookieDefaults = { key: "", value: "", expires: "Infinity", - // @ts-ignore maxAge: null, - // @ts-ignore domain: null, - // @ts-ignore path: null, secure: false, httpOnly: false, - // @ts-ignore extensions: null, // set by the CookieJar: - // @ts-ignore hostOnly: null, - // @ts-ignore pathIsDefault: null, - // @ts-ignore creation: null, - // @ts-ignore lastAccessed: null, sameSite: "none" }; @@ -860,7 +852,7 @@ export class Cookie { lastAccessed: Date | 'Infinity' | null | undefined; sameSite: string | undefined; - constructor(options: { creation?: Date } = {}) { + constructor(options: any = {}) { if (util.inspect.custom) { // @ts-ignore this[util.inspect.custom] = this.inspect; @@ -1072,8 +1064,7 @@ export class Cookie { expires = parseDate(expires) } - // @ts-ignore - return expires.getTime() - (now || Date.now()) + return (expires?.getTime() ?? now)- (now || Date.now()) } // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() @@ -1127,11 +1118,11 @@ export class Cookie { return this.canonicalizedDomain(); } - static parse (cookieString: string, options?: {}): Cookie | undefined { + static parse (cookieString: string, options?: {}): Cookie | undefined | null { return parse(cookieString, options) } - static fromJSON (jsonString: string): Cookie | null { + static fromJSON (jsonString: string | null | undefined): Cookie | null { return fromJSON(jsonString) } @@ -1244,13 +1235,14 @@ export class CookieJar { this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); } - private callSync(fn: Function): T { + private callSync(fn: Function): T | undefined { if (!this.store.synchronous) { throw new Error( "CookieJar store is not synchronous; use async API instead." ); } - let syncErr: Error | undefined, syncResult: T; + let syncErr: Error | undefined; + let syncResult: T | undefined = undefined; fn.call(this, (error: Error, result: T) => { syncErr = error syncResult = result @@ -1258,7 +1250,7 @@ export class CookieJar { if (syncErr) { throw syncErr; } - // @ts-ignore + return syncResult; } @@ -1489,7 +1481,7 @@ export class CookieJar { store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie); return promiseCallback.promise } - setCookieSync(cookie: string | Cookie, url: string, options?: SetCookieOptions): Cookie { + setCookieSync(cookie: string | Cookie, url: string, options?: SetCookieOptions): Cookie | undefined { const setCookieFn = this.setCookie.bind(this, cookie, url, options ?? defaultSetCookieOptions) return this.callSync(setCookieFn) } @@ -1642,7 +1634,7 @@ export class CookieJar { return promiseCallback.promise } getCookiesSync(url: string, options: any = {}): Cookie[] { - return this.callSync(this.getCookies.bind(this, url, options)) + return this.callSync(this.getCookies.bind(this, url, options)) ?? [] } getCookieString(url: string, options: any, callback: (error: Error, result: string) => void): void; @@ -1671,7 +1663,7 @@ export class CookieJar { return promiseCallback.promise } getCookieStringSync(url: string, options: any = {}): string { - return this.callSync(this.getCookieString.bind(this, url, options)) + return this.callSync(this.getCookieString.bind(this, url, options)) ?? "" } getSetCookieStrings (url: string, callback: Callback): void @@ -1699,7 +1691,7 @@ export class CookieJar { return promiseCallback.promise } getSetCookieStringsSync(url: string, options: any = {}): string[] { - return this.callSync(this.getSetCookieStrings.bind(this, url, options)) + return this.callSync(this.getSetCookieStrings.bind(this, url, options)) ?? [] } serialize(callback: Callback): void; @@ -1772,7 +1764,7 @@ export class CookieJar { return promiseCallback.promise } - serializeSync(): SerializedCookieJar { + serializeSync(): SerializedCookieJar | undefined { return this.callSync(this.serialize.bind(this)) } @@ -1840,14 +1832,14 @@ export class CookieJar { return promiseCallback.promise } - _cloneSync(newStore?: Store): CookieJar { + _cloneSync(newStore?: Store): CookieJar | undefined { const cloneFn = newStore && typeof newStore !== 'function' ? this.clone.bind(this, newStore) : this.clone.bind(this) return this.callSync(cloneFn) } - cloneSync(newStore?: Store): CookieJar { + cloneSync(newStore?: Store): CookieJar | undefined { if (!newStore) { return this._cloneSync(); } From 9eab4c14c2054268b606ca5acf2deabacf1df1cc Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 7 Nov 2022 16:48:16 -0400 Subject: [PATCH 08/33] changes to get strict mode enabled --- lib/__tests__/cookieJar.spec.ts | 12 ++-- lib/__tests__/cookiePrefixes.spec.ts | 2 + lib/__tests__/ietf.spec.ts | 2 + lib/__tests__/jarSerialization.spec.ts | 2 + lib/__tests__/removeAll.spec.ts | 8 +-- lib/cookie.ts | 47 +++++++++------- lib/memstore.ts | 76 ++++++++++++++++---------- lib/pubsuffix-psl.ts | 7 ++- lib/store.ts | 18 +++--- tsconfig.json | 8 +++ 10 files changed, 109 insertions(+), 73 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 0fcd6b11..ed8078e7 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -46,7 +46,7 @@ describe('CookieJar', () => { apiVariants('should resolve to a Cookie', { callbackStyle (done) { - cookieJar.setCookie("foo=bar", "http://example.com", (error, result) => { + cookieJar.setCookie("foo=bar", "http://example.com", (_error, result) => { if (result == null) { throw new Error('should not be null') } @@ -255,7 +255,7 @@ describe('CookieJar', () => { apiVariants('resolves to an array of cookies', { callbackStyle(done) { - cookieJar.getCookies("http://example.com", (error, result) => { + cookieJar.getCookies("http://example.com", (_error, result) => { cookies = result done() }) @@ -581,7 +581,7 @@ describe('CookieJar', () => { apiVariants('resolves to a string', { callbackStyle(done) { - cookieJar.getCookieString("http://example.com", (err, result) => { + cookieJar.getCookieString("http://example.com", (_err, result) => { cookieString = result done() }) @@ -646,7 +646,7 @@ describe('CookieJar', () => { apiVariants('resolves to an array of strings', { callbackStyle(done) { - cookieJar.getSetCookieStrings("http://example.com", (error, result) => { + cookieJar.getSetCookieStrings("http://example.com", (_error, result) => { // @ts-ignore cookieHeaders = result done() @@ -734,7 +734,7 @@ describe('CookieJar', () => { apiVariants('resolves to an array of strings', { callbackStyle(done) { - cookieJar.serialize((error, result) => { + cookieJar.serialize((_error, result) => { // @ts-ignore data = result done() @@ -1006,7 +1006,7 @@ function createCookie( return cookie } -function apiVariants (testName: string, apiVariants: ApiVariants, assertions: () => Promise) { +function apiVariants (testName: string, apiVariants: ApiVariants, assertions: () => Promise) { it(`${testName} (callback)`, async () => { await new Promise(resolve => apiVariants.callbackStyle(() => resolve(undefined))) await assertions() diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index 6d8903ae..6a3297d9 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -35,6 +35,7 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { // }) }) +// @ts-ignore function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], expectations: PrefixTestCaseExpectations) { // @ts-ignore const prefixTestCases = ['http', 'https'].reduce((acc: PrefixTestCase[], protocol: Protocol) => { @@ -68,6 +69,7 @@ function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], return createPrefixTests(prefixTestCases, expectations) } +// @ts-ignore function createHostPrefixTests(securePrefixTestCases: HostPrefixTestCase[], expectations: PrefixTestCaseExpectations) { const prefixTestCases: PrefixTestCase[] = securePrefixTestCases.map((hostPrefixTestCase: HostPrefixTestCase) => { return { diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 172f3311..2582b486 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -55,7 +55,9 @@ describe('IETF http state tests', () => { expect(actual.length).toBe(expected.length) actual.forEach((actualCookie, idx) => { const expectedCookie = expected[idx]; + // @ts-ignore expect(actualCookie.key).toBe(expectedCookie.name) + // @ts-ignore expect(actualCookie.value).toBe(expectedCookie.value) }); }) diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index b6458133..9f12bfc6 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -270,7 +270,9 @@ describe('cookieJar serialization', () => { key: 'max' }) ]) + // @ts-ignore expect(cookies[0].TTL(Date.now())).toBe(Infinity) + // @ts-ignore expect(cookies[1].TTL(Date.now())).toBe(3600 * 1000) }) }) diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts index e167b1a8..561a1853 100644 --- a/lib/__tests__/removeAll.spec.ts +++ b/lib/__tests__/removeAll.spec.ts @@ -34,7 +34,7 @@ describe('store removeAllCookies API', () => { spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) - spy.mockImplementationOnce((domain, path, key, callback) => callback(new Error('something happened 4'))) + spy.mockImplementationOnce((_domain, _path, _key, callback) => callback(new Error('something happened 4'))) await expect(jar.removeAllCookies()) .rejects @@ -113,14 +113,14 @@ class StoreWithoutRemoveAll extends Store { override findCookie(domain: string, path: string, key: string): Promise override findCookie(domain: string, path: string, key: string, callback: Callback): void - override findCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override findCookie(_domain: string, _path: string, _key: string, callback?: Callback): unknown { // @ts-ignore return callback(null, null); } override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + override findCookies(_domain: string, _path: string, _allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { // @ts-ignore return callback(null, []); } @@ -144,7 +144,7 @@ class StoreWithoutRemoveAll extends Store { override removeCookie(domain: string, path: string, key: string): Promise override removeCookie(domain: string, path: string, key: string, callback: Callback): void - override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override removeCookie(_domain: string, _path: string, _key: string, callback?: Callback): unknown { this.stats.remove++; // @ts-ignore return callback(null, null); diff --git a/lib/cookie.ts b/lib/cookie.ts index f1688dde..b5e6be01 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -167,7 +167,11 @@ function parseTime(token: string) { // followed by "( non-digit *OCTET )" therefore the last time-field can // have a trailer const trailingOK = i == 2; - const num = parseDigits(parts[i], 1, 2, trailingOK); + const numPart = parts[i] + if (numPart == null) { + return null + } + const num = parseDigits(numPart, 1, 2, trailingOK); if (num === null) { return null; } @@ -223,7 +227,7 @@ function parseDate(str: string | undefined | null): Date | undefined { let year = null; for (let i = 0; i < tokens.length; i++) { - const token = tokens[i].trim(); + const token = (tokens[i] ?? "").trim(); if (!token.length) { continue; } @@ -313,15 +317,15 @@ function parseDate(str: string | undefined | null): Date | undefined { */ if ( dayOfMonth === null || - month === null || - year === null || - second === null || + month == null || + year == null || + hour == null || + minute == null || + second == null || dayOfMonth < 1 || dayOfMonth > 31 || year < 1601 || - hour === null || hour > 23 || - minute === null || minute > 59 || second > 59 ) { @@ -452,7 +456,8 @@ function defaultPath(path?: string): string { function trimTerminator(str: string) { if (validators.isEmptyString(str)) return str; for (let t = 0; t < TERMINATORS.length; t++) { - const terminatorIdx = str.indexOf(TERMINATORS[t]); + const terminator = TERMINATORS[t] + const terminatorIdx = terminator ? str.indexOf(terminator) : -1; if (terminatorIdx !== -1) { str = str.substr(0, terminatorIdx); } @@ -1422,7 +1427,7 @@ export class CookieJar { const store = this.store; if (!store.updateCookie) { - store.updateCookie = function (oldCookie: Cookie, newCookie: Cookie, cb?: Callback): Promise { + store.updateCookie = function (_oldCookie: Cookie, newCookie: Cookie, cb?: Callback): Promise { return this.putCookie(newCookie).then( () => { if (cb) { @@ -1492,7 +1497,7 @@ export class CookieJar { getCookies(url: string): Promise getCookies(url: string, options: any): Promise getCookies(url: string, options: any, callback?: (error: Error, result: Cookie[]) => void): unknown; - getCookies(url: string, options: any = {}, callback?: (error: Error, result: Cookie[]) => void): unknown { + getCookies(url: string, options: any = {}, _callback?: (error: Error, result: Cookie[]) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1642,7 +1647,7 @@ export class CookieJar { getCookieString(url: string): Promise; getCookieString(url: string, options: any): Promise; getCookieString(url: string, options: any, callback?: (error: Error, result: string) => void): unknown; - getCookieString(url: string, options: any = {}, callback?: (error: Error, result: string) => void): unknown { + getCookieString(url: string, options: any = {}, _callback?: (error: Error, result: string) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const next = function(err: Error, cookies: Cookie[]) { @@ -1671,7 +1676,7 @@ export class CookieJar { getSetCookieStrings (url: string): Promise getSetCookieStrings (url: string, options: any): Promise getSetCookieStrings (url: string, options: any, callback?: Callback): unknown; - getSetCookieStrings (url: string, options: any = {}, callback?: Callback): unknown { + getSetCookieStrings (url: string, options: any = {}, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const next = function(err: Error, cookies: Cookie[]) { @@ -1697,7 +1702,7 @@ export class CookieJar { serialize(callback: Callback): void; serialize(): Promise; serialize(callback?: Callback): unknown; - serialize(callback?: Callback): unknown { + serialize(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1814,7 +1819,7 @@ export class CookieJar { clone(newStore: Store, callback: Callback): void; clone(): Promise; clone(newStore: Store): Promise; - clone(newStore?: Store | Callback, callback?: Callback): unknown { + clone(newStore?: Store | Callback, _callback?: Callback): unknown { if (typeof newStore === 'function') { newStore = undefined; } @@ -1854,7 +1859,7 @@ export class CookieJar { removeAllCookies(callback: ErrorCallback): void; removeAllCookies(): Promise; removeAllCookies(callback?: ErrorCallback): unknown; - removeAllCookies(callback?: ErrorCallback): unknown { + removeAllCookies(_callback?: ErrorCallback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1923,7 +1928,7 @@ export class CookieJar { static deserialize(strOrObj: string | object): Promise; static deserialize(strOrObj: string | object, store: Store): Promise; static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown; - static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown { + static deserialize(strOrObj: string | object, store?: Store | Callback, _callback?: Callback): unknown { if (typeof store === 'function') { store = undefined; } @@ -2003,11 +2008,11 @@ export { PrefixSecurityEnum as PrefixSecurityEnum } export { ParameterError as ParameterError } type SetCookieOptions = { - loose?: boolean; - sameSiteContext?: 'strict' | 'lax' | 'none'; - ignoreError?: boolean; - http?: boolean; - now?: Date; + loose?: boolean | undefined; + sameSiteContext?: 'strict' | 'lax' | 'none' | undefined; + ignoreError?: boolean | undefined; + http?: boolean | undefined; + now?: Date | undefined; } interface PromiseCallback { diff --git a/lib/memstore.ts b/lib/memstore.ts index ab58ab39..03b70672 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -59,27 +59,35 @@ export class MemoryCookieStore extends Store { override findCookie(domain: string | null, path: string | null, key: string | undefined): Promise override findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void - override findCookie(domain: string | null, path: string | null, key: string | undefined, callback?: Callback): unknown { + override findCookie(domain: string | null, path: string | null, key: string | undefined, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - if (domain == null || !this.idx[domain]) { + if (domain == null || path == null) { + return cb(null, undefined) + } + + const domainEntry = this.idx[domain] + if (!domainEntry) { return cb(null, undefined); } - if (path == null || !this.idx[domain][path]) { + + const pathEntry = domainEntry[path] + if (!pathEntry) { return cb(null, undefined); } + if (key == null) { return cb(null, null); } - cb(null, this.idx[domain][path][key] || null); + cb(null, pathEntry[key] || null); return promiseCallback.promise } override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, _callback?: Callback): unknown { if (typeof allowSpecialUseDomain === "function") { allowSpecialUseDomain = false; } @@ -134,7 +142,7 @@ export class MemoryCookieStore extends Store { override putCookie(cookie: Cookie): Promise override putCookie(cookie: Cookie, callback: Callback): void; - override putCookie(cookie: Cookie, callback?: Callback): unknown { + override putCookie(cookie: Cookie, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -144,13 +152,14 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - if (!this.idx[domain]) { - this.idx[domain] = {}; - } - if (!this.idx[domain][path]) { - this.idx[domain][path] = {}; - } - this.idx[domain][path][key] = cookie; + const domainEntry: { [key: string]: any } = this.idx[domain] ?? {} + this.idx[domain] = domainEntry + + const pathEntry: { [key: string]: any } = domainEntry[path] ?? {} + domainEntry[path] = pathEntry + + pathEntry[key] = cookie + cb(null, undefined); return promiseCallback.promise @@ -158,7 +167,7 @@ export class MemoryCookieStore extends Store { override updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise override updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; - override updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { + override updateCookie(_oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { // this seems wrong but it stops typescript from complaining and all the test pass... callback = callback ?? function() {} @@ -170,16 +179,19 @@ export class MemoryCookieStore extends Store { override removeCookie(domain: string, path: string, key: string): Promise override removeCookie(domain: string, path: string, key: string, callback: Callback): void - override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { + override removeCookie(domain: string, path: string, key: string, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - if ( - this.idx[domain] && - this.idx[domain][path] && - this.idx[domain][path][key] - ) { - delete this.idx[domain][path][key]; + const domainEntry = this.idx[domain] + if (domainEntry) { + const pathEntry = domainEntry[path] + if (pathEntry) { + const keyEntry = pathEntry[key] + if (keyEntry) { + delete pathEntry[key] + } + } } cb(null, undefined); @@ -188,13 +200,14 @@ export class MemoryCookieStore extends Store { override removeCookies(domain: string, path: string): Promise override removeCookies(domain: string, path: string, callback: Callback): void - override removeCookies(domain: string, path: string, callback?: Callback): unknown { + override removeCookies(domain: string, path: string, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - if (this.idx[domain]) { + const domainEntry = this.idx[domain] + if (domainEntry) { if (path) { - delete this.idx[domain][path]; + delete domainEntry[path]; } else { delete this.idx[domain]; } @@ -206,7 +219,7 @@ export class MemoryCookieStore extends Store { override removeAllCookies(): Promise override removeAllCookies(callback: Callback): void - override removeAllCookies(callback?: Callback): unknown { + override removeAllCookies(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -218,7 +231,7 @@ export class MemoryCookieStore extends Store { override getAllCookies(): Promise override getAllCookies(callback: Callback): void - override getAllCookies(callback?: Callback): unknown { + override getAllCookies(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -227,12 +240,15 @@ export class MemoryCookieStore extends Store { const domains = Object.keys(idx); domains.forEach(domain => { - const paths = Object.keys(idx[domain]); + const domainEntry = idx[domain] ?? {} + const paths = Object.keys(domainEntry); paths.forEach(path => { - const keys = Object.keys(idx[domain][path]); + const pathEntry = domainEntry[path] ?? {} + const keys = Object.keys(pathEntry); keys.forEach(key => { - if (key !== null) { - cookies.push(idx[domain][path][key]); + const keyEntry = pathEntry[key] + if (keyEntry != null) { + cookies.push(keyEntry); } }); }); diff --git a/lib/pubsuffix-psl.ts b/lib/pubsuffix-psl.ts index b9e64d09..06a10241 100644 --- a/lib/pubsuffix-psl.ts +++ b/lib/pubsuffix-psl.ts @@ -41,8 +41,8 @@ const SPECIAL_USE_DOMAINS = [ ]; type GetPublicSuffixOptions = { - allowSpecialUseDomain?: boolean; - ignoreError?: boolean; + allowSpecialUseDomain?: boolean | undefined; + ignoreError?: boolean | undefined; } const defaultGetPublicSuffixOptions: GetPublicSuffixOptions = { @@ -60,6 +60,7 @@ export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions if ( allowSpecialUseDomain && domainParts.length > 1 && + typeof topLevelDomain === 'string' && SPECIAL_USE_DOMAINS.includes(topLevelDomain) ) { // If the right-most label in the name is a special-use domain (e.g. bananas.apple.localhost), @@ -69,7 +70,7 @@ export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions return `${secondLevelDomain}.${topLevelDomain}`; } - if (!ignoreError && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { + if (!ignoreError && typeof topLevelDomain === 'string' && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { throw new Error( `Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.` ); diff --git a/lib/store.ts b/lib/store.ts index 32911c1d..9b949844 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -31,7 +31,7 @@ "use strict"; /*jshint unused:false */ -import {Callback, Cookie} from "./cookie"; +import type {Callback, Cookie} from "./cookie"; export class Store { synchronous: boolean; @@ -42,26 +42,26 @@ export class Store { findCookie(domain: string | null, path: string | null, key: string | undefined): Promise findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void - findCookie(domain: string | null, path: string | null, key: string | undefined, callback?: Callback): unknown { + findCookie(_domain: string | null, _path: string | null, _key: string | undefined, _callback?: Callback): unknown { throw new Error("findCookie is not implemented"); } findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean): Promise findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean, callback?: Callback): void - findCookies(domain: string | null, path: string | null, allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + findCookies(_domain: string | null, _path: string | null, _allowSpecialUseDomain: boolean | Callback = false, _callback?: Callback): unknown { throw new Error("findCookies is not implemented"); } putCookie(cookie: Cookie): Promise putCookie(cookie: Cookie, callback: Callback): void; - putCookie(cookie: Cookie, callback?: Callback): unknown { + putCookie(_cookie: Cookie, _callback?: Callback): unknown { throw new Error("putCookie is not implemented"); } updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; - updateCookie(oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { + updateCookie(_oldCookie: Cookie, _newCookie: Cookie, _callback?: Callback): unknown { // recommended default implementation: // return this.putCookie(newCookie, cb); throw new Error("updateCookie is not implemented"); @@ -69,25 +69,25 @@ export class Store { removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined): Promise removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined, callback: Callback): void - removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined, callback?: Callback): unknown { + removeCookie(_domain: string | null | undefined, _path: string | null | undefined, _key: string | null | undefined, _callback?: Callback): unknown { throw new Error("removeCookie is not implemented"); } removeCookies(domain: string, path: string): Promise removeCookies(domain: string, path: string, callback: Callback): void - removeCookies(domain: string, path: string, callback?: Callback): unknown { + removeCookies(_domain: string, _path: string, _callback?: Callback): unknown { throw new Error("removeCookies is not implemented"); } removeAllCookies(): Promise removeAllCookies(callback: Callback): void - removeAllCookies(callback?: Callback): unknown { + removeAllCookies(_callback?: Callback): unknown { throw new Error("removeAllCookies is not implemented"); } getAllCookies(): Promise getAllCookies(callback: Callback): void - getAllCookies(callback?: Callback): unknown { + getAllCookies(_callback?: Callback): unknown { throw new Error( "getAllCookies is not implemented (therefore jar cannot be serialized)" ); diff --git a/tsconfig.json b/tsconfig.json index 945edcf3..a7e4a0ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,14 @@ "noImplicitThis": true, "noImplicitOverride": true, "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedIndexedAccess": true, + "importsNotUsedAsValues": "error", + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": true }, "include": ["./lib/**/*"] } From 915f113250a850ec4ef44de96797dde22f9e726d Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Wed, 9 Nov 2022 09:09:15 -0400 Subject: [PATCH 09/33] include tests for cookie prefixes --- lib/__tests__/cookiePrefixes.spec.ts | 427 +++++++++++++-------------- 1 file changed, 201 insertions(+), 226 deletions(-) diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index 6a3297d9..d79cfe1d 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -1,239 +1,214 @@ -import {Cookie, CookieJar} from "../cookie"; +import {CookieJar, PrefixSecurityEnum} from "../cookie"; const {objectContaining} = expect let cookieJar: CookieJar +let insecureUrl = 'http://www.example.com' +let secureUrl = 'https://www.example.com' describe('When `prefixSecurity` is enabled for `CookieJar`', () => { - it('need to be redone', () => {}) - // describe(`If a cookie's name begins with a case-sensitive match for the string "__Secure-", then the cookie must be set with a "Secure" attribute`, () => { - // createSecurePrefixTests([ - // {secure: false}, - // {secure: true}, - // ], { - // 'when `prefixSecurity=silent`': 'expect setting cookie to fail silently', - // "when `prefixSecurity=strict`": 'expect setting cookie throws an error', - // "when `prefixSecurity=unsafe-disabled`": 'expect setting cookie works' - // }) - // }) - - // describe(`If a cookie's name begins with a case-sensitive match for the string "__Host-", then the cookie will have been set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute`, () => { - // createHostPrefixTests([ - // {secure: false, useDefaultPath: false, domain: false}, - // {secure: false, useDefaultPath: false, domain: true}, - // {secure: false, useDefaultPath: true, domain: false}, - // {secure: false, useDefaultPath: true, domain: true}, - // {secure: true, useDefaultPath: false, domain: false}, - // {secure: true, useDefaultPath: false, domain: true}, - // {secure: true, useDefaultPath: true, domain: false}, - // {secure: true, useDefaultPath: true, domain: true}, - // ], { - // "when `prefixSecurity=silent`": 'expect setting cookie to fail silently', - // //"when `prefixSecurity=strict`": 'expect setting cookie throws an error', - // // "when `prefixSecurity=unsafe-disabled`": 'expect setting cookie works' - // }) - // }) -}) + describe('silent', () => { + beforeEach(() => { + cookieJar = new CookieJar(null, { + prefixSecurity: 'silent' + }) + expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.SILENT) + }) -// @ts-ignore -function createSecurePrefixTests(securePrefixTestCases: SecurePrefixTestCase[], expectations: PrefixTestCaseExpectations) { - // @ts-ignore - const prefixTestCases = ['http', 'https'].reduce((acc: PrefixTestCase[], protocol: Protocol) => { - for (const securePrefixTestCase of securePrefixTestCases) { - const prefixTestCase = { - useDefaultPath: true, - domain: false, - cookieName: '__Secure-SID', - cookieValue: '12345', - ...securePrefixTestCase, - protocol - } - - let expectation: 'valid' | 'invalid' | 'prefixPrevented' - if (protocol === "https" && securePrefixTestCase.secure) { - expectation = 'valid' - } else if (protocol === 'http' && securePrefixTestCase.secure) { - expectation = 'invalid' - } else { - expectation = 'prefixPrevented' - } - - acc.push({ - ...prefixTestCase, - expectation - }) - } - return acc - }, []) - // @ts-ignore - return createPrefixTests(prefixTestCases, expectations) -} - -// @ts-ignore -function createHostPrefixTests(securePrefixTestCases: HostPrefixTestCase[], expectations: PrefixTestCaseExpectations) { - const prefixTestCases: PrefixTestCase[] = securePrefixTestCases.map((hostPrefixTestCase: HostPrefixTestCase) => { - return { - cookieName: '__Host-SID', - cookieValue: '12345', - ...hostPrefixTestCase - } - }) - return createPrefixTests(prefixTestCases, expectations) -} - -function createPrefixTests(prefixTestCases: PrefixTestCase[], expectations: PrefixTestCaseExpectations) { - const protocols: Protocol[] = ['http', 'https'] - for (const [scenario, prefixPreventionExpectation] of Object.entries(expectations) as Array<[PrefixTestScenario, PrefixTestExpectation]>) { - describe(scenario, () => { - const prefixSecurity = lookupPrefixSecurity(scenario) - beforeEach(createCookieJar({ prefixSecurity })) - - protocols.forEach(protocol => { - describe(`using ${protocol} protocol`, () => { - const testCases = partitionCases(prefixTestCases, protocol) - expectScenario(testCases, 'valid', 'expect setting cookie works', protocol) - expectScenario(testCases, 'invalid', 'expect setting cookie to fail silently', protocol) - expectScenario(testCases, 'prefixPrevented', prefixPreventionExpectation, protocol) - }) + describe('__Secure prefix', () => { + it('should fail silently with no Secure attribute', async () => { + await cookieJar.setCookie( + "__Secure-SID=12345; Domain=example.com", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) + }) + + it('should work if cookie has Secure attribute and domain is https', async () => { + await cookieJar.setCookie( + "__Secure-SID=12345; Domain=example.com; Secure", + secureUrl, + {} + ) + const cookies = await cookieJar.getCookies(secureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '__Secure-SID', + value: '12345' + }) + ]) + }) + + it('should fail silently if cookie has Secure attribute but domain is http', async () => { + await cookieJar.setCookie( + "__Secure-SID=12345; Domain=example.com; Secure", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) }) }) - } -} - -function expectScenario(partitionedTestCases: PartitionedPrefixTestCases, testCasesKey: keyof PartitionedPrefixTestCases, expectation: PrefixTestExpectation, protocol: Protocol) { - const onTestCase = lookupExpectationHandler(expectation) - const testCases = partitionedTestCases[testCasesKey] - testCases.forEach(testCase => { - it(`[${testCasesKey}] ${expectation}: ${toCookie(testCase)}`, () => { - onTestCase(testCase, protocol) + + describe('__Host prefix', () => { + it('should fail silently when no Secure attribute, Domain, or Path', async () => { + await cookieJar.setCookie( + "__Host-SID=12345", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) + }) + + it('should fail silently when no Domain or Path', async () => { + await cookieJar.setCookie( + "__Host-SID=12345; Secure", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) + }) + + it('should fail silently when no Path', async () => { + await cookieJar.setCookie( + "__Host-SID=12345; Secure; Domain=example.com", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) + }) + + it('should fail silently with Domain', async () => { + await cookieJar.setCookie( + "__Host-SID=12345; Secure; Domain=example.com; Path=/", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([]) + }) + + it('should work with Secure and Path but no Domain over https', async () => { + await cookieJar.setCookie( + "__Host-SID=12345; Secure; Path=/", + secureUrl, + {} + ) + const cookies = await cookieJar.getCookies(secureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '__Host-SID', + value: '12345' + }) + ]) + }) }) }) -} - -function toCookie(testCase: PrefixTestCase) { - return [ - `${testCase.cookieName}=${testCase.cookieValue}`, - testCase.secure ? 'Secure' : undefined, - testCase.useDefaultPath ? undefined : 'Path=/some/path', - testCase.domain ? 'Domain=example.com' : undefined - ].filter(v => v).join('; ') -} - -function lookupPrefixSecurity(prefixTestScenario: PrefixTestScenario): PrefixSecurityValue { - switch (prefixTestScenario) { - case 'when `prefixSecurity=silent`': - return 'silent' - case 'when `prefixSecurity=strict`': - return 'strict' - case 'when `prefixSecurity=unsafe-disabled`': - return 'unsafe-disabled' - } -} - -// @ts-ignore -function lookupExpectationHandler(expectation): (testCase: PrefixTestCase, protocol: Protocol) => Promise { - switch (expectation) { - case "expect setting cookie to fail silently": - return expectSettingCookieToFailSilently - case "expect setting cookie throws an error": - return expectSettingCookieThrowsAnError - case "expect setting cookie works": - return expectSettingCookieWorks - } -} - -async function expectSettingCookieToFailSilently(testCase: PrefixTestCase, protocol: Protocol) { - const cookie = await createCookieFrom(testCase, protocol) - expect(cookie).toBeUndefined() -} - -async function expectSettingCookieThrowsAnError(testCase: PrefixTestCase, protocol: Protocol) { - const errorMessage = testCase.cookieName.startsWith('__Secure') - ? `Cookie has __Secure prefix but Secure attribute is not set` - : `Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'` - await expect(createCookieFrom(testCase, protocol)) - .rejects - .toThrowError(errorMessage) -} - -async function expectSettingCookieWorks(testCase: PrefixTestCase, protocol: Protocol) { - const cookie = await createCookieFrom(testCase, protocol) - expect(cookie).toEqual(objectContaining({ - key: testCase.cookieName, - value: testCase.cookieValue - })) -} - -function createCookieJar(options: { prefixSecurity: PrefixSecurityValue }): () => void { - return () => { - const { prefixSecurity } = options - cookieJar = new CookieJar(null, { prefixSecurity }) - expect(cookieJar.prefixSecurity).toBe(prefixSecurity) - } -} - -async function createCookieFrom(testCase: PrefixTestCase, protocol: Protocol): Promise { - const cookie = toCookie(testCase) - const domainWithProtocol = `${protocol}://www.example.com/${testCase.useDefaultPath ? '' : 'some/path'}` - try { - await cookieJar.setCookie(cookie, domainWithProtocol) - } catch (e) { - throw e - } - const cookies = await cookieJar.getCookies(domainWithProtocol) - return cookies[0] -} - -function partitionCases(testCases: PrefixTestCase[], protocol: Protocol): PartitionedPrefixTestCases { - const valid = testCases.filter(testCase => { - if (testCase.cookieName.startsWith('__Host')) { - return testCase.secure && protocol === 'https' && testCase.useDefaultPath - } - return testCase.secure && protocol === 'https' + + describe('strict', () => { + beforeEach(() => { + cookieJar = new CookieJar(null, { + prefixSecurity: 'strict' + }) + expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.STRICT) + }) + + describe('__Secure prefix', () => { + it('should work for a valid cookie', async () => { + await cookieJar.setCookie( + "__Secure-SID=12345; Secure; Domain=example.com", + secureUrl, + {} + ) + const cookies = await cookieJar.getCookies(secureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '__Secure-SID', + value: '12345' + }) + ]) + }) + + it('should error for an invalid cookie', async () => { + await expect(cookieJar.setCookie( + "__Secure-SID=12345; Domain=example.com", + insecureUrl, + {} + )).rejects.toThrowError('Cookie has __Secure prefix but Secure attribute is not set') + }) + }) + + describe('__Host prefix', () => { + it('should work for a valid cookie', async () => { + await cookieJar.setCookie( + "___Host-SID=12345; Secure; Path=/", + secureUrl, + {} + ) + const cookies = await cookieJar.getCookies(secureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '___Host-SID', + value: '12345' + }) + ]) + }) + + it('should error for an invalid cookie', async () => { + await expect(cookieJar.setCookie( + "__Host-SID=12345; Secure; Domain=example.com", + secureUrl, + {} + )).rejects.toThrowError(`Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'`) + }) + }) }) - const invalid = testCases.filter(testCase => { - if (testCase.cookieName.startsWith('__Host')) { - return testCase.secure && protocol !== 'https' && !testCase.domain - } - return testCase.secure && protocol !== 'https' + + describe('disabled', () => { + beforeEach(() => { + cookieJar = new CookieJar(null, { + prefixSecurity: 'unsafe-disabled' + }) + expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.DISABLED) + }) + + describe('__Secure prefix', () => { + it('does not fail', async () => { + await cookieJar.setCookie( + "__Secure-SID=12345; Domain=example.com", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '__Secure-SID', + value: '12345' + }) + ]) + }) + }) + + describe('__Host prefix', () => { + it('does not fail', async () => { + await cookieJar.setCookie( + "__Host-SID=12345; Domain=example.com", + insecureUrl, + {} + ) + const cookies = await cookieJar.getCookies(insecureUrl) + expect(cookies).toEqual([ + objectContaining({ + key: '__Host-SID', + value: '12345' + }) + ]) + }) + }) }) - const prefixPrevented = testCases.filter(testCase => !valid.includes(testCase) && !invalid.includes(testCase)) - expect(valid.length + invalid.length + prefixPrevented.length).toBe(testCases.length) - return { valid, invalid, prefixPrevented } -} - -type PartitionedPrefixTestCases = { - valid: PrefixTestCase[]; - invalid: PrefixTestCase[]; - prefixPrevented: PrefixTestCase[]; -} - -type Protocol = 'http' | 'https'; - -type PrefixTestCase = { - secure: boolean; - useDefaultPath: boolean; - domain: boolean; - cookieName: string; - cookieValue: string; - protocol: Protocol; - expectation: 'prefixPrevented' | 'valid' | 'invalid' -} - -type PrefixSecurityValue = 'strict' | 'silent' | 'unsafe-disabled' - -type PrefixTestScenario = 'when `prefixSecurity=silent`' | - 'when `prefixSecurity=strict`' | - 'when `prefixSecurity=unsafe-disabled`'; - -type PrefixTestExpectation = 'expect setting cookie to fail silently' | - 'expect setting cookie throws an error' | - 'expect setting cookie works'; - -type PrefixTestCaseExpectations = { - [key in PrefixTestScenario]?: PrefixTestExpectation; -}; - -type SecurePrefixTestCase = Pick - -type HostPrefixTestCase = Omit +}) From 9b6ce2b766f0ebcdda9c87384685ff69a902a597 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Wed, 9 Nov 2022 09:10:10 -0400 Subject: [PATCH 10/33] validate changes against jsdom --- lib/__tests__/cookieJar.spec.ts | 20 ++++++++++++++++++++ lib/cookie.ts | 32 ++++++++++++++++---------------- package.json | 3 ++- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index ed8078e7..7173ab08 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -226,6 +226,26 @@ describe('CookieJar', () => { )).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") }) + it('should not fail when using an httpOnly cookie when using a non-HTTP API', async () => { + assertions(1) + await cookieJar.setCookie( + "OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly", + "https://127.0.0.1/TestPath/somewhere", + ) + const cookies = await cookieJar.getCookies("https://127.0.0.1/TestPath/somewhere") + expect(cookies).not.toHaveLength(0) + }) + + it('should not fail when using an httpOnly cookie when using a non-HTTP API (setCookieSync)', () => { + assertions(1) + cookieJar.setCookieSync( + "OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly", + "https://127.0.0.1/TestPath/somewhere", + ) + const cookies = cookieJar.getCookiesSync("https://127.0.0.1/TestPath/somewhere") + expect(cookies).not.toHaveLength(0) + }) + it.each([ { testCase: 'basic', IPv6: '[::1]' }, { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, diff --git a/lib/cookie.ts b/lib/cookie.ts index b5e6be01..8ad0cea3 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -1264,7 +1264,7 @@ export class CookieJar { setCookie(cookie: string | Cookie, url: string): Promise setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions): Promise setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions | Callback, callback?: Callback): unknown; - setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions | Callback = defaultSetCookieOptions, callback?: Callback): unknown { + setCookie(cookie: string | Cookie, url: string, options?: SetCookieOptions | Callback, callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -1287,10 +1287,10 @@ export class CookieJar { } const host = canonicalDomain(context.hostname); - const loose = options.loose || this.enableLooseMode; + const loose = options?.loose || this.enableLooseMode; let sameSiteContext = null; - if (options.sameSiteContext) { + if (options?.sameSiteContext) { sameSiteContext = checkSameSiteContext(options.sameSiteContext); if (!sameSiteContext) { return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); @@ -1302,7 +1302,7 @@ export class CookieJar { const parsedCookie = Cookie.parse(cookie.toString(), {loose: loose}); if (!parsedCookie) { err = new Error("Cookie failed to parse"); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } cookie = parsedCookie } else if (!(cookie instanceof Cookie)) { @@ -1311,11 +1311,11 @@ export class CookieJar { err = new Error( "First argument to setCookie must be a Cookie object or string" ); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } // S5.3 step 2 - const now = options.now || new Date(); // will assign later to save effort in the face of errors + const now = options?.now || new Date(); // will assign later to save effort in the face of errors // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie() @@ -1327,15 +1327,15 @@ export class CookieJar { const cdomain = cookie.cdomain() const suffix = typeof cdomain === 'string' ? pubsuffix.getPublicSuffix(cdomain, { allowSpecialUseDomain: this.allowSpecialUseDomain, - ignoreError: options.ignoreError + ignoreError: options?.ignoreError }) : null; if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" err = new Error("Cookie has domain set to a public suffix"); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } } catch (err) { - if (options.ignoreError) { + if (options?.ignoreError) { return cb(null) } else { if (err instanceof Error) { @@ -1353,7 +1353,7 @@ export class CookieJar { err = new Error( `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}` ); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } if (cookie.hostOnly == null) { @@ -1377,9 +1377,9 @@ export class CookieJar { // S5.3 step 9: NOOP; httpOnly attribute // S5.3 step 10 - if (options.http === false && cookie.httpOnly) { + if (options?.http === false && cookie.httpOnly) { err = new Error("Cookie is HttpOnly and this isn't an HTTP API"); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } // 6252bis-02 S5.4 Step 13 & 14: @@ -1392,7 +1392,7 @@ export class CookieJar { err = new Error( "Cookie is SameSite but this is a cross-origin request" ); - return cb(options.ignoreError ? null : err); + return cb(options?.ignoreError ? null : err); } } @@ -1417,7 +1417,7 @@ export class CookieJar { } if (errorFound) { return cb( - options.ignoreError || ignoreErrorForPrefixSecurity + options?.ignoreError || ignoreErrorForPrefixSecurity ? null : new Error(errorMsg) ); @@ -1460,7 +1460,7 @@ export class CookieJar { if (oldCookie) { // S5.3 step 11 - "If the cookie store contains a cookie with the same name, // domain, and path as the newly created cookie:" - if ('http' in options && options.http === false && oldCookie.httpOnly) { + if (options && 'http' in options && options.http === false && oldCookie.httpOnly) { // step 11.2 err = new Error("old Cookie is HttpOnly and this isn't an HTTP API"); cb(options.ignoreError ? null : err); @@ -1487,7 +1487,7 @@ export class CookieJar { return promiseCallback.promise } setCookieSync(cookie: string | Cookie, url: string, options?: SetCookieOptions): Cookie | undefined { - const setCookieFn = this.setCookie.bind(this, cookie, url, options ?? defaultSetCookieOptions) + const setCookieFn = this.setCookie.bind(this, cookie, url, options as SetCookieOptions) return this.callSync(setCookieFn) } diff --git a/package.json b/package.json index 393f7222..83994116 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,8 @@ "bugs": { "url": "https://github.com/salesforce/tough-cookie/issues" }, - "main": "./lib/cookie", + "main": "./dist/lib/cookie", + "types": "./dist/lib/cookie", "files": [ "lib" ], From 70d0cf4dd9cd9f2651db555fc6b7fe5e1685bc6a Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Fri, 17 Mar 2023 13:28:11 -0300 Subject: [PATCH 11/33] added missing tests from changes to the main branch These wouldn't be included by simply catching up with `master` branch since the jest and typescript changes happen in parallel to those changes and have to be manually tracked. --- README.md | 2 +- lib/__tests__/cookieJar.spec.ts | 122 +++++++++++++++++++++++++++++- lib/__tests__/domainMatch.spec.ts | 3 + lib/__tests__/parse.spec.ts | 91 +++++++++++++++++++++- 4 files changed, 213 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8568905c..fb11f3ce 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ Retrieve a cookie with the given domain, path, and key (name). The RFC maintains Callback takes an error and the resulting `Cookie` object. If no cookie is found then `null` MUST be passed instead (that is, not an error). -#### `store.findCookies(domain, path, callback(err, cookies))` +#### `store.findCookies(domain, path, allowSpecialUseDomain, callback(err, cookies))` Locates cookies matching the given domain and path. This is most often called in the context of [`cookiejar.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index ebd87217..1ac404d2 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -966,13 +966,31 @@ it('should fix issue #197', async () => { )).rejects.toThrowError('Cookie failed to parse') }) +// special use domains under a sub-domain describe.each([ "local", "example", "invalid", "localhost", "test" -])('when special use domain is %s', (specialUseDomain) => { +])('when special use domain is dev.%s', (specialUseDomain) => { + it('should allow special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + const cookieJar = new CookieJar() + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}` + ) + expect(cookie).toEqual(objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `dev.${specialUseDomain}` + })) + const cookies = await cookieJar.getCookies(`http://dev.${specialUseDomain}`, { + http: true + }) + expect(cookies).toEqual([cookie]) + }) + it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { const cookieJar = new CookieJar(new MemoryCookieStore(), { rejectPublicSuffixes: true, @@ -1011,6 +1029,108 @@ describe.each([ }) }) +// special use domains under the top-level domain +describe.each([ + "local", + "example", + "invalid", + "localhost", + "test" +])('when special use domain is %s', (specialUseDomain) => { + // the restriction on special use domains at the top-level is loosened for + // the following domains due to legacy behavior + const isAllowed = ['localhost', 'invalid'].includes(specialUseDomain) + + if (isAllowed) { + it('should allow special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + const cookieJar = new CookieJar() + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}` + ) + expect(cookie).toEqual(objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `${specialUseDomain}` + })) + const cookies = await cookieJar.getCookies(`http://${specialUseDomain}`, { + http: true + }) + expect(cookies).toEqual([cookie]) + }) + } else { + it('should reject special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + assertions(1) + const cookieJar = new CookieJar() + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}` + ) + } catch(e) { + // @ts-ignore + expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + } + }) + } + + if (isAllowed) { + it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true + }) + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}` + ) + expect(cookie).toEqual(objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `${specialUseDomain}` + })) + const cookies = await cookieJar.getCookies(`http://${specialUseDomain}`, { + http: true + }) + expect(cookies).toEqual([cookie]) + }) + } else { + it('should reject special domain cookies if allowSpecialUseDomain: true', async () => { + assertions(1) + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true + }) + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}` + ) + } catch(e) { + // @ts-ignore + expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + } + }) + } + + it('should reject special domain cookies if allowSpecialUseDomain: true', async () => { + assertions(1) + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: false + }) + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}` + ) + } catch(e) { + // @ts-ignore + expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + } + }) +}) + function createCookie( cookieString: string, options: { diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index 5fdd1ffe..b6efff33 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -27,6 +27,9 @@ describe('domainMatch', () => { ["www.aaaa.com", "aaa.com", false], ["www.aaa.com", "aaa.com", true], ["www.aexample.com", "example.com", false], // has to match on "." boundary + ["computer.com", "com", true], // suffix string found at start of domain + ["becoming.com", "com", true], // suffix string found in middle of domain + ["sitcom.com", "com", true], // suffix string found just before the '.' boundary // S5.1.3 "The string is a host name (i.e., not an IP address)" ["192.168.0.1", "168.0.1", false], // because str is an IP (v4) diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts index 83d05456..0529ed00 100644 --- a/lib/__tests__/parse.spec.ts +++ b/lib/__tests__/parse.spec.ts @@ -3,6 +3,15 @@ import {performance} from 'node:perf_hooks' describe('Cookie.parse', () => { it.each([ + // simple + { + input: 'a=bcd', + output: { + key: 'a', + value: 'bcd' + } + }, + // with expiry { input: 'a=bcd; Expires=Tue, 18 Oct 2011 07:05:03 GMT', output: { @@ -11,6 +20,7 @@ describe('Cookie.parse', () => { expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')) } }, + // with expiry and path { input: 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc', output: { @@ -22,6 +32,7 @@ describe('Cookie.parse', () => { secure: false } }, + // with most things { input: 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc; Domain=example.com; Secure; HTTPOnly; Max-Age=1234; Foo=Bar; Baz', output: { @@ -36,6 +47,7 @@ describe('Cookie.parse', () => { extensions: ['Foo=Bar', 'Baz'] } }, + // invalid expires { input: 'a=b; Expires=xyzzy', output: { @@ -44,6 +56,7 @@ describe('Cookie.parse', () => { expires: "Infinity" } }, + // zero max-age { input: 'a=b; Max-Age=0', output: { @@ -52,6 +65,7 @@ describe('Cookie.parse', () => { maxAge: 0 } }, + // negative max-age { input: 'a=b; Max-Age=-1', output: { @@ -60,6 +74,7 @@ describe('Cookie.parse', () => { maxAge: -1 } }, + // empty domain { input: 'a=b; domain=', output: { @@ -68,6 +83,7 @@ describe('Cookie.parse', () => { domain: null } }, + // dot domain { input: 'a=b; domain=.', output: { @@ -76,6 +92,7 @@ describe('Cookie.parse', () => { domain: null } }, + // uppercase domain { input: 'a=b; domain=EXAMPLE.COM', output: { @@ -84,14 +101,17 @@ describe('Cookie.parse', () => { domain: "example.com" } }, + // trailing dot in domain { input: 'a=b; Domain=example.com.', output: { key: 'a', value: 'b', domain: "example.com." - } + }, + assertValidateReturns: false }, + // empty path { input: 'a=b; path=', output: { @@ -100,6 +120,7 @@ describe('Cookie.parse', () => { path: null } }, + // no-slash path { input: 'a=b; path=xyzzy', output: { @@ -108,6 +129,7 @@ describe('Cookie.parse', () => { path: null } }, + // trailing semi-colons after path #1 { input: 'a=b; path=/;', output: { @@ -116,6 +138,7 @@ describe('Cookie.parse', () => { path: '/' } }, + // trailing semi-colons after path #2 { input: 'c=d;;;;', output: { @@ -124,6 +147,7 @@ describe('Cookie.parse', () => { path: null } }, + // secure-with-value { input: 'a=b; Secure=xyzzy', output: { @@ -132,6 +156,7 @@ describe('Cookie.parse', () => { secure: true } }, + // httponly-with-value { input: 'a=b; HttpOnly=xyzzy', output: { @@ -140,10 +165,12 @@ describe('Cookie.parse', () => { httpOnly: true } }, + // garbage { input: '\x08', output: undefined }, + // public suffix domain { input: 'a=b; domain=kyoto.jp', output: { @@ -151,7 +178,9 @@ describe('Cookie.parse', () => { value: 'b', domain: 'kyoto.jp' }, + assertValidateReturns: false }, + // public suffix foonet.net - top level { input: 'a=b; domain=foonet.net', output: { @@ -159,7 +188,9 @@ describe('Cookie.parse', () => { value: 'b', domain: 'foonet.net' }, + assertValidateReturns: true }, + // public suffix foonet.net - www { input: 'a=b; domain=www.foonet.net', output: { @@ -167,7 +198,9 @@ describe('Cookie.parse', () => { value: 'b', domain: 'www.foonet.net' }, + assertValidateReturns: true }, + // public suffix foonet.net - with a dot { input: 'a=b; domain=.foonet.net', output: { @@ -175,7 +208,9 @@ describe('Cookie.parse', () => { value: 'b', domain: 'foonet.net' }, + assertValidateReturns: true }, + // Ironically, Google 'GAPS' cookie has very little whitespace { input: 'GAPS=1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-;Path=/;Expires=Thu, 17-Apr-2014 02:12:29 GMT;Secure;HttpOnly', output: { @@ -187,6 +222,7 @@ describe('Cookie.parse', () => { httpOnly: true }, }, + // lots of equal signs { input: 'queryPref=b=c&d=e; Path=/f=g; Expires=Thu, 17 Apr 2014 02:12:29 GMT; HttpOnly', output: { @@ -197,6 +233,7 @@ describe('Cookie.parse', () => { httpOnly: true }, }, + // spaces in value { input: 'a=one two three', output: { @@ -207,6 +244,7 @@ describe('Cookie.parse', () => { extensions: null }, }, + // quoted spaces in value { input: 'a="one two three"', output: { @@ -217,6 +255,7 @@ describe('Cookie.parse', () => { extensions: null }, }, + // non-ASCII in value { input: 'farbe=weiß', output: { @@ -227,6 +266,7 @@ describe('Cookie.parse', () => { extensions: null }, }, + // empty key { input: '=abc', output: { @@ -238,6 +278,7 @@ describe('Cookie.parse', () => { }, parseOptions: { loose: true } }, + // non-existent key { input: 'abc', output: { @@ -249,6 +290,7 @@ describe('Cookie.parse', () => { }, parseOptions: { loose: true } }, + // weird format { input: '=foo=bar', output: { @@ -260,6 +302,7 @@ describe('Cookie.parse', () => { }, parseOptions: { loose: true } }, + // way too many semicolons followed by non-semicolon { input: `foo=bar${";".repeat(65535)} domain=example.com`, output: { @@ -270,14 +313,17 @@ describe('Cookie.parse', () => { extensions: null } }, + // way too many spaces - small one doesn't parse { input: `x x`, output: undefined }, + // way too many spaces - large one doesn't parse { input: `x${" ".repeat(65535)}x`, output: undefined }, + // same-site - lax { input: `abc=xyzzy; SameSite=Lax`, output: { @@ -287,6 +333,7 @@ describe('Cookie.parse', () => { extensions: null } }, + // same-site - strict { input: `abc=xyzzy; SameSite=StRiCt`, output: { @@ -296,31 +343,60 @@ describe('Cookie.parse', () => { extensions: null } }, + // same-site - none + { + input: `abc=xyzzy; SameSite=NoNe`, + output: { + key: 'abc', + value: 'xyzzy', + sameSite: 'none', + extensions: null + } + }, + // same-site - bad { input: `abc=xyzzy; SameSite=example.com`, output: { key: 'abc', value: 'xyzzy', + sameSite: undefined, + extensions: null + } + }, + // same-site - absent + { + input: `abc=xyzzy;`, + output: { + key: 'abc', + value: 'xyzzy', + sameSite: undefined, extensions: null } }, + // empty string { input: ``, output: null }, + // missing string { input: undefined, output: null }, + // some string object { input: new String(''), output: null }, + // some empty string object { input: new String(), output: null } - ])('Cookie.parse("$input")', ({input, output, parseOptions = {}}) => { + ])('Cookie.parse("$input")', (testCase) => { + const {input, output } = testCase + const parseOptions = testCase.parseOptions || {} + const value = input === undefined ? undefined : input.valueOf() // @ts-ignore const cookie = Cookie.parse(value, parseOptions) @@ -329,8 +405,17 @@ describe('Cookie.parse', () => { } else { expect(cookie).toBe(output) } + + if (cookie && typeof testCase.assertValidateReturns === 'boolean') { + expect(cookie.validate()).toBe(testCase.assertValidateReturns) + } }) + // perf cases for: + // - way too many spaces (loose mode) + // - way too many spaces (strict mode) + // - way too many spaces with value (loose mode) + // - way too many spaces with value (strict mode) it.each([ { shortVersion: 'x x', @@ -364,7 +449,7 @@ describe('Cookie.parse', () => { }) }) - +// way too many spaces - takes about the same time for each it('should parse a long cookie string with spaces in roughly the same amount of time as one with short spaces', () => { const longCookie = `x${" ".repeat(65535)}x` const shortCookie = `x x` From 2a21fe627b57dff026de54d3f6193dd2dae756d9 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Fri, 17 Mar 2023 18:10:15 -0300 Subject: [PATCH 12/33] comments added from code review --- lib/__tests__/canonicalDomain.spec.ts | 1 + lib/__tests__/cookie.spec.ts | 3 +- lib/__tests__/cookieJar.spec.ts | 55 +++++++++++++++++++-------- lib/__tests__/cookiePrefixes.spec.ts | 1 + lib/__tests__/defaultPath.spec.ts | 1 + lib/__tests__/domainMatch.spec.ts | 1 + lib/__tests__/pathMatch.spec.ts | 1 + lib/__tests__/permuteDomain.spec.ts | 6 ++- lib/__tests__/permutePath.spec.ts | 6 ++- lib/cookie.ts | 4 ++ lib/version.ts | 1 + test/api_test.js | 4 +- test/cookie_jar_test.js | 2 + test/domain_and_path_test.js | 7 +++- 14 files changed, 72 insertions(+), 21 deletions(-) diff --git a/lib/__tests__/canonicalDomain.spec.ts b/lib/__tests__/canonicalDomain.spec.ts index ec94221b..18c9a389 100644 --- a/lib/__tests__/canonicalDomain.spec.ts +++ b/lib/__tests__/canonicalDomain.spec.ts @@ -1,5 +1,6 @@ import {canonicalDomain} from "../cookie"; +// port of tests/domain_and_path_test.js (canonicalDomain tests for domain normalization) describe('canonicalDomain', () => { it.each([ { diff --git a/lib/__tests__/cookie.spec.ts b/lib/__tests__/cookie.spec.ts index 69d1c766..8daf7dc0 100644 --- a/lib/__tests__/cookie.spec.ts +++ b/lib/__tests__/cookie.spec.ts @@ -33,6 +33,7 @@ import {Cookie} from '../cookie' jest.useFakeTimers() +// ported from test/api_test.js (cookie tests) describe('Cookie', () => { let cookie: Cookie @@ -46,7 +47,7 @@ describe('Cookie', () => { }) it("should check for key property", () => { - expect(cookie.key).toEqual('test') + expect(cookie.key).toBe('test') }) it('should check for value property', () => { diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 1ac404d2..66647627 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -34,6 +34,9 @@ import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJa const { objectContaining, assertions } = expect jest.useFakeTimers() +// ported from: +// - test/api_test.js (cookie jar tests) +// - test/cookie_jar_test.js describe('CookieJar', () => { let cookieJar: CookieJar @@ -124,7 +127,7 @@ describe('CookieJar', () => { }) }) - it('should set a timestamp when storing a cookie', async () => { + it('should set a timestamp when storing or retrieving a cookie', async () => { // @ts-ignore cookie = Cookie.parse("a=b; Domain=example.com; Path=/") const t0 = new Date() @@ -146,6 +149,18 @@ describe('CookieJar', () => { })) expect(cookie.TTL()).toBe(Infinity) expect(cookie.isPersistent()).toBe(false) + + // updates the last access when retrieving a cookie + jest.advanceTimersByTime(10000) + const t2 = new Date() + const cookies = await cookieJar.getCookies("http://example.com/index.html") + expect(cookies).toEqual([ + objectContaining({ + hostOnly: false, + creation: t1, + lastAccessed: t2 + }) + ]) }) it('should be able to set a no-path cookie', async () => { @@ -171,10 +186,12 @@ describe('CookieJar', () => { }) it('should be able to set a session cookie', async () => { - cookie = createCookie("a=b") + cookie = createCookie("SID=31d4d96e407aad42") expect(cookie.path).toBeNull() cookie = await cookieJar.setCookie(cookie, "http://www.example.com/dir/index.html") expect(cookie).toEqual(objectContaining({ + key: 'SID', + value: '31d4d96e407aad42', domain: 'www.example.com', path: '/dir', hostOnly: true @@ -226,6 +243,7 @@ describe('CookieJar', () => { )).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") }) + // TODO: where did this come from? it('should not fail when using an httpOnly cookie when using a non-HTTP API', async () => { assertions(1) await cookieJar.setCookie( @@ -236,6 +254,7 @@ describe('CookieJar', () => { expect(cookies).not.toHaveLength(0) }) + // TODO: where did this come from? it('should not fail when using an httpOnly cookie when using a non-HTTP API (setCookieSync)', () => { assertions(1) cookieJar.setCookieSync( @@ -248,17 +267,18 @@ describe('CookieJar', () => { it.each([ { testCase: 'basic', IPv6: '[::1]' }, - { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, + { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, // TODO: is this valid IP6? { testCase: 'classic', IPv6: '[2001:4860:4860::8888]' }, { testCase: 'short', IPv6: '[2600::]' } ])('should store a $testCase IPv6', async (test) => { + const t0 = new Date() cookie = await cookieJar.setCookie( `a=b; Domain=${test.IPv6}; Path=/`, `http://${test.IPv6}/` ) expect(cookie).toEqual(objectContaining({ - creation: new Date(), - lastAccessed: new Date(), + creation: t0, + lastAccessed: t0, })) expect(cookie.TTL()).toBe(Infinity) expect(cookie.isPersistent()).toBe(false) @@ -291,7 +311,7 @@ describe('CookieJar', () => { objectContaining({ key: 'foo', value: 'bar' - }) + }), ]) }) }) @@ -397,6 +417,7 @@ describe('CookieJar', () => { ]) }) + // TODO: https vs. secure flag is confusing af! it('should be able to get the cookies for https://example.com', async () => { const cookies = await cookieJar.getCookies("https://example.com") expect(cookies).toEqual([ @@ -648,11 +669,6 @@ describe('CookieJar', () => { cookieString = await cookieJar.getCookieString("http://example.com") expect(cookieString).toBe("a=1; b=2; e=5") }) - - it('be able to get the cookie string for http://example.com', async () => { - cookieString = await cookieJar.getCookieString("http://example.com") - expect(cookieString).toBe("a=1; b=2; e=5") - }) }) }) @@ -834,16 +850,20 @@ describe('CookieJar', () => { }) }) +// TODO: remove the 'at' helper, it('should allow cookies with the same name under different domains and/or paths', async () => { const cookieJar = new CookieJar() const url = "http://www.example.com/" await Promise.all([ - cookieJar.setCookie("aaaa=xxxx", url, at(0)), + cookieJar.setCookie("aaaa=xxxx; Domain=www.example.com", url, at(0)), cookieJar.setCookie("aaaa=1111; Domain=www.example.com", url, at(1000)), + cookieJar.setCookie("aaaa=yyyy; Domain=example.com", url, at(1500)), cookieJar.setCookie("aaaa=2222; Domain=example.com", url, at(2000)), + cookieJar.setCookie("aaaa=zzzz; Domain=www.example.com; Path=/pathA", url, at(2500)), cookieJar.setCookie("aaaa=3333; Domain=www.example.com; Path=/pathA", url, at(3000)), ]) const cookies = await cookieJar.getCookies("http://www.example.com/pathA") + // may break with sorting; sorting should put 3333 first due to longest path expect(cookies).toEqual([ objectContaining({value: '3333'}), objectContaining({value: '1111'}), @@ -920,6 +940,7 @@ describe('loose mode', () => { }) }) +// TODO: carve out a spec file for random issues it('should fix issue #132', async () => { const cookieJar = new CookieJar() await expect(cookieJar.setCookie( @@ -929,10 +950,11 @@ it('should fix issue #132', async () => { )).rejects.toThrowError("First argument to setCookie must be a Cookie object or string") }) +// TODO: what is this test doing? how does this parse? it('should fix issue #144', async () => { const cookieJar = new CookieJar() const cookieString = `AWSELB=69b2c0038b16e8e27056d1178e0d556c; - Path=/, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; + Path=/foo, jses_WS41=5f8dc2f6-ea37-49de-8dfa-b58336c2d9ce; path=/; Secure; HttpOnly, AuthToken=EFKFFFCH@K@GHIHEJCJMMGJM>CDHDEK>CFGK?MHJ >>JI@B??@CAEHBJH@H@A@GCFDLIMLJEEJEIFGALA?BIM?@G@DEDI@JE?I?HKJBIDDHJMEFEFM >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; @@ -942,12 +964,15 @@ it('should fix issue #144', async () => { const cookies = await cookieJar.getCookies("https://google.com") expect(cookies).toEqual([ objectContaining({ + key: 'AWSELB', + value: '69b2c0038b16e8e27056d1178e0d556c', + path: '/', secure: true }) ]) }) -it('should fix issue #145', async () => { +it('should fix issue #145 - missing 2nd url parameter', async () => { assertions(1) const cookieJar = new CookieJar() try { @@ -958,7 +983,7 @@ it('should fix issue #145', async () => { } }) -it('should fix issue #197', async () => { +it('should fix issue #197 - CookieJar().setCookie throws an error when empty cookie is passed', async () => { const cookieJar = new CookieJar() await expect(cookieJar.setCookie( "", diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index d79cfe1d..4b30cfaa 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -6,6 +6,7 @@ let cookieJar: CookieJar let insecureUrl = 'http://www.example.com' let secureUrl = 'https://www.example.com' +// TODO: this should have more of a matrix coverage describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('silent', () => { beforeEach(() => { diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts index 612a43b3..c0ffaa63 100644 --- a/lib/__tests__/defaultPath.spec.ts +++ b/lib/__tests__/defaultPath.spec.ts @@ -1,5 +1,6 @@ import {defaultPath} from "../cookie"; +// port of tests/domain_and_path_test.js (default path tests) describe('defaultPath', () => { it.each([ { diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index b6efff33..2b4b7cb7 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -1,5 +1,6 @@ import {domainMatch} from "../cookie"; +// port of tests/domain_and_path_test.js (domain match tests) describe('domainMatch', () => { it.each([ // string, domain, expect diff --git a/lib/__tests__/pathMatch.spec.ts b/lib/__tests__/pathMatch.spec.ts index 141849c1..64dfafba 100644 --- a/lib/__tests__/pathMatch.spec.ts +++ b/lib/__tests__/pathMatch.spec.ts @@ -1,5 +1,6 @@ import {pathMatch} from "../cookie"; +// port of tests/domain_and_path_test.js (path match tests) describe('pathMatch', () => { it.each([ // request, cookie, match diff --git a/lib/__tests__/permuteDomain.spec.ts b/lib/__tests__/permuteDomain.spec.ts index 75d52814..c3ebba90 100644 --- a/lib/__tests__/permuteDomain.spec.ts +++ b/lib/__tests__/permuteDomain.spec.ts @@ -1,5 +1,6 @@ import {permuteDomain} from "../cookie"; +// port of tests/domain_and_path_test.js (permute domain tests) describe('permuteDomain', () => { it.each([ { @@ -31,7 +32,8 @@ describe('permuteDomain', () => { ] }, ])('permuteDomain("%s", %s") => %o', ({domain, permutations}) => { - const allowSpecialUseDomain = undefined - expect(permuteDomain(domain, allowSpecialUseDomain)).toEqual(permutations) + expect(permuteDomain(domain)).toEqual(permutations) }) }) + +// TODO: this set of tests only tests when allowSpecialUseDomain is unspecified (needs more coverage) diff --git a/lib/__tests__/permutePath.spec.ts b/lib/__tests__/permutePath.spec.ts index 90f4c540..ce8bd456 100644 --- a/lib/__tests__/permutePath.spec.ts +++ b/lib/__tests__/permutePath.spec.ts @@ -1,5 +1,6 @@ -import {permutePath} from "../cookie"; +import {pathMatch, permutePath} from "../cookie"; +// port of tests/domain_and_path_test.js (permute path tests) describe('permutePath', () => { it.each([ { @@ -20,5 +21,8 @@ describe('permutePath', () => { }, ])('permuteDomain("%s", %s") => %o', ({path, permutations}) => { expect(permutePath(path)).toEqual(permutations) + permutations.forEach(permutation => { + expect(pathMatch(path, permutation)).toBe(true) + }) }) }) diff --git a/lib/cookie.ts b/lib/cookie.ts index 269ffcb5..c17d0a7d 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -1508,10 +1508,14 @@ export class CookieJar { // RFC6365 S5.4 getCookies(url: string, callback: Callback): void; + // TODO: provide types for the options getCookies(url: string, options: any, callback: Callback): void getCookies(url: string): Promise + // TODO: provide types for the options getCookies(url: string, options: any): Promise + // TODO: provide types for the options getCookies(url: string, options: any, callback?: (error: Error, result: Cookie[]) => void): unknown; + // TODO: provide types for the options getCookies(url: string, options: any = {}, _callback?: (error: Error, result: Cookie[]) => void): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback diff --git a/lib/version.ts b/lib/version.ts index cdd07b23..98c9a1d0 100644 --- a/lib/version.ts +++ b/lib/version.ts @@ -1 +1,2 @@ +// TODO: this should be sourced from package.json (maybe?) export default '4.1.2' diff --git a/test/api_test.js b/test/api_test.js index b82d4f48..865fb4b2 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -55,6 +55,7 @@ vows assert.equal(tough.version, require("../package.json").version); } }) + // see lib/__tests__/cookie.spec.ts .addBatch({ Constructor: { topic: function() { @@ -429,6 +430,7 @@ vows } } }) + // TODO: confirm these are covered .addBatch({ "Synchronous API on async CookieJar": { topic: function() { @@ -555,7 +557,7 @@ vows topic: function() { const some = [ "=a;domain=example.com", // index 0, falsey - "=b;domain=example.com", // index 1, truthy + "=b;domain=example.com", // index 1, falsey "c=d;domain=example.com" // index 2, truthy ]; return some.map(Cookie.parse); diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index d492e820..913d4812 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -42,6 +42,7 @@ function at(offset) { return { now: new Date(atNow + offset) }; } +// see lib/__tests__/cookie_jar_test.js (set cookie tests) vows .describe("CookieJar") .addBatch({ @@ -676,6 +677,7 @@ vows } } }, + // TODO: what is this? git blame and find out "Remove all cookies (the underlying store does not support removeAllCookies)": { topic: function() { const jar = new CookieJar(); diff --git a/test/domain_and_path_test.js b/test/domain_and_path_test.js index f30a7a04..78e4e1c5 100644 --- a/test/domain_and_path_test.js +++ b/test/domain_and_path_test.js @@ -67,6 +67,7 @@ function transformVows(fn, table) { vows .describe("Domain and Path") + // see lib/__tests__/canonicalDomain.spec.ts .addBatch({ "domain normalization": transformVows(tough.canonicalDomain, [ ["example.com", "example.com", "already canonical"], @@ -78,6 +79,7 @@ vows ["δοκιμή.δοκιμή", "xn--jxalpdlp.xn--jxalpdlp", "IDN: test.test in greek"] ]) }) + // see lib/__tests__/domainMatch.spec.ts .addBatch({ "Domain Match": matchVows(tough.domainMatch, [ // str, dom, expect @@ -148,7 +150,7 @@ vows ["NOTATLD", "notaTLD", true] // "are identical" rule (after canonicalization) ]) }) - + // see lib/__tests__/defaultPath.spec.ts .addBatch({ "default-path": transformVows(tough.defaultPath, [ [null, "/"], @@ -158,6 +160,7 @@ vows ["noslash", "/"] ]) }) + // see lib/__tests__/pathMatch.spec.ts .addBatch({ "Path-Match": matchVows(tough.pathMatch, [ // request, cookie, match @@ -170,6 +173,7 @@ vows ["/directory", "/dir", false] ]) }) + // see lib/__tests__/permuteDomain.spec.ts .addBatch({ permuteDomain: { "base case": { @@ -209,6 +213,7 @@ vows } } }, + // see lib/__tests__/permutePath.spec.ts permutePath: { "base case": { topic: tough.permutePath.bind(null, "/"), From 770b9904ea7e29eda5c7b8374c2379065ada43e1 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Sun, 26 Mar 2023 23:35:52 -0300 Subject: [PATCH 13/33] addressed todos noted in code review --- lib/__tests__/cookieJar.spec.ts | 125 ++++++++++++++++++--------- lib/__tests__/cookiePrefixes.spec.ts | 1 - lib/__tests__/permuteDomain.spec.ts | 2 - lib/cookie.ts | 100 +++++++++++---------- test/api_test.js | 1 - test/cookie_jar_test.js | 1 - 6 files changed, 137 insertions(+), 93 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 66647627..5f23f4a5 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar} from '../cookie' +import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store} from '../cookie' const { objectContaining, assertions } = expect jest.useFakeTimers() @@ -243,7 +243,6 @@ describe('CookieJar', () => { )).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") }) - // TODO: where did this come from? it('should not fail when using an httpOnly cookie when using a non-HTTP API', async () => { assertions(1) await cookieJar.setCookie( @@ -254,7 +253,6 @@ describe('CookieJar', () => { expect(cookies).not.toHaveLength(0) }) - // TODO: where did this come from? it('should not fail when using an httpOnly cookie when using a non-HTTP API (setCookieSync)', () => { assertions(1) cookieJar.setCookieSync( @@ -267,7 +265,7 @@ describe('CookieJar', () => { it.each([ { testCase: 'basic', IPv6: '[::1]' }, - { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, // TODO: is this valid IP6? + { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, { testCase: 'classic', IPv6: '[2001:4860:4860::8888]' }, { testCase: 'short', IPv6: '[2600::]' } ])('should store a $testCase IPv6', async (test) => { @@ -384,25 +382,34 @@ describe('CookieJar', () => { beforeEach(async () => { const url = "http://example.com/index.html" - const cookies = await Promise.all([ - cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), - cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), - cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), - // path - cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), - // host only - cookieJar.setCookie("e=5", url, at(4000)), - // other domain - cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), - // expired - cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), - // expired via Max-Age - cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), - ]) + await cookieJar.setCookie("a=1; Domain=example.com; Path=/", url) + jest.advanceTimersByTime(1000) - jest.advanceTimersByTime(2000) // so that 'h=8' expires + await cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url) + jest.advanceTimersByTime(1000) - expect(cookies).toHaveLength(8) + await cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url) + jest.advanceTimersByTime(1000) + + // path + await cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url) + jest.advanceTimersByTime(1000) + + // host only + await cookieJar.setCookie("e=5", url) + jest.advanceTimersByTime(1000) + + // other domain + await cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org") + jest.advanceTimersByTime(1000) + + // expired + await cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url) + jest.advanceTimersByTime(1000) + + // expired via Max-Age + await cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url) + jest.advanceTimersByTime(2000) // so that 'h=8' expires }) it('should be able to get the cookies for http://nodejs.org', async () => { @@ -417,7 +424,6 @@ describe('CookieJar', () => { ]) }) - // TODO: https vs. secure flag is confusing af! it('should be able to get the cookies for https://example.com', async () => { const cookies = await cookieJar.getCookies("https://example.com") expect(cookies).toEqual([ @@ -451,9 +457,7 @@ describe('CookieJar', () => { }) it('should be able to get the cookies for https://example.com with the secure: true option', async () => { - const cookies = await cookieJar.getCookies("https://example.com", { - secure: true - }) + const cookies = await cookieJar.getCookies("https://example.com") expect(cookies).toEqual([ objectContaining({ key: 'a', @@ -623,8 +627,12 @@ describe('CookieJar', () => { apiVariants('resolves to a string', { callbackStyle(done) { cookieJar.getCookieString("http://example.com", (_err, result) => { - cookieString = result - done() + if (typeof result === 'string') { + cookieString = result + done() + } else { + fail("Result should not have been undefined") + } }) }, async asyncStyle() { @@ -850,18 +858,28 @@ describe('CookieJar', () => { }) }) -// TODO: remove the 'at' helper, it('should allow cookies with the same name under different domains and/or paths', async () => { const cookieJar = new CookieJar() const url = "http://www.example.com/" - await Promise.all([ - cookieJar.setCookie("aaaa=xxxx; Domain=www.example.com", url, at(0)), - cookieJar.setCookie("aaaa=1111; Domain=www.example.com", url, at(1000)), - cookieJar.setCookie("aaaa=yyyy; Domain=example.com", url, at(1500)), - cookieJar.setCookie("aaaa=2222; Domain=example.com", url, at(2000)), - cookieJar.setCookie("aaaa=zzzz; Domain=www.example.com; Path=/pathA", url, at(2500)), - cookieJar.setCookie("aaaa=3333; Domain=www.example.com; Path=/pathA", url, at(3000)), - ]) + + await cookieJar.setCookie("aaaa=xxxx; Domain=www.example.com", url) + jest.advanceTimersByTime(1000) + + await cookieJar.setCookie("aaaa=1111; Domain=www.example.com", url) + jest.advanceTimersByTime(1000) + + await cookieJar.setCookie("aaaa=yyyy; Domain=example.com", url) + jest.advanceTimersByTime(1000) + + await cookieJar.setCookie("aaaa=2222; Domain=example.com", url) + jest.advanceTimersByTime(1000) + + await cookieJar.setCookie("aaaa=zzzz; Domain=www.example.com; Path=/pathA", url) + jest.advanceTimersByTime(1000) + + await cookieJar.setCookie("aaaa=3333; Domain=www.example.com; Path=/pathA", url) + jest.advanceTimersByTime(1000) + const cookies = await cookieJar.getCookies("http://www.example.com/pathA") // may break with sorting; sorting should put 3333 first due to longest path expect(cookies).toEqual([ @@ -940,7 +958,6 @@ describe('loose mode', () => { }) }) -// TODO: carve out a spec file for random issues it('should fix issue #132', async () => { const cookieJar = new CookieJar() await expect(cookieJar.setCookie( @@ -1156,6 +1173,34 @@ describe.each([ }) }) +describe("Synchronous API on async CookieJar", () => { + let store: Store + + beforeEach(() => { + store = new Store() + }) + + it('should throw an error when calling `setCookieSync` if store is not synchronous', () => { + const cookieJar = new CookieJar(store) + expect(() => cookieJar.setCookieSync("a=b", "http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + }) + + it('should throw an error when calling `getCookieSync` if store is not synchronous', () => { + const cookieJar = new CookieJar(store) + expect(() => cookieJar.getCookiesSync("http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + }) + + it('should throw an error when calling `getSetCookieStringsSync` if store is not synchronous', () => { + const cookieJar = new CookieJar(store) + expect(() => cookieJar.getSetCookieStringsSync("http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + }) + + it('should throw an error when calling `removeAllCookiesSync` if store is not synchronous', () => { + const cookieJar = new CookieJar(store) + expect(() => cookieJar.removeAllCookiesSync()).toThrow("CookieJar store is not synchronous; use async API instead.") + }) +}) + function createCookie( cookieString: string, options: { @@ -1197,9 +1242,3 @@ interface ApiVariants { asyncStyle: PromiseApiVariant, syncStyle: SyncApiVariant } - -function at (timeFromNow: number) { - return { - now: new Date(Date.now() + timeFromNow) - } -} diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index 4b30cfaa..d79cfe1d 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -6,7 +6,6 @@ let cookieJar: CookieJar let insecureUrl = 'http://www.example.com' let secureUrl = 'https://www.example.com' -// TODO: this should have more of a matrix coverage describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('silent', () => { beforeEach(() => { diff --git a/lib/__tests__/permuteDomain.spec.ts b/lib/__tests__/permuteDomain.spec.ts index c3ebba90..230919ba 100644 --- a/lib/__tests__/permuteDomain.spec.ts +++ b/lib/__tests__/permuteDomain.spec.ts @@ -35,5 +35,3 @@ describe('permuteDomain', () => { expect(permuteDomain(domain)).toEqual(permutations) }) }) - -// TODO: this set of tests only tests when allowSpecialUseDomain is unspecified (needs more coverage) diff --git a/lib/cookie.ts b/lib/cookie.ts index c17d0a7d..631a0536 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -1184,7 +1184,15 @@ const defaultSetCookieOptions: SetCookieOptions = { loose: false, sameSiteContext: undefined, ignoreError: false, - http: false + http: true, +} + +const defaultGetCookieOptions: GetCookiesOptions = { + http: true, + expire: true, + allPaths: false, + sameSiteContext: undefined, + sort: undefined } export function createPromiseCallback(args: IArguments): PromiseCallback { @@ -1508,22 +1516,18 @@ export class CookieJar { // RFC6365 S5.4 getCookies(url: string, callback: Callback): void; - // TODO: provide types for the options - getCookies(url: string, options: any, callback: Callback): void + getCookies(url: string, options: GetCookiesOptions | undefined, callback: Callback): void getCookies(url: string): Promise - // TODO: provide types for the options - getCookies(url: string, options: any): Promise - // TODO: provide types for the options - getCookies(url: string, options: any, callback?: (error: Error, result: Cookie[]) => void): unknown; - // TODO: provide types for the options - getCookies(url: string, options: any = {}, _callback?: (error: Error, result: Cookie[]) => void): unknown { + getCookies(url: string, options: GetCookiesOptions | undefined): Promise + getCookies(url: string, options: GetCookiesOptions | undefined | Callback, callback?: Callback): unknown + getCookies(url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback validators.validate(validators.isNonEmptyString(url), cb, url); const context = getCookieContext(url); - if (validators.isFunction(options)) { - options = {}; + if (typeof options === 'function' || options === undefined) { + options = defaultGetCookieOptions; } validators.validate(validators.isObject(options), cb, options); validators.validate(validators.isFunction(cb), cb); @@ -1531,17 +1535,10 @@ export class CookieJar { const host = canonicalDomain(context.hostname); const path = context.pathname || "/"; - let secure = options.secure; - if ( - secure == null && - context.protocol && - (context.protocol == "https:" || context.protocol == "wss:") - ) { - secure = true; - } + let secure = context.protocol && (context.protocol == "https:" || context.protocol == "wss:") let sameSiteLevel = 0; - if (options.sameSiteContext) { + if (options?.sameSiteContext) { const sameSiteContext = checkSameSiteContext(options.sameSiteContext) if (sameSiteContext == null) { return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); @@ -1552,14 +1549,11 @@ export class CookieJar { } } - let http = options.http; - if (http == null) { - http = true; - } + let http = options?.http ?? true; - const now = options.now || Date.now(); - const expireCheck = options.expire !== false; - const allPaths = !!options.allPaths; + const now = Date.now(); + const expireCheck = options?.expire ?? true; + const allPaths = options?.allPaths ?? false; const store = this.store; function matchingCookie(c: Cookie) { @@ -1640,7 +1634,7 @@ export class CookieJar { cookies = cookies.filter(matchingCookie); // sorting of S5.4 part 2 - if (options.sort !== false) { + if (options && 'sort' in options && options.sort !== false) { cookies = cookies.sort(cookieCompare); } @@ -1657,24 +1651,28 @@ export class CookieJar { return promiseCallback.promise } - getCookiesSync(url: string, options: any = {}): Cookie[] { + getCookiesSync(url: string, options?: GetCookiesOptions): Cookie[] { return this.callSync(this.getCookies.bind(this, url, options)) ?? [] } - getCookieString(url: string, options: any, callback: (error: Error, result: string) => void): void; - getCookieString(url: string, callback: (error: Error, result: string) => void): void; + getCookieString(url: string, options: GetCookiesOptions, callback: Callback): void; + getCookieString(url: string, callback: Callback): void; getCookieString(url: string): Promise; - getCookieString(url: string, options: any): Promise; - getCookieString(url: string, options: any, callback?: (error: Error, result: string) => void): unknown; - getCookieString(url: string, options: any = {}, _callback?: (error: Error, result: string) => void): unknown { + getCookieString(url: string, options: GetCookiesOptions): Promise; + getCookieString(url: string, options: GetCookiesOptions | Callback, callback?: Callback): unknown; + getCookieString(url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) - const next = function(err: Error, cookies: Cookie[]) { - if (err) { + if (typeof options === 'function') { + options = undefined + } + + const next: Callback = function(err: Error | undefined, cookies: Cookie[] | undefined) { + if (err || cookies === undefined) { promiseCallback.callback(err); } else { promiseCallback.callback( - null, + undefined, cookies .sort(cookieCompare) .map(c => c.cookieString()) @@ -1686,20 +1684,24 @@ export class CookieJar { this.getCookies(url, options, next) return promiseCallback.promise } - getCookieStringSync(url: string, options: any = {}): string { - return this.callSync(this.getCookieString.bind(this, url, options)) ?? "" + getCookieStringSync(url: string, options?: GetCookiesOptions): string { + return this.callSync(this.getCookieString.bind(this, url, options as GetCookiesOptions)) ?? "" } getSetCookieStrings (url: string, callback: Callback): void - getSetCookieStrings (url: string, options: any, callback: Callback): void + getSetCookieStrings (url: string, options: GetCookiesOptions, callback: Callback): void getSetCookieStrings (url: string): Promise - getSetCookieStrings (url: string, options: any): Promise - getSetCookieStrings (url: string, options: any, callback?: Callback): unknown; - getSetCookieStrings (url: string, options: any = {}, _callback?: Callback): unknown { + getSetCookieStrings (url: string, options: GetCookiesOptions): Promise + getSetCookieStrings (url: string, options: GetCookiesOptions, callback?: Callback): unknown; + getSetCookieStrings (url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) - const next = function(err: Error, cookies: Cookie[]) { - if (err) { + if (typeof options === 'function') { + options = undefined + } + + const next: Callback = function(err: Error | undefined, cookies: Cookie[] | undefined) { + if (err || cookies === undefined) { promiseCallback.callback(err); } else { promiseCallback.callback( @@ -2034,6 +2036,14 @@ type SetCookieOptions = { now?: Date | undefined; } +type GetCookiesOptions = { + http?: boolean | undefined + expire?: boolean | undefined + allPaths?: boolean | undefined + sameSiteContext?: 'none' | 'lax' | 'strict' | undefined + sort?: boolean | undefined +} + interface PromiseCallback { promise: Promise; callback: (error: Error | undefined | null, result?: T) => Promise; diff --git a/test/api_test.js b/test/api_test.js index 865fb4b2..f3539e56 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -430,7 +430,6 @@ vows } } }) - // TODO: confirm these are covered .addBatch({ "Synchronous API on async CookieJar": { topic: function() { diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 913d4812..9e79621c 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -677,7 +677,6 @@ vows } } }, - // TODO: what is this? git blame and find out "Remove all cookies (the underlying store does not support removeAllCookies)": { topic: function() { const jar = new CookieJar(); From f1eb4b072c9938b542cd22381a03ce281f4907ac Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 27 Mar 2023 15:45:52 -0300 Subject: [PATCH 14/33] addressed todos noted in code review --- lib/__tests__/jarSerialization.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 6d934fba..7a1bb607 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -36,6 +36,10 @@ const { any, objectContaining } = expect jest.useFakeTimers() describe('cookieJar serialization', () => { + it('should use the expected version', () => { + expect(version).toBe('4.1.2') + }) + it('should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`', () => { expect(Cookie.serializableProperties).toEqual([ "key", @@ -282,7 +286,7 @@ describe('cookieJar serialization', () => { function expectDataToMatchSerializationSchema (serializedJar) { expect(serializedJar).not.toBeNull() expect(serializedJar).toBeInstanceOf(Object) - expect(serializedJar.version).toBe('tough-cookie@4.1.2') + expect(serializedJar.version).toBe(`tough-cookie@${version}`) expect(serializedJar.storeType).toBe('MemoryCookieStore') expect(serializedJar.rejectPublicSuffixes).toBe(true) expect(serializedJar.cookies).toBeInstanceOf(Array) From 2c296e1e36d6ee73d2e6e0aa8e3f6b7d3565196c Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 27 Mar 2023 16:10:09 -0300 Subject: [PATCH 15/33] cleaned up typescript output directory --- jest.config.js | 1 + package.json | 8 ++++---- test/api_test.js | 2 +- test/cookie_jar_test.js | 2 +- test/cookie_prefixes_test.js | 2 +- test/cookie_sorting_test.js | 2 +- test/cookie_to_json_test.js | 2 +- test/cookie_to_string_test.js | 2 +- test/date_test.js | 2 +- test/domain_and_path_test.js | 2 +- test/ietf_test.js | 2 +- test/jar_serialization_test.js | 2 +- test/lifetime_test.js | 2 +- test/node_util_fallback_test.js | 6 +++--- test/parsing_test.js | 2 +- test/regression_test.js | 2 +- test/remove_all_test.js | 2 +- test/same_site_test.js | 2 +- tsconfig.json | 4 +++- 19 files changed, 26 insertions(+), 23 deletions(-) diff --git a/jest.config.js b/jest.config.js index 4c05aef8..d286cbbb 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", maxWorkers: 1, globals: { "ts-jest": { diff --git a/package.json b/package.json index f1f0f7f8..3eac1c56 100644 --- a/package.json +++ b/package.json @@ -76,15 +76,15 @@ "bugs": { "url": "https://github.com/salesforce/tough-cookie/issues" }, - "main": "./dist/lib/cookie", - "types": "./dist/lib/cookie", + "main": "./dist/cookie.js", + "types": "./dist/cookie.d.ts", "files": [ - "lib" + "dist" ], "scripts": { "version": "genversion lib/version.js && git add lib/version.js", "test": "jest && tsc && vows test/*_test.js", - "cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js", + "cover": "jest --coverage", "eslint": "eslint --env node --ext .js .", "prettier": "prettier '**/*.{json,ts,yaml,md}'", "format": "npm run eslint -- --fix" diff --git a/test/api_test.js b/test/api_test.js index f3539e56..28920e20 100644 --- a/test/api_test.js +++ b/test/api_test.js @@ -32,7 +32,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index 9e79621c..ed13d850 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -32,7 +32,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/cookie_prefixes_test.js b/test/cookie_prefixes_test.js index 81ef4a1b..20f9fb04 100644 --- a/test/cookie_prefixes_test.js +++ b/test/cookie_prefixes_test.js @@ -31,7 +31,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const CookieJar = tough.CookieJar; const PrefixSecurityEnum = tough.PrefixSecurityEnum; diff --git a/test/cookie_sorting_test.js b/test/cookie_sorting_test.js index 0e50685c..d8edf221 100644 --- a/test/cookie_sorting_test.js +++ b/test/cookie_sorting_test.js @@ -31,7 +31,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/cookie_to_json_test.js b/test/cookie_to_json_test.js index 8506cedc..a7b4ca7f 100644 --- a/test/cookie_to_json_test.js +++ b/test/cookie_to_json_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/cookie_to_string_test.js b/test/cookie_to_string_test.js index 2a853efe..12d8930c 100644 --- a/test/cookie_to_string_test.js +++ b/test/cookie_to_string_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/date_test.js b/test/date_test.js index 5b81003c..dbb725fc 100644 --- a/test/date_test.js +++ b/test/date_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); function dateVows(table) { const theVows = {}; diff --git a/test/domain_and_path_test.js b/test/domain_and_path_test.js index 78e4e1c5..93655519 100644 --- a/test/domain_and_path_test.js +++ b/test/domain_and_path_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; function matchVows(func, table) { diff --git a/test/ietf_test.js b/test/ietf_test.js index a15d5fda..8e0abc04 100644 --- a/test/ietf_test.js +++ b/test/ietf_test.js @@ -35,7 +35,7 @@ const assert = require("assert"); const fs = require("fs"); const path = require("path"); const url = require("url"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const CookieJar = tough.CookieJar; function readJson(filePath) { diff --git a/test/jar_serialization_test.js b/test/jar_serialization_test.js index 5bbb9758..481cdf55 100644 --- a/test/jar_serialization_test.js +++ b/test/jar_serialization_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const Store = tough.Store; diff --git a/test/lifetime_test.js b/test/lifetime_test.js index 9a5c0357..1e0a320f 100644 --- a/test/lifetime_test.js +++ b/test/lifetime_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; vows diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 64b1e892..558cc36a 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -32,13 +32,13 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const util = require("util"); -const inspectFallback = require("../dist/lib/memstore").inspectFallback; +const inspectFallback = require("../dist/memstore").inspectFallback; const { getCustomInspectSymbol, getUtilInspect -} = require("../dist/lib/utilHelper"); +} = require("../dist/utilHelper"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/test/parsing_test.js b/test/parsing_test.js index a40c86a5..0ed27dd1 100644 --- a/test/parsing_test.js +++ b/test/parsing_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const LOTS_OF_SEMICOLONS = ";".repeat(65535); diff --git a/test/regression_test.js b/test/regression_test.js index b1f96912..ce38b341 100644 --- a/test/regression_test.js +++ b/test/regression_test.js @@ -33,7 +33,7 @@ const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const MemoryCookieStore = tough.MemoryCookieStore; diff --git a/test/remove_all_test.js b/test/remove_all_test.js index bed18b18..76bdbfa5 100644 --- a/test/remove_all_test.js +++ b/test/remove_all_test.js @@ -33,7 +33,7 @@ const util = require("util"); const vows = require("vows"); const assert = require("assert"); const async = require("async"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; const Store = tough.Store; diff --git a/test/same_site_test.js b/test/same_site_test.js index f0801ce3..27405932 100644 --- a/test/same_site_test.js +++ b/test/same_site_test.js @@ -32,7 +32,7 @@ "use strict"; const vows = require("vows"); const assert = require("assert"); -const tough = require("../dist/lib/cookie"); +const tough = require("../dist/cookie"); const Cookie = tough.Cookie; const CookieJar = tough.CookieJar; diff --git a/tsconfig.json b/tsconfig.json index a7e4a0ee..1c859b64 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "target": "es5", "resolveJsonModule": true, "esModuleInterop": true, + "declaration": true, "strict": true, "noImplicitAny": true, "noImplicitThis": true, @@ -20,5 +21,6 @@ "allowUnreachableCode": false, "exactOptionalPropertyTypes": true }, - "include": ["./lib/**/*"] + "include": ["./lib/**/*"], + "exclude": ["./lib/**/*.spec.ts"] } From 0821af425679615353d569912866c73ecb9b01e4 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 27 Mar 2023 23:21:46 -0300 Subject: [PATCH 16/33] updates to eslint and prettier tooling --- .eslintignore | 2 ++ .eslintrc.json | 17 ++++++++--------- .prettierignore | 2 +- .prettierrc | 5 +++++ jest.config.js | 12 ------------ jest.config.ts | 15 +++++++++++++++ package.json | 30 ++++++++++++++++-------------- 7 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 .prettierrc delete mode 100644 jest.config.js create mode 100644 jest.config.ts diff --git a/.eslintignore b/.eslintignore index 1521c8b7..44e26072 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,3 @@ dist +node_modules +test diff --git a/.eslintrc.json b/.eslintrc.json index 76a4200c..d85ba378 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,13 @@ { - "extends": ["plugin:prettier/recommended"], - "parserOptions": { - "ecmaVersion": 6 - }, + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], "rules": { - "no-debugger": "error", - "no-var": "error", - "prefer-arrow-callback": "error", - "prefer-const": "error", - "prefer-template": "error", + "no-control-regex": "off", + "@typescript-eslint/ban-ts-comment": "off", "no-restricted-modules": ["error", // we can't rely on node standard modules in "native" or "browser" environments // - exceptions: diff --git a/.prettierignore b/.prettierignore index c3062b7c..9726f453 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ -lib/version.js dist/*.js +test/*.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..e3b414c7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index d286cbbb..00000000 --- a/jest.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", - maxWorkers: 1, - globals: { - "ts-jest": { - isolatedModules: false - } - } -}; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..76952882 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,15 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +import type { JestConfigWithTsJest } from "ts-jest"; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest', + testEnvironment: 'node', + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$', + testPathIgnorePatterns: ["dist"], + maxWorkers: 1, + globals: { + '*.ts': ['ts-jest', { isolatedModules: false }], + }, +} + +export default config diff --git a/package.json b/package.json index 3eac1c56..7facbaf2 100644 --- a/package.json +++ b/package.json @@ -85,27 +85,29 @@ "version": "genversion lib/version.js && git add lib/version.js", "test": "jest && tsc && vows test/*_test.js", "cover": "jest --coverage", - "eslint": "eslint --env node --ext .js .", - "prettier": "prettier '**/*.{json,ts,yaml,md}'", + "lint": "eslint --fix lib", + "prettier": "prettier --write '**/*.{json,ts,yaml,md}'", "format": "npm run eslint -- --fix" }, "engines": { "node": ">=6" }, "devDependencies": { - "@types/jest": "^27.4.0", - "@types/punycode": "^2.1.0", - "@types/psl": "^1.1.0", - "async": "^2.6.2", - "eslint": "^5.16.0", - "eslint-config-prettier": "^4.2.0", - "eslint-plugin-prettier": "^3.0.1", + "@types/jest": "^29", + "@types/psl": "^1", + "@types/punycode": "^2", + "async": "2.6.4", + "eslint": "^8.36.0", + "@typescript-eslint/parser": "^5.57.0", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", "genversion": "^2.1.0", - "jest": "^27.4.7", - "nyc": "^14.0.0", - "prettier": "^1.17.0", - "ts-jest": "^27.1.3", - "typescript": "4.5.5", + "jest": "^29.5.0", + "prettier": "^2.8.7", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.9.5", "vows": "^0.8.2" }, "dependencies": { From 0acb1e972f483c40dd6855e5c3d48233707a5be0 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 27 Mar 2023 23:22:43 -0300 Subject: [PATCH 17/33] changes from formatter --- .github/workflows/integration.yaml | 7 +- CHANGELOG.md | 1 - CODE_OF_CONDUCT.md | 56 +- README.md | 38 +- lib/__tests__/canonicalDomain.spec.ts | 16 +- lib/__tests__/cookie.spec.ts | 18 +- lib/__tests__/cookieJar.spec.ts | 1295 ++++++++++-------- lib/__tests__/cookiePrefixes.spec.ts | 114 +- lib/__tests__/cookieSorting.spec.ts | 37 +- lib/__tests__/cookieToAndFromJson.spec.ts | 68 +- lib/__tests__/cookieToString.spec.ts | 30 +- lib/__tests__/date.spec.ts | 161 +-- lib/__tests__/defaultPath.spec.ts | 12 +- lib/__tests__/domainMatch.spec.ts | 108 +- lib/__tests__/ietf.spec.ts | 70 +- lib/__tests__/jarSerialization.spec.ts | 215 +-- lib/__tests__/lifetime.spec.ts | 17 +- lib/__tests__/memoryCookieStore.spec.ts | 28 +- lib/__tests__/nodeUtilFallback.spec.ts | 79 +- lib/__tests__/parse.spec.ts | 210 +-- lib/__tests__/pathMatch.spec.ts | 25 +- lib/__tests__/permuteDomain.spec.ts | 28 +- lib/__tests__/permutePath.spec.ts | 16 +- lib/__tests__/regression.spec.ts | 89 +- lib/__tests__/removeAll.spec.ts | 216 ++- lib/__tests__/sameSite.spec.ts | 76 +- lib/cookie.ts | 1480 ++++++++++++--------- lib/memstore.ts | 274 ++-- lib/pathMatch.ts | 17 +- lib/permuteDomain.ts | 35 +- lib/pubsuffix-psl.ts | 55 +- lib/store.ts | 109 +- lib/utilHelper.ts | 35 +- lib/validators.ts | 32 +- test/ietf_data/dates/bsd-examples.json | 162 ++- test/ietf_data/dates/examples.json | 42 +- test/ietf_data/parser.json | 1308 +++++------------- 37 files changed, 3360 insertions(+), 3219 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 29cbf133..d6d1e8f4 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -1,14 +1,13 @@ -name: Actions-CI +name: Actions-CI on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: - runs-on: ubuntu-latest strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index 94427e49..64a9e615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,4 +2,3 @@ All notable changes to this project can be found at on the [Releases](https://github.com/salesforce/tough-cookie/releases) page. - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b6724718..7f547bd2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,8 +8,8 @@ culture where everyone feels included. Salesforce open-source projects are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity and expression, -sexual orientation, disability, physical appearance, body size, ethnicity, nationality, -race, age, religion, level of experience, education, socioeconomic status, or +sexual orientation, disability, physical appearance, body size, ethnicity, nationality, +race, age, religion, level of experience, education, socioeconomic status, or other similar personal characteristics. The goal of this code of conduct is to specify a baseline standard of behavior so @@ -25,9 +25,9 @@ Open Source Conduct Committee at ossconduct@salesforce.com. In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of gender -identity and expression, sexual orientation, disability, physical appearance, -body size, ethnicity, nationality, race, age, religion, level of experience, education, +our community a harassment-free experience for everyone, regardless of gender +identity and expression, sexual orientation, disability, physical appearance, +body size, ethnicity, nationality, race, age, religion, level of experience, education, socioeconomic status, or other similar personal characteristics. ## Our Standards @@ -35,23 +35,23 @@ socioeconomic status, or other similar personal characteristics. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy toward other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy toward other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Personal attacks, insulting/derogatory comments, or trolling -* Public or private harassment -* Publishing, or threatening to publish, others' private information—such as -a physical or electronic address—without explicit permission -* Other conduct which could reasonably be considered inappropriate in a -professional setting -* Advocating for or encouraging any of the above behaviors +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Personal attacks, insulting/derogatory comments, or trolling +- Public or private harassment +- Publishing, or threatening to publish, others' private information—such as + a physical or electronic address—without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting +- Advocating for or encouraging any of the above behaviors ## Our Responsibilities @@ -77,28 +77,28 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the Salesforce Open Source Conduct Committee -at ossconduct@salesforce.com. All complaints will be reviewed and investigated -and will result in a response that is deemed necessary and appropriate to the -circumstances. The committee is obligated to maintain confidentiality with -regard to the reporter of an incident. Further details of specific enforcement +reported by contacting the Salesforce Open Source Conduct Committee +at ossconduct@salesforce.com. All complaints will be reviewed and investigated +and will result in a response that is deemed necessary and appropriate to the +circumstances. The committee is obligated to maintain confidentiality with +regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other -members of the project's leadership and the Salesforce Open Source Conduct +members of the project's leadership and the Salesforce Open Source Conduct Committee. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], -version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. -It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], +version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. +It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. -[contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) +[contributor-covenant-home]: https://www.contributor-covenant.org 'https://www.contributor-covenant.org/' [golang-coc]: https://golang.org/conduct [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ diff --git a/README.md b/README.md index fb11f3ce..883b6031 100644 --- a/README.md +++ b/README.md @@ -9,31 +9,31 @@ ## Synopsis ```javascript -var tough = require("tough-cookie"); -var Cookie = tough.Cookie; -var cookie = Cookie.parse(header); -cookie.value = "somethingdifferent"; -header = cookie.toString(); -var cookiejar = new tough.CookieJar(); +var tough = require('tough-cookie') +var Cookie = tough.Cookie +var cookie = Cookie.parse(header) +cookie.value = 'somethingdifferent' +header = cookie.toString() +var cookiejar = new tough.CookieJar() // Asynchronous! var cookie = await cookiejar.setCookie( cookie, - "https://currentdomain.example.com/path" -); -var cookies = await cookiejar.getCookies("https://example.com/otherpath"); + 'https://currentdomain.example.com/path', +) +var cookies = await cookiejar.getCookies('https://example.com/otherpath') // Or with callbacks! cookiejar.setCookie( cookie, - "https://currentdomain.example.com/path", + 'https://currentdomain.example.com/path', function (err, cookie) { /* ... */ - } -); -cookiejar.getCookies("http://example.com/otherpath", function (err, cookies) { + }, +) +cookiejar.getCookies('http://example.com/otherpath', function (err, cookies) { /* ... */ -}); +}) ``` Why the name? NPM modules `cookie`, `cookies` and `cookiejar` were already taken. @@ -113,8 +113,8 @@ For use with `.sort()`, sorts a list of cookies into the recommended order given ```javascript var cookies = [ /* unsorted array of Cookie objects */ -]; -cookies = cookies.sort(cookieCompare); +] +cookies = cookies.sort(cookieCompare) ``` > **Note**: Since the JavaScript `Date` is limited to a 1-ms precision, cookies within the same millisecond are entirely possible. This is especially true when using the `now` option to `.setCookie()`. The `.creationIndex` property is a per-process global counter, assigned during construction with `new Cookie()`, which preserves the spirit of the RFC sorting: older cookies go first. This works great for `MemoryCookieStore` since `Set-Cookie` headers are parsed in order, but is not so great for distributed systems. Sophisticated `Store`s may wish to set this to some other _logical clock_ so that if cookies A and B are created in the same millisecond, but cookie A is created before cookie B, then `A.creationIndex < B.creationIndex`. If you want to alter the global counter, which you probably _shouldn't_ do, it's stored in `Cookie.cookiesCreated`. @@ -144,9 +144,9 @@ If options is not an object it is ignored, which means it can be used with [`Arr To process the Set-Cookie header(s) on a node HTTP/HTTPS response: ```javascript -if (Array.isArray(res.headers["set-cookie"])) - cookies = res.headers["set-cookie"].map(Cookie.parse); -else cookies = [Cookie.parse(res.headers["set-cookie"])]; +if (Array.isArray(res.headers['set-cookie'])) + cookies = res.headers['set-cookie'].map(Cookie.parse) +else cookies = [Cookie.parse(res.headers['set-cookie'])] ``` _Note:_ In version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation was removed in version 2.3.4. diff --git a/lib/__tests__/canonicalDomain.spec.ts b/lib/__tests__/canonicalDomain.spec.ts index 18c9a389..3e5fba65 100644 --- a/lib/__tests__/canonicalDomain.spec.ts +++ b/lib/__tests__/canonicalDomain.spec.ts @@ -1,4 +1,4 @@ -import {canonicalDomain} from "../cookie"; +import { canonicalDomain } from '../cookie' // port of tests/domain_and_path_test.js (canonicalDomain tests for domain normalization) describe('canonicalDomain', () => { @@ -6,37 +6,37 @@ describe('canonicalDomain', () => { { description: 'already canonical', input: 'example.com', - output: 'example.com' + output: 'example.com', }, { description: 'simple', input: 'EXAMPLE.com', - output: 'example.com' + output: 'example.com', }, { description: 'leading dot stripped', input: '.EXAMPLE.com', - output: 'example.com' + output: 'example.com', }, { description: 'trailing dot', input: 'EXAMPLE.com.', - output: 'example.com.' + output: 'example.com.', }, { description: 'leading and trailing dot', input: '.EXAMPLE.com.', - output: 'example.com.' + output: 'example.com.', }, { description: 'internal dots', input: '.EXAMPLE...com.', - output: 'example...com.' + output: 'example...com.', }, { description: 'IDN: test.test in greek', input: 'δοκιμή.δοκιμή', - output: 'xn--jxalpdlp.xn--jxalpdlp' + output: 'xn--jxalpdlp.xn--jxalpdlp', }, ])('$description: $input → $output', ({ input, output }) => { expect(canonicalDomain(input)).toBe(output) diff --git a/lib/__tests__/cookie.spec.ts b/lib/__tests__/cookie.spec.ts index 8daf7dc0..a058c769 100644 --- a/lib/__tests__/cookie.spec.ts +++ b/lib/__tests__/cookie.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie} from '../cookie' +import { Cookie } from '../cookie' jest.useFakeTimers() @@ -40,25 +40,25 @@ describe('Cookie', () => { describe('constructor', () => { beforeEach(() => { cookie = new Cookie({ - key: "test", - value: "b", - maxAge: 60 + key: 'test', + value: 'b', + maxAge: 60, }) }) - it("should check for key property", () => { + it('should check for key property', () => { expect(cookie.key).toBe('test') }) it('should check for value property', () => { - expect(cookie.value).toBe("b"); + expect(cookie.value).toBe('b') }) - it("should check for maxAge", () => { - expect(cookie.maxAge).toBe(60); + it('should check for maxAge', () => { + expect(cookie.maxAge).toBe(60) }) - it("should check for default values for unspecified properties", () => { + it('should check for default values for unspecified properties', () => { expect(cookie.expires).toBe('Infinity') expect(cookie.secure).toBe(false) expect(cookie.httpOnly).toBe(false) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 5f23f4a5..4f8a8afd 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store} from '../cookie' +import { Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store } from "../cookie"; const { objectContaining, assertions } = expect jest.useFakeTimers() @@ -47,219 +47,276 @@ describe('CookieJar', () => { describe('setCookie', () => { let cookie: Cookie - apiVariants('should resolve to a Cookie', { - callbackStyle (done) { - cookieJar.setCookie("foo=bar", "http://example.com", (_error, result) => { - if (result == null) { - throw new Error('should not be null') - } - cookie = result - done() - }) + apiVariants( + 'should resolve to a Cookie', + { + callbackStyle(done) { + cookieJar.setCookie( + 'foo=bar', + 'http://example.com', + (_error, result) => { + if (result == null) { + throw new Error('should not be null') + } + cookie = result + done() + }, + ) + }, + async asyncStyle() { + cookie = await cookieJar.setCookie('foo=bar', 'http://example.com') + }, + syncStyle() { + // @ts-ignore + cookie = cookieJar.setCookieSync('foo=bar', 'http://example.com') + }, }, - async asyncStyle () { - cookie = await cookieJar.setCookie("foo=bar", "http://example.com") + async () => { + expect(cookie).toBeInstanceOf(Cookie) + expect(cookie.key).toBe('foo') + expect(cookie.value).toBe('bar') }, - syncStyle () { - // @ts-ignore - cookie = cookieJar.setCookieSync("foo=bar", "http://example.com") - } - }, async () => { - expect(cookie).toBeInstanceOf(Cookie) - expect(cookie.key).toBe('foo') - expect(cookie.value).toBe('bar') - }) + ) it('supports the "expiry" option', async () => { const cookie = await cookieJar.setCookie( - "near=expiry; Domain=example.com; Path=/; Max-Age=1", - "http://www.example.com", - { now: new Date(Date.now() - 1) } + 'near=expiry; Domain=example.com; Path=/; Max-Age=1', + 'http://www.example.com', + { now: new Date(Date.now() - 1) }, + ) + expect(cookie).toEqual( + objectContaining({ + key: 'near', + value: 'expiry', + }), ) - expect(cookie).toEqual(objectContaining({ - key: 'near', - value: 'expiry' - })) jest.advanceTimersByTime(1) - const cookies = await cookieJar.getCookies("http://www.example.com", { + const cookies = await cookieJar.getCookies('http://www.example.com', { http: true, - expire: false + expire: false, }) expect(cookies).toHaveLength(1) - expect(cookies[0]).toEqual(objectContaining({ - key: 'near', - value: 'expiry' - })) + expect(cookies[0]).toEqual( + objectContaining({ + key: 'near', + value: 'expiry', + }), + ) }) describe('the "loose" option', () => { it('should allow keyless cookie to be accepted when loose: true', async () => { - const cookie = await cookieJar.setCookie("=b", "http://example.com/index.html", { - loose: true - }) - expect(cookie).toEqual(objectContaining({ - key: '', - value: 'b' - })) + const cookie = await cookieJar.setCookie( + '=b', + 'http://example.com/index.html', + { + loose: true, + }, + ) + expect(cookie).toEqual( + objectContaining({ + key: '', + value: 'b', + }), + ) }) it('should not allow keyless cookie to be accepted when loose: false', async () => { assertions(1) - await expect(cookieJar.setCookie("=b", "http://example.com/index.html", { - loose: false - })).rejects.toThrowError('Cookie failed to parse') + await expect( + cookieJar.setCookie('=b', 'http://example.com/index.html', { + loose: false, + }), + ).rejects.toThrowError('Cookie failed to parse') }) it('should not default to loose: true when using map', () => { const cookies = [ - "=a;domain=example.com", - "=b;domain=example.com", - "c=d;domain=example.com" - ].map(Cookie.parse); + '=a;domain=example.com', + '=b;domain=example.com', + 'c=d;domain=example.com', + ].map(Cookie.parse) expect(cookies).toEqual([ undefined, undefined, objectContaining({ key: 'c', - value: 'd' - }) + value: 'd', + }), ]) }) }) it('should set a timestamp when storing or retrieving a cookie', async () => { // @ts-ignore - cookie = Cookie.parse("a=b; Domain=example.com; Path=/") + cookie = Cookie.parse('a=b; Domain=example.com; Path=/') const t0 = new Date() - expect(cookie).toEqual(objectContaining({ - hostOnly: null, - creation: t0, - lastAccessed: null - })) + expect(cookie).toEqual( + objectContaining({ + hostOnly: null, + creation: t0, + lastAccessed: null, + }), + ) jest.advanceTimersByTime(10000) const t1 = new Date() - cookie = await cookieJar.setCookie(cookie, "http://example.com/index.html") + cookie = await cookieJar.setCookie( + cookie, + 'http://example.com/index.html', + ) - expect(cookie).toEqual(objectContaining({ - hostOnly: false, - creation: t1, - lastAccessed: t1 - })) + expect(cookie).toEqual( + objectContaining({ + hostOnly: false, + creation: t1, + lastAccessed: t1, + }), + ) expect(cookie.TTL()).toBe(Infinity) expect(cookie.isPersistent()).toBe(false) // updates the last access when retrieving a cookie jest.advanceTimersByTime(10000) const t2 = new Date() - const cookies = await cookieJar.getCookies("http://example.com/index.html") + const cookies = await cookieJar.getCookies( + 'http://example.com/index.html', + ) expect(cookies).toEqual([ objectContaining({ hostOnly: false, creation: t1, - lastAccessed: t2 - }) + lastAccessed: t2, + }), ]) }) it('should be able to set a no-path cookie', async () => { - cookie = await cookieJar.setCookie("a=b; Domain=example.com", "http://example.com/index.html") - expect(cookie).toEqual(objectContaining({ - domain: 'example.com', - path: '/', - pathIsDefault: true - })) + cookie = await cookieJar.setCookie( + 'a=b; Domain=example.com', + 'http://example.com/index.html', + ) + expect(cookie).toEqual( + objectContaining({ + domain: 'example.com', + path: '/', + pathIsDefault: true, + }), + ) }) it('should be able to set a cookie already marked as host-only', async () => { cookie = await cookieJar.setCookie( - createCookie("a=b; Domain=example.com", { - hostOnly: true + createCookie('a=b; Domain=example.com', { + hostOnly: true, + }), + 'http://example.com/index.html', + ) + expect(cookie).toEqual( + objectContaining({ + domain: 'example.com', + hostOnly: true, }), - "http://example.com/index.html" ) - expect(cookie).toEqual(objectContaining({ - domain: 'example.com', - hostOnly: true - })) }) it('should be able to set a session cookie', async () => { - cookie = createCookie("SID=31d4d96e407aad42") + cookie = createCookie('SID=31d4d96e407aad42') expect(cookie.path).toBeNull() - cookie = await cookieJar.setCookie(cookie, "http://www.example.com/dir/index.html") - expect(cookie).toEqual(objectContaining({ - key: 'SID', - value: '31d4d96e407aad42', - domain: 'www.example.com', - path: '/dir', - hostOnly: true - })) + cookie = await cookieJar.setCookie( + cookie, + 'http://www.example.com/dir/index.html', + ) + expect(cookie).toEqual( + objectContaining({ + key: 'SID', + value: '31d4d96e407aad42', + domain: 'www.example.com', + path: '/dir', + hostOnly: true, + }), + ) }) it('should fail when the cookie domain does not match the provided domain', async () => { assertions(1) - await expect(cookieJar.setCookie( - "a=b; Domain=fooxample.com; Path=/", - "http://example.com/index.html" - )).rejects.toThrowError("Cookie not in this host's domain. Cookie:fooxample.com Request:example.com") + await expect( + cookieJar.setCookie( + 'a=b; Domain=fooxample.com; Path=/', + 'http://example.com/index.html', + ), + ).rejects.toThrowError( + "Cookie not in this host's domain. Cookie:fooxample.com Request:example.com", + ) }) it('should fail when the cookie has a sub-domain but the provided domain is the root', async () => { assertions(1) - await expect(cookieJar.setCookie( - "a=b; Domain=www.example.com; Path=/", - "http://example.com/index.html" - )).rejects.toThrowError("Cookie not in this host's domain. Cookie:www.example.com Request:example.com") + await expect( + cookieJar.setCookie( + 'a=b; Domain=www.example.com; Path=/', + 'http://example.com/index.html', + ), + ).rejects.toThrowError( + "Cookie not in this host's domain. Cookie:www.example.com Request:example.com", + ) }) it('should allow for a cookie with a root domain to be stored under a super-domain', async () => { cookie = await cookieJar.setCookie( - "a=b; Domain=example.com; Path=/", - "http://www.app.example.com/index.html" + 'a=b; Domain=example.com; Path=/', + 'http://www.app.example.com/index.html', ) expect(cookie.domain).toBe('example.com') }) it('should allow a sub-path cookie on a super-domain', async () => { cookie = await cookieJar.setCookie( - "a=b; Domain=example.com; Path=/subpath", - "http://www.example.com/index.html" + 'a=b; Domain=example.com; Path=/subpath', + 'http://www.example.com/index.html', + ) + expect(cookie).toEqual( + objectContaining({ + domain: 'example.com', + path: '/subpath', + pathIsDefault: null, + }), ) - expect(cookie).toEqual(objectContaining({ - domain: 'example.com', - path: '/subpath', - pathIsDefault: null - })) }) it('should fail when using an httpOnly cookie when using a non-HTTP API', async () => { assertions(1) - await expect(cookieJar.setCookie( - "a=b; Domain=example.com; Path=/; HttpOnly", - "http://example.com/index.html", - { http: false } - )).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") + await expect( + cookieJar.setCookie( + 'a=b; Domain=example.com; Path=/; HttpOnly', + 'http://example.com/index.html', + { http: false }, + ), + ).rejects.toThrowError("Cookie is HttpOnly and this isn't an HTTP API") }) it('should not fail when using an httpOnly cookie when using a non-HTTP API', async () => { assertions(1) await cookieJar.setCookie( - "OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly", - "https://127.0.0.1/TestPath/somewhere", + 'OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly', + 'https://127.0.0.1/TestPath/somewhere', + ) + const cookies = await cookieJar.getCookies( + 'https://127.0.0.1/TestPath/somewhere', ) - const cookies = await cookieJar.getCookies("https://127.0.0.1/TestPath/somewhere") expect(cookies).not.toHaveLength(0) }) it('should not fail when using an httpOnly cookie when using a non-HTTP API (setCookieSync)', () => { assertions(1) cookieJar.setCookieSync( - "OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly", - "https://127.0.0.1/TestPath/somewhere", + 'OptionsTest=FooBar; expires=Wed, 13-Jan-2051 22:23:01 GMT; path=/TestPath; HttpOnly', + 'https://127.0.0.1/TestPath/somewhere', + ) + const cookies = cookieJar.getCookiesSync( + 'https://127.0.0.1/TestPath/somewhere', ) - const cookies = cookieJar.getCookiesSync("https://127.0.0.1/TestPath/somewhere") expect(cookies).not.toHaveLength(0) }) @@ -267,17 +324,19 @@ describe('CookieJar', () => { { testCase: 'basic', IPv6: '[::1]' }, { testCase: 'prefix', IPv6: '[::ffff:127.0.0.1]' }, { testCase: 'classic', IPv6: '[2001:4860:4860::8888]' }, - { testCase: 'short', IPv6: '[2600::]' } + { testCase: 'short', IPv6: '[2600::]' }, ])('should store a $testCase IPv6', async (test) => { const t0 = new Date() cookie = await cookieJar.setCookie( `a=b; Domain=${test.IPv6}; Path=/`, - `http://${test.IPv6}/` + `http://${test.IPv6}/`, + ) + expect(cookie).toEqual( + objectContaining({ + creation: t0, + lastAccessed: t0, + }), ) - expect(cookie).toEqual(objectContaining({ - creation: t0, - lastAccessed: t0, - })) expect(cookie.TTL()).toBe(Infinity) expect(cookie.isPersistent()).toBe(false) }) @@ -288,53 +347,53 @@ describe('CookieJar', () => { let cookies: Cookie[] | undefined beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") + await cookieJar.setCookie('foo=bar', 'http://example.com') }) - apiVariants('resolves to an array of cookies', { - callbackStyle(done) { - cookieJar.getCookies("http://example.com", (_error, result) => { - cookies = result - done() - }) + apiVariants( + 'resolves to an array of cookies', + { + callbackStyle(done) { + cookieJar.getCookies('http://example.com', (_error, result) => { + cookies = result + done() + }) + }, + async asyncStyle() { + cookies = await cookieJar.getCookies('http://example.com') + }, + syncStyle() { + cookies = cookieJar.getCookiesSync('http://example.com') + }, }, - async asyncStyle() { - cookies = await cookieJar.getCookies("http://example.com") + async () => { + expect(cookies).toEqual([ + objectContaining({ + key: 'foo', + value: 'bar', + }), + ]) }, - syncStyle() { - cookies = cookieJar.getCookiesSync("http://example.com") - } - }, async () => { - expect(cookies).toEqual([ - objectContaining({ - key: 'foo', - value: 'bar' - }), - ]) - }) + ) }) - describe('the "allPaths" option', () => { + describe('the "allPaths" option', () => { beforeEach(async () => { await cookieJar.removeAllCookies() const cookiesByUrl = { - "http://example.com": [ - "nopath_dom=qq; Path=/; Domain=example.com", - "path_dom=qq; Path=/foo; Domain=example.com" + 'http://example.com': [ + 'nopath_dom=qq; Path=/; Domain=example.com', + 'path_dom=qq; Path=/foo; Domain=example.com', ], - "http://www.example.com": [ - "nopath_host=qq; Path=/", - "path_host=qq; Path=/foo" + 'http://www.example.com': [ + 'nopath_host=qq; Path=/', + 'path_host=qq; Path=/foo', ], - "http://other.example.com": [ - "other=qq; Path=/" - ], - "http://other.example.com/foo": [ - "other2=qq; Path=/foo" - ] + 'http://other.example.com': ['other=qq; Path=/'], + 'http://other.example.com/foo': ['other2=qq; Path=/foo'], } - for await (let [url, cookies] of Object.entries(cookiesByUrl)) { - for await (let cookie of cookies) { + for await (const [url, cookies] of Object.entries(cookiesByUrl)) { + for await (const cookie of cookies) { await cookieJar.setCookie(cookie, url) } } @@ -346,271 +405,294 @@ describe('CookieJar', () => { }) it('should scope cookies by path as the default behavior', async () => { - const cookies = await cookieJar.getCookies("http://www.example.com/") + const cookies = await cookieJar.getCookies('http://www.example.com/') expect(cookies).toHaveLength(2) - const allHaveRootPath = cookies.every(cookie => cookie.path === '/') + const allHaveRootPath = cookies.every((cookie) => cookie.path === '/') expect(allHaveRootPath).toBe(true) // @ts-ignore - const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + const noCookiesWithAnOtherKeyRetrieved = cookies.every( + (cookie) => !/^other/.test(cookie.key as string), + ) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) it('should scope cookies by path when reading from the /foo path', async () => { - const cookies = await cookieJar.getCookies("http://www.example.com/foo") + const cookies = await cookieJar.getCookies('http://www.example.com/foo') expect(cookies).toHaveLength(4) // @ts-ignore - const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + const noCookiesWithAnOtherKeyRetrieved = cookies.every( + (cookie) => !/^other/.test(cookie.key as string), + ) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) it('should not scope cookies by path when using allPaths: true', async () => { - const cookies = await cookieJar.getCookies("http://www.example.com/", { - allPaths: true + const cookies = await cookieJar.getCookies('http://www.example.com/', { + allPaths: true, }) expect(cookies).toHaveLength(4) // @ts-ignore - const noCookiesWithAnOtherKeyRetrieved = cookies.every(cookie => !/^other/.test(cookie.key)) + const noCookiesWithAnOtherKeyRetrieved = cookies.every( + (cookie) => !/^other/.test(cookie.key as string), + ) expect(noCookiesWithAnOtherKeyRetrieved).toBe(true) }) }) describe('retrieving cookies', () => { beforeEach(async () => { - const url = "http://example.com/index.html" + const url = 'http://example.com/index.html' - await cookieJar.setCookie("a=1; Domain=example.com; Path=/", url) + await cookieJar.setCookie('a=1; Domain=example.com; Path=/', url) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url) + await cookieJar.setCookie( + 'b=2; Domain=example.com; Path=/; HttpOnly', + url, + ) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url) + await cookieJar.setCookie( + 'c=3; Domain=example.com; Path=/; Secure', + url, + ) jest.advanceTimersByTime(1000) // path - await cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url) + await cookieJar.setCookie('d=4; Domain=example.com; Path=/foo', url) jest.advanceTimersByTime(1000) // host only - await cookieJar.setCookie("e=5", url) + await cookieJar.setCookie('e=5', url) jest.advanceTimersByTime(1000) // other domain - await cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org") + await cookieJar.setCookie( + 'f=6; Domain=nodejs.org; Path=/', + 'http://nodejs.org', + ) jest.advanceTimersByTime(1000) // expired - await cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url) + await cookieJar.setCookie( + 'g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT', + url, + ) jest.advanceTimersByTime(1000) // expired via Max-Age - await cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url) + await cookieJar.setCookie( + 'h=8; Domain=example.com; Path=/; Max-Age=1', + url, + ) jest.advanceTimersByTime(2000) // so that 'h=8' expires }) it('should be able to get the cookies for http://nodejs.org', async () => { - const cookies = await cookieJar.getCookies("http://nodejs.org") + const cookies = await cookieJar.getCookies('http://nodejs.org') expect(cookies).toEqual([ objectContaining({ key: 'f', value: '6', path: '/', - domain: 'nodejs.org' - }) + domain: 'nodejs.org', + }), ]) }) it('should be able to get the cookies for https://example.com', async () => { - const cookies = await cookieJar.getCookies("https://example.com") + const cookies = await cookieJar.getCookies('https://example.com') expect(cookies).toEqual([ objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true + httpOnly: true, }), objectContaining({ key: 'c', value: '3', path: '/', domain: 'example.com', - secure: true + secure: true, }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for https://example.com with the secure: true option', async () => { - const cookies = await cookieJar.getCookies("https://example.com") + const cookies = await cookieJar.getCookies('https://example.com') expect(cookies).toEqual([ objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true + httpOnly: true, }), objectContaining({ key: 'c', value: '3', path: '/', domain: 'example.com', - secure: true + secure: true, }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for http://example.com', async () => { - const cookies = await cookieJar.getCookies("http://example.com") + const cookies = await cookieJar.getCookies('http://example.com') expect(cookies).toEqual([ objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true + httpOnly: true, }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for http://EXAMPlE.com (case-insensitive)', async () => { - const cookies = await cookieJar.getCookies("http://EXAMPlE.com") + const cookies = await cookieJar.getCookies('http://EXAMPlE.com') expect(cookies).toEqual([ objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true + httpOnly: true, }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for http://example.com with the http: false option', async () => { - const cookies = await cookieJar.getCookies("http://example.com", { - http: false + const cookies = await cookieJar.getCookies('http://example.com', { + http: false, }) expect(cookies).toEqual([ objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for http://example.com/foo/bar', async () => { - const cookies = await cookieJar.getCookies("http://example.com/foo/bar") + const cookies = await cookieJar.getCookies('http://example.com/foo/bar') expect(cookies).toEqual([ objectContaining({ key: 'd', value: '4', path: '/foo', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true + httpOnly: true, }), objectContaining({ key: 'e', value: '5', path: '/', - domain: 'example.com' - }) + domain: 'example.com', + }), ]) }) it('should be able to get the cookies for http://www.example.com/foo/bar', async () => { - const cookies = await cookieJar.getCookies("http://www.example.com/foo/bar") + const cookies = await cookieJar.getCookies( + 'http://www.example.com/foo/bar', + ) expect(cookies).toEqual([ objectContaining({ key: 'd', value: '4', path: '/foo', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'a', value: '1', path: '/', - domain: 'example.com' + domain: 'example.com', }), objectContaining({ key: 'b', value: '2', path: '/', domain: 'example.com', - httpOnly: true - }) + httpOnly: true, + }), ]) }) }) @@ -621,51 +703,80 @@ describe('CookieJar', () => { describe('api', () => { beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") + await cookieJar.setCookie('foo=bar', 'http://example.com') }) - apiVariants('resolves to a string', { - callbackStyle(done) { - cookieJar.getCookieString("http://example.com", (_err, result) => { - if (typeof result === 'string') { - cookieString = result - done() - } else { - fail("Result should not have been undefined") - } - }) + apiVariants( + 'resolves to a string', + { + callbackStyle(done) { + cookieJar.getCookieString('http://example.com', (_err, result) => { + if (typeof result === 'string') { + cookieString = result + done() + } else { + fail('Result should not have been undefined') + } + }) + }, + async asyncStyle() { + cookieString = await cookieJar.getCookieString('http://example.com') + }, + syncStyle() { + cookieString = cookieJar.getCookieStringSync('http://example.com') + }, }, - async asyncStyle() { - cookieString = await cookieJar.getCookieString("http://example.com") + async () => { + expect(cookieString).toBe('foo=bar') }, - syncStyle() { - cookieString = cookieJar.getCookieStringSync("http://example.com") - } - }, async () => { - expect(cookieString).toBe('foo=bar') - }) + ) }) describe('retrieving cookie strings', () => { beforeEach(async () => { - const url = "http://example.com/index.html" + const url = 'http://example.com/index.html' // @ts-ignore - const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) + const at = (timeFromNow) => ({ + now: new Date(Date.now() + timeFromNow), + }) const cookies = await Promise.all([ - cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), - cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), - cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), + cookieJar.setCookie('a=1; Domain=example.com; Path=/', url, at(0)), + cookieJar.setCookie( + 'b=2; Domain=example.com; Path=/; HttpOnly', + url, + at(1000), + ), + cookieJar.setCookie( + 'c=3; Domain=example.com; Path=/; Secure', + url, + at(2000), + ), // path - cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), + cookieJar.setCookie( + 'd=4; Domain=example.com; Path=/foo', + url, + at(3000), + ), // host only - cookieJar.setCookie("e=5", url, at(4000)), + cookieJar.setCookie('e=5', url, at(4000)), // other domain - cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), + cookieJar.setCookie( + 'f=6; Domain=nodejs.org; Path=/', + 'http://nodejs.org', + at(5000), + ), // expired - cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), + cookieJar.setCookie( + 'g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT', + url, + at(6000), + ), // expired via Max-Age - cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), + cookieJar.setCookie( + 'h=8; Domain=example.com; Path=/; Max-Age=1', + url, + ), ]) jest.advanceTimersByTime(2000) // so that 'h=8' expires @@ -674,8 +785,8 @@ describe('CookieJar', () => { }) it('be able to get the cookie string for http://example.com', async () => { - cookieString = await cookieJar.getCookieString("http://example.com") - expect(cookieString).toBe("a=1; b=2; e=5") + cookieString = await cookieJar.getCookieString('http://example.com') + expect(cookieString).toBe('a=1; b=2; e=5') }) }) }) @@ -685,48 +796,83 @@ describe('CookieJar', () => { describe('api', () => { beforeEach(async () => { - await cookieJar.setCookie("foo=bar", "http://example.com") + await cookieJar.setCookie('foo=bar', 'http://example.com') }) - apiVariants('resolves to an array of strings', { - callbackStyle(done) { - cookieJar.getSetCookieStrings("http://example.com", (_error, result) => { - // @ts-ignore - cookieHeaders = result - done() - }) + apiVariants( + 'resolves to an array of strings', + { + callbackStyle(done) { + cookieJar.getSetCookieStrings( + 'http://example.com', + (_error, result) => { + // @ts-ignore + cookieHeaders = result + done() + }, + ) + }, + async asyncStyle() { + cookieHeaders = await cookieJar.getSetCookieStrings( + 'http://example.com', + ) + }, + syncStyle() { + cookieHeaders = + cookieJar.getSetCookieStringsSync('http://example.com') + }, }, - async asyncStyle() { - cookieHeaders = await cookieJar.getSetCookieStrings("http://example.com") + async () => { + expect(cookieHeaders).toEqual(['foo=bar; Path=/']) }, - syncStyle() { - cookieHeaders = cookieJar.getSetCookieStringsSync("http://example.com") - } - }, async () => { - expect(cookieHeaders).toEqual(['foo=bar; Path=/']) - }) + ) }) describe('retrieving cookie strings', () => { beforeEach(async () => { - const url = "http://example.com/index.html" + const url = 'http://example.com/index.html' // @ts-ignore - const at = (timeFromNow) => ({now: new Date(Date.now() + timeFromNow)}) + const at = (timeFromNow) => ({ + now: new Date(Date.now() + timeFromNow), + }) const cookies = await Promise.all([ - cookieJar.setCookie("a=1; Domain=example.com; Path=/", url, at(0)), - cookieJar.setCookie("b=2; Domain=example.com; Path=/; HttpOnly", url, at(1000)), - cookieJar.setCookie("c=3; Domain=example.com; Path=/; Secure", url, at(2000)), + cookieJar.setCookie('a=1; Domain=example.com; Path=/', url, at(0)), + cookieJar.setCookie( + 'b=2; Domain=example.com; Path=/; HttpOnly', + url, + at(1000), + ), + cookieJar.setCookie( + 'c=3; Domain=example.com; Path=/; Secure', + url, + at(2000), + ), // path - cookieJar.setCookie("d=4; Domain=example.com; Path=/foo", url, at(3000)), + cookieJar.setCookie( + 'd=4; Domain=example.com; Path=/foo', + url, + at(3000), + ), // host only - cookieJar.setCookie("e=5", url, at(4000)), + cookieJar.setCookie('e=5', url, at(4000)), // other domain - cookieJar.setCookie("f=6; Domain=nodejs.org; Path=/", "http://nodejs.org", at(5000)), + cookieJar.setCookie( + 'f=6; Domain=nodejs.org; Path=/', + 'http://nodejs.org', + at(5000), + ), // expired - cookieJar.setCookie("g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT", url, at(6000)), + cookieJar.setCookie( + 'g=7; Domain=example.com; Path=/; Expires=Tue, 18 Oct 2011 00:00:00 GMT', + url, + at(6000), + ), // expired via Max-Age - cookieJar.setCookie("h=8; Domain=example.com; Path=/; Max-Age=1", url), + cookieJar.setCookie( + 'h=8; Domain=example.com; Path=/; Max-Age=1', + url, + ), ]) jest.advanceTimersByTime(2000) // so that 'h=8' expires @@ -735,11 +881,13 @@ describe('CookieJar', () => { }) it('be able to get the set-cookie header strings for http://example.com', async () => { - cookieHeaders = await cookieJar.getSetCookieStrings("http://example.com") + cookieHeaders = await cookieJar.getSetCookieStrings( + 'http://example.com', + ) expect(cookieHeaders).toEqual([ - "a=1; Domain=example.com; Path=/", - "b=2; Domain=example.com; Path=/; HttpOnly", - "e=5; Path=/" + 'a=1; Domain=example.com; Path=/', + 'b=2; Domain=example.com; Path=/; HttpOnly', + 'e=5; Path=/', ]) }) }) @@ -747,24 +895,28 @@ describe('CookieJar', () => { describe('removeAllCookies', () => { beforeEach(async () => { - await cookieJar.setCookie("a=b", "http://example.com") - await cookieJar.setCookie("c=d", "http://example.com") - expect(await cookieJar.getCookies("http://example.com")).toHaveLength(2) + await cookieJar.setCookie('a=b', 'http://example.com') + await cookieJar.setCookie('c=d', 'http://example.com') + expect(await cookieJar.getCookies('http://example.com')).toHaveLength(2) }) - apiVariants('should remove all the stored cookies', { - callbackStyle(done) { - cookieJar.removeAllCookies(() => done()) + apiVariants( + 'should remove all the stored cookies', + { + callbackStyle(done) { + cookieJar.removeAllCookies(() => done()) + }, + async asyncStyle() { + await cookieJar.removeAllCookies() + }, + syncStyle() { + cookieJar.removeAllCookiesSync() + }, }, - async asyncStyle() { - await cookieJar.removeAllCookies() + async () => { + expect(await cookieJar.getCookies('http://example.com')).toHaveLength(0) }, - syncStyle() { - cookieJar.removeAllCookiesSync() - } - }, async () => { - expect(await cookieJar.getCookies("http://example.com")).toHaveLength(0) - }) + ) }) describe('serialize', () => { @@ -773,62 +925,66 @@ describe('CookieJar', () => { beforeEach(async () => { now = new Date().toISOString() - await cookieJar.setCookie("foo=bar", "http://example.com") + await cookieJar.setCookie('foo=bar', 'http://example.com') }) - apiVariants('resolves to an array of strings', { - callbackStyle(done) { - cookieJar.serialize((_error, result) => { + apiVariants( + 'resolves to an array of strings', + { + callbackStyle(done) { + cookieJar.serialize((_error, result) => { + // @ts-ignore + data = result + done() + }) + }, + async asyncStyle() { + data = await cookieJar.serialize() + }, + syncStyle() { // @ts-ignore - data = result - done() - }) + data = cookieJar.serializeSync() + }, }, - async asyncStyle() { - data = await cookieJar.serialize() + async () => { + const expected: SerializedCookieJar = { + allowSpecialUseDomain: true, + cookies: [ + { + creation: now, + domain: 'example.com', + hostOnly: true, + key: 'foo', + lastAccessed: now, + path: '/', + pathIsDefault: true, + value: 'bar', + }, + ], + enableLooseMode: false, + prefixSecurity: 'silent', + rejectPublicSuffixes: true, + storeType: 'MemoryCookieStore', + version: 'tough-cookie@4.1.2', + } + expect(data).toEqual(expected) }, - syncStyle() { - // @ts-ignore - data = cookieJar.serializeSync() - } - }, async () => { - const expected: SerializedCookieJar = { - "allowSpecialUseDomain": true, - "cookies": [ - { - "creation": now, - "domain": "example.com", - "hostOnly": true, - "key": "foo", - "lastAccessed": now, - "path": "/", - "pathIsDefault": true, - "value": "bar" - } - ], - "enableLooseMode": false, - "prefixSecurity": "silent", - "rejectPublicSuffixes": true, - "storeType": "MemoryCookieStore", - "version": "tough-cookie@4.1.2" - } - expect(data).toEqual(expected) - }) + ) }) describe('remove cookies', () => { beforeEach(async () => { const cookiesByDomain = { - "http://example.com/index.html": [ - Cookie.parse("a=b; Domain=example.com; Path=/") + 'http://example.com/index.html': [ + Cookie.parse('a=b; Domain=example.com; Path=/'), + ], + 'http://foo.com/index.html': [ + Cookie.parse('a=b; Domain=foo.com; Path=/'), + Cookie.parse('foo=bar; Domain=foo.com; Path=/'), ], - "http://foo.com/index.html": [ - Cookie.parse("a=b; Domain=foo.com; Path=/"), - Cookie.parse("foo=bar; Domain=foo.com; Path=/") - ] } - for await (let [path, cookies] of Object.entries(cookiesByDomain)) { - for await (let cookie of cookies) { + for await (const [path, cookies] of Object.entries(cookiesByDomain)) { + for await (const cookie of cookies) { // @ts-ignore await cookieJar.setCookie(cookie, path) } @@ -837,22 +993,25 @@ describe('CookieJar', () => { it('should remove all from matching domain', async () => { // @ts-ignore - await cookieJar.store.removeCookies("example.com", null) + await cookieJar.store.removeCookies('example.com', null) - const exampleCookies = await cookieJar.store.findCookies("example.com", null) + const exampleCookies = await cookieJar.store.findCookies( + 'example.com', + null, + ) expect(exampleCookies).toHaveLength(0) - const fooCookies = await cookieJar.store.findCookies("foo.com", null) + const fooCookies = await cookieJar.store.findCookies('foo.com', null) expect(fooCookies).toHaveLength(2) }) it('should remove all with matching domain and key', async () => { - await cookieJar.store.removeCookie("foo.com", "/", "foo") - const cookies = await cookieJar.store.findCookies("foo.com", null) + await cookieJar.store.removeCookie('foo.com', '/', 'foo') + const cookies = await cookieJar.store.findCookies('foo.com', null) expect(cookies).toEqual([ objectContaining({ - key: 'a' - }) + key: 'a', + }), ]) }) }) @@ -860,66 +1019,79 @@ describe('CookieJar', () => { it('should allow cookies with the same name under different domains and/or paths', async () => { const cookieJar = new CookieJar() - const url = "http://www.example.com/" + const url = 'http://www.example.com/' - await cookieJar.setCookie("aaaa=xxxx; Domain=www.example.com", url) + await cookieJar.setCookie('aaaa=xxxx; Domain=www.example.com', url) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("aaaa=1111; Domain=www.example.com", url) + await cookieJar.setCookie('aaaa=1111; Domain=www.example.com', url) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("aaaa=yyyy; Domain=example.com", url) + await cookieJar.setCookie('aaaa=yyyy; Domain=example.com', url) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("aaaa=2222; Domain=example.com", url) + await cookieJar.setCookie('aaaa=2222; Domain=example.com', url) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("aaaa=zzzz; Domain=www.example.com; Path=/pathA", url) + await cookieJar.setCookie( + 'aaaa=zzzz; Domain=www.example.com; Path=/pathA', + url, + ) jest.advanceTimersByTime(1000) - await cookieJar.setCookie("aaaa=3333; Domain=www.example.com; Path=/pathA", url) + await cookieJar.setCookie( + 'aaaa=3333; Domain=www.example.com; Path=/pathA', + url, + ) jest.advanceTimersByTime(1000) - const cookies = await cookieJar.getCookies("http://www.example.com/pathA") + const cookies = await cookieJar.getCookies('http://www.example.com/pathA') // may break with sorting; sorting should put 3333 first due to longest path expect(cookies).toEqual([ - objectContaining({value: '3333'}), - objectContaining({value: '1111'}), - objectContaining({value: '2222'}), + objectContaining({ value: '3333' }), + objectContaining({ value: '1111' }), + objectContaining({ value: '2222' }), ]) }) describe('setCookie errors', () => { it('should throw an error if domain is set to a public suffix', async () => { const cookieJar = new CookieJar() - await expect(cookieJar.setCookie( - "i=9; Domain=kyoto.jp; Path=/", - "kyoto.jp" - )).rejects.toThrowError("Cookie has domain set to a public suffix") + await expect( + cookieJar.setCookie('i=9; Domain=kyoto.jp; Path=/', 'kyoto.jp'), + ).rejects.toThrowError('Cookie has domain set to a public suffix') }) it('should throw an error if domains do not match', async () => { const cookieJar = new CookieJar() - await expect(cookieJar.setCookie( - "j=10; Domain=google.com; Path=/", - "http://google.ca" - )).rejects.toThrowError(`Cookie not in this host's domain. Cookie:google.com Request:google.ca`) + await expect( + cookieJar.setCookie( + 'j=10; Domain=google.com; Path=/', + 'http://google.ca', + ), + ).rejects.toThrowError( + `Cookie not in this host's domain. Cookie:google.com Request:google.ca`, + ) }) it('should throw an error if trying to overwrite an http cookie with a non-http one', async () => { const cookieJar = new CookieJar() const httpCookie = await cookieJar.setCookie( - "k=11; Domain=example.ca; Path=/; HttpOnly", - "http://example.ca", - { http: true } + 'k=11; Domain=example.ca; Path=/; HttpOnly', + 'http://example.ca', + { http: true }, ) - await expect(cookieJar.setCookie( - "k=12; Domain=example.ca; Path=/", - "http://example.ca", - { http: false } - )).rejects.toThrowError("old Cookie is HttpOnly and this isn't an HTTP API") - - const cookies = await cookieJar.getCookies("http://example.ca", { http: true }) + await expect( + cookieJar.setCookie( + 'k=12; Domain=example.ca; Path=/', + 'http://example.ca', + { http: false }, + ), + ).rejects.toThrowError("old Cookie is HttpOnly and this isn't an HTTP API") + + const cookies = await cookieJar.getCookies('http://example.ca', { + http: true, + }) expect(cookies).toEqual([httpCookie]) }) }) @@ -927,16 +1099,13 @@ describe('setCookie errors', () => { describe('loose mode', () => { it('should accept a cookie in loose mode', async () => { const cookieJar = new CookieJar(null, { looseMode: true }) - await cookieJar.setCookie( - "FooBar", - "http://www.foonet.net" - ) - const cookies = await cookieJar.getCookies("http://www.foonet.net") + await cookieJar.setCookie('FooBar', 'http://www.foonet.net') + const cookies = await cookieJar.getCookies('http://www.foonet.net') expect(cookies).toEqual([ objectContaining({ - key: "", - value: "FooBar" - }) + key: '', + value: 'FooBar', + }), ]) }) @@ -944,27 +1113,28 @@ describe('loose mode', () => { const cookieJar = new CookieJar(null, { looseMode: true }) // @ts-ignore const clonedCookieJar = CookieJar.fromJSON(cookieJar.toJSON()) - await clonedCookieJar.setCookie( - "FooBar", - "http://www.foonet.net" - ) - const cookies = await clonedCookieJar.getCookies("http://www.foonet.net") + await clonedCookieJar.setCookie('FooBar', 'http://www.foonet.net') + const cookies = await clonedCookieJar.getCookies('http://www.foonet.net') expect(cookies).toEqual([ objectContaining({ - key: "", - value: "FooBar" - }) + key: '', + value: 'FooBar', + }), ]) }) }) it('should fix issue #132', async () => { const cookieJar = new CookieJar() - await expect(cookieJar.setCookie( - // @ts-ignore - { key: "x", value: "y" }, - "http://example.com/" - )).rejects.toThrowError("First argument to setCookie must be a Cookie object or string") + await expect( + cookieJar.setCookie( + // @ts-ignore + { key: 'x', value: 'y' }, + 'http://example.com/', + ), + ).rejects.toThrowError( + 'First argument to setCookie must be a Cookie object or string', + ) }) // TODO: what is this test doing? how does this parse? @@ -977,15 +1147,15 @@ it('should fix issue #144', async () => { >G@J?I??B@C>>LAH?GCGJ@FMEGHBGAF; expires=Sun, 31-Jan-9021 02:39:04 GMT; path=/; Secure; HttpOnly, FirstReferrer=; expires=Fri, 31-Jan-9020 20:50:44 GMT; path=/` - await cookieJar.setCookie(cookieString, "https://google.com") - const cookies = await cookieJar.getCookies("https://google.com") + await cookieJar.setCookie(cookieString, 'https://google.com') + const cookies = await cookieJar.getCookies('https://google.com') expect(cookies).toEqual([ objectContaining({ key: 'AWSELB', value: '69b2c0038b16e8e27056d1178e0d556c', path: '/', - secure: true - }) + secure: true, + }), ]) }) @@ -994,186 +1164,207 @@ it('should fix issue #145 - missing 2nd url parameter', async () => { const cookieJar = new CookieJar() try { // @ts-ignore - await cookieJar.setCookie("x=y; Domain=example.com; Path=/") - } catch(e) { + await cookieJar.setCookie('x=y; Domain=example.com; Path=/') + } catch (e) { expect(e).toBeInstanceOf(ParameterError) } }) it('should fix issue #197 - CookieJar().setCookie throws an error when empty cookie is passed', async () => { const cookieJar = new CookieJar() - await expect(cookieJar.setCookie( - "", - "https://google.com" - )).rejects.toThrowError('Cookie failed to parse') + await expect( + cookieJar.setCookie('', 'https://google.com'), + ).rejects.toThrowError('Cookie failed to parse') }) // special use domains under a sub-domain -describe.each([ - "local", - "example", - "invalid", - "localhost", - "test" -])('when special use domain is dev.%s', (specialUseDomain) => { - it('should allow special domain cookies if allowSpecialUseDomain is set to the default value', async () => { - const cookieJar = new CookieJar() - const cookie = await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, - `http://dev.${specialUseDomain}` - ) - expect(cookie).toEqual(objectContaining({ - key: 'settingThisShouldPass', - value: 'true', - domain: `dev.${specialUseDomain}` - })) - const cookies = await cookieJar.getCookies(`http://dev.${specialUseDomain}`, { - http: true - }) - expect(cookies).toEqual([cookie]) - }) - - it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { - const cookieJar = new CookieJar(new MemoryCookieStore(), { - rejectPublicSuffixes: true, - allowSpecialUseDomain: true - }) - const cookie = await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, - `http://dev.${specialUseDomain}` - ) - expect(cookie).toEqual(objectContaining({ - key: 'settingThisShouldPass', - value: 'true', - domain: `dev.${specialUseDomain}` - })) - const cookies = await cookieJar.getCookies(`http://dev.${specialUseDomain}`, { - http: true - }) - expect(cookies).toEqual([cookie]) - }) - - it('should reject special domain cookies if allowSpecialUseDomain: false', async () => { - assertions(1) - const cookieJar = new CookieJar(new MemoryCookieStore(), { - rejectPublicSuffixes: true, - allowSpecialUseDomain: false - }) - try { - await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, - `http://dev.${specialUseDomain}` - ) - } catch(e) { - // @ts-ignore - expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) - } - }) -}) - -// special use domains under the top-level domain -describe.each([ - "local", - "example", - "invalid", - "localhost", - "test" -])('when special use domain is %s', (specialUseDomain) => { - // the restriction on special use domains at the top-level is loosened for - // the following domains due to legacy behavior - const isAllowed = ['localhost', 'invalid'].includes(specialUseDomain) - - if (isAllowed) { +describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( + 'when special use domain is dev.%s', + (specialUseDomain) => { it('should allow special domain cookies if allowSpecialUseDomain is set to the default value', async () => { const cookieJar = new CookieJar() const cookie = await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, - `http://${specialUseDomain}` + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}`, + ) + expect(cookie).toEqual( + objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `dev.${specialUseDomain}`, + }), + ) + const cookies = await cookieJar.getCookies( + `http://dev.${specialUseDomain}`, + { + http: true, + }, ) - expect(cookie).toEqual(objectContaining({ - key: 'settingThisShouldPass', - value: 'true', - domain: `${specialUseDomain}` - })) - const cookies = await cookieJar.getCookies(`http://${specialUseDomain}`, { - http: true + expect(cookies).toEqual([cookie]) + }) + + it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true, }) + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}`, + ) + expect(cookie).toEqual( + objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `dev.${specialUseDomain}`, + }), + ) + const cookies = await cookieJar.getCookies( + `http://dev.${specialUseDomain}`, + { + http: true, + }, + ) expect(cookies).toEqual([cookie]) }) - } else { - it('should reject special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + + it('should reject special domain cookies if allowSpecialUseDomain: false', async () => { assertions(1) - const cookieJar = new CookieJar() + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: false, + }) try { await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, - `http://${specialUseDomain}` + `settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`, + `http://dev.${specialUseDomain}`, ) - } catch(e) { + } catch (e) { // @ts-ignore - expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + expect(e.message).toBe( + `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, + ) } }) - } + }, +) - if (isAllowed) { - it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { - const cookieJar = new CookieJar(new MemoryCookieStore(), { - rejectPublicSuffixes: true, - allowSpecialUseDomain: true +// special use domains under the top-level domain +describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( + 'when special use domain is %s', + (specialUseDomain) => { + // the restriction on special use domains at the top-level is loosened for + // the following domains due to legacy behavior + const isAllowed = ['localhost', 'invalid'].includes(specialUseDomain) + + if (isAllowed) { + it('should allow special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + const cookieJar = new CookieJar() + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}`, + ) + expect(cookie).toEqual( + objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `${specialUseDomain}`, + }), + ) + const cookies = await cookieJar.getCookies( + `http://${specialUseDomain}`, + { + http: true, + }, + ) + expect(cookies).toEqual([cookie]) }) - const cookie = await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, - `http://${specialUseDomain}` - ) - expect(cookie).toEqual(objectContaining({ - key: 'settingThisShouldPass', - value: 'true', - domain: `${specialUseDomain}` - })) - const cookies = await cookieJar.getCookies(`http://${specialUseDomain}`, { - http: true + } else { + it('should reject special domain cookies if allowSpecialUseDomain is set to the default value', async () => { + assertions(1) + const cookieJar = new CookieJar() + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}`, + ) + } catch (e) { + // @ts-ignore + expect(e.message).toBe( + `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, + ) + } }) - expect(cookies).toEqual([cookie]) - }) - } else { + } + + if (isAllowed) { + it('should allow special domain cookies if allowSpecialUseDomain: true', async () => { + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true, + }) + const cookie = await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}`, + ) + expect(cookie).toEqual( + objectContaining({ + key: 'settingThisShouldPass', + value: 'true', + domain: `${specialUseDomain}`, + }), + ) + const cookies = await cookieJar.getCookies( + `http://${specialUseDomain}`, + { + http: true, + }, + ) + expect(cookies).toEqual([cookie]) + }) + } else { + it('should reject special domain cookies if allowSpecialUseDomain: true', async () => { + assertions(1) + const cookieJar = new CookieJar(new MemoryCookieStore(), { + rejectPublicSuffixes: true, + allowSpecialUseDomain: true, + }) + try { + await cookieJar.setCookie( + `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, + `http://${specialUseDomain}`, + ) + } catch (e) { + // @ts-ignore + expect(e.message).toBe( + `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, + ) + } + }) + } + it('should reject special domain cookies if allowSpecialUseDomain: true', async () => { assertions(1) const cookieJar = new CookieJar(new MemoryCookieStore(), { rejectPublicSuffixes: true, - allowSpecialUseDomain: true + allowSpecialUseDomain: false, }) try { await cookieJar.setCookie( `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, - `http://${specialUseDomain}` + `http://${specialUseDomain}`, ) - } catch(e) { + } catch (e) { // @ts-ignore - expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) + expect(e.message).toBe( + `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, + ) } }) - } + }, +) - it('should reject special domain cookies if allowSpecialUseDomain: true', async () => { - assertions(1) - const cookieJar = new CookieJar(new MemoryCookieStore(), { - rejectPublicSuffixes: true, - allowSpecialUseDomain: false - }) - try { - await cookieJar.setCookie( - `settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`, - `http://${specialUseDomain}` - ) - } catch(e) { - // @ts-ignore - expect(e.message).toBe(`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`) - } - }) -}) - -describe("Synchronous API on async CookieJar", () => { +describe('Synchronous API on async CookieJar', () => { let store: Store beforeEach(() => { @@ -1182,30 +1373,38 @@ describe("Synchronous API on async CookieJar", () => { it('should throw an error when calling `setCookieSync` if store is not synchronous', () => { const cookieJar = new CookieJar(store) - expect(() => cookieJar.setCookieSync("a=b", "http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + expect(() => + cookieJar.setCookieSync('a=b', 'http://example.com/index.html'), + ).toThrow('CookieJar store is not synchronous; use async API instead.') }) it('should throw an error when calling `getCookieSync` if store is not synchronous', () => { const cookieJar = new CookieJar(store) - expect(() => cookieJar.getCookiesSync("http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + expect(() => + cookieJar.getCookiesSync('http://example.com/index.html'), + ).toThrow('CookieJar store is not synchronous; use async API instead.') }) it('should throw an error when calling `getSetCookieStringsSync` if store is not synchronous', () => { const cookieJar = new CookieJar(store) - expect(() => cookieJar.getSetCookieStringsSync("http://example.com/index.html")).toThrow("CookieJar store is not synchronous; use async API instead.") + expect(() => + cookieJar.getSetCookieStringsSync('http://example.com/index.html'), + ).toThrow('CookieJar store is not synchronous; use async API instead.') }) it('should throw an error when calling `removeAllCookiesSync` if store is not synchronous', () => { const cookieJar = new CookieJar(store) - expect(() => cookieJar.removeAllCookiesSync()).toThrow("CookieJar store is not synchronous; use async API instead.") + expect(() => cookieJar.removeAllCookiesSync()).toThrow( + 'CookieJar store is not synchronous; use async API instead.', + ) }) }) function createCookie( cookieString: string, options: { - hostOnly?: boolean; - } = {} + hostOnly?: boolean + } = {}, ): Cookie { const cookie = Cookie.parse(cookieString) if (options?.hostOnly) { @@ -1216,9 +1415,15 @@ function createCookie( return cookie } -function apiVariants (testName: string, apiVariants: ApiVariants, assertions: () => Promise) { +function apiVariants( + testName: string, + apiVariants: ApiVariants, + assertions: () => Promise, +) { it(`${testName} (callback)`, async () => { - await new Promise(resolve => apiVariants.callbackStyle(() => resolve(undefined))) + await new Promise((resolve) => + apiVariants.callbackStyle(() => resolve(undefined)), + ) await assertions() }) @@ -1238,7 +1443,7 @@ type PromiseApiVariant = () => Promise type SyncApiVariant = () => void interface ApiVariants { - callbackStyle: CallbackApiVariant, - asyncStyle: PromiseApiVariant, + callbackStyle: CallbackApiVariant + asyncStyle: PromiseApiVariant syncStyle: SyncApiVariant } diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index d79cfe1d..e68f0dfc 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -1,6 +1,6 @@ -import {CookieJar, PrefixSecurityEnum} from "../cookie"; +import { CookieJar, PrefixSecurityEnum } from '../cookie' -const {objectContaining} = expect +const { objectContaining } = expect let cookieJar: CookieJar let insecureUrl = 'http://www.example.com' @@ -10,7 +10,7 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('silent', () => { beforeEach(() => { cookieJar = new CookieJar(null, { - prefixSecurity: 'silent' + prefixSecurity: 'silent', }) expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.SILENT) }) @@ -18,9 +18,9 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('__Secure prefix', () => { it('should fail silently with no Secure attribute', async () => { await cookieJar.setCookie( - "__Secure-SID=12345; Domain=example.com", + '__Secure-SID=12345; Domain=example.com', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) @@ -28,24 +28,24 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { it('should work if cookie has Secure attribute and domain is https', async () => { await cookieJar.setCookie( - "__Secure-SID=12345; Domain=example.com; Secure", + '__Secure-SID=12345; Domain=example.com; Secure', secureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(secureUrl) expect(cookies).toEqual([ objectContaining({ key: '__Secure-SID', - value: '12345' - }) + value: '12345', + }), ]) }) it('should fail silently if cookie has Secure attribute but domain is http', async () => { await cookieJar.setCookie( - "__Secure-SID=12345; Domain=example.com; Secure", + '__Secure-SID=12345; Domain=example.com; Secure', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) @@ -54,30 +54,22 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('__Host prefix', () => { it('should fail silently when no Secure attribute, Domain, or Path', async () => { - await cookieJar.setCookie( - "__Host-SID=12345", - insecureUrl, - {} - ) + await cookieJar.setCookie('__Host-SID=12345', insecureUrl, {}) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) }) it('should fail silently when no Domain or Path', async () => { - await cookieJar.setCookie( - "__Host-SID=12345; Secure", - insecureUrl, - {} - ) + await cookieJar.setCookie('__Host-SID=12345; Secure', insecureUrl, {}) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) }) it('should fail silently when no Path', async () => { await cookieJar.setCookie( - "__Host-SID=12345; Secure; Domain=example.com", + '__Host-SID=12345; Secure; Domain=example.com', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) @@ -85,9 +77,9 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { it('should fail silently with Domain', async () => { await cookieJar.setCookie( - "__Host-SID=12345; Secure; Domain=example.com; Path=/", + '__Host-SID=12345; Secure; Domain=example.com; Path=/', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([]) @@ -95,16 +87,16 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { it('should work with Secure and Path but no Domain over https', async () => { await cookieJar.setCookie( - "__Host-SID=12345; Secure; Path=/", + '__Host-SID=12345; Secure; Path=/', secureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(secureUrl) expect(cookies).toEqual([ objectContaining({ key: '__Host-SID', - value: '12345' - }) + value: '12345', + }), ]) }) }) @@ -113,7 +105,7 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('strict', () => { beforeEach(() => { cookieJar = new CookieJar(null, { - prefixSecurity: 'strict' + prefixSecurity: 'strict', }) expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.STRICT) }) @@ -121,50 +113,58 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('__Secure prefix', () => { it('should work for a valid cookie', async () => { await cookieJar.setCookie( - "__Secure-SID=12345; Secure; Domain=example.com", + '__Secure-SID=12345; Secure; Domain=example.com', secureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(secureUrl) expect(cookies).toEqual([ objectContaining({ key: '__Secure-SID', - value: '12345' - }) + value: '12345', + }), ]) }) it('should error for an invalid cookie', async () => { - await expect(cookieJar.setCookie( - "__Secure-SID=12345; Domain=example.com", - insecureUrl, - {} - )).rejects.toThrowError('Cookie has __Secure prefix but Secure attribute is not set') + await expect( + cookieJar.setCookie( + '__Secure-SID=12345; Domain=example.com', + insecureUrl, + {}, + ), + ).rejects.toThrowError( + 'Cookie has __Secure prefix but Secure attribute is not set', + ) }) }) describe('__Host prefix', () => { it('should work for a valid cookie', async () => { await cookieJar.setCookie( - "___Host-SID=12345; Secure; Path=/", + '___Host-SID=12345; Secure; Path=/', secureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(secureUrl) expect(cookies).toEqual([ objectContaining({ key: '___Host-SID', - value: '12345' - }) + value: '12345', + }), ]) }) it('should error for an invalid cookie', async () => { - await expect(cookieJar.setCookie( - "__Host-SID=12345; Secure; Domain=example.com", - secureUrl, - {} - )).rejects.toThrowError(`Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'`) + await expect( + cookieJar.setCookie( + '__Host-SID=12345; Secure; Domain=example.com', + secureUrl, + {}, + ), + ).rejects.toThrowError( + `Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'`, + ) }) }) }) @@ -172,7 +172,7 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('disabled', () => { beforeEach(() => { cookieJar = new CookieJar(null, { - prefixSecurity: 'unsafe-disabled' + prefixSecurity: 'unsafe-disabled', }) expect(cookieJar.prefixSecurity).toBe(PrefixSecurityEnum.DISABLED) }) @@ -180,16 +180,16 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('__Secure prefix', () => { it('does not fail', async () => { await cookieJar.setCookie( - "__Secure-SID=12345; Domain=example.com", + '__Secure-SID=12345; Domain=example.com', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([ objectContaining({ key: '__Secure-SID', - value: '12345' - }) + value: '12345', + }), ]) }) }) @@ -197,16 +197,16 @@ describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('__Host prefix', () => { it('does not fail', async () => { await cookieJar.setCookie( - "__Host-SID=12345; Domain=example.com", + '__Host-SID=12345; Domain=example.com', insecureUrl, - {} + {}, ) const cookies = await cookieJar.getCookies(insecureUrl) expect(cookies).toEqual([ objectContaining({ key: '__Host-SID', - value: '12345' - }) + value: '12345', + }), ]) }) }) diff --git a/lib/__tests__/cookieSorting.spec.ts b/lib/__tests__/cookieSorting.spec.ts index e0ad071f..6df39917 100644 --- a/lib/__tests__/cookieSorting.spec.ts +++ b/lib/__tests__/cookieSorting.spec.ts @@ -1,4 +1,4 @@ -import {Cookie, cookieCompare, CookieJar} from "../cookie"; +import { Cookie, cookieCompare, CookieJar } from '../cookie' jest.useFakeTimers() @@ -34,12 +34,20 @@ describe('Cookie sorting', () => { it('should preserve the creation index during update with setCookie', async () => { const cookieJar = new CookieJar() - const cookie = new Cookie({ key: 'k', value: 'v1', domain: 'example.com' }) + const cookie = new Cookie({ + key: 'k', + value: 'v1', + domain: 'example.com', + }) const { creationIndex } = cookie await cookieJar.setCookie(cookie, 'http://example.com/') expect(cookie.creationIndex).toBe(creationIndex) - const updatedCookie = new Cookie({ key: 'k', value: 'v2', domain: 'example.com' }) + const updatedCookie = new Cookie({ + key: 'k', + value: 'v2', + domain: 'example.com', + }) await cookieJar.setCookie(updatedCookie, 'http://example.com/') expect(cookie.creationIndex).toBe(updatedCookie.creationIndex) }) @@ -51,9 +59,26 @@ describe('Cookie sorting', () => { new Cookie({ key: 'b', value: '' }), new Cookie({ key: 'c', value: '', path: '/path' }), new Cookie({ key: 'd', value: '', path: '/path' }), - new Cookie({ key: 'e', value: '', path: '/longer/path', creation: new Date(Date.now() + 1) }), - new Cookie({ key: 'f', value: '', path: '/longer/path', creation: new Date(Date.now() + 2) }), + new Cookie({ + key: 'e', + value: '', + path: '/longer/path', + creation: new Date(Date.now() + 1), + }), + new Cookie({ + key: 'f', + value: '', + path: '/longer/path', + creation: new Date(Date.now() + 2), + }), ].sort(cookieCompare) - expect(cookies.map(cookie => cookie.key)).toEqual(['e', 'f', 'c', 'd', 'a', 'b']) + expect(cookies.map((cookie) => cookie.key)).toEqual([ + 'e', + 'f', + 'c', + 'd', + 'a', + 'b', + ]) }) }) diff --git a/lib/__tests__/cookieToAndFromJson.spec.ts b/lib/__tests__/cookieToAndFromJson.spec.ts index a7f330f8..c5622493 100644 --- a/lib/__tests__/cookieToAndFromJson.spec.ts +++ b/lib/__tests__/cookieToAndFromJson.spec.ts @@ -1,19 +1,21 @@ -import {Cookie} from "../cookie"; +import { Cookie } from '../cookie' jest.useFakeTimers() describe('Cookie.toJSON()', () => { it('should serialize a cookie to JSON', () => { - const cookie = Cookie.parse("alpha=beta; Domain=example.com; Path=/foo; Expires=Tue, 19 Jan 2038 03:14:07 GMT; HttpOnly") + const cookie = Cookie.parse( + 'alpha=beta; Domain=example.com; Path=/foo; Expires=Tue, 19 Jan 2038 03:14:07 GMT; HttpOnly', + ) // @ts-ignore expect(cookie.toJSON()).toEqual({ - "creation": new Date().toISOString(), - "domain": "example.com", - "expires": "2038-01-19T03:14:07.000Z", - "httpOnly": true, - "key": "alpha", - "path": "/foo", - "value": "beta", + creation: new Date().toISOString(), + domain: 'example.com', + expires: '2038-01-19T03:14:07.000Z', + httpOnly: true, + key: 'alpha', + path: '/foo', + value: 'beta', }) }) }) @@ -21,24 +23,26 @@ describe('Cookie.toJSON()', () => { describe('Cookie.fromJSON()', () => { it('should deserialize a cookie from JSON', () => { const json = JSON.stringify({ - "key": "alpha", - "value": "beta", - "domain": "example.com", - "path": "/foo", - "expires": "2038-01-19T03:14:07.000Z", - "httpOnly": true, - "lastAccessed": 2000000000123 + key: 'alpha', + value: 'beta', + domain: 'example.com', + path: '/foo', + expires: '2038-01-19T03:14:07.000Z', + httpOnly: true, + lastAccessed: 2000000000123, }) - expect(Cookie.fromJSON(json)).toEqual(new Cookie({ - "creation": new Date(), - "domain": "example.com", - "expires": new Date(Date.parse("2038-01-19T03:14:07.000Z")), - "httpOnly": true, - "key": "alpha", - "path": "/foo", - "value": "beta", - "lastAccessed": new Date(2000000000123) - })) + expect(Cookie.fromJSON(json)).toEqual( + new Cookie({ + creation: new Date(), + domain: 'example.com', + expires: new Date(Date.parse('2038-01-19T03:14:07.000Z')), + httpOnly: true, + key: 'alpha', + path: '/foo', + value: 'beta', + lastAccessed: new Date(2000000000123), + }), + ) }) it('should be able to handle a null value deserialization', () => { @@ -47,15 +51,15 @@ describe('Cookie.fromJSON()', () => { it('should be able to handle expiry, creation, or lastAccessed with Infinity during deserialization', () => { const json = JSON.stringify({ - "expires": "Infinity", - "creation": "Infinity", - "lastAccessed": "Infinity", + expires: 'Infinity', + creation: 'Infinity', + lastAccessed: 'Infinity', }) // @ts-ignore - expect(Cookie.fromJSON(json).expires).toBe("Infinity") + expect(Cookie.fromJSON(json).expires).toBe('Infinity') // @ts-ignore - expect(Cookie.fromJSON(json).creation).toBe("Infinity") + expect(Cookie.fromJSON(json).creation).toBe('Infinity') // @ts-ignore - expect(Cookie.fromJSON(json).lastAccessed).toBe("Infinity") + expect(Cookie.fromJSON(json).lastAccessed).toBe('Infinity') }) }) diff --git a/lib/__tests__/cookieToString.spec.ts b/lib/__tests__/cookieToString.spec.ts index db7a5550..52c6b47e 100644 --- a/lib/__tests__/cookieToString.spec.ts +++ b/lib/__tests__/cookieToString.spec.ts @@ -1,44 +1,48 @@ -import {Cookie} from "../cookie"; +import { Cookie } from '../cookie' describe('Cookie.toString()', () => { it('should produce a string from a simple cookie', () => { // @ts-ignore - expect(Cookie.parse("a=b").toString()).toBe('a=b') + expect(Cookie.parse('a=b').toString()).toBe('a=b') }) it('should trim spaces from the cookie value', () => { // @ts-ignore - expect(Cookie.parse("a= b ").toString()).toBe('a=b') + expect(Cookie.parse('a= b ').toString()).toBe('a=b') }) it('should produce a string with an empty value and an attribute', () => { // @ts-ignore - expect(Cookie.parse("a=;HttpOnly").toString()).toBe('a=; HttpOnly') + expect(Cookie.parse('a=;HttpOnly').toString()).toBe('a=; HttpOnly') }) it('should produce a string from a cookie with several attributes', () => { expect( // @ts-ignore - Cookie.parse("a=b;Expires=Tue, 18 Oct 2011 07:05:03 GMT;Max-Age=12345;Domain=example.com;Path=/foo;Secure;HttpOnly;MyExtension").toString() - ).toBe('a=b; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Max-Age=12345; Domain=example.com; Path=/foo; Secure; HttpOnly; MyExtension') + Cookie.parse( + 'a=b;Expires=Tue, 18 Oct 2011 07:05:03 GMT;Max-Age=12345;Domain=example.com;Path=/foo;Secure;HttpOnly;MyExtension', + ).toString(), + ).toBe( + 'a=b; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Max-Age=12345; Domain=example.com; Path=/foo; Secure; HttpOnly; MyExtension', + ) }) it('should not include the domain on a host-only cookie', () => { const cookie = new Cookie({ - key: "a", - value: "b", + key: 'a', + value: 'b', hostOnly: true, - domain: "shouldnt-stringify.example.com", - path: "/should-stringify" + domain: 'shouldnt-stringify.example.com', + path: '/should-stringify', }) expect(cookie.toString()).toBe('a=b; Path=/should-stringify') }) it('should output the right expires date when minutes are 10', () => { const cookie = new Cookie({ - key: "a", - value: "b", - expires: new Date(1284113410000) + key: 'a', + value: 'b', + expires: new Date(1284113410000), }) expect(cookie.toString()).toBe('a=b; Expires=Fri, 10 Sep 2010 10:10:10 GMT') }) diff --git a/lib/__tests__/date.spec.ts b/lib/__tests__/date.spec.ts index b8fdc10f..0252da8c 100644 --- a/lib/__tests__/date.spec.ts +++ b/lib/__tests__/date.spec.ts @@ -37,140 +37,151 @@ type EquivalenceDateParsingTestCase = { [key: string]: string } -const {parseDate} = require("../cookie"); +const { parseDate } = require('../cookie') const dateTests: DateParsingTestCase = { - "Wed, 09 Jun 2021 10:18:14 GMT": true, - "Wed, 09 JUN 2021 10:18:14 GMT": true, - "Wed, 09 Jun 2021 22:18:14 GMT": true, - "Tue, 18 Oct 2011 07:42:42.123 GMT": true, - "18 Oct 2011 07:42:42 GMT": true, - "8 Oct 2011 7:42:42 GMT": true, - "8 Oct 2011 7:2:42 GMT": true, - "8 Oct 2011 7:2:2 GMT": true, - "Oct 18 2011 07:42:42 GMT": true, - "Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)": true, - "09 Jun 2021 10:18:14 GMT": true, - "99 Jix 3038 48:86:72 ZMT": false, - "01 Jan 1970 00:00:00 GMT": true, - "01 Jan 1600 00:00:00 GMT": false, // before 1601 - "01 Jan 1601 00:00:00 GMT": true, - "10 Feb 81 13:00:00 GMT": true, // implicit year - "Thu, 17-Apr-2014 02:12:29 GMT": true, // dashes - "Thu, 17-Apr-2014 02:12:29 UTC": true, // dashes and UTC + 'Wed, 09 Jun 2021 10:18:14 GMT': true, + 'Wed, 09 JUN 2021 10:18:14 GMT': true, + 'Wed, 09 Jun 2021 22:18:14 GMT': true, + 'Tue, 18 Oct 2011 07:42:42.123 GMT': true, + '18 Oct 2011 07:42:42 GMT': true, + '8 Oct 2011 7:42:42 GMT': true, + '8 Oct 2011 7:2:42 GMT': true, + '8 Oct 2011 7:2:2 GMT': true, + 'Oct 18 2011 07:42:42 GMT': true, + 'Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)': true, + '09 Jun 2021 10:18:14 GMT': true, + '99 Jix 3038 48:86:72 ZMT': false, + '01 Jan 1970 00:00:00 GMT': true, + '01 Jan 1600 00:00:00 GMT': false, // before 1601 + '01 Jan 1601 00:00:00 GMT': true, + '10 Feb 81 13:00:00 GMT': true, // implicit year + 'Thu, 17-Apr-2014 02:12:29 GMT': true, // dashes + 'Thu, 17-Apr-2014 02:12:29 UTC': true, // dashes and UTC // garbage after parts: - "Wedxxx, 09 Jun 2021 10:18:14 GMT": true, // day of week doesn't matter - "Wed, 09e9 Jun 2021 10:18:14 GMT": true, // garbage after day ignored - "Wed, 09 Junxxx 2021 10:18:14 GMT": true, // prefix match on month - "Wed, 09 Jun 2021e9 10:18:14 GMT": true, // garbage after year OK - "Wed, 09 Jun 2021 10e9:18:14 GMT": false, // can't have garbage after HH - "Wed, 09 Jun 2021 10:18e9:14 GMT": false, // can't have garbage after MM - "Wed, 09 Jun 2021 10:18:14e9 GMT": true, // garbage after SS ignored + 'Wedxxx, 09 Jun 2021 10:18:14 GMT': true, // day of week doesn't matter + 'Wed, 09e9 Jun 2021 10:18:14 GMT': true, // garbage after day ignored + 'Wed, 09 Junxxx 2021 10:18:14 GMT': true, // prefix match on month + 'Wed, 09 Jun 2021e9 10:18:14 GMT': true, // garbage after year OK + 'Wed, 09 Jun 2021 10e9:18:14 GMT': false, // can't have garbage after HH + 'Wed, 09 Jun 2021 10:18e9:14 GMT': false, // can't have garbage after MM + 'Wed, 09 Jun 2021 10:18:14e9 GMT': true, // garbage after SS ignored // extra digit in time parts: - "Thu, 01 Jan 1970 000:00:01 GMT": false, - "Thu, 01 Jan 1970 00:000:01 GMT": false, - "Thu, 01 Jan 1970 00:00:010 GMT": false, + 'Thu, 01 Jan 1970 000:00:01 GMT': false, + 'Thu, 01 Jan 1970 00:000:01 GMT': false, + 'Thu, 01 Jan 1970 00:00:010 GMT': false, // hex in time - "Wed, 09 Jun 2021 1a:33:44 GMT": false, - "Wed, 09 Jun 2021 a1:33:44 GMT": false, - "Wed, 09 Jun 2021 11:f3:44 GMT": false, - "Wed, 09 Jun 2021 11:3f:44 GMT": false, - "Wed, 09 Jun 2021 11:33:e4 GMT": false, - "Wed, 09 Jun 2021 11:33:4e GMT": true, // garbage after seconds is OK + 'Wed, 09 Jun 2021 1a:33:44 GMT': false, + 'Wed, 09 Jun 2021 a1:33:44 GMT': false, + 'Wed, 09 Jun 2021 11:f3:44 GMT': false, + 'Wed, 09 Jun 2021 11:3f:44 GMT': false, + 'Wed, 09 Jun 2021 11:33:e4 GMT': false, + 'Wed, 09 Jun 2021 11:33:4e GMT': true, // garbage after seconds is OK // negatives in time - "Wed, 09 Jun 2021 -1:33:44 GMT": true, // parses as 1:33; - is a delimiter - "Wed, 09 Jun 2021 11:-3:44 GMT": false, - "Wed, 09 Jun 2021 11:33:-4 GMT": false, + 'Wed, 09 Jun 2021 -1:33:44 GMT': true, // parses as 1:33; - is a delimiter + 'Wed, 09 Jun 2021 11:-3:44 GMT': false, + 'Wed, 09 Jun 2021 11:33:-4 GMT': false, - "": false + '': false, } const equivalenceTests: EquivalenceDateParsingTestCase = { // milliseconds ignored - "Tue, 18 Oct 2011 07:42:42.123 GMT": "Tue, 18 Oct 2011 07:42:42 GMT", + 'Tue, 18 Oct 2011 07:42:42.123 GMT': 'Tue, 18 Oct 2011 07:42:42 GMT', // shorter HH:MM:SS works how you'd expect: - "8 Oct 2011 7:32:42 GMT": "8 Oct 2011 07:32:42 GMT", - "8 Oct 2011 7:2:42 GMT": "8 Oct 2011 07:02:42 GMT", - "8 Oct 2011 7:2:2 GMT": "8 Oct 2011 07:02:02 GMT", + '8 Oct 2011 7:32:42 GMT': '8 Oct 2011 07:32:42 GMT', + '8 Oct 2011 7:2:42 GMT': '8 Oct 2011 07:02:42 GMT', + '8 Oct 2011 7:2:2 GMT': '8 Oct 2011 07:02:02 GMT', // MDY versus DMY: - "Oct 18 2011 07:42:42 GMT": "18 Oct 2011 07:42:42 GMT", + 'Oct 18 2011 07:42:42 GMT': '18 Oct 2011 07:42:42 GMT', // some other messy auto format - "Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)": - "Tue, 18 Oct 2011 07:05:03 GMT", + 'Tue Oct 18 2011 07:05:03 GMT+0000 (GMT)': 'Tue, 18 Oct 2011 07:05:03 GMT', // short year - "10 Feb 81 13:00:00 GMT": "10 Feb 1981 13:00:00 GMT", - "10 Feb 17 13:00:00 GMT": "10 Feb 2017 13:00:00 GMT", + '10 Feb 81 13:00:00 GMT': '10 Feb 1981 13:00:00 GMT', + '10 Feb 17 13:00:00 GMT': '10 Feb 2017 13:00:00 GMT', // dashes - "Thu, 17-Apr-2014 02:12:29 GMT": "Thu, 17 Apr 2014 02:12:29 GMT", + 'Thu, 17-Apr-2014 02:12:29 GMT': 'Thu, 17 Apr 2014 02:12:29 GMT', // dashes and "UTC" (timezone is always ignored) - "Thu, 17-Apr-2014 02:12:29 UTC": "Thu, 17 Apr 2014 02:12:29 GMT", + 'Thu, 17-Apr-2014 02:12:29 UTC': 'Thu, 17 Apr 2014 02:12:29 GMT', // no weekday - "09 Jun 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + '09 Jun 2021 10:18:14 GMT': 'Wed, 09 Jun 2021 10:18:14 GMT', // garbage after seconds is OK - "Wed, 09 Jun 2021 11:33:4e GMT": "Wed, 09 Jun 2021 11:33:04 GMT", + 'Wed, 09 Jun 2021 11:33:4e GMT': 'Wed, 09 Jun 2021 11:33:04 GMT', // - is delimiter in this position - "Wed, 09 Jun 2021 -1:33:44 GMT": "Wed, 09 Jun 2021 01:33:44 GMT", + 'Wed, 09 Jun 2021 -1:33:44 GMT': 'Wed, 09 Jun 2021 01:33:44 GMT', // prefix match on month - "Wed, 09 Junxxx 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", - "09 November 2021 10:18:14 GMT": "09 Nov 2021 10:18:14 GMT", + 'Wed, 09 Junxxx 2021 10:18:14 GMT': 'Wed, 09 Jun 2021 10:18:14 GMT', + '09 November 2021 10:18:14 GMT': '09 Nov 2021 10:18:14 GMT', // case of Month - "Wed, 09 JUN 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", - "Wed, 09 jUN 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT", + 'Wed, 09 JUN 2021 10:18:14 GMT': 'Wed, 09 Jun 2021 10:18:14 GMT', + 'Wed, 09 jUN 2021 10:18:14 GMT': 'Wed, 09 Jun 2021 10:18:14 GMT', // test the framework :wink: - "Wed, 09 Jun 2021 10:18:14 GMT": "Wed, 09 Jun 2021 10:18:14 GMT" + 'Wed, 09 Jun 2021 10:18:14 GMT': 'Wed, 09 Jun 2021 10:18:14 GMT', } -describe("Dates", () => { +describe('Dates', () => { describe('parsing', () => { - const validDateTestCases = Object.entries(dateTests).filter(testCase => testCase[1]) - const invalidDateTestCases = Object.entries(dateTests).filter(testCase => !testCase[1]) + const validDateTestCases = Object.entries(dateTests).filter( + (testCase) => testCase[1], + ) + const invalidDateTestCases = Object.entries(dateTests).filter( + (testCase) => !testCase[1], + ) const equivalenceTestCases = Object.entries(equivalenceTests) - it.each(validDateTestCases) - (`'%s' is valid`, (date: string) => { + it.each(validDateTestCases)(`'%s' is valid`, (date: string) => { expect(parseDate(date)).toBeInstanceOf(Date) }) - it.each(invalidDateTestCases) - (`'%s' is not valid`, (date: string) => { - expect(parseDate(date)).toBeUndefined(); + it.each(invalidDateTestCases)(`'%s' is not valid`, (date: string) => { + expect(parseDate(date)).toBeUndefined() }) - it.each(equivalenceTestCases) - (`'%s' parses the same as '%s'`, (date: string, equivalentDate: string) => { - expect(parseDate(date)).toStrictEqual(parseDate(equivalentDate)) - }) + it.each(equivalenceTestCases)( + `'%s' parses the same as '%s'`, + (date: string, equivalentDate: string) => { + expect(parseDate(date)).toStrictEqual(parseDate(equivalentDate)) + }, + ) }) describe('regexp denial of service attack vectors', () => { - const TOO_MANY_XS = String("x").repeat(65535); + const TOO_MANY_XS = String('x').repeat(65535) it('should avoid unbounded regexps when parsing the hour from a date', () => { - expect(parseDate(`Wed, 09 Jun 2021 10${TOO_MANY_XS}:18:14 GMT`)).toBeUndefined() + expect( + parseDate(`Wed, 09 Jun 2021 10${TOO_MANY_XS}:18:14 GMT`), + ).toBeUndefined() }) it('should avoid unbounded regexps when parsing the minute from a date', () => { - expect(parseDate(`Wed, 09 Jun 2021 10:18${TOO_MANY_XS}:14 GMT`)).toBeUndefined() + expect( + parseDate(`Wed, 09 Jun 2021 10:18${TOO_MANY_XS}:14 GMT`), + ).toBeUndefined() }) it('should avoid unbounded regexps when parsing the seconds from a date', () => { - const dateWithMillisIgnored = new Date(Date.parse('2021-06-09T10:18:14.000Z')) - expect(parseDate(`Wed, 09 Jun 2021 10:18:14${TOO_MANY_XS} GMT`)).toStrictEqual(dateWithMillisIgnored) + const dateWithMillisIgnored = new Date( + Date.parse('2021-06-09T10:18:14.000Z'), + ) + expect( + parseDate(`Wed, 09 Jun 2021 10:18:14${TOO_MANY_XS} GMT`), + ).toStrictEqual(dateWithMillisIgnored) }) }) }) diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts index c0ffaa63..8f279a18 100644 --- a/lib/__tests__/defaultPath.spec.ts +++ b/lib/__tests__/defaultPath.spec.ts @@ -1,27 +1,27 @@ -import {defaultPath} from "../cookie"; +import { defaultPath } from '../cookie' // port of tests/domain_and_path_test.js (default path tests) describe('defaultPath', () => { it.each([ { input: null, - output: '/' + output: '/', }, { input: '/', - output: '/' + output: '/', }, { input: '/file', - output: '/' + output: '/', }, { input: '/dir/file', - output: '/dir' + output: '/dir', }, { input: 'noslash', - output: '/' + output: '/', }, ])('defaultPath("$input") => $output', ({ input, output }) => { // @ts-ignore diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index 2b4b7cb7..b9e14a2d 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -1,74 +1,74 @@ -import {domainMatch} from "../cookie"; +import { domainMatch } from '../cookie' // port of tests/domain_and_path_test.js (domain match tests) describe('domainMatch', () => { it.each([ // string, domain, expect - ["example.com", "example.com", true], // identical - ["eXaMpLe.cOm", "ExAmPlE.CoM", true], // both canonicalized - ["no.ca", "yes.ca", false], - ["wwwexample.com", "example.com", false], - ["www.subdom.example.com", "example.com", true], - ["www.subdom.example.com", "subdom.example.com", true], - ["example.com", "example.com.", false], // RFC6265 S4.1.2.3 + ['example.com', 'example.com', true], // identical + ['eXaMpLe.cOm', 'ExAmPlE.CoM', true], // both canonicalized + ['no.ca', 'yes.ca', false], + ['wwwexample.com', 'example.com', false], + ['www.subdom.example.com', 'example.com', true], + ['www.subdom.example.com', 'subdom.example.com', true], + ['example.com', 'example.com.', false], // RFC6265 S4.1.2.3 // nulls and undefineds - [null, "example.com", null], - ["example.com", null, null], + [null, 'example.com', null], + ['example.com', null, null], [null, null, null], [undefined, undefined, null], // suffix matching: - ["www.example.com", "example.com", true], // substr AND suffix - ["www.example.com.org", "example.com", false], // substr but not suffix - ["example.com", "www.example.com.org", false], // neither - ["example.com", "www.example.com", false], // super-str - ["aaa.com", "aaaa.com", false], // str can't be suffix of domain - ["aaaa.com", "aaa.com", false], // dom is suffix, but has to match on "." boundary! - ["www.aaaa.com", "aaa.com", false], - ["www.aaa.com", "aaa.com", true], - ["www.aexample.com", "example.com", false], // has to match on "." boundary - ["computer.com", "com", true], // suffix string found at start of domain - ["becoming.com", "com", true], // suffix string found in middle of domain - ["sitcom.com", "com", true], // suffix string found just before the '.' boundary + ['www.example.com', 'example.com', true], // substr AND suffix + ['www.example.com.org', 'example.com', false], // substr but not suffix + ['example.com', 'www.example.com.org', false], // neither + ['example.com', 'www.example.com', false], // super-str + ['aaa.com', 'aaaa.com', false], // str can't be suffix of domain + ['aaaa.com', 'aaa.com', false], // dom is suffix, but has to match on "." boundary! + ['www.aaaa.com', 'aaa.com', false], + ['www.aaa.com', 'aaa.com', true], + ['www.aexample.com', 'example.com', false], // has to match on "." boundary + ['computer.com', 'com', true], // suffix string found at start of domain + ['becoming.com', 'com', true], // suffix string found in middle of domain + ['sitcom.com', 'com', true], // suffix string found just before the '.' boundary // S5.1.3 "The string is a host name (i.e., not an IP address)" - ["192.168.0.1", "168.0.1", false], // because str is an IP (v4) - ["100.192.168.0.1", "168.0.1", true], // WEIRD: because str is not a valid IPv4 - ["100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not a valid IPv4 - ["::ffff:192.168.0.1", "168.0.1", false], // because str is an IP (v6) - ["::ffff:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6) - ["::FFFF:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6) - ["::192.168.0.1", "192.168.0.1", false], // because str is an IP (yes, v6!) - [":192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6 - [":ffff:100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not valid IPv6 - [":ffff:192.168.0.1", "192.168.0.1", false], - [":ffff:192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6 - ["::Fxxx:192.168.0.1", "168.0.1", true], // WEIRD: because str isnt IPv6 - ["192.168.0.1", "68.0.1", false], - ["192.168.0.1", "2.68.0.1", false], - ["192.168.0.1", "92.68.0.1", false], - ["10.1.2.3", "210.1.2.3", false], - ["2008::1", "::1", false], - ["::1", "2008::1", false], - ["::1", "::1", true], // "are identical" rule, despite IPv6 - ["::3xam:1e", "2008::3xam:1e", false], // malformed IPv6 - ["::3Xam:1e", "::3xaM:1e", true], // identical, even though malformed - ["3xam::1e", "3xam::1e", true], // identical - ["::3xam::1e", "3xam::1e", false], - ["3xam::1e", "::3xam:1e", false], - ["::f00f:10.0.0.1", "10.0.0.1", false], - ["10.0.0.1", "::f00f:10.0.0.1", false], + ['192.168.0.1', '168.0.1', false], // because str is an IP (v4) + ['100.192.168.0.1', '168.0.1', true], // WEIRD: because str is not a valid IPv4 + ['100.192.168.0.1', '192.168.0.1', true], // WEIRD: because str is not a valid IPv4 + ['::ffff:192.168.0.1', '168.0.1', false], // because str is an IP (v6) + ['::ffff:192.168.0.1', '192.168.0.1', false], // because str is an IP (v6) + ['::FFFF:192.168.0.1', '192.168.0.1', false], // because str is an IP (v6) + ['::192.168.0.1', '192.168.0.1', false], // because str is an IP (yes, v6!) + [':192.168.0.1', '168.0.1', true], // WEIRD: because str is not valid IPv6 + [':ffff:100.192.168.0.1', '192.168.0.1', true], // WEIRD: because str is not valid IPv6 + [':ffff:192.168.0.1', '192.168.0.1', false], + [':ffff:192.168.0.1', '168.0.1', true], // WEIRD: because str is not valid IPv6 + ['::Fxxx:192.168.0.1', '168.0.1', true], // WEIRD: because str isnt IPv6 + ['192.168.0.1', '68.0.1', false], + ['192.168.0.1', '2.68.0.1', false], + ['192.168.0.1', '92.68.0.1', false], + ['10.1.2.3', '210.1.2.3', false], + ['2008::1', '::1', false], + ['::1', '2008::1', false], + ['::1', '::1', true], // "are identical" rule, despite IPv6 + ['::3xam:1e', '2008::3xam:1e', false], // malformed IPv6 + ['::3Xam:1e', '::3xaM:1e', true], // identical, even though malformed + ['3xam::1e', '3xam::1e', true], // identical + ['::3xam::1e', '3xam::1e', false], + ['3xam::1e', '::3xam:1e', false], + ['::f00f:10.0.0.1', '10.0.0.1', false], + ['10.0.0.1', '::f00f:10.0.0.1', false], // "IP like" hostnames: - ["1.example.com", "example.com", true], - ["11.example.com", "example.com", true], - ["192.168.0.1.example.com", "example.com", true], + ['1.example.com', 'example.com', true], + ['11.example.com', 'example.com', true], + ['192.168.0.1.example.com', 'example.com', true], // exact length "TLD" tests: - ["com", "net", false], // same len, non-match - ["com", "com", true], // "are identical" rule - ["NOTATLD", "notaTLD", true], // "are identical" rule (after canonicalization) + ['com', 'net', false], // same len, non-match + ['com', 'com', true], // "are identical" rule + ['NOTATLD', 'notaTLD', true], // "are identical" rule (after canonicalization) ])('domainMatch(%s, %s) => %s', (string, domain, expectedValue) => { // @ts-ignore expect(domainMatch(string, domain)).toBe(expectedValue) diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 2582b486..d97a6a7d 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {CookieJar, parseDate} from '../cookie' +import { CookieJar, parseDate } from '../cookie' import url from 'url' import parserData from '../../test/ietf_data/parser.json' import bsdExampleDates from '../../test/ietf_data/dates/bsd-examples.json' @@ -37,51 +37,57 @@ import exampleDates from '../../test/ietf_data/dates/examples.json' describe('IETF http state tests', () => { describe('Set/get cookie tests', () => { - it.each(parserData) - (`$test`, (testCase) => { - const jar = new CookieJar(); + it.each(parserData)(`$test`, (testCase) => { + const jar = new CookieJar() const expected = testCase.sent - const sentFrom = `http://home.example.org/cookie-parser?${testCase.test}`; - const sentTo = testCase["sent-to"] - ? url.resolve("http://home.example.org", testCase["sent-to"]) - : `http://home.example.org/cookie-parser-result?${testCase.test}`; + const sentFrom = `http://home.example.org/cookie-parser?${testCase.test}` + const sentTo = testCase['sent-to'] + ? url.resolve('http://home.example.org', testCase['sent-to']) + : `http://home.example.org/cookie-parser-result?${testCase.test}` - testCase["received"].forEach(cookieStr => { - jar.setCookieSync(cookieStr, sentFrom, {ignoreError: true}); - }); + testCase['received'].forEach((cookieStr) => { + jar.setCookieSync(cookieStr, sentFrom, { ignoreError: true }) + }) - const actual = jar.getCookiesSync(sentTo, {sort: true}) as Array<{ key: string, value: string }>; + const actual = jar.getCookiesSync(sentTo, { sort: true }) as Array<{ + key: string + value: string + }> expect(actual.length).toBe(expected.length) actual.forEach((actualCookie, idx) => { - const expectedCookie = expected[idx]; + const expectedCookie = expected[idx] // @ts-ignore expect(actualCookie.key).toBe(expectedCookie.name) // @ts-ignore expect(actualCookie.value).toBe(expectedCookie.value) - }); + }) }) }) describe('Date handling', () => { - it.each(exampleDates) - (`ietf_data/dates/examples: $test`, ({test, expected}) => { - if (expected) { - // @ts-ignore - expect(parseDate(test).toUTCString()).toBe(expected) - } else { - expect(parseDate(test)).toBeUndefined() - } - }) + it.each(exampleDates)( + `ietf_data/dates/examples: $test`, + ({ test, expected }) => { + if (expected) { + // @ts-ignore + expect(parseDate(test).toUTCString()).toBe(expected) + } else { + expect(parseDate(test)).toBeUndefined() + } + }, + ) - it.each(bsdExampleDates) - (`ietf_data/dates/bsd_examples: $test`, ({test, expected}) => { - if (expected) { - // @ts-ignore - expect(parseDate(test).toUTCString()).toBe(expected) - } else { - expect(parseDate(test)).toBeUndefined() - } - }) + it.each(bsdExampleDates)( + `ietf_data/dates/bsd_examples: $test`, + ({ test, expected }) => { + if (expected) { + // @ts-ignore + expect(parseDate(test).toUTCString()).toBe(expected) + } else { + expect(parseDate(test)).toBeUndefined() + } + }, + ) }) }) diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 7a1bb607..fe3f9353 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie, CookieJar, MemoryCookieStore, Store, version} from '../cookie' +import { Cookie, CookieJar, MemoryCookieStore, Store, version } from '../cookie' const { any, objectContaining } = expect @@ -42,20 +42,20 @@ describe('cookieJar serialization', () => { it('should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`', () => { expect(Cookie.serializableProperties).toEqual([ - "key", - "value", - "expires", - "maxAge", - "domain", - "path", - "secure", - "httpOnly", - "extensions", - "hostOnly", - "pathIsDefault", - "creation", - "lastAccessed", - "sameSite", + 'key', + 'value', + 'expires', + 'maxAge', + 'domain', + 'path', + 'secure', + 'httpOnly', + 'extensions', + 'hostOnly', + 'pathIsDefault', + 'creation', + 'lastAccessed', + 'sameSite', ]) }) @@ -65,7 +65,9 @@ describe('cookieJar serialization', () => { store.synchronous = true const jar = new CookieJar(store) - expect(() => jar.toJSON()).toThrowError('getAllCookies is not implemented (therefore jar cannot be serialized)') + expect(() => jar.toJSON()).toThrowError( + 'getAllCookies is not implemented (therefore jar cannot be serialized)', + ) }) }) @@ -74,7 +76,9 @@ describe('cookieJar serialization', () => { const store = new MemoryCookieStore() store.synchronous = false const jar = new CookieJar(store) - expect(() => jar.toJSON()).toThrowError('CookieJar store is not synchronous; use async API instead.') + expect(() => jar.toJSON()).toThrowError( + 'CookieJar store is not synchronous; use async API instead.', + ) }) }) @@ -86,13 +90,13 @@ describe('cookieJar serialization', () => { // domain cookie with custom extension await jar.setCookie( - "sid=one; domain=example.com; path=/; fubar", - "http://example.com/" + 'sid=one; domain=example.com; path=/; fubar', + 'http://example.com/', ) await jar.setCookie( - "sid=two; domain=example.net; path=/; fubar", - "http://example.net/" + 'sid=two; domain=example.net; path=/; fubar', + 'http://example.net/', ) }) @@ -107,7 +111,7 @@ describe('cookieJar serialization', () => { const serializedJar = jar.serializeSync() // @ts-ignore const deserializedJar = CookieJar.deserializeSync(serializedJar) - expect(jar.store).toEqual(deserializedJar.store); + expect(jar.store).toEqual(deserializedJar.store) }) it('should serialize asynchronously', async () => { @@ -119,7 +123,7 @@ describe('cookieJar serialization', () => { it('should deserialize asynchronously', async () => { const serializedJar = await jar.serialize() const deserializedJar = await CookieJar.deserialize(serializedJar) - expect(jar.store).toEqual(deserializedJar.store); + expect(jar.store).toEqual(deserializedJar.store) }) }) @@ -131,13 +135,13 @@ describe('cookieJar serialization', () => { // domain cookie with custom extension await jar.setCookie( - "sid=three; domain=example.com; path=/; cloner", - "http://example.com/" + 'sid=three; domain=example.com; path=/; cloner', + 'http://example.com/', ) await jar.setCookie( - "sid=four; domain=example.net; path=/; cloner", - "http://example.net/" + 'sid=four; domain=example.net; path=/; cloner', + 'http://example.net/', ) }) @@ -153,9 +157,11 @@ describe('cookieJar serialization', () => { }) it('should raise an error when attempting to synchronously clone to an async store', async () => { - const newStore = new MemoryCookieStore(); - newStore.synchronous = false; - expect(() => jar.cloneSync(newStore)).toThrowError('CookieJar clone destination store is not synchronous; use async API instead.') + const newStore = new MemoryCookieStore() + newStore.synchronous = false + expect(() => jar.cloneSync(newStore)).toThrowError( + 'CookieJar clone destination store is not synchronous; use async API instead.', + ) }) }) @@ -171,44 +177,44 @@ describe('cookieJar serialization', () => { // Do paths first since the MemoryCookieStore index is domain at the top // level. This should cause the preservation of creation order in // getAllCookies to be exercised. - const paths = ["/", "/foo", "/foo/bar"] - const domains = ["example.com", "www.example.com", "example.net"] + const paths = ['/', '/foo', '/foo/bar'] + const domains = ['example.com', 'www.example.com', 'example.net'] for await (const path of paths) { for await (const domain of domains) { - const key = "key" + const key = 'key' const value = JSON.stringify({ path, domain }) - const cookie = new Cookie({ expires, domain, path, key, value }) - await jar.setCookie(cookie, `http://${domain}/`); + const cookie = new Cookie({ expires, domain, path, key, value }) + await jar.setCookie(cookie, `http://${domain}/`) } } // corner cases const cornerCases = [ - { expires: "Infinity", key: "infExp", value: "infExp" }, - { maxAge: 3600, key: "max", value: "max" }, + { expires: 'Infinity', key: 'infExp', value: 'infExp' }, + { maxAge: 3600, key: 'max', value: 'max' }, { expires, - key: "flags", - value: "flags", + key: 'flags', + value: 'flags', secure: true, - httpOnly: true + httpOnly: true, }, { expires, - key: "honly", - value: "honly", + key: 'honly', + value: 'honly', hostOnly: true, - domain: "www.example.org" - } - ]; + domain: 'www.example.org', + }, + ] for await (const cornerCase of cornerCases) { - const domain = cornerCase.domain ?? "example.org" + const domain = cornerCase.domain ?? 'example.org' const path = '/' - const cookie = new Cookie({ ...cornerCase, domain, path }) - await jar.setCookie(cookie, "https://www.example.org/", { - ignoreError: true - }); + const cookie = new Cookie({ ...cornerCase, domain, path }) + await jar.setCookie(cookie, 'https://www.example.org/', { + ignoreError: true, + }) } }) @@ -223,16 +229,21 @@ describe('cookieJar serialization', () => { const serializedJar = await jar.serialize() expect(serializedJar.cookies.length).toBe(13) expectDataToMatchSerializationSchema(serializedJar) - serializedJar.cookies.forEach(serializedCookie => { + serializedJar.cookies.forEach((serializedCookie) => { if (serializedCookie.key === 'key') { // @ts-ignore - expect(JSON.parse(serializedCookie.value)).toEqual(objectContaining({ - domain: any(String), - path: any(String) - })) + expect(JSON.parse(serializedCookie.value)).toEqual( + objectContaining({ + domain: any(String), + path: any(String), + }), + ) } - if (serializedCookie.key === 'infExp' || serializedCookie.key === 'max') { + if ( + serializedCookie.key === 'infExp' || + serializedCookie.key === 'max' + ) { expect(serializedCookie.expires).toBeFalsy() } else { expect(serializedCookie.expires).toBe(expires.toISOString()) @@ -252,7 +263,7 @@ describe('cookieJar serialization', () => { expect(serializedCookie.httpOnly).toBeUndefined() } - expect(serializedCookie.hostOnly).toBe(serializedCookie.key === "honly") + expect(serializedCookie.hostOnly).toBe(serializedCookie.key === 'honly') expect(serializedCookie.creation).toBe(new Date().toISOString()) expect(serializedCookie.lastAccessed).toBe(new Date().toISOString()) @@ -271,8 +282,8 @@ describe('cookieJar serialization', () => { expires: 'Infinity', }), objectContaining({ - key: 'max' - }) + key: 'max', + }), ]) // @ts-ignore expect(cookies[0].TTL(Date.now())).toBe(Infinity) @@ -283,7 +294,7 @@ describe('cookieJar serialization', () => { }) // @ts-ignore -function expectDataToMatchSerializationSchema (serializedJar) { +function expectDataToMatchSerializationSchema(serializedJar) { expect(serializedJar).not.toBeNull() expect(serializedJar).toBeInstanceOf(Object) expect(serializedJar.version).toBe(`tough-cookie@${version}`) @@ -291,62 +302,62 @@ function expectDataToMatchSerializationSchema (serializedJar) { expect(serializedJar.rejectPublicSuffixes).toBe(true) expect(serializedJar.cookies).toBeInstanceOf(Array) // @ts-ignore - serializedJar.cookies.forEach(cookie => validateSerializedCookie(cookie)) + serializedJar.cookies.forEach((cookie) => validateSerializedCookie(cookie)) } const serializedCookiePropTypes = { - key: "string", - value: "string", - expires: "isoDate", // if "Infinity" it's supposed to be missing - maxAge: "intOrInf", - domain: "string", - path: "string", - secure: "boolean", - httpOnly: "boolean", - extensions: "array", // of strings, technically - hostOnly: "boolean", - pathIsDefault: "boolean", - creation: "isoDate", - lastAccessed: "isoDate", - sameSite: "string" -}; + key: 'string', + value: 'string', + expires: 'isoDate', // if "Infinity" it's supposed to be missing + maxAge: 'intOrInf', + domain: 'string', + path: 'string', + secure: 'boolean', + httpOnly: 'boolean', + extensions: 'array', // of strings, technically + hostOnly: 'boolean', + pathIsDefault: 'boolean', + creation: 'isoDate', + lastAccessed: 'isoDate', + sameSite: 'string', +} // @ts-ignore function validateSerializedCookie(cookie) { - expect(typeof cookie).toBe('object'); - expect(cookie).not.toBeInstanceOf(Cookie); + expect(typeof cookie).toBe('object') + expect(cookie).not.toBeInstanceOf(Cookie) - Object.keys(cookie).forEach(prop => { + Object.keys(cookie).forEach((prop) => { // @ts-ignore - const type = serializedCookiePropTypes[prop]; + const type = serializedCookiePropTypes[prop] switch (type) { - case "string": - case "boolean": - case "number": - expect(typeof cookie[prop]).toBe(type); - break; - - case "array": - expect(Array.isArray(cookie[prop])).toBe(true); - break; - - case "intOrInf": - if (cookie[prop] !== "Infinity" && cookie[prop] !== "-Infinity") { + case 'string': + case 'boolean': + case 'number': + expect(typeof cookie[prop]).toBe(type) + break + + case 'array': + expect(Array.isArray(cookie[prop])).toBe(true) + break + + case 'intOrInf': + if (cookie[prop] !== 'Infinity' && cookie[prop] !== '-Infinity') { expect(isInteger(cookie[prop])).toBe(true) } - break; + break - case "isoDate": + case 'isoDate': if (cookie[prop] != null) { - const parsed = new Date(Date.parse(cookie[prop])); - expect(cookie[prop]).toBe(parsed.toISOString()); + const parsed = new Date(Date.parse(cookie[prop])) + expect(cookie[prop]).toBe(parsed.toISOString()) } - break; + break default: - fail(`unexpected serialized property: ${prop}`); + fail(`unexpected serialized property: ${prop}`) } - }); + }) } // @ts-ignore @@ -357,8 +368,6 @@ function isInteger(value) { // Node 0.10 (still supported) doesn't have Number.isInteger // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger return ( - typeof value === "number" && - isFinite(value) && - Math.floor(value) === value - ); + typeof value === 'number' && isFinite(value) && Math.floor(value) === value + ) } diff --git a/lib/__tests__/lifetime.spec.ts b/lib/__tests__/lifetime.spec.ts index 7af3378e..5f2a7aef 100644 --- a/lib/__tests__/lifetime.spec.ts +++ b/lib/__tests__/lifetime.spec.ts @@ -1,4 +1,4 @@ -import {Cookie} from "../cookie"; +import { Cookie } from '../cookie' jest.useFakeTimers() @@ -25,19 +25,28 @@ describe('Lifetime', () => { }) it('should be able control the TTL with max-age and expiry in the future', () => { - const cookie = new Cookie({ key: 'a', value: 'b', maxAge: 123, expires: new Date(Date.now() + 9_000) }) + const cookie = new Cookie({ + key: 'a', + value: 'b', + maxAge: 123, + expires: new Date(Date.now() + 9_000), + }) expect(cookie.TTL()).toBe(123_000) expect(cookie.isPersistent()).toBe(true) }) it('should be able control the TTL with expiry in the future', () => { - const cookie = new Cookie({ key: 'a', value: 'b', expires: new Date(Date.now() + 9_000) }) + const cookie = new Cookie({ + key: 'a', + value: 'b', + expires: new Date(Date.now() + 9_000), + }) expect(cookie.TTL()).toBe(9_000) expect(cookie.expiryTime()).toEqual((cookie.expires as Date).getTime()) }) it('should be able control the TTL with expiry in the past', () => { - const cookie = new Cookie({ key: 'a', value: 'b'}) + const cookie = new Cookie({ key: 'a', value: 'b' }) cookie.setExpires('17 Oct 2010 00:00:00 GMT') expect(cookie.TTL()).toBeLessThan(0) expect(cookie.isPersistent()).toBe(true) diff --git a/lib/__tests__/memoryCookieStore.spec.ts b/lib/__tests__/memoryCookieStore.spec.ts index 1a59fe0c..fad701eb 100644 --- a/lib/__tests__/memoryCookieStore.spec.ts +++ b/lib/__tests__/memoryCookieStore.spec.ts @@ -1,4 +1,4 @@ -import {Cookie, MemoryCookieStore} from "../cookie"; +import { Cookie, MemoryCookieStore } from '../cookie' describe('MemoryCookieStore', () => { it('should have no static methods', () => { @@ -7,13 +7,23 @@ describe('MemoryCookieStore', () => { it('should have instance methods that return promises', () => { const memoryCookieStore = new MemoryCookieStore() - expect(memoryCookieStore.findCookie("example.com", "/", "key")).toBeInstanceOf(Promise); - expect(memoryCookieStore.findCookies("example.com", "/")).toBeInstanceOf(Promise); - expect(memoryCookieStore.putCookie(new Cookie())).toBeInstanceOf(Promise); - expect(memoryCookieStore.updateCookie(new Cookie(), new Cookie())).toBeInstanceOf(Promise); - expect(memoryCookieStore.removeCookie("example.com", "/", "key")).toBeInstanceOf(Promise); - expect(memoryCookieStore.removeCookies("example.com", "/")).toBeInstanceOf(Promise); - expect(memoryCookieStore.removeAllCookies()).toBeInstanceOf(Promise); - expect(memoryCookieStore.getAllCookies()).toBeInstanceOf(Promise); + expect( + memoryCookieStore.findCookie('example.com', '/', 'key'), + ).toBeInstanceOf(Promise) + expect(memoryCookieStore.findCookies('example.com', '/')).toBeInstanceOf( + Promise, + ) + expect(memoryCookieStore.putCookie(new Cookie())).toBeInstanceOf(Promise) + expect( + memoryCookieStore.updateCookie(new Cookie(), new Cookie()), + ).toBeInstanceOf(Promise) + expect( + memoryCookieStore.removeCookie('example.com', '/', 'key'), + ).toBeInstanceOf(Promise) + expect(memoryCookieStore.removeCookies('example.com', '/')).toBeInstanceOf( + Promise, + ) + expect(memoryCookieStore.removeAllCookies()).toBeInstanceOf(Promise) + expect(memoryCookieStore.getAllCookies()).toBeInstanceOf(Promise) }) }) diff --git a/lib/__tests__/nodeUtilFallback.spec.ts b/lib/__tests__/nodeUtilFallback.spec.ts index c1513d57..93ea4fb6 100644 --- a/lib/__tests__/nodeUtilFallback.spec.ts +++ b/lib/__tests__/nodeUtilFallback.spec.ts @@ -1,47 +1,53 @@ -import {getCustomInspectSymbol, getUtilInspect} from "../utilHelper"; +import { getCustomInspectSymbol, getUtilInspect } from '../utilHelper' import util from 'util' -import {Cookie, CookieJar, MemoryCookieStore} from "../cookie"; -import {inspectFallback} from "../memstore"; +import { Cookie, CookieJar, MemoryCookieStore } from '../cookie' +import { inspectFallback } from '../memstore' jest.useFakeTimers() describe('Node util module fallback for non-node environments', () => { describe('getCustomInspectSymbol', () => { it('should not be null in a node environment', () => { - expect(getCustomInspectSymbol()).toEqual(Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom) + expect(getCustomInspectSymbol()).toEqual( + Symbol.for('nodejs.util.inspect.custom') || util.inspect.custom, + ) }) it('should not be null in a node environment when custom inspect symbol cannot be retrieved (< node v10.12.0', () => { - expect(getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null - })).toEqual(Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom) + expect( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null, + }), + ).toEqual(Symbol.for('nodejs.util.inspect.custom') || util.inspect.custom) }) it('should be null in a non-node environment since `util` features cannot be relied on', () => { - expect(getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null, - requireUtil: () => null - })).toBeNull() + expect( + getCustomInspectSymbol({ + lookupCustomInspectSymbol: () => null, + requireUtil: () => null, + }), + ).toBeNull() }) }) describe('getUtilInspect', () => { it('should use util.inspect in a node environment', () => { - const inspect = getUtilInspect(() => "fallback"); - expect(inspect("util.inspect")).toEqual(util.inspect("util.inspect")) + const inspect = getUtilInspect(() => 'fallback') + expect(inspect('util.inspect')).toEqual(util.inspect('util.inspect')) }) it('should use fallback inspect function in a non-node environment', () => { - const inspect = getUtilInspect(() => "fallback", { - requireUtil: () => null + const inspect = getUtilInspect(() => 'fallback', { + requireUtil: () => null, }) - expect(inspect("util.inspect")).toEqual("fallback") + expect(inspect('util.inspect')).toEqual('fallback') }) }) describe('util usage in Cookie', () => { it('custom inspect for Cookie still works', () => { - const cookie = Cookie.parse("a=1; Domain=example.com; Path=/") + const cookie = Cookie.parse('a=1; Domain=example.com; Path=/') // @ts-ignore expect(cookie.inspect()).toEqual(util.inspect(cookie)) }) @@ -59,8 +65,10 @@ describe('Node util module fallback for non-node environments', () => { expect(memoryStore.inspect()).toEqual(util.inspect(memoryStore)) }) - it("fallback produces equivalent output to custom inspect", () => { - expect(util.inspect(memoryStore.idx)).toEqual(inspectFallback(memoryStore.idx)) + it('fallback produces equivalent output to custom inspect', () => { + expect(util.inspect(memoryStore.idx)).toEqual( + inspectFallback(memoryStore.idx), + ) }) }) @@ -68,8 +76,8 @@ describe('Node util module fallback for non-node environments', () => { beforeEach(async () => { const cookieJar = new CookieJar(memoryStore) await cookieJar.setCookie( - "a=1; Domain=example.com; Path=/", - "http://example.com/index.html" + 'a=1; Domain=example.com; Path=/', + 'http://example.com/index.html', ) }) @@ -77,29 +85,42 @@ describe('Node util module fallback for non-node environments', () => { expect(memoryStore.inspect()).toEqual(util.inspect(memoryStore)) }) - it("fallback produces equivalent output to custom inspect", () => { - expect(util.inspect(memoryStore.idx)).toEqual(inspectFallback(memoryStore.idx)) + it('fallback produces equivalent output to custom inspect', () => { + expect(util.inspect(memoryStore.idx)).toEqual( + inspectFallback(memoryStore.idx), + ) }) }) describe('when store has multiple cookies', () => { beforeEach(async () => { const cookieJar = new CookieJar(memoryStore) - const url = "http://example.com/index.html" + const url = 'http://example.com/index.html' await cookieJar.setCookie('a=0; Domain=example.com; Path=/', url) await cookieJar.setCookie('b=1; Domain=example.com; Path=/', url) await cookieJar.setCookie('c=2; Domain=example.com; Path=/', url) - await cookieJar.setCookie('d=3; Domain=example.com; Path=/some-path/', url) - await cookieJar.setCookie('e=4; Domain=example.com; Path=/some-path/', url) - await cookieJar.setCookie('f=5; Domain=another.com; Path=/', "http://another.com/index.html") + await cookieJar.setCookie( + 'd=3; Domain=example.com; Path=/some-path/', + url, + ) + await cookieJar.setCookie( + 'e=4; Domain=example.com; Path=/some-path/', + url, + ) + await cookieJar.setCookie( + 'f=5; Domain=another.com; Path=/', + 'http://another.com/index.html', + ) }) it('custom inspect for MemoryCookie still works', () => { expect(memoryStore.inspect()).toEqual(util.inspect(memoryStore)) }) - it("fallback produces equivalent output to custom inspect", () => { - expect(util.inspect(memoryStore.idx)).toEqual(inspectFallback(memoryStore.idx)) + it('fallback produces equivalent output to custom inspect', () => { + expect(util.inspect(memoryStore.idx)).toEqual( + inspectFallback(memoryStore.idx), + ) }) }) }) diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts index 0529ed00..102fc00e 100644 --- a/lib/__tests__/parse.spec.ts +++ b/lib/__tests__/parse.spec.ts @@ -1,5 +1,5 @@ -import {Cookie} from "../cookie"; -import {performance} from 'node:perf_hooks' +import { Cookie } from '../cookie' +import { performance } from 'node:perf_hooks' describe('Cookie.parse', () => { it.each([ @@ -8,8 +8,8 @@ describe('Cookie.parse', () => { input: 'a=bcd', output: { key: 'a', - value: 'bcd' - } + value: 'bcd', + }, }, // with expiry { @@ -17,8 +17,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'bcd', - expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')) - } + expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')), + }, }, // with expiry and path { @@ -29,12 +29,13 @@ describe('Cookie.parse', () => { expires: new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')), path: '/aBc', httpOnly: false, - secure: false - } + secure: false, + }, }, // with most things { - input: 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc; Domain=example.com; Secure; HTTPOnly; Max-Age=1234; Foo=Bar; Baz', + input: + 'abc="xyzzy!"; Expires=Tue, 18 Oct 2011 07:05:03 GMT; Path=/aBc; Domain=example.com; Secure; HTTPOnly; Max-Age=1234; Foo=Bar; Baz', output: { key: 'abc', value: '"xyzzy!"', @@ -44,8 +45,8 @@ describe('Cookie.parse', () => { secure: true, httpOnly: true, maxAge: 1234, - extensions: ['Foo=Bar', 'Baz'] - } + extensions: ['Foo=Bar', 'Baz'], + }, }, // invalid expires { @@ -53,8 +54,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - expires: "Infinity" - } + expires: 'Infinity', + }, }, // zero max-age { @@ -62,8 +63,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - maxAge: 0 - } + maxAge: 0, + }, }, // negative max-age { @@ -71,8 +72,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - maxAge: -1 - } + maxAge: -1, + }, }, // empty domain { @@ -80,8 +81,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: null - } + domain: null, + }, }, // dot domain { @@ -89,8 +90,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: null - } + domain: null, + }, }, // uppercase domain { @@ -98,8 +99,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: "example.com" - } + domain: 'example.com', + }, }, // trailing dot in domain { @@ -107,9 +108,9 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: "example.com." + domain: 'example.com.', }, - assertValidateReturns: false + assertValidateReturns: false, }, // empty path { @@ -117,8 +118,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - path: null - } + path: null, + }, }, // no-slash path { @@ -126,8 +127,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - path: null - } + path: null, + }, }, // trailing semi-colons after path #1 { @@ -135,8 +136,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - path: '/' - } + path: '/', + }, }, // trailing semi-colons after path #2 { @@ -144,8 +145,8 @@ describe('Cookie.parse', () => { output: { key: 'c', value: 'd', - path: null - } + path: null, + }, }, // secure-with-value { @@ -153,8 +154,8 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - secure: true - } + secure: true, + }, }, // httponly-with-value { @@ -162,13 +163,13 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - httpOnly: true - } + httpOnly: true, + }, }, // garbage { input: '\x08', - output: undefined + output: undefined, }, // public suffix domain { @@ -176,9 +177,9 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: 'kyoto.jp' + domain: 'kyoto.jp', }, - assertValidateReturns: false + assertValidateReturns: false, }, // public suffix foonet.net - top level { @@ -186,9 +187,9 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: 'foonet.net' + domain: 'foonet.net', }, - assertValidateReturns: true + assertValidateReturns: true, }, // public suffix foonet.net - www { @@ -196,9 +197,9 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: 'www.foonet.net' + domain: 'www.foonet.net', }, - assertValidateReturns: true + assertValidateReturns: true, }, // public suffix foonet.net - with a dot { @@ -206,31 +207,33 @@ describe('Cookie.parse', () => { output: { key: 'a', value: 'b', - domain: 'foonet.net' + domain: 'foonet.net', }, - assertValidateReturns: true + assertValidateReturns: true, }, // Ironically, Google 'GAPS' cookie has very little whitespace { - input: 'GAPS=1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-;Path=/;Expires=Thu, 17-Apr-2014 02:12:29 GMT;Secure;HttpOnly', + input: + 'GAPS=1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-;Path=/;Expires=Thu, 17-Apr-2014 02:12:29 GMT;Secure;HttpOnly', output: { key: 'GAPS', value: '1:A1aaaaAaAAa1aaAaAaaAAAaaa1a11a:aaaAaAaAa-aaaA1-', path: '/', expires: new Date(Date.parse('Thu, 17-Apr-2014 02:12:29 GMT')), secure: true, - httpOnly: true + httpOnly: true, }, }, // lots of equal signs { - input: 'queryPref=b=c&d=e; Path=/f=g; Expires=Thu, 17 Apr 2014 02:12:29 GMT; HttpOnly', + input: + 'queryPref=b=c&d=e; Path=/f=g; Expires=Thu, 17 Apr 2014 02:12:29 GMT; HttpOnly', output: { key: 'queryPref', value: 'b=c&d=e', path: '/f=g', expires: new Date(Date.parse('Thu, 17 Apr 2014 02:12:29 GMT')), - httpOnly: true + httpOnly: true, }, }, // spaces in value @@ -241,7 +244,7 @@ describe('Cookie.parse', () => { value: 'one two three', path: null, domain: null, - extensions: null + extensions: null, }, }, // quoted spaces in value @@ -252,7 +255,7 @@ describe('Cookie.parse', () => { value: '"one two three"', path: null, domain: null, - extensions: null + extensions: null, }, }, // non-ASCII in value @@ -263,7 +266,7 @@ describe('Cookie.parse', () => { value: 'weiß', path: null, domain: null, - extensions: null + extensions: null, }, }, // empty key @@ -274,9 +277,9 @@ describe('Cookie.parse', () => { value: 'abc', path: null, domain: null, - extensions: null + extensions: null, }, - parseOptions: { loose: true } + parseOptions: { loose: true }, }, // non-existent key { @@ -286,9 +289,9 @@ describe('Cookie.parse', () => { value: 'abc', path: null, domain: null, - extensions: null + extensions: null, }, - parseOptions: { loose: true } + parseOptions: { loose: true }, }, // weird format { @@ -298,30 +301,30 @@ describe('Cookie.parse', () => { value: 'bar', path: null, domain: null, - extensions: null + extensions: null, }, - parseOptions: { loose: true } + parseOptions: { loose: true }, }, // way too many semicolons followed by non-semicolon { - input: `foo=bar${";".repeat(65535)} domain=example.com`, + input: `foo=bar${';'.repeat(65535)} domain=example.com`, output: { key: 'foo', value: 'bar', path: null, domain: 'example.com', - extensions: null - } + extensions: null, + }, }, // way too many spaces - small one doesn't parse { input: `x x`, - output: undefined + output: undefined, }, // way too many spaces - large one doesn't parse { - input: `x${" ".repeat(65535)}x`, - output: undefined + input: `x${' '.repeat(65535)}x`, + output: undefined, }, // same-site - lax { @@ -330,8 +333,8 @@ describe('Cookie.parse', () => { key: 'abc', value: 'xyzzy', sameSite: 'lax', - extensions: null - } + extensions: null, + }, }, // same-site - strict { @@ -340,8 +343,8 @@ describe('Cookie.parse', () => { key: 'abc', value: 'xyzzy', sameSite: 'strict', - extensions: null - } + extensions: null, + }, }, // same-site - none { @@ -350,8 +353,8 @@ describe('Cookie.parse', () => { key: 'abc', value: 'xyzzy', sameSite: 'none', - extensions: null - } + extensions: null, + }, }, // same-site - bad { @@ -360,8 +363,8 @@ describe('Cookie.parse', () => { key: 'abc', value: 'xyzzy', sameSite: undefined, - extensions: null - } + extensions: null, + }, }, // same-site - absent { @@ -370,31 +373,31 @@ describe('Cookie.parse', () => { key: 'abc', value: 'xyzzy', sameSite: undefined, - extensions: null - } + extensions: null, + }, }, // empty string { input: ``, - output: null + output: null, }, // missing string { input: undefined, - output: null + output: null, }, // some string object { input: new String(''), - output: null + output: null, }, // some empty string object { input: new String(), - output: null - } + output: null, + }, ])('Cookie.parse("$input")', (testCase) => { - const {input, output } = testCase + const { input, output } = testCase const parseOptions = testCase.parseOptions || {} const value = input === undefined ? undefined : input.valueOf() @@ -419,39 +422,44 @@ describe('Cookie.parse', () => { it.each([ { shortVersion: 'x x', - longVersion: `x${" ".repeat(65535)}x`, + longVersion: `x${' '.repeat(65535)}x`, }, { shortVersion: 'x x', - longVersion: `x${" ".repeat(65535)}x`, - parseOptions: { loose: true } + longVersion: `x${' '.repeat(65535)}x`, + parseOptions: { loose: true }, }, { shortVersion: 'x =x', - longVersion: `x${" ".repeat(65535)}=x` + longVersion: `x${' '.repeat(65535)}=x`, }, { shortVersion: 'x =x', - longVersion: `x${" ".repeat(65535)}=x`, - parseOptions: { loose: true } - }, - ])('Cookie.parse("$shortVersion") should not take significantly longer to run than Cookie.parse("$longVersion")', ({ shortVersion, longVersion , parseOptions = {}}) => { - const startShortVersionParse = performance.now() - Cookie.parse(shortVersion, parseOptions) - const endShortVersionParse = performance.now() + longVersion: `x${' '.repeat(65535)}=x`, + parseOptions: { loose: true }, + }, + ])( + 'Cookie.parse("$shortVersion") should not take significantly longer to run than Cookie.parse("$longVersion")', + ({ shortVersion, longVersion, parseOptions = {} }) => { + const startShortVersionParse = performance.now() + Cookie.parse(shortVersion, parseOptions) + const endShortVersionParse = performance.now() - const startLongVersionParse = performance.now() - Cookie.parse(longVersion, parseOptions) - const endLongVersionParse = performance.now() + const startLongVersionParse = performance.now() + Cookie.parse(longVersion, parseOptions) + const endLongVersionParse = performance.now() - const ratio = (endLongVersionParse - startLongVersionParse) / (endShortVersionParse - startShortVersionParse) - expect(ratio).toBeLessThan(250) // if broken this ratio goes 2000-4000x higher - }) + const ratio = + (endLongVersionParse - startLongVersionParse) / + (endShortVersionParse - startShortVersionParse) + expect(ratio).toBeLessThan(250) // if broken this ratio goes 2000-4000x higher + }, + ) }) // way too many spaces - takes about the same time for each it('should parse a long cookie string with spaces in roughly the same amount of time as one with short spaces', () => { - const longCookie = `x${" ".repeat(65535)}x` + const longCookie = `x${' '.repeat(65535)}x` const shortCookie = `x x` const startLongCookieParse = performance.now() @@ -462,6 +470,8 @@ it('should parse a long cookie string with spaces in roughly the same amount of Cookie.parse(shortCookie) const endShortCookieParse = performance.now() - const ratio = (endLongCookieParse - startLongCookieParse) / (endShortCookieParse - startShortCookieParse) + const ratio = + (endLongCookieParse - startLongCookieParse) / + (endShortCookieParse - startShortCookieParse) expect(ratio).toBeLessThan(250) // if broken this ratio goes 2000-4000x higher }) diff --git a/lib/__tests__/pathMatch.spec.ts b/lib/__tests__/pathMatch.spec.ts index 64dfafba..1d42f5cd 100644 --- a/lib/__tests__/pathMatch.spec.ts +++ b/lib/__tests__/pathMatch.spec.ts @@ -1,17 +1,20 @@ -import {pathMatch} from "../cookie"; +import { pathMatch } from '../cookie' // port of tests/domain_and_path_test.js (path match tests) describe('pathMatch', () => { it.each([ // request, cookie, match - ["/", "/", true], - ["/dir", "/", true], - ["/", "/dir", false], - ["/dir/", "/dir/", true], - ["/dir/file", "/dir/", true], - ["/dir/file", "/dir", true], - ["/directory", "/dir", false] - ])('pathMatch("%s", "%s") => %s', (requestPath, cookiePath, expectedValue) => { - expect(pathMatch(requestPath, cookiePath)).toBe(expectedValue) - }) + ['/', '/', true], + ['/dir', '/', true], + ['/', '/dir', false], + ['/dir/', '/dir/', true], + ['/dir/file', '/dir/', true], + ['/dir/file', '/dir', true], + ['/directory', '/dir', false], + ])( + 'pathMatch("%s", "%s") => %s', + (requestPath, cookiePath, expectedValue) => { + expect(pathMatch(requestPath, cookiePath)).toBe(expectedValue) + }, + ) }) diff --git a/lib/__tests__/permuteDomain.spec.ts b/lib/__tests__/permuteDomain.spec.ts index 230919ba..206dcd01 100644 --- a/lib/__tests__/permuteDomain.spec.ts +++ b/lib/__tests__/permuteDomain.spec.ts @@ -1,37 +1,29 @@ -import {permuteDomain} from "../cookie"; +import { permuteDomain } from '../cookie' // port of tests/domain_and_path_test.js (permute domain tests) describe('permuteDomain', () => { it.each([ { domain: 'example.com', - permutations: ["example.com"] + permutations: ['example.com'], }, { domain: 'foo.bar.example.com', - permutations: [ - "example.com", - "bar.example.com", - "foo.bar.example.com" - ] + permutations: ['example.com', 'bar.example.com', 'foo.bar.example.com'], }, { domain: 'foo.bar.example.localduhmain', permutations: [ - "example.localduhmain", - "bar.example.localduhmain", - "foo.bar.example.localduhmain" - ] + 'example.localduhmain', + 'bar.example.localduhmain', + 'foo.bar.example.localduhmain', + ], }, { - domain: "foo.bar.example.com.", - permutations: [ - "example.com", - "bar.example.com", - "foo.bar.example.com" - ] + domain: 'foo.bar.example.com.', + permutations: ['example.com', 'bar.example.com', 'foo.bar.example.com'], }, - ])('permuteDomain("%s", %s") => %o', ({domain, permutations}) => { + ])('permuteDomain("%s", %s") => %o', ({ domain, permutations }) => { expect(permuteDomain(domain)).toEqual(permutations) }) }) diff --git a/lib/__tests__/permutePath.spec.ts b/lib/__tests__/permutePath.spec.ts index ce8bd456..ee05ff34 100644 --- a/lib/__tests__/permutePath.spec.ts +++ b/lib/__tests__/permutePath.spec.ts @@ -1,27 +1,27 @@ -import {pathMatch, permutePath} from "../cookie"; +import { pathMatch, permutePath } from '../cookie' // port of tests/domain_and_path_test.js (permute path tests) describe('permutePath', () => { it.each([ { path: '/', - permutations: ["/"] + permutations: ['/'], }, { path: '/foo', - permutations: ["/foo", "/"] + permutations: ['/foo', '/'], }, { path: '/foo/bar', - permutations: ["/foo/bar", "/foo", "/"] + permutations: ['/foo/bar', '/foo', '/'], }, { - path: "/foo/bar/", - permutations: ["/foo/bar/", "/foo/bar", "/foo", "/"] + path: '/foo/bar/', + permutations: ['/foo/bar/', '/foo/bar', '/foo', '/'], }, - ])('permuteDomain("%s", %s") => %o', ({path, permutations}) => { + ])('permuteDomain("%s", %s") => %o', ({ path, permutations }) => { expect(permutePath(path)).toEqual(permutations) - permutations.forEach(permutation => { + permutations.forEach((permutation) => { expect(pathMatch(path, permutation)).toBe(true) }) }) diff --git a/lib/__tests__/regression.spec.ts b/lib/__tests__/regression.spec.ts index ea3f7657..51cede3b 100644 --- a/lib/__tests__/regression.spec.ts +++ b/lib/__tests__/regression.spec.ts @@ -1,6 +1,6 @@ -import {CookieJar} from "../cookie"; +import { CookieJar } from '../cookie' -const { objectContaining, assertions } = expect; +const { objectContaining, assertions } = expect const url = 'http://www.example.com' describe('Regression Tests', () => { @@ -13,13 +13,13 @@ describe('Regression Tests', () => { objectContaining({ key: 'broken_path', value: 'testme', - path: '/' + path: '/', }), objectContaining({ key: 'b', value: '2', - path: '/' - }) + path: '/', + }), ]) }) @@ -27,7 +27,9 @@ describe('Regression Tests', () => { const malformedUri = `${url}/?test=100%` const cookieJar = new CookieJar() await cookieJar.setCookie('Test=Test', malformedUri) - await expect(cookieJar.getCookieString(malformedUri)).resolves.toBe('Test=Test') + await expect(cookieJar.getCookieString(malformedUri)).resolves.toBe( + 'Test=Test', + ) }) it('should allow setCookie (without options) callback works even if it is not instanceof Function (GH-158/GH-175)', () => { @@ -35,12 +37,14 @@ describe('Regression Tests', () => { const cookieJar = new CookieJar() // @ts-ignore - const callback = function(err, cookie) { + const callback = function (err, cookie) { expect(err).toBeNull() - expect(cookie).toEqual(objectContaining({ - key: 'a', - value: 'b' - })) + expect(cookie).toEqual( + objectContaining({ + key: 'a', + value: 'b', + }), + ) } Object.setPrototypeOf(callback, null) @@ -56,13 +60,13 @@ describe('Regression Tests', () => { const cookieJar = new CookieJar() // @ts-ignore - const callback = function(err, cookie) { + const callback = function (err, cookie) { expect(err).toBeNull() expect(cookie).toEqual([ objectContaining({ key: 'a', - value: 'b' - }) + value: 'b', + }), ]) } @@ -76,38 +80,41 @@ describe('Regression Tests', () => { }) it('should allow setCookie with localhost (GH-215)', async () => { - const cookieJar = new CookieJar(); - await expect(cookieJar.setCookie( - "a=b; Domain=localhost", - "http://localhost" - )).resolves.toEqual(objectContaining({ - key: 'a', - value: 'b', - domain: 'localhost' - })) + const cookieJar = new CookieJar() + await expect( + cookieJar.setCookie('a=b; Domain=localhost', 'http://localhost'), + ).resolves.toEqual( + objectContaining({ + key: 'a', + value: 'b', + domain: 'localhost', + }), + ) }) it('should allow setCookie with localhost and null domain (GH-215)', async () => { - const cookieJar = new CookieJar(); - await expect(cookieJar.setCookie( - "a=b; Domain=", - "http://localhost" - )).resolves.toEqual(objectContaining({ - key: 'a', - value: 'b', - domain: 'localhost' - })) + const cookieJar = new CookieJar() + await expect( + cookieJar.setCookie('a=b; Domain=', 'http://localhost'), + ).resolves.toEqual( + objectContaining({ + key: 'a', + value: 'b', + domain: 'localhost', + }), + ) }) it('setCookie with localhost (.localhost domain), (GH-215)', async () => { - const cookieJar = new CookieJar(); - await expect(cookieJar.setCookie( - "a=b; Domain=.localhost", - "http://localhost" - )).resolves.toEqual(objectContaining({ - key: 'a', - value: 'b', - domain: 'localhost' - })) + const cookieJar = new CookieJar() + await expect( + cookieJar.setCookie('a=b; Domain=.localhost', 'http://localhost'), + ).resolves.toEqual( + objectContaining({ + key: 'a', + value: 'b', + domain: 'localhost', + }), + ) }) }) diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts index 561a1853..cb7b3320 100644 --- a/lib/__tests__/removeAll.spec.ts +++ b/lib/__tests__/removeAll.spec.ts @@ -1,8 +1,14 @@ -import {Callback, Cookie, CookieJar, MemoryCookieStore, Store} from "../cookie"; -import spyOn = jest.spyOn; -import SpyInstance = jest.SpyInstance; +import { + Callback, + Cookie, + CookieJar, + MemoryCookieStore, + Store, +} from '../cookie' +import spyOn = jest.spyOn +import SpyInstance = jest.SpyInstance -const url = "http://example.com/index.html" +const url = 'http://example.com/index.html' describe('store removeAllCookies API', () => { describe(`with a store that doesn't implement removeAllCookies`, () => { @@ -15,7 +21,7 @@ describe('store removeAllCookies API', () => { expect(store.stats).toEqual({ put: 2, getAll: 1, - remove: 2 + remove: 2, }) }) @@ -23,27 +29,38 @@ describe('store removeAllCookies API', () => { const store = new StoreWithoutRemoveAll() const jar = new CookieJar(store) - await jar.setCookieSync("a=b", url); - await jar.setCookieSync("c=d", url); - await jar.setCookieSync("e=f", url); - await jar.setCookieSync("g=h", url); + await jar.setCookieSync('a=b', url) + await jar.setCookieSync('c=d', url) + await jar.setCookieSync('e=f', url) + await jar.setCookieSync('g=h', url) // replace remove cookie behavior to throw an error on the 4th invocation const _removeCookie = store.removeCookie - const spy: SpyInstance> = spyOn(store, 'removeCookie') - spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) - spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) - spy.mockImplementationOnce((domain, path, key, callback) => _removeCookie.call(store, domain, path, key, callback)) - spy.mockImplementationOnce((_domain, _path, _key, callback) => callback(new Error('something happened 4'))) + const spy: SpyInstance> = spyOn( + store, + 'removeCookie', + ) + spy.mockImplementationOnce((domain, path, key, callback) => + _removeCookie.call(store, domain, path, key, callback), + ) + spy.mockImplementationOnce((domain, path, key, callback) => + _removeCookie.call(store, domain, path, key, callback), + ) + spy.mockImplementationOnce((domain, path, key, callback) => + _removeCookie.call(store, domain, path, key, callback), + ) + spy.mockImplementationOnce((_domain, _path, _key, callback) => + callback(new Error('something happened 4')), + ) - await expect(jar.removeAllCookies()) - .rejects - .toThrowError('something happened 4') + await expect(jar.removeAllCookies()).rejects.toThrowError( + 'something happened 4', + ) expect(store.stats).toEqual({ put: 4, getAll: 1, - remove: 3 + remove: 3, }) }) @@ -51,35 +68,40 @@ describe('store removeAllCookies API', () => { const store = new StoreWithoutRemoveAll() const jar = new CookieJar(store) - await jar.setCookieSync("a=b", url); - await jar.setCookieSync("c=d", url); - await jar.setCookieSync("e=f", url); - await jar.setCookieSync("g=h", url); + await jar.setCookieSync('a=b', url) + await jar.setCookieSync('c=d', url) + await jar.setCookieSync('e=f', url) + await jar.setCookieSync('g=h', url) // replace remove cookie behavior to throw an error on the 4th invocation const _removeCookie = store.removeCookie - const spy: SpyInstance> = spyOn(store, 'removeCookie') + const spy: SpyInstance> = spyOn( + store, + 'removeCookie', + ) spy.mockImplementation((domain, path, key, callback) => { if (spy.mock.calls.length % 2 === 1) { - return callback(new Error(`something happened ${spy.mock.calls.length}`)) + return callback( + new Error(`something happened ${spy.mock.calls.length}`), + ) } return _removeCookie.call(store, domain, path, key, callback) }) - await expect(jar.removeAllCookies()) - .rejects - .toThrowError('something happened 1') + await expect(jar.removeAllCookies()).rejects.toThrowError( + 'something happened 1', + ) expect(store.stats).toEqual({ put: 4, getAll: 1, - remove: 2 + remove: 2, }) }) }) describe('with a store that does implement removeAllCookies', () => { - it("should remove the cookies using a batch operation", async () => { + it('should remove the cookies using a batch operation', async () => { const store = new MemoryStoreExtension() const jar = new CookieJar(store) await jar.setCookie('a=b', url) @@ -88,7 +110,7 @@ describe('store removeAllCookies API', () => { expect(store.stats).toEqual({ getAll: 0, remove: 0, - removeAll: 1 + removeAll: 1, }) expect(store.idx).toEqual({}) }) @@ -97,93 +119,149 @@ describe('store removeAllCookies API', () => { class StoreWithoutRemoveAll extends Store { stats: { - put: number; - getAll: number; - remove: number; + put: number + getAll: number + remove: number } private cookies: Cookie[] constructor() { - super(); - this.synchronous = true; - this.stats = { put: 0, getAll: 0, remove: 0 }; - this.cookies = []; + super() + this.synchronous = true + this.stats = { put: 0, getAll: 0, remove: 0 } + this.cookies = [] } - override findCookie(domain: string, path: string, key: string): Promise - override findCookie(domain: string, path: string, key: string, callback: Callback): void - override findCookie(_domain: string, _path: string, _key: string, callback?: Callback): unknown { + override findCookie( + domain: string, + path: string, + key: string, + ): Promise + override findCookie( + domain: string, + path: string, + key: string, + callback: Callback, + ): void + override findCookie( + _domain: string, + _path: string, + _key: string, + callback?: Callback, + ): unknown { // @ts-ignore - return callback(null, null); + return callback(null, null) } - override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise - override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - override findCookies(_domain: string, _path: string, _allowSpecialUseDomain: boolean | Callback = false, callback?: Callback): unknown { + override findCookies( + domain: string, + path: string, + allowSpecialUseDomain?: boolean, + ): Promise + override findCookies( + domain: string, + path: string, + allowSpecialUseDomain?: boolean, + callback?: Callback, + ): void + override findCookies( + _domain: string, + _path: string, + _allowSpecialUseDomain: boolean | Callback = false, + callback?: Callback, + ): unknown { // @ts-ignore - return callback(null, []); + return callback(null, []) } override putCookie(cookie: Cookie): Promise - override putCookie(cookie: Cookie, callback: Callback): void; + override putCookie(cookie: Cookie, callback: Callback): void override putCookie(cookie: Cookie, callback?: Callback): unknown { - this.stats.put++; - this.cookies.push(cookie); + this.stats.put++ + this.cookies.push(cookie) // @ts-ignore - return callback(null); + return callback(null) } override getAllCookies(): Promise override getAllCookies(callback: Callback): void override getAllCookies(callback?: Callback): unknown { - this.stats.getAll++; + this.stats.getAll++ // @ts-ignore - return callback(null, this.cookies.slice()); + return callback(null, this.cookies.slice()) } - override removeCookie(domain: string, path: string, key: string): Promise - override removeCookie(domain: string, path: string, key: string, callback: Callback): void - override removeCookie(_domain: string, _path: string, _key: string, callback?: Callback): unknown { - this.stats.remove++; + override removeCookie( + domain: string, + path: string, + key: string, + ): Promise + override removeCookie( + domain: string, + path: string, + key: string, + callback: Callback, + ): void + override removeCookie( + _domain: string, + _path: string, + _key: string, + callback?: Callback, + ): unknown { + this.stats.remove++ // @ts-ignore - return callback(null, null); + return callback(null, null) } } class MemoryStoreExtension extends MemoryCookieStore { stats: { - getAll: number; - remove: number; - removeAll: number; + getAll: number + remove: number + removeAll: number } constructor() { - super(); - this.stats = { getAll: 0, remove: 0, removeAll: 0 }; + super() + this.stats = { getAll: 0, remove: 0, removeAll: 0 } } override getAllCookies(): Promise override getAllCookies(callback: Callback): void override getAllCookies(callback?: Callback): unknown { - this.stats.getAll++; + this.stats.getAll++ // @ts-ignore return super.getAllCookies(callback) } - override removeCookie(domain: string, path: string, key: string): Promise - override removeCookie(domain: string, path: string, key: string, callback: Callback): void - override removeCookie(domain: string, path: string, key: string, callback?: Callback): unknown { - this.stats.remove++; + override removeCookie( + domain: string, + path: string, + key: string, + ): Promise + override removeCookie( + domain: string, + path: string, + key: string, + callback: Callback, + ): void + override removeCookie( + domain: string, + path: string, + key: string, + callback?: Callback, + ): unknown { + this.stats.remove++ // @ts-ignore - return super.removeCookie(domain, path, key, callback); + return super.removeCookie(domain, path, key, callback) } override removeAllCookies(): Promise override removeAllCookies(callback: Callback): void override removeAllCookies(callback?: Callback): unknown { - this.stats.removeAll++; + this.stats.removeAll++ // @ts-ignore - return super.removeAllCookies(callback); + return super.removeAllCookies(callback) } } diff --git a/lib/__tests__/sameSite.spec.ts b/lib/__tests__/sameSite.spec.ts index 466774cc..030efc46 100644 --- a/lib/__tests__/sameSite.spec.ts +++ b/lib/__tests__/sameSite.spec.ts @@ -1,7 +1,7 @@ -import {Cookie, CookieJar} from "../cookie"; +import { Cookie, CookieJar } from '../cookie' -const url = "http://example.com/index.html" -const {objectContaining} = expect +const url = 'http://example.com/index.html' +const { objectContaining } = expect describe('Same-Site Cookies', function () { let cookieJar: CookieJar @@ -13,84 +13,92 @@ describe('Same-Site Cookies', function () { beforeEach(() => { cookieJar = new CookieJar() // @ts-ignore - garbage = Cookie.parse("garbageIn=treatedAsNone; SameSite=garbage") + garbage = Cookie.parse('garbageIn=treatedAsNone; SameSite=garbage') // @ts-ignore - strict = Cookie.parse("strict=authorized; SameSite=sTrIcT") + strict = Cookie.parse('strict=authorized; SameSite=sTrIcT') // @ts-ignore - lax = Cookie.parse("lax=okay; SameSite=lax") + lax = Cookie.parse('lax=okay; SameSite=lax') // @ts-ignore - normal = Cookie.parse("normal=whatever") + normal = Cookie.parse('normal=whatever') }) describe('Retrieval', () => { beforeEach(async () => { - await cookieJar.setCookie("strict=authorized; SameSite=strict", url) - await cookieJar.setCookie("lax=okay; SameSite=lax", url) - await cookieJar.setCookie("normal=whatever", url) + await cookieJar.setCookie('strict=authorized; SameSite=strict', url) + await cookieJar.setCookie('lax=okay; SameSite=lax', url) + await cookieJar.setCookie('normal=whatever', url) }) it('should return all cookies when making a "strict" same-site request', async () => { - const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'strict' }) + const cookies = await cookieJar.getCookies(url, { + sameSiteContext: 'strict', + }) expect(cookies).toEqual([ objectContaining({ key: 'strict', value: 'authorized', - sameSite: 'strict' + sameSite: 'strict', }), objectContaining({ key: 'lax', value: 'okay', - sameSite: 'lax' + sameSite: 'lax', }), objectContaining({ key: 'normal', value: 'whatever', - }) + }), ]) }) it('should return no "strict" cookies when making a "lax" same-site request', async () => { - const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'lax' }) + const cookies = await cookieJar.getCookies(url, { + sameSiteContext: 'lax', + }) expect(cookies).toEqual([ objectContaining({ key: 'lax', value: 'okay', - sameSite: 'lax' + sameSite: 'lax', }), objectContaining({ key: 'normal', value: 'whatever', - }) + }), ]) }) it('should return only the "none" cookie when making a cross-origin request', async () => { - const cookies = await cookieJar.getCookies(url, { sameSiteContext: 'none' }) + const cookies = await cookieJar.getCookies(url, { + sameSiteContext: 'none', + }) expect(cookies).toEqual([ objectContaining({ key: 'normal', value: 'whatever', - }) + }), ]) }) it('should return all cookies when making an unqualified request', async () => { - const cookies = await cookieJar.getCookies(url, { sameSiteContext: undefined }) + const cookies = await cookieJar.getCookies(url, { + sameSiteContext: undefined, + }) expect(cookies).toEqual([ objectContaining({ key: 'strict', value: 'authorized', - sameSite: 'strict' + sameSite: 'strict', }), objectContaining({ key: 'lax', value: 'okay', - sameSite: 'lax' + sameSite: 'lax', }), objectContaining({ key: 'normal', value: 'whatever', - }) + }), ]) }) }) @@ -125,15 +133,19 @@ describe('Same-Site Cookies', function () { }) it('should not allow strict cookie to be set', async () => { - await expect(cookieJar.setCookie(strict, url, { sameSiteContext: 'none' })) - .rejects - .toThrowError('Cookie is SameSite but this is a cross-origin request') + await expect( + cookieJar.setCookie(strict, url, { sameSiteContext: 'none' }), + ).rejects.toThrowError( + 'Cookie is SameSite but this is a cross-origin request', + ) }) it('should not allow lax cookie to be set', async () => { - await expect(cookieJar.setCookie(lax, url, { sameSiteContext: 'none' })) - .rejects - .toThrowError('Cookie is SameSite but this is a cross-origin request') + await expect( + cookieJar.setCookie(lax, url, { sameSiteContext: 'none' }), + ).rejects.toThrowError( + 'Cookie is SameSite but this is a cross-origin request', + ) }) it('should treat the normal cookie as sameSite=none', async () => { @@ -168,7 +180,9 @@ describe('Same-Site Cookies', function () { describe('Canonicalized Strings', () => { it('garbage in = garbage out', () => { garbage.sameSite = 'GaRbAGe' - expect(garbage.toString()).toBe('garbageIn=treatedAsNone; SameSite=GaRbAGe') + expect(garbage.toString()).toBe( + 'garbageIn=treatedAsNone; SameSite=GaRbAGe', + ) }) it('turn strict to "Strict"', () => { @@ -183,4 +197,4 @@ describe('Same-Site Cookies', function () { expect(normal.toString()).toBe('normal=whatever') }) }) -}); +}) diff --git a/lib/cookie.ts b/lib/cookie.ts index 631a0536..9da0a0f8 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -30,34 +30,34 @@ */ import * as punycode from "punycode/"; -import {parse as urlParse} from 'url' -import * as pubsuffix from './pubsuffix-psl' -import {Store} from './store' -import {MemoryCookieStore} from './memstore' -import {pathMatch} from "./pathMatch"; -import * as validators from './validators' -import VERSION from './version' -import {permuteDomain} from "./permuteDomain" -import {getCustomInspectSymbol} from './utilHelper' +import { parse as urlParse } from "url"; +import * as pubsuffix from "./pubsuffix-psl"; +import { Store } from "./store"; +import { MemoryCookieStore } from "./memstore"; +import { pathMatch } from "./pathMatch"; +import * as validators from "./validators"; +import VERSION from "./version"; +import { permuteDomain } from "./permuteDomain"; +import { getCustomInspectSymbol } from "./utilHelper"; // From RFC6265 S4.1.1 // note that it excludes \x3B ";" -const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/; +const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/ -const CONTROL_CHARS = /[\x00-\x1F]/; +const CONTROL_CHARS = /[\x00-\x1F]/ // From Chromium // '\r', '\n' and '\0' should be treated as a terminator in // the "relaxed" mode, see: // https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60 -const TERMINATORS = ["\n", "\r", "\0"]; +const TERMINATORS = ['\n', '\r', '\0'] // RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"' // Note ';' is \x3B -const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/; +const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/ // date-time parsing constants (RFC6265 S5.1.1) -const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/; +const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/ const MONTH_TO_NUM = { jan: 0, @@ -71,35 +71,36 @@ const MONTH_TO_NUM = { sep: 8, oct: 9, nov: 10, - dec: 11 -}; + dec: 11, +} -const MAX_TIME = 2147483647000; // 31-bit max -const MIN_TIME = 0; // 31-bit min +const MAX_TIME = 2147483647000 // 31-bit max +const MIN_TIME = 0 // 31-bit min const SAME_SITE_CONTEXT_VAL_ERR = - 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'; + 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"' function checkSameSiteContext(value: string) { - validators.validate(validators.isNonEmptyString(value), value); - const context = String(value).toLowerCase(); - if (context === "none" || context === "lax" || context === "strict") { - return context; + validators.validate(validators.isNonEmptyString(value), value) + const context = String(value).toLowerCase() + if (context === 'none' || context === 'lax' || context === 'strict') { + return context } else { - return null; + return null } } const PrefixSecurityEnum = Object.freeze({ - SILENT: "silent", - STRICT: "strict", - DISABLED: "unsafe-disabled" -}); + SILENT: 'silent', + STRICT: 'strict', + DISABLED: 'unsafe-disabled', +}) // Dumped from ip-regex@4.0.0, with the following changes: // * all capturing groups converted to non-capturing -- "(?:)" // * support for IPv6 Scoped Literal ("%eth1") removed // * lowercase hexadecimal only -const IP_REGEX_LOWERCASE = /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/; +const IP_REGEX_LOWERCASE = + /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/ const IP_V6_REGEX = ` \\[?(?: (?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)| @@ -112,10 +113,10 @@ const IP_V6_REGEX = ` (?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)) )(?:%[0-9a-zA-Z]{1,})?\\]? ` - .replace(/\s*\/\/.*$/gm, "") - .replace(/\n/g, "") - .trim(); -const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`); + .replace(/\s*\/\/.*$/gm, '') + .replace(/\n/g, '') + .trim() +const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`) /* * Parses a Natural number (i.e., non-negative integer) with either the @@ -127,32 +128,37 @@ const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`); * The "trailingOK" boolean controls if the grammar accepts a * "( non-digit *OCTET )" trailer. */ -function parseDigits(token: string, minDigits: number, maxDigits: number, trailingOK: boolean) { - let count = 0; +function parseDigits( + token: string, + minDigits: number, + maxDigits: number, + trailingOK: boolean, +) { + let count = 0 while (count < token.length) { - const c = token.charCodeAt(count); + const c = token.charCodeAt(count) // "non-digit = %x00-2F / %x3A-FF" if (c <= 0x2f || c >= 0x3a) { - break; + break } - count++; + count++ } // constrain to a minimum and maximum number of digits. if (count < minDigits || count > maxDigits) { - return null; + return null } if (!trailingOK && count != token.length) { - return null; + return null } - return parseInt(token.substr(0, count), 10); + return parseInt(token.substr(0, count), 10) } function parseTime(token: string) { - const parts = token.split(":"); - const result = [0, 0, 0]; + const parts = token.split(':') + const result = [0, 0, 0] /* RF6256 S5.1.1: * time = hms-time ( non-digit *OCTET ) @@ -161,46 +167,57 @@ function parseTime(token: string) { */ if (parts.length !== 3) { - return null; + return null } for (let i = 0; i < 3; i++) { // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be // followed by "( non-digit *OCTET )" therefore the last time-field can // have a trailer - const trailingOK = i == 2; + const trailingOK = i == 2 const numPart = parts[i] if (numPart == null) { return null } - const num = parseDigits(numPart, 1, 2, trailingOK); + const num = parseDigits(numPart, 1, 2, trailingOK) if (num === null) { - return null; + return null } - result[i] = num; + result[i] = num } - return result; + return result } function parseMonth(token: string) { - token = String(token) - .substr(0, 3) - .toLowerCase(); + token = String(token).substr(0, 3).toLowerCase() switch (token) { - case 'jan': return MONTH_TO_NUM.jan - case 'feb': return MONTH_TO_NUM.feb - case 'mar': return MONTH_TO_NUM.mar - case 'apr': return MONTH_TO_NUM.apr - case 'may': return MONTH_TO_NUM.may - case 'jun': return MONTH_TO_NUM.jun - case 'jul': return MONTH_TO_NUM.jul - case 'aug': return MONTH_TO_NUM.aug - case 'sep': return MONTH_TO_NUM.sep - case 'oct': return MONTH_TO_NUM.oct - case 'nov': return MONTH_TO_NUM.nov - case 'dec': return MONTH_TO_NUM.dec - default: return null + case 'jan': + return MONTH_TO_NUM.jan + case 'feb': + return MONTH_TO_NUM.feb + case 'mar': + return MONTH_TO_NUM.mar + case 'apr': + return MONTH_TO_NUM.apr + case 'may': + return MONTH_TO_NUM.may + case 'jun': + return MONTH_TO_NUM.jun + case 'jul': + return MONTH_TO_NUM.jul + case 'aug': + return MONTH_TO_NUM.aug + case 'sep': + return MONTH_TO_NUM.sep + case 'oct': + return MONTH_TO_NUM.oct + case 'nov': + return MONTH_TO_NUM.nov + case 'dec': + return MONTH_TO_NUM.dec + default: + return null } } @@ -209,32 +226,32 @@ function parseMonth(token: string) { */ function parseDate(str: string | undefined | null): Date | undefined { if (!str) { - return undefined; + return undefined } /* RFC6265 S5.1.1: * 2. Process each date-token sequentially in the order the date-tokens * appear in the cookie-date */ - const tokens = str.split(DATE_DELIM); + const tokens = str.split(DATE_DELIM) if (!tokens) { - return undefined; + return undefined } - let hour = null; - let minute = null; - let second = null; - let dayOfMonth = null; - let month = null; - let year = null; + let hour = null + let minute = null + let second = null + let dayOfMonth = null + let month = null + let year = null for (let i = 0; i < tokens.length; i++) { - const token = (tokens[i] ?? "").trim(); + const token = (tokens[i] ?? '').trim() if (!token.length) { - continue; + continue } - let result; + let result /* 2.1. If the found-time flag is not set and the token matches the time * production, set the found-time flag and set the hour- value, @@ -243,12 +260,12 @@ function parseDate(str: string | undefined | null): Date | undefined { * to the next date-token. */ if (second === null) { - result = parseTime(token); + result = parseTime(token) if (result) { - hour = result[0]; - minute = result[1]; - second = result[2]; - continue; + hour = result[0] + minute = result[1] + second = result[2] + continue } } @@ -259,10 +276,10 @@ function parseDate(str: string | undefined | null): Date | undefined { */ if (dayOfMonth === null) { // "day-of-month = 1*2DIGIT ( non-digit *OCTET )" - result = parseDigits(token, 1, 2, true); + result = parseDigits(token, 1, 2, true) if (result !== null) { - dayOfMonth = result; - continue; + dayOfMonth = result + continue } } @@ -272,10 +289,10 @@ function parseDate(str: string | undefined | null): Date | undefined { * continue to the next date-token. */ if (month === null) { - result = parseMonth(token); + result = parseMonth(token) if (result !== null) { - month = result; - continue; + month = result + continue } } @@ -286,9 +303,9 @@ function parseDate(str: string | undefined | null): Date | undefined { */ if (year === null) { // "year = 2*4DIGIT ( non-digit *OCTET )" - result = parseDigits(token, 2, 4, true); + result = parseDigits(token, 2, 4, true) if (result !== null) { - year = result; + year = result /* From S5.1.1: * 3. If the year-value is greater than or equal to 70 and less * than or equal to 99, increment the year-value by 1900. @@ -296,9 +313,9 @@ function parseDate(str: string | undefined | null): Date | undefined { * than or equal to 69, increment the year-value by 2000. */ if (year >= 70 && year <= 99) { - year += 1900; + year += 1900 } else if (year >= 0 && year <= 69) { - year += 2000; + year += 2000 } } } @@ -331,55 +348,59 @@ function parseDate(str: string | undefined | null): Date | undefined { minute > 59 || second > 59 ) { - return undefined; + return undefined } - return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)); + return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)) } function formatDate(date: Date) { - validators.validate(validators.isDate(date), date); - return date.toUTCString(); + validators.validate(validators.isDate(date), date) + return date.toUTCString() } // S5.1.2 Canonicalized Host Names function canonicalDomain(str: string | null) { if (str == null) { - return null; + return null } - let _str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading . + let _str = str.trim().replace(/^\./, '') // S4.1.2.3 & S5.2.3: ignore leading . if (IP_V6_REGEX_OBJECT.test(_str)) { - _str = _str.replace("[", "").replace("]", ""); + _str = _str.replace('[', '').replace(']', '') } // convert to IDN if any non-ASCII characters if (punycode && /[^\u0001-\u007f]/.test(_str)) { - _str = punycode.toASCII(_str); + _str = punycode.toASCII(_str) } - return _str.toLowerCase(); + return _str.toLowerCase() } // S5.1.3 Domain Matching -function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boolean | null { +function domainMatch( + str?: string, + domStr?: string, + canonicalize?: boolean, +): boolean | null { if (str == null || domStr == null) { - return null; + return null } let _str: string | null let _domStr: string | null if (canonicalize !== false) { - _str = canonicalDomain(str); - _domStr = canonicalDomain(domStr); + _str = canonicalDomain(str) + _domStr = canonicalDomain(domStr) } else { - _str = str; + _str = str _domStr = domStr } if (_str == null || _domStr == null) { - return null; + return null } /* @@ -392,36 +413,36 @@ function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boo * this point)" */ if (_str == _domStr) { - return true; + return true } /* " o All of the following [three] conditions hold:" */ /* "* The domain string is a suffix of the string" */ - const idx = _str.lastIndexOf(domStr); + const idx = _str.lastIndexOf(domStr) if (idx <= 0) { - return false; // it's a non-match (-1) or prefix (0) + return false // it's a non-match (-1) or prefix (0) } // next, check it's a proper suffix // e.g., "a.b.c".indexOf("b.c") === 2 // 5 === 3+2 if (_str.length !== _domStr.length + idx) { - return false; // it's not a suffix + return false // it's not a suffix } /* " * The last character of the string that is not included in the * domain string is a %x2E (".") character." */ - if (_str.substr(idx - 1, 1) !== ".") { - return false; // doesn't align on "." + if (_str.substr(idx - 1, 1) !== '.') { + return false // doesn't align on "." } /* " * The string is a host name (i.e., not an IP address)." */ if (IP_REGEX_LOWERCASE.test(_str)) { - return false; // it's an IP address + return false // it's an IP address } - return true; + return true } // RFC6265 S5.1.4 Paths and Path-Match @@ -435,105 +456,105 @@ function domainMatch(str?: string, domStr?: string, canonicalize?: boolean): boo function defaultPath(path?: string): string { // "2. If the uri-path is empty or if the first character of the uri-path is not // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. - if (!path || path.substr(0, 1) !== "/") { - return "/"; + if (!path || path.substr(0, 1) !== '/') { + return '/' } // "3. If the uri-path contains no more than one %x2F ("/") character, output // %x2F ("/") and skip the remaining step." - if (path === "/") { - return path; + if (path === '/') { + return path } - const rightSlash = path.lastIndexOf("/"); + const rightSlash = path.lastIndexOf('/') if (rightSlash === 0) { - return "/"; + return '/' } // "4. Output the characters of the uri-path from the first character up to, // but not including, the right-most %x2F ("/")." - return path.slice(0, rightSlash); + return path.slice(0, rightSlash) } function trimTerminator(str: string) { - if (validators.isEmptyString(str)) return str; + if (validators.isEmptyString(str)) return str for (let t = 0; t < TERMINATORS.length; t++) { const terminator = TERMINATORS[t] - const terminatorIdx = terminator ? str.indexOf(terminator) : -1; + const terminatorIdx = terminator ? str.indexOf(terminator) : -1 if (terminatorIdx !== -1) { - str = str.substr(0, terminatorIdx); + str = str.substr(0, terminatorIdx) } } - return str; + return str } function parseCookiePair(cookiePair: string, looseMode: boolean) { - cookiePair = trimTerminator(cookiePair); - validators.validate(validators.isString(cookiePair), cookiePair); + cookiePair = trimTerminator(cookiePair) + validators.validate(validators.isString(cookiePair), cookiePair) - let firstEq = cookiePair.indexOf("="); + let firstEq = cookiePair.indexOf('=') if (looseMode) { if (firstEq === 0) { // '=' is immediately at start - cookiePair = cookiePair.substr(1); - firstEq = cookiePair.indexOf("="); // might still need to split on '=' + cookiePair = cookiePair.substr(1) + firstEq = cookiePair.indexOf('=') // might still need to split on '=' } } else { // non-loose mode if (firstEq <= 0) { // no '=' or is at start - return undefined; // needs to have non-empty "cookie-name" + return undefined // needs to have non-empty "cookie-name" } } - let cookieName, cookieValue; + let cookieName, cookieValue if (firstEq <= 0) { - cookieName = ""; - cookieValue = cookiePair.trim(); + cookieName = '' + cookieValue = cookiePair.trim() } else { - cookieName = cookiePair.substr(0, firstEq).trim(); - cookieValue = cookiePair.substr(firstEq + 1).trim(); + cookieName = cookiePair.substr(0, firstEq).trim() + cookieValue = cookiePair.substr(firstEq + 1).trim() } if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) { - return undefined; + return undefined } - const c = new Cookie(); - c.key = cookieName; - c.value = cookieValue; - return c; + const c = new Cookie() + c.key = cookieName + c.value = cookieValue + return c } function parse(str: string, options: any = {}): Cookie | undefined | null { if (validators.isEmptyString(str) || !validators.isString(str)) { - return null; + return null } - str = str.trim(); + str = str.trim() // We use a regex to parse the "name-value-pair" part of S5.2 - const firstSemi = str.indexOf(";"); // S5.2 step 1 - const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi); - const c = parseCookiePair(cookiePair, !!options.loose); + const firstSemi = str.indexOf(';') // S5.2 step 1 + const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi) + const c = parseCookiePair(cookiePair, !!options.loose) if (!c) { - return undefined; + return undefined } if (firstSemi === -1) { - return c; + return c } // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string // (including the %x3B (";") in question)." plus later on in the same section // "discard the first ";" and trim". - const unparsed = str.slice(firstSemi + 1).trim(); + const unparsed = str.slice(firstSemi + 1).trim() // "If the unparsed-attributes string is empty, skip the rest of these // steps." if (unparsed.length === 0) { - return c; + return c } /* @@ -544,73 +565,73 @@ function parse(str: string, options: any = {}): Cookie | undefined | null { * cookie-attribute-list". Therefore, in this implementation, we overwrite * the previous value. */ - const cookie_avs = unparsed.split(";"); + const cookie_avs = unparsed.split(';') while (cookie_avs.length) { - const av = (cookie_avs.shift() ?? "").trim(); + const av = (cookie_avs.shift() ?? '').trim() if (av.length === 0) { // happens if ";;" appears - continue; + continue } - const av_sep = av.indexOf("="); - let av_key, av_value; + const av_sep = av.indexOf('=') + let av_key, av_value if (av_sep === -1) { - av_key = av; - av_value = null; + av_key = av + av_value = null } else { - av_key = av.substr(0, av_sep); - av_value = av.substr(av_sep + 1); + av_key = av.substr(0, av_sep) + av_value = av.substr(av_sep + 1) } - av_key = av_key.trim().toLowerCase(); + av_key = av_key.trim().toLowerCase() if (av_value) { - av_value = av_value.trim(); + av_value = av_value.trim() } switch (av_key) { - case "expires": // S5.2.1 + case 'expires': // S5.2.1 if (av_value) { - const exp = parseDate(av_value); + const exp = parseDate(av_value) // "If the attribute-value failed to parse as a cookie date, ignore the // cookie-av." if (exp) { // over and underflow not realistically a concern: V8's getTime() seems to // store something larger than a 32-bit time_t (even with 32-bit node) - c.expires = exp; + c.expires = exp } } - break; + break - case "max-age": // S5.2.2 + case 'max-age': // S5.2.2 if (av_value) { // "If the first character of the attribute-value is not a DIGIT or a "-" // character ...[or]... If the remainder of attribute-value contains a // non-DIGIT character, ignore the cookie-av." if (/^-?[0-9]+$/.test(av_value)) { - const delta = parseInt(av_value, 10); + const delta = parseInt(av_value, 10) // "If delta-seconds is less than or equal to zero (0), let expiry-time // be the earliest representable date and time." - c.setMaxAge(delta); + c.setMaxAge(delta) } } - break; + break - case "domain": // S5.2.3 + case 'domain': // S5.2.3 // "If the attribute-value is empty, the behavior is undefined. However, // the user agent SHOULD ignore the cookie-av entirely." if (av_value) { // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E // (".") character." - const domain = av_value.trim().replace(/^\./, ""); + const domain = av_value.trim().replace(/^\./, '') if (domain) { // "Convert the cookie-domain to lower case." - c.domain = domain.toLowerCase(); + c.domain = domain.toLowerCase() } } - break; + break - case "path": // S5.2.4 + case 'path': // S5.2.4 /* * "If the attribute-value is empty or if the first character of the * attribute-value is not %x2F ("/"): @@ -621,48 +642,48 @@ function parse(str: string, options: any = {}): Cookie | undefined | null { * We'll represent the default-path as null since it depends on the * context of the parsing. */ - c.path = av_value && av_value[0] === "/" ? av_value : null; - break; + c.path = av_value && av_value[0] === '/' ? av_value : null + break - case "secure": // S5.2.5 + case 'secure': // S5.2.5 /* * "If the attribute-name case-insensitively matches the string "Secure", * the user agent MUST append an attribute to the cookie-attribute-list * with an attribute-name of Secure and an empty attribute-value." */ - c.secure = true; - break; + c.secure = true + break - case "httponly": // S5.2.6 -- effectively the same as 'secure' - c.httpOnly = true; - break; + case 'httponly': // S5.2.6 -- effectively the same as 'secure' + c.httpOnly = true + break - case "samesite": // RFC6265bis-02 S5.3.7 - const enforcement = av_value ? av_value.toLowerCase() : ""; + case 'samesite': // RFC6265bis-02 S5.3.7 + const enforcement = av_value ? av_value.toLowerCase() : '' switch (enforcement) { - case "strict": - c.sameSite = "strict"; - break; - case "lax": - c.sameSite = "lax"; - break; - case "none": - c.sameSite = "none"; - break; + case 'strict': + c.sameSite = 'strict' + break + case 'lax': + c.sameSite = 'lax' + break + case 'none': + c.sameSite = 'none' + break default: - c.sameSite = undefined; - break; + c.sameSite = undefined + break } - break; + break default: - c.extensions = c.extensions || []; - c.extensions.push(av); - break; + c.extensions = c.extensions || [] + c.extensions.push(av) + break } } - return c; + return c } /** @@ -673,8 +694,9 @@ function parse(str: string, options: any = {}): Cookie | undefined | null { * @returns boolean */ function isSecurePrefixConditionMet(cookie: Cookie) { - validators.validate(validators.isObject(cookie), cookie); - const startsWithSecurePrefix = typeof cookie.key === 'string' && cookie.key.startsWith("__Secure-") + validators.validate(validators.isObject(cookie), cookie) + const startsWithSecurePrefix = + typeof cookie.key === 'string' && cookie.key.startsWith('__Secure-') return !startsWithSecurePrefix || cookie.secure } @@ -690,65 +712,66 @@ function isSecurePrefixConditionMet(cookie: Cookie) { * @returns boolean */ function isHostPrefixConditionMet(cookie: Cookie) { - validators.validate(validators.isObject(cookie)); - const startsWithHostPrefix = typeof cookie.key === 'string' && cookie.key.startsWith("__Host-") + validators.validate(validators.isObject(cookie)) + const startsWithHostPrefix = + typeof cookie.key === 'string' && cookie.key.startsWith('__Host-') return ( !startsWithHostPrefix || (cookie.secure && cookie.hostOnly && cookie.path != null && - cookie.path === "/") - ); + cookie.path === '/') + ) } // avoid the V8 deoptimization monster! function jsonParse(str: string) { - let obj; + let obj try { - obj = JSON.parse(str); + obj = JSON.parse(str) } catch (e) { - return e; + return e } - return obj; + return obj } function fromJSON(str: string | SerializedCookie | null | undefined) { if (!str || validators.isEmptyString(str)) { - return null; + return null } - let obj; - if (typeof str === "string") { - obj = jsonParse(str); + let obj + if (typeof str === 'string') { + obj = jsonParse(str) if (obj instanceof Error) { - return null; + return null } } else { // assume it's an Object - obj = str; + obj = str } - const c = new Cookie(); + const c = new Cookie() for (let i = 0; i < Cookie.serializableProperties.length; i++) { - const prop = Cookie.serializableProperties[i]; + const prop = Cookie.serializableProperties[i] // @ts-ignore if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) { - continue; // leave as prototype default + continue // leave as prototype default } - if (prop === "expires" || prop === "creation" || prop === "lastAccessed") { + if (prop === 'expires' || prop === 'creation' || prop === 'lastAccessed') { if (obj[prop] === null) { - c[prop] = null; + c[prop] = null } else { - c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]); + c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(obj[prop]) } } else { // @ts-ignore - c[prop] = obj[prop]; + c[prop] = obj[prop] } } - return c; + return c } /* Section 5.4 part 2: @@ -761,62 +784,64 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { */ function cookieCompare(a: Cookie, b: Cookie) { - validators.validate(validators.isObject(a), a); - validators.validate(validators.isObject(b), b); - let cmp = 0; + validators.validate(validators.isObject(a), a) + validators.validate(validators.isObject(b), b) + let cmp = 0 // descending for length: b CMP a - const aPathLen = a.path ? a.path.length : 0; - const bPathLen = b.path ? b.path.length : 0; - cmp = bPathLen - aPathLen; + const aPathLen = a.path ? a.path.length : 0 + const bPathLen = b.path ? b.path.length : 0 + cmp = bPathLen - aPathLen if (cmp !== 0) { - return cmp; + return cmp } // ascending for time: a CMP b - const aTime = a.creation && a.creation instanceof Date ? a.creation.getTime() : MAX_TIME; - const bTime = b.creation && b.creation instanceof Date ? b.creation.getTime() : MAX_TIME; - cmp = aTime - bTime; + const aTime = + a.creation && a.creation instanceof Date ? a.creation.getTime() : MAX_TIME + const bTime = + b.creation && b.creation instanceof Date ? b.creation.getTime() : MAX_TIME + cmp = aTime - bTime if (cmp !== 0) { - return cmp; + return cmp } // break ties for the same millisecond (precision of JavaScript's clock) - cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0); + cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0) - return cmp; + return cmp } // Gives the permutation of all possible pathMatch()es of a given path. The // array is in longest-to-shortest order. Handy for indexing. function permutePath(path: string): string[] { - validators.validate(validators.isString(path)); - if (path === "/") { - return ["/"]; + validators.validate(validators.isString(path)) + if (path === '/') { + return ['/'] } - const permutations = [path]; + const permutations = [path] while (path.length > 1) { - const lindex = path.lastIndexOf("/"); + const lindex = path.lastIndexOf('/') if (lindex === 0) { - break; + break } - path = path.substr(0, lindex); - permutations.push(path); + path = path.substr(0, lindex) + permutations.push(path) } - permutations.push("/"); - return permutations; + permutations.push('/') + return permutations } function getCookieContext(url: string | URL) { if (url instanceof URL && 'query' in url) { - return url; + return url } if (typeof url === 'string') { try { return urlParse(decodeURI(url)) } catch { - return urlParse(url); + return urlParse(url) } } @@ -825,9 +850,9 @@ function getCookieContext(url: string | URL) { const cookieDefaults = { // the order in which the RFC has them: - key: "", - value: "", - expires: "Infinity", + key: '', + value: '', + expires: 'Infinity', maxAge: null, domain: null, path: null, @@ -839,73 +864,75 @@ const cookieDefaults = { pathIsDefault: null, creation: null, lastAccessed: null, - sameSite: undefined -}; + sameSite: undefined, +} export class Cookie { - key: string | undefined; - value: string | undefined; - expires: Date | 'Infinity' | null | undefined; - maxAge: number | 'Infinity' | '-Infinity' | undefined; - domain: string | null | undefined; - path: string | null | undefined; - secure: boolean | undefined; - httpOnly: boolean | undefined; - extensions: string[] | null | undefined; - creation: Date | 'Infinity' | null; - creationIndex: number | undefined; - hostOnly: boolean | null | undefined; - pathIsDefault: boolean | null | undefined; - lastAccessed: Date | 'Infinity' | null | undefined; - sameSite: string | undefined; + key: string | undefined + value: string | undefined + expires: Date | 'Infinity' | null | undefined + maxAge: number | 'Infinity' | '-Infinity' | undefined + domain: string | null | undefined + path: string | null | undefined + secure: boolean | undefined + httpOnly: boolean | undefined + extensions: string[] | null | undefined + creation: Date | 'Infinity' | null + creationIndex: number | undefined + hostOnly: boolean | null | undefined + pathIsDefault: boolean | null | undefined + lastAccessed: Date | 'Infinity' | null | undefined + sameSite: string | undefined constructor(options: any = {}) { - const customInspectSymbol = getCustomInspectSymbol(); + const customInspectSymbol = getCustomInspectSymbol() if (customInspectSymbol) { // @ts-ignore - this[customInspectSymbol] = this.inspect; + this[customInspectSymbol] = this.inspect } - Object.assign(this, cookieDefaults, options); - this.creation = options.creation ?? cookieDefaults.creation ?? new Date(); + Object.assign(this, cookieDefaults, options) + this.creation = options.creation ?? cookieDefaults.creation ?? new Date() // used to break creation ties in cookieCompare(): - Object.defineProperty(this, "creationIndex", { + Object.defineProperty(this, 'creationIndex', { configurable: false, enumerable: false, // important for assert.deepEqual checks writable: true, - value: ++Cookie.cookiesCreated - }); + value: ++Cookie.cookiesCreated, + }) } inspect() { - const now = Date.now(); - const hostOnly = this.hostOnly != null ? this.hostOnly : "?"; - const createAge = this.creation && this.creation !== 'Infinity' - ? `${now - this.creation.getTime()}ms` - : "?"; - const accessAge = this.lastAccessed && this.lastAccessed !== 'Infinity' - ? `${now - this.lastAccessed.getTime()}ms` - : "?"; - return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`; + const now = Date.now() + const hostOnly = this.hostOnly != null ? this.hostOnly : '?' + const createAge = + this.creation && this.creation !== 'Infinity' + ? `${now - this.creation.getTime()}ms` + : '?' + const accessAge = + this.lastAccessed && this.lastAccessed !== 'Infinity' + ? `${now - this.lastAccessed.getTime()}ms` + : '?' + return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"` } toJSON(): SerializedCookie { - const obj: SerializedCookie = {}; + const obj: SerializedCookie = {} for (const prop of Cookie.serializableProperties) { // @ts-ignore if (this[prop] === cookieDefaults[prop]) { - continue; // leave as prototype default + continue // leave as prototype default } if ( - prop === "expires" || - prop === "creation" || - prop === "lastAccessed" + prop === 'expires' || + prop === 'creation' || + prop === 'lastAccessed' ) { if (this[prop] == null) { - obj[prop] = null; + obj[prop] = null } else { const value = this[prop] if (value === 'Infinity') { @@ -914,7 +941,7 @@ export class Cookie { obj[prop] = value && value.toISOString() } } - } else if (prop === "maxAge") { + } else if (prop === 'maxAge') { const maxAge = this[prop] if (maxAge != null) { @@ -922,130 +949,136 @@ export class Cookie { obj[prop] = maxAge == Infinity || maxAge == -Infinity ? maxAge.toString() - : maxAge; + : maxAge } } else { // @ts-ignore if (this[prop] !== cookieDefaults[prop]) { // @ts-ignore - obj[prop] = this[prop]; + obj[prop] = this[prop] } } } - return obj; + return obj } clone() { - return fromJSON(this.toJSON()); + return fromJSON(this.toJSON()) } validate() { if (this.value == null || !COOKIE_OCTETS.test(this.value)) { - return false; + return false } if ( this.expires != 'Infinity' && !(this.expires instanceof Date) && !parseDate(this.expires) ) { - return false; + return false } if (this.maxAge != null && this.maxAge <= 0) { - return false; // "Max-Age=" non-zero-digit *DIGIT + return false // "Max-Age=" non-zero-digit *DIGIT } if (this.path != null && !PATH_VALUE.test(this.path)) { - return false; + return false } - const cdomain = this.cdomain(); + const cdomain = this.cdomain() if (cdomain) { if (cdomain.match(/\.$/)) { - return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this + return false // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this } - const suffix = pubsuffix.getPublicSuffix(cdomain); + const suffix = pubsuffix.getPublicSuffix(cdomain) if (suffix == null) { // it's a public suffix - return false; + return false } } - return true; + return true } setExpires(exp: string | Date) { if (exp instanceof Date) { - this.expires = exp; + this.expires = exp } else { - this.expires = parseDate(exp) || "Infinity"; + this.expires = parseDate(exp) || 'Infinity' } } setMaxAge(age: number) { if (age === Infinity) { - this.maxAge = "Infinity"; + this.maxAge = 'Infinity' } else if (age === -Infinity) { - this.maxAge = '-Infinity'; + this.maxAge = '-Infinity' } else { - this.maxAge = age; + this.maxAge = age } } cookieString() { - let val = this.value; + let val = this.value if (val == null) { - val = ""; + val = '' } - if (this.key === "") { - return val; + if (this.key === '') { + return val } - return `${this.key}=${val}`; + return `${this.key}=${val}` } // gives Set-Cookie header format toString() { - let str = this.cookieString(); + let str = this.cookieString() if (this.expires != 'Infinity') { if (this.expires instanceof Date) { - str += `; Expires=${formatDate(this.expires)}`; + str += `; Expires=${formatDate(this.expires)}` } else { - str += `; Expires=${this.expires}`; + str += `; Expires=${this.expires}` } } if (this.maxAge != null && this.maxAge != Infinity) { - str += `; Max-Age=${this.maxAge}`; + str += `; Max-Age=${this.maxAge}` } if (this.domain && !this.hostOnly) { - str += `; Domain=${this.domain}`; + str += `; Domain=${this.domain}` } if (this.path) { - str += `; Path=${this.path}`; + str += `; Path=${this.path}` } if (this.secure) { - str += "; Secure"; + str += '; Secure' } if (this.httpOnly) { - str += "; HttpOnly"; + str += '; HttpOnly' } - if (this.sameSite && this.sameSite !== "none") { - if (this.sameSite.toLowerCase() === Cookie.sameSiteCanonical.lax.toLowerCase()) { - str += `; SameSite=${Cookie.sameSiteCanonical.lax}`; - } else if (this.sameSite.toLowerCase() === Cookie.sameSiteCanonical.strict.toLowerCase()) { - str += `; SameSite=${Cookie.sameSiteCanonical.strict}`; + if (this.sameSite && this.sameSite !== 'none') { + if ( + this.sameSite.toLowerCase() === + Cookie.sameSiteCanonical.lax.toLowerCase() + ) { + str += `; SameSite=${Cookie.sameSiteCanonical.lax}` + } else if ( + this.sameSite.toLowerCase() === + Cookie.sameSiteCanonical.strict.toLowerCase() + ) { + str += `; SameSite=${Cookie.sameSiteCanonical.strict}` } else { - str += `; SameSite=${this.sameSite}`; + str += `; SameSite=${this.sameSite}` } } if (this.extensions) { - this.extensions.forEach(ext => { - str += `; ${ext}`; - }); + this.extensions.forEach((ext) => { + str += `; ${ext}` + }) } - return str; + return str } // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie() @@ -1059,10 +1092,10 @@ export class Cookie { * (Concurs with S5.3 step 3) */ if (this.maxAge != null && typeof this.maxAge === 'number') { - return this.maxAge <= 0 ? 0 : this.maxAge * 1000; + return this.maxAge <= 0 ? 0 : this.maxAge * 1000 } - let expires = this.expires; + let expires = this.expires if (expires === 'Infinity') { return Infinity } @@ -1072,79 +1105,79 @@ export class Cookie { expires = parseDate(expires) } - return (expires?.getTime() ?? now)- (now || Date.now()) + return (expires?.getTime() ?? now) - (now || Date.now()) } // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() // elsewhere) expiryTime(now?: Date): number { if (this.maxAge != null) { - const relativeTo = now || this.creation || new Date(); - const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity; - const age = maxAge <= 0 ? -Infinity : maxAge * 1000; + const relativeTo = now || this.creation || new Date() + const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity + const age = maxAge <= 0 ? -Infinity : maxAge * 1000 if (relativeTo === 'Infinity') { return Infinity } - return relativeTo.getTime() + age; + return relativeTo.getTime() + age } if (this.expires == 'Infinity') { - return Infinity; + return Infinity } // @ts-ignore - return this.expires.getTime(); + return this.expires.getTime() } // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() // elsewhere), except it returns a Date expiryDate(now: Date) { - const millisec = this.expiryTime(now); + const millisec = this.expiryTime(now) if (millisec == Infinity) { - return new Date(MAX_TIME); + return new Date(MAX_TIME) } else if (millisec == -Infinity) { - return new Date(MIN_TIME); + return new Date(MIN_TIME) } else { - return new Date(millisec); + return new Date(millisec) } } // This replaces the "persistent-flag" parts of S5.3 step 3 isPersistent(): boolean { - return this.maxAge != null || this.expires != 'Infinity'; + return this.maxAge != null || this.expires != 'Infinity' } // Mostly S5.1.2 and S5.2.3: canonicalizedDomain() { if (this.domain == null) { - return null; + return null } - return canonicalDomain(this.domain); + return canonicalDomain(this.domain) } cdomain() { - return this.canonicalizedDomain(); + return this.canonicalizedDomain() } - static parse (cookieString: string, options?: {}): Cookie | undefined | null { + static parse(cookieString: string, options?: {}): Cookie | undefined | null { return parse(cookieString, options) } - static fromJSON (jsonString: string | null | undefined): Cookie | null { + static fromJSON(jsonString: string | null | undefined): Cookie | null { return fromJSON(jsonString) } - static cookiesCreated: number = 0 + static cookiesCreated = 0 static sameSiteLevel = { strict: 3, lax: 2, - none: 1 + none: 1, } static sameSiteCanonical = { - strict: "Strict", - lax: "Lax" + strict: 'Strict', + lax: 'Lax', } static serializableProperties = [ @@ -1161,23 +1194,23 @@ export class Cookie { 'pathIsDefault', 'creation', 'lastAccessed', - 'sameSite' + 'sameSite', ] } function getNormalizedPrefixSecurity(prefixSecurity: string) { if (prefixSecurity != null) { - const normalizedPrefixSecurity = prefixSecurity.toLowerCase(); + const normalizedPrefixSecurity = prefixSecurity.toLowerCase() /* The three supported options */ switch (normalizedPrefixSecurity) { case PrefixSecurityEnum.STRICT: case PrefixSecurityEnum.SILENT: case PrefixSecurityEnum.DISABLED: - return normalizedPrefixSecurity; + return normalizedPrefixSecurity } } /* Default is SILENT */ - return PrefixSecurityEnum.SILENT; + return PrefixSecurityEnum.SILENT } const defaultSetCookieOptions: SetCookieOptions = { @@ -1192,11 +1225,14 @@ const defaultGetCookieOptions: GetCookiesOptions = { expire: true, allPaths: false, sameSiteContext: undefined, - sort: undefined + sort: undefined, } export function createPromiseCallback(args: IArguments): PromiseCallback { - let callback: (error: Error | null | undefined, result: T | undefined) => Promise + let callback: ( + error: Error | null | undefined, + result: T | undefined, + ) => Promise let resolve: (result: T | undefined) => void let reject: (error: Error | null) => void @@ -1210,7 +1246,7 @@ export function createPromiseCallback(args: IArguments): PromiseCallback { callback = (err, result) => { try { cb(err, result) - } catch(e) { + } catch (e) { reject(e instanceof Error ? e : new Error(`${e}`)) } return promise @@ -1228,73 +1264,96 @@ export function createPromiseCallback(args: IArguments): PromiseCallback { return { promise, - callback + callback, } } export class CookieJar { - readonly store: Store; - private readonly rejectPublicSuffixes: boolean; - private readonly enableLooseMode: boolean; - private readonly allowSpecialUseDomain: boolean; - readonly prefixSecurity: string; + readonly store: Store + private readonly rejectPublicSuffixes: boolean + private readonly enableLooseMode: boolean + private readonly allowSpecialUseDomain: boolean + readonly prefixSecurity: string constructor(store?: any, options: any = { rejectPublicSuffixes: true }) { - if (typeof options === "boolean") { - options = { rejectPublicSuffixes: options }; + if (typeof options === 'boolean') { + options = { rejectPublicSuffixes: options } } - validators.validate(validators.isObject(options), options); - this.rejectPublicSuffixes = options.rejectPublicSuffixes; - this.enableLooseMode = !!options.looseMode; + validators.validate(validators.isObject(options), options) + this.rejectPublicSuffixes = options.rejectPublicSuffixes + this.enableLooseMode = !!options.looseMode this.allowSpecialUseDomain = - typeof options.allowSpecialUseDomain === "boolean" + typeof options.allowSpecialUseDomain === 'boolean' ? options.allowSpecialUseDomain - : true; - this.store = store || new MemoryCookieStore(); - this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity); + : true + this.store = store || new MemoryCookieStore() + this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity) } private callSync(fn: Function): T | undefined { if (!this.store.synchronous) { throw new Error( - "CookieJar store is not synchronous; use async API instead." - ); + 'CookieJar store is not synchronous; use async API instead.', + ) } - let syncErr: Error | undefined; - let syncResult: T | undefined = undefined; + let syncErr: Error | undefined + let syncResult: T | undefined = undefined fn.call(this, (error: Error, result: T) => { syncErr = error syncResult = result }) if (syncErr) { - throw syncErr; + throw syncErr } - return syncResult; + return syncResult } - setCookie(cookie: string | Cookie, url: string, callback: Callback): void; - setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions, callback: Callback): void; + setCookie( + cookie: string | Cookie, + url: string, + callback: Callback, + ): void + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions, + callback: Callback, + ): void setCookie(cookie: string | Cookie, url: string): Promise - setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions): Promise - setCookie(cookie: string | Cookie, url: string, options: SetCookieOptions | Callback, callback?: Callback): unknown; - setCookie(cookie: string | Cookie, url: string, options?: SetCookieOptions | Callback, callback?: Callback): unknown { + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions, + ): Promise + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions | Callback, + callback?: Callback, + ): unknown + setCookie( + cookie: string | Cookie, + url: string, + options?: SetCookieOptions | Callback, + callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - validators.validate(validators.isNonEmptyString(url), callback, options); - let err; + validators.validate(validators.isNonEmptyString(url), callback, options) + let err if (validators.isFunction(url)) { - return cb(new Error("No URL was specified")); + return cb(new Error('No URL was specified')) } - const context = getCookieContext(url); + const context = getCookieContext(url) if (typeof options === 'function') { - options = defaultSetCookieOptions; + options = defaultSetCookieOptions } - validators.validate(validators.isFunction(cb), cb); + validators.validate(validators.isFunction(cb), cb) if ( !validators.isNonEmptyString(cookie) && @@ -1302,39 +1361,39 @@ export class CookieJar { cookie instanceof String && cookie.length == 0 ) { - return cb(null); + return cb(null) } - const host = canonicalDomain(context.hostname); - const loose = options?.loose || this.enableLooseMode; + const host = canonicalDomain(context.hostname) + const loose = options?.loose || this.enableLooseMode - let sameSiteContext = null; + let sameSiteContext = null if (options?.sameSiteContext) { - sameSiteContext = checkSameSiteContext(options.sameSiteContext); + sameSiteContext = checkSameSiteContext(options.sameSiteContext) if (!sameSiteContext) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); + return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } } // S5.3 step 1 - if (typeof cookie === "string" || cookie instanceof String) { - const parsedCookie = Cookie.parse(cookie.toString(), {loose: loose}); + if (typeof cookie === 'string' || cookie instanceof String) { + const parsedCookie = Cookie.parse(cookie.toString(), { loose: loose }) if (!parsedCookie) { - err = new Error("Cookie failed to parse"); - return cb(options?.ignoreError ? null : err); + err = new Error('Cookie failed to parse') + return cb(options?.ignoreError ? null : err) } cookie = parsedCookie } else if (!(cookie instanceof Cookie)) { // If you're seeing this error, and are passing in a Cookie object, // it *might* be a Cookie object from another loaded version of tough-cookie. err = new Error( - "First argument to setCookie must be a Cookie object or string" - ); - return cb(options?.ignoreError ? null : err); + 'First argument to setCookie must be a Cookie object or string', + ) + return cb(options?.ignoreError ? null : err) } // S5.3 step 2 - const now = options?.now || new Date(); // will assign later to save effort in the face of errors + const now = options?.now || new Date() // will assign later to save effort in the face of errors // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie() @@ -1344,14 +1403,17 @@ export class CookieJar { if (this.rejectPublicSuffixes && cookie.domain) { try { const cdomain = cookie.cdomain() - const suffix = typeof cdomain === 'string' ? pubsuffix.getPublicSuffix(cdomain, { - allowSpecialUseDomain: this.allowSpecialUseDomain, - ignoreError: options?.ignoreError - }) : null; + const suffix = + typeof cdomain === 'string' + ? pubsuffix.getPublicSuffix(cdomain, { + allowSpecialUseDomain: this.allowSpecialUseDomain, + ignoreError: options?.ignoreError, + }) + : null if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" - err = new Error("Cookie has domain set to a public suffix"); - return cb(options?.ignoreError ? null : err); + err = new Error('Cookie has domain set to a public suffix') + return cb(options?.ignoreError ? null : err) } } catch (err) { if (options?.ignoreError) { @@ -1368,28 +1430,30 @@ export class CookieJar { // S5.3 step 6: if (cookie.domain) { - if (!domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false)) { + if ( + !domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false) + ) { err = new Error( - `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}` - ); - return cb(options?.ignoreError ? null : err); + `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}`, + ) + return cb(options?.ignoreError ? null : err) } if (cookie.hostOnly == null) { // don't reset if already set - cookie.hostOnly = false; + cookie.hostOnly = false } } else { - cookie.hostOnly = true; - cookie.domain = host; + cookie.hostOnly = true + cookie.domain = host } //S5.2.4 If the attribute-value is empty or if the first character of the //attribute-value is not %x2F ("/"): //Let cookie-path be the default-path. - if (!cookie.path || cookie.path[0] !== "/") { - cookie.path = defaultPath(context.pathname ?? undefined); - cookie.pathIsDefault = true; + if (!cookie.path || cookie.path[0] !== '/') { + cookie.path = defaultPath(context.pathname ?? undefined) + cookie.pathIsDefault = true } // S5.3 step 8: NOOP; secure attribute @@ -1397,13 +1461,13 @@ export class CookieJar { // S5.3 step 10 if (options?.http === false && cookie.httpOnly) { - err = new Error("Cookie is HttpOnly and this isn't an HTTP API"); - return cb(options?.ignoreError ? null : err); + err = new Error("Cookie is HttpOnly and this isn't an HTTP API") + return cb(options?.ignoreError ? null : err) } // 6252bis-02 S5.4 Step 13 & 14: if ( - cookie.sameSite !== "none" && + cookie.sameSite !== 'none' && cookie.sameSite !== undefined && sameSiteContext ) { @@ -1411,46 +1475,48 @@ export class CookieJar { // is being set from a context whose "site for cookies" is not an // exact match for request-uri's host's registered domain, then // abort these steps and ignore the newly created cookie entirely." - if (sameSiteContext === "none") { - err = new Error( - "Cookie is SameSite but this is a cross-origin request" - ); - return cb(options?.ignoreError ? null : err); + if (sameSiteContext === 'none') { + err = new Error('Cookie is SameSite but this is a cross-origin request') + return cb(options?.ignoreError ? null : err) } } /* 6265bis-02 S5.4 Steps 15 & 16 */ const ignoreErrorForPrefixSecurity = - this.prefixSecurity === PrefixSecurityEnum.SILENT; + this.prefixSecurity === PrefixSecurityEnum.SILENT const prefixSecurityDisabled = - this.prefixSecurity === PrefixSecurityEnum.DISABLED; + this.prefixSecurity === PrefixSecurityEnum.DISABLED /* If prefix checking is not disabled ...*/ if (!prefixSecurityDisabled) { - let errorFound = false; - let errorMsg; + let errorFound = false + let errorMsg /* Check secure prefix condition */ if (!isSecurePrefixConditionMet(cookie)) { - errorFound = true; - errorMsg = "Cookie has __Secure prefix but Secure attribute is not set"; + errorFound = true + errorMsg = 'Cookie has __Secure prefix but Secure attribute is not set' } else if (!isHostPrefixConditionMet(cookie)) { /* Check host prefix condition */ - errorFound = true; + errorFound = true errorMsg = - "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'"; + "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'" } if (errorFound) { return cb( options?.ignoreError || ignoreErrorForPrefixSecurity ? null - : new Error(errorMsg) - ); + : new Error(errorMsg), + ) } } - const store = this.store; + const store = this.store if (!store.updateCookie) { - store.updateCookie = function (_oldCookie: Cookie, newCookie: Cookie, cb?: Callback): Promise { + store.updateCookie = function ( + _oldCookie: Cookie, + newCookie: Cookie, + cb?: Callback, + ): Promise { return this.putCookie(newCookie).then( () => { if (cb) { @@ -1461,100 +1527,134 @@ export class CookieJar { if (cb) { cb(error, undefined) } - } - ); - }; + }, + ) + } } - function withCookie(err: Error | undefined, oldCookie: Cookie | undefined | null): void { + function withCookie( + err: Error | undefined, + oldCookie: Cookie | undefined | null, + ): void { if (err) { - cb(err); + cb(err) return } const next = function (err: Error | undefined): void { if (err || typeof cookie === 'string') { - cb(err); + cb(err) } else { - cb(null, cookie); + cb(null, cookie) } - }; + } if (oldCookie) { // S5.3 step 11 - "If the cookie store contains a cookie with the same name, // domain, and path as the newly created cookie:" - if (options && 'http' in options && options.http === false && oldCookie.httpOnly) { + if ( + options && + 'http' in options && + options.http === false && + oldCookie.httpOnly + ) { // step 11.2 - err = new Error("old Cookie is HttpOnly and this isn't an HTTP API"); - cb(options.ignoreError ? null : err); + err = new Error("old Cookie is HttpOnly and this isn't an HTTP API") + cb(options.ignoreError ? null : err) return } if (cookie instanceof Cookie) { - cookie.creation = oldCookie.creation; + cookie.creation = oldCookie.creation // step 11.3 - cookie.creationIndex = oldCookie.creationIndex; + cookie.creationIndex = oldCookie.creationIndex // preserve tie-breaker - cookie.lastAccessed = now; + cookie.lastAccessed = now // Step 11.4 (delete cookie) is implied by just setting the new one: - store.updateCookie(oldCookie, cookie, next); // step 12 + store.updateCookie(oldCookie, cookie, next) // step 12 } } else { if (cookie instanceof Cookie) { - cookie.creation = cookie.lastAccessed = now; - store.putCookie(cookie, next); // step 12 + cookie.creation = cookie.lastAccessed = now + store.putCookie(cookie, next) // step 12 } } } - store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie); + store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie) return promiseCallback.promise } - setCookieSync(cookie: string | Cookie, url: string, options?: SetCookieOptions): Cookie | undefined { - const setCookieFn = this.setCookie.bind(this, cookie, url, options as SetCookieOptions) + setCookieSync( + cookie: string | Cookie, + url: string, + options?: SetCookieOptions, + ): Cookie | undefined { + const setCookieFn = this.setCookie.bind( + this, + cookie, + url, + options as SetCookieOptions, + ) return this.callSync(setCookieFn) } // RFC6365 S5.4 - getCookies(url: string, callback: Callback): void; - getCookies(url: string, options: GetCookiesOptions | undefined, callback: Callback): void + getCookies(url: string, callback: Callback): void + getCookies( + url: string, + options: GetCookiesOptions | undefined, + callback: Callback, + ): void getCookies(url: string): Promise - getCookies(url: string, options: GetCookiesOptions | undefined): Promise - getCookies(url: string, options: GetCookiesOptions | undefined | Callback, callback?: Callback): unknown - getCookies(url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { + getCookies( + url: string, + options: GetCookiesOptions | undefined, + ): Promise + getCookies( + url: string, + options: GetCookiesOptions | undefined | Callback, + callback?: Callback, + ): unknown + getCookies( + url: string, + options?: GetCookiesOptions | Callback, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - validators.validate(validators.isNonEmptyString(url), cb, url); - const context = getCookieContext(url); + validators.validate(validators.isNonEmptyString(url), cb, url) + const context = getCookieContext(url) if (typeof options === 'function' || options === undefined) { - options = defaultGetCookieOptions; + options = defaultGetCookieOptions } - validators.validate(validators.isObject(options), cb, options); - validators.validate(validators.isFunction(cb), cb); + validators.validate(validators.isObject(options), cb, options) + validators.validate(validators.isFunction(cb), cb) - const host = canonicalDomain(context.hostname); - const path = context.pathname || "/"; + const host = canonicalDomain(context.hostname) + const path = context.pathname || '/' - let secure = context.protocol && (context.protocol == "https:" || context.protocol == "wss:") + const secure = + context.protocol && + (context.protocol == 'https:' || context.protocol == 'wss:') - let sameSiteLevel = 0; + let sameSiteLevel = 0 if (options?.sameSiteContext) { const sameSiteContext = checkSameSiteContext(options.sameSiteContext) if (sameSiteContext == null) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); + return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } - sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext]; + sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext] if (!sameSiteLevel) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)); + return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } } - let http = options?.http ?? true; + const http = options?.http ?? true - const now = Date.now(); - const expireCheck = options?.expire ?? true; - const allPaths = options?.allPaths ?? false; - const store = this.store; + const now = Date.now() + const expireCheck = options?.expire ?? true + const allPaths = options?.allPaths ?? false + const store = this.store function matchingCookie(c: Cookie) { // "Either: @@ -1565,29 +1665,29 @@ export class CookieJar { // request-host domain-matches the cookie's domain." if (c.hostOnly) { if (c.domain != host) { - return false; + return false } } else { if (!domainMatch(host ?? undefined, c.domain ?? undefined, false)) { - return false; + return false } } // "The request-uri's path path-matches the cookie's path." if (!allPaths && typeof c.path === 'string' && !pathMatch(path, c.path)) { - return false; + return false } // "If the cookie's secure-only-flag is true, then the request-uri's // scheme must denote a "secure" protocol" if (c.secure && !secure) { - return false; + return false } // "If the cookie's http-only-flag is true, then exclude the cookie if the // cookie-string is being generated for a "non-HTTP" API" if (c.httpOnly && !http) { - return false; + return false } // RFC6265bis-02 S5.3.7 @@ -1602,27 +1702,27 @@ export class CookieJar { } if (cookieLevel > sameSiteLevel) { // only allow cookies at or below the request level - return false; + return false } } // deferred from S5.3 // non-RFC: allow retention of expired cookies by choice if (expireCheck && c.expiryTime() <= now) { - store.removeCookie(c.domain, c.path, c.key, () => {}); // result ignored - return false; + store.removeCookie(c.domain, c.path, c.key, () => {}) // result ignored + return false } - return true; + return true } store.findCookies( host, allPaths ? null : path, this.allowSpecialUseDomain, - (err, cookies): void => { + (err, cookies): void => { if (err) { - cb(err); + cb(err) return } @@ -1631,106 +1731,150 @@ export class CookieJar { return } - cookies = cookies.filter(matchingCookie); + cookies = cookies.filter(matchingCookie) // sorting of S5.4 part 2 if (options && 'sort' in options && options.sort !== false) { - cookies = cookies.sort(cookieCompare); + cookies = cookies.sort(cookieCompare) } // S5.4 part 3 - const now = new Date(); + const now = new Date() for (const cookie of cookies) { - cookie.lastAccessed = now; + cookie.lastAccessed = now } // TODO persist lastAccessed - cb(null, cookies); - } - ); + cb(null, cookies) + }, + ) return promiseCallback.promise } getCookiesSync(url: string, options?: GetCookiesOptions): Cookie[] { - return this.callSync(this.getCookies.bind(this, url, options)) ?? [] - } - - getCookieString(url: string, options: GetCookiesOptions, callback: Callback): void; - getCookieString(url: string, callback: Callback): void; - getCookieString(url: string): Promise; - getCookieString(url: string, options: GetCookiesOptions): Promise; - getCookieString(url: string, options: GetCookiesOptions | Callback, callback?: Callback): unknown; - getCookieString(url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { + return ( + this.callSync(this.getCookies.bind(this, url, options)) ?? [] + ) + } + + getCookieString( + url: string, + options: GetCookiesOptions, + callback: Callback, + ): void + getCookieString(url: string, callback: Callback): void + getCookieString(url: string): Promise + getCookieString(url: string, options: GetCookiesOptions): Promise + getCookieString( + url: string, + options: GetCookiesOptions | Callback, + callback?: Callback, + ): unknown + getCookieString( + url: string, + options?: GetCookiesOptions | Callback, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) if (typeof options === 'function') { options = undefined } - const next: Callback = function(err: Error | undefined, cookies: Cookie[] | undefined) { + const next: Callback = function ( + err: Error | undefined, + cookies: Cookie[] | undefined, + ) { if (err || cookies === undefined) { - promiseCallback.callback(err); + promiseCallback.callback(err) } else { promiseCallback.callback( undefined, cookies .sort(cookieCompare) - .map(c => c.cookieString()) - .join("; ") - ); + .map((c) => c.cookieString()) + .join('; '), + ) } - }; + } this.getCookies(url, options, next) return promiseCallback.promise } getCookieStringSync(url: string, options?: GetCookiesOptions): string { - return this.callSync(this.getCookieString.bind(this, url, options as GetCookiesOptions)) ?? "" - } - - getSetCookieStrings (url: string, callback: Callback): void - getSetCookieStrings (url: string, options: GetCookiesOptions, callback: Callback): void - getSetCookieStrings (url: string): Promise - getSetCookieStrings (url: string, options: GetCookiesOptions): Promise - getSetCookieStrings (url: string, options: GetCookiesOptions, callback?: Callback): unknown; - getSetCookieStrings (url: string, options?: GetCookiesOptions | Callback, _callback?: Callback): unknown { + return ( + this.callSync( + this.getCookieString.bind(this, url, options as GetCookiesOptions), + ) ?? '' + ) + } + + getSetCookieStrings(url: string, callback: Callback): void + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + callback: Callback, + ): void + getSetCookieStrings(url: string): Promise + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + ): Promise + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + callback?: Callback, + ): unknown + getSetCookieStrings( + url: string, + options?: GetCookiesOptions | Callback, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) if (typeof options === 'function') { options = undefined } - const next: Callback = function(err: Error | undefined, cookies: Cookie[] | undefined) { + const next: Callback = function ( + err: Error | undefined, + cookies: Cookie[] | undefined, + ) { if (err || cookies === undefined) { - promiseCallback.callback(err); + promiseCallback.callback(err) } else { promiseCallback.callback( null, - cookies.map(c => { - return c.toString(); - }) - ); + cookies.map((c) => { + return c.toString() + }), + ) } - }; + } - this.getCookies(url, options, next); + this.getCookies(url, options, next) return promiseCallback.promise } getSetCookieStringsSync(url: string, options: any = {}): string[] { - return this.callSync(this.getSetCookieStrings.bind(this, url, options)) ?? [] + return ( + this.callSync( + this.getSetCookieStrings.bind(this, url, options), + ) ?? [] + ) } - serialize(callback: Callback): void; - serialize(): Promise; - serialize(callback?: Callback): unknown; + serialize(callback: Callback): void + serialize(): Promise + serialize(callback?: Callback): unknown serialize(_callback?: Callback): unknown { - const promiseCallback = createPromiseCallback(arguments) + const promiseCallback = + createPromiseCallback(arguments) const cb = promiseCallback.callback - validators.validate(validators.isFunction(cb), cb); - let type: string | null = this.store.constructor.name; + validators.validate(validators.isFunction(cb), cb) + let type: string | null = this.store.constructor.name if (validators.isObject(type)) { - type = null; + type = null } // update README.md "Serialization Format" if you change this, please! @@ -1750,43 +1894,44 @@ export class CookieJar { prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), // this gets filled from getAllCookies: - cookies: [] - }; + cookies: [], + } if ( !( this.store.getAllCookies && - typeof this.store.getAllCookies === "function" + typeof this.store.getAllCookies === 'function' ) ) { return cb( new Error( - "store does not support getAllCookies and cannot be serialized" - ) - ); + 'store does not support getAllCookies and cannot be serialized', + ), + ) } this.store.getAllCookies((err, cookies) => { if (err) { - return cb(err); + return cb(err) } if (cookies == null) { return cb(undefined, serialized) } - serialized.cookies = cookies.map(cookie => { + serialized.cookies = cookies.map((cookie) => { // convert to serialized 'raw' cookies - const serializedCookie = cookie instanceof Cookie ? cookie.toJSON() : cookie; + const serializedCookie = + cookie instanceof Cookie ? cookie.toJSON() : cookie // Remove the index so new ones get assigned during deserialization - delete serializedCookie.creationIndex; + delete serializedCookie.creationIndex - return serializedCookie; - }); + return serializedCookie + }) - return cb(null, serialized); - }); + return cb(null, serialized) + }) return promiseCallback.promise } @@ -1795,54 +1940,57 @@ export class CookieJar { } toJSON() { - return this.serializeSync(); + return this.serializeSync() } // use the class method CookieJar.deserialize instead of calling this directly - _importCookies(serialized: { cookies: any; }, cb: Callback) { - let cookies = serialized.cookies; + _importCookies(serialized: { cookies: any }, cb: Callback) { + let cookies = serialized.cookies if (!cookies || !Array.isArray(cookies)) { - return cb(new Error("serialized jar has no cookies array"), undefined); + return cb(new Error('serialized jar has no cookies array'), undefined) } - cookies = cookies.slice(); // do not modify the original + cookies = cookies.slice() // do not modify the original const putNext = (err?: Error): void => { if (err) { - return cb(err, undefined); + return cb(err, undefined) } if (!cookies.length) { - return cb(err, this); + return cb(err, this) } - let cookie; + let cookie try { - cookie = fromJSON(cookies.shift()); + cookie = fromJSON(cookies.shift()) } catch (e) { - return cb(e instanceof Error ? e : new Error(`${e}`), undefined); + return cb(e instanceof Error ? e : new Error(`${e}`), undefined) } if (cookie === null) { - return putNext(undefined); // skip this cookie + return putNext(undefined) // skip this cookie } - this.store.putCookie(cookie, putNext); - }; + this.store.putCookie(cookie, putNext) + } - putNext(); + putNext() } - _importCookiesSync (serialized: any): void { + _importCookiesSync(serialized: any): void { this.callSync(this._importCookies.bind(this, serialized)) } - clone(callback: Callback): void; - clone(newStore: Store, callback: Callback): void; - clone(): Promise; - clone(newStore: Store): Promise; - clone(newStore?: Store | Callback, _callback?: Callback): unknown { + clone(callback: Callback): void + clone(newStore: Store, callback: Callback): void + clone(): Promise + clone(newStore: Store): Promise + clone( + newStore?: Store | Callback, + _callback?: Callback, + ): unknown { if (typeof newStore === 'function') { - newStore = undefined; + newStore = undefined } const promiseCallback = createPromiseCallback(arguments) @@ -1850,56 +1998,57 @@ export class CookieJar { this.serialize((err, serialized) => { if (err) { - return cb(err); + return cb(err) } - return CookieJar.deserialize(serialized ?? "", newStore, cb); - }); + return CookieJar.deserialize(serialized ?? '', newStore, cb) + }) return promiseCallback.promise } _cloneSync(newStore?: Store): CookieJar | undefined { - const cloneFn = newStore && typeof newStore !== 'function' ? - this.clone.bind(this, newStore) : - this.clone.bind(this) + const cloneFn = + newStore && typeof newStore !== 'function' + ? this.clone.bind(this, newStore) + : this.clone.bind(this) return this.callSync(cloneFn) } cloneSync(newStore?: Store): CookieJar | undefined { if (!newStore) { - return this._cloneSync(); + return this._cloneSync() } if (!newStore.synchronous) { throw new Error( - "CookieJar clone destination store is not synchronous; use async API instead." - ); + 'CookieJar clone destination store is not synchronous; use async API instead.', + ) } - return this._cloneSync(newStore); + return this._cloneSync(newStore) } - removeAllCookies(callback: ErrorCallback): void; - removeAllCookies(): Promise; - removeAllCookies(callback?: ErrorCallback): unknown; + removeAllCookies(callback: ErrorCallback): void + removeAllCookies(): Promise + removeAllCookies(callback?: ErrorCallback): unknown removeAllCookies(_callback?: ErrorCallback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - const store = this.store; + const store = this.store // Check that the store implements its own removeAllCookies(). The default // implementation in Store will immediately call the callback with a "not // implemented" Error. if ( - typeof store.removeAllCookies === "function" && + typeof store.removeAllCookies === 'function' && store.removeAllCookies !== Store.prototype.removeAllCookies ) { - store.removeAllCookies(cb); + store.removeAllCookies(cb) return promiseCallback.promise } store.getAllCookies((err, cookies): void => { if (err) { - cb(err); + cb(err) return } @@ -1908,35 +2057,35 @@ export class CookieJar { } if (cookies.length === 0) { - cb(null); + cb(null) return } - let completedCount = 0; - const removeErrors: Error[] = []; + let completedCount = 0 + const removeErrors: Error[] = [] function removeCookieCb(removeErr: Error | undefined) { if (removeErr) { - removeErrors.push(removeErr); + removeErrors.push(removeErr) } - completedCount++; + completedCount++ if (completedCount === cookies?.length) { - cb(removeErrors.length ? removeErrors[0] : null); + cb(removeErrors.length ? removeErrors[0] : null) return } } - cookies.forEach(cookie => { + cookies.forEach((cookie) => { store.removeCookie( cookie.domain, cookie.path, cookie.key, - removeCookieCb - ); - }); - }); + removeCookieCb, + ) + }) + }) return promiseCallback.promise } @@ -1944,65 +2093,86 @@ export class CookieJar { return this.callSync(this.removeAllCookies.bind(this)) } - static deserialize(strOrObj: string | object, callback: Callback): void; - static deserialize(strOrObj: string | object, store: Store, callback: Callback): void; - static deserialize(strOrObj: string | object): Promise; - static deserialize(strOrObj: string | object, store: Store): Promise; - static deserialize(strOrObj: string | object, store?: Store | Callback, callback?: Callback): unknown; - static deserialize(strOrObj: string | object, store?: Store | Callback, _callback?: Callback): unknown { + static deserialize( + strOrObj: string | object, + callback: Callback, + ): void + static deserialize( + strOrObj: string | object, + store: Store, + callback: Callback, + ): void + static deserialize(strOrObj: string | object): Promise + static deserialize( + strOrObj: string | object, + store: Store, + ): Promise + static deserialize( + strOrObj: string | object, + store?: Store | Callback, + callback?: Callback, + ): unknown + static deserialize( + strOrObj: string | object, + store?: Store | Callback, + _callback?: Callback, + ): unknown { if (typeof store === 'function') { - store = undefined; + store = undefined } const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - let serialized; - if (typeof strOrObj === "string") { - serialized = jsonParse(strOrObj); + let serialized + if (typeof strOrObj === 'string') { + serialized = jsonParse(strOrObj) if (serialized instanceof Error) { - return cb(serialized, undefined); + return cb(serialized, undefined) } } else { - serialized = strOrObj; + serialized = strOrObj } const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, looseMode: serialized.enableLooseMode, allowSpecialUseDomain: serialized.allowSpecialUseDomain, - prefixSecurity: serialized.prefixSecurity - }); - jar._importCookies(serialized, err => { + prefixSecurity: serialized.prefixSecurity, + }) + jar._importCookies(serialized, (err) => { if (err) { - return cb(err, undefined); + return cb(err, undefined) } - return cb(undefined, jar); - }); + return cb(undefined, jar) + }) return promiseCallback.promise } - static deserializeSync(strOrObj: string | SerializedCookieJar, store?: Store): CookieJar { + static deserializeSync( + strOrObj: string | SerializedCookieJar, + store?: Store, + ): CookieJar { const serialized = - typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj; + typeof strOrObj === 'string' ? JSON.parse(strOrObj) : strOrObj const jar = new CookieJar(store, { rejectPublicSuffixes: serialized.rejectPublicSuffixes, - looseMode: serialized.enableLooseMode - }); + looseMode: serialized.enableLooseMode, + }) // catch this mistake early: if (!jar.store.synchronous) { throw new Error( - "CookieJar store is not synchronous; use async API instead." - ); + 'CookieJar store is not synchronous; use async API instead.', + ) } - jar._importCookiesSync(serialized); - return jar; + jar._importCookiesSync(serialized) + return jar } - static fromJSON (jsonString: SerializedCookieJar, store?: Store): CookieJar { + static fromJSON(jsonString: SerializedCookieJar, store?: Store): CookieJar { return CookieJar.deserializeSync(jsonString, store) } } @@ -2029,11 +2199,11 @@ export { PrefixSecurityEnum as PrefixSecurityEnum } export { ParameterError as ParameterError } type SetCookieOptions = { - loose?: boolean | undefined; - sameSiteContext?: 'strict' | 'lax' | 'none' | undefined; - ignoreError?: boolean | undefined; - http?: boolean | undefined; - now?: Date | undefined; + loose?: boolean | undefined + sameSiteContext?: 'strict' | 'lax' | 'none' | undefined + ignoreError?: boolean | undefined + http?: boolean | undefined + now?: Date | undefined } type GetCookiesOptions = { @@ -2045,23 +2215,29 @@ type GetCookiesOptions = { } interface PromiseCallback { - promise: Promise; - callback: (error: Error | undefined | null, result?: T) => Promise; + promise: Promise + callback: ( + error: Error | undefined | null, + result?: T, + ) => Promise } export interface SerializedCookieJar { - version: string; - storeType: string | null; - rejectPublicSuffixes: boolean; - [key: string]: any; - cookies: SerializedCookie[]; + version: string + storeType: string | null + rejectPublicSuffixes: boolean + [key: string]: any + cookies: SerializedCookie[] } export interface SerializedCookie { - key?: string; - value?: string; - [key: string]: any; + key?: string + value?: string + [key: string]: any } -export type Callback = (error: Error | undefined, result: T | undefined) => void +export type Callback = ( + error: Error | undefined, + result: T | undefined, +) => void export type ErrorCallback = (error: Error) => void diff --git a/lib/memstore.ts b/lib/memstore.ts index 156a8882..d4a99420 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -28,40 +28,60 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; -import {Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain} from "./cookie"; -import {Store} from './store' -import {getCustomInspectSymbol, getUtilInspect} from './utilHelper' +'use strict' +import { + Callback, + Cookie, + createPromiseCallback, + pathMatch, + permuteDomain, +} from './cookie' +import { Store } from './store' +import { getCustomInspectSymbol, getUtilInspect } from './utilHelper' export class MemoryCookieStore extends Store { - override synchronous: boolean; + override synchronous: boolean idx: { [domain: string]: { [path: string]: { [key: string]: Cookie } } - }; + } constructor() { - super(); - this.synchronous = true; - this.idx = {}; - const customInspectSymbol = getCustomInspectSymbol(); + super() + this.synchronous = true + this.idx = {} + const customInspectSymbol = getCustomInspectSymbol() if (customInspectSymbol) { // @ts-ignore - this[customInspectSymbol] = this.inspect; + this[customInspectSymbol] = this.inspect } } inspect() { - const util = { inspect: getUtilInspect(inspectFallback) }; - return `{ idx: ${util.inspect(this.idx, false, 2)} }`; + const util = { inspect: getUtilInspect(inspectFallback) } + return `{ idx: ${util.inspect(this.idx, false, 2)} }` } - override findCookie(domain: string | null, path: string | null, key: string | undefined): Promise - override findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void - override findCookie(domain: string | null, path: string | null, key: string | undefined, _callback?: Callback): unknown { + override findCookie( + domain: string | null, + path: string | null, + key: string | undefined, + ): Promise + override findCookie( + domain: string | null, + path: string | null, + key: string | undefined, + callback: Callback, + ): void + override findCookie( + domain: string | null, + path: string | null, + key: string | undefined, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -71,79 +91,93 @@ export class MemoryCookieStore extends Store { const domainEntry = this.idx[domain] if (!domainEntry) { - return cb(null, undefined); + return cb(null, undefined) } const pathEntry = domainEntry[path] if (!pathEntry) { - return cb(null, undefined); + return cb(null, undefined) } if (key == null) { - return cb(null, null); + return cb(null, null) } - cb(null, pathEntry[key] || null); + cb(null, pathEntry[key] || null) return promiseCallback.promise } - override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean): Promise - override findCookies(domain: string, path: string, allowSpecialUseDomain?: boolean, callback?: Callback): void - override findCookies(domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, _callback?: Callback): unknown { - if (typeof allowSpecialUseDomain === "function") { - allowSpecialUseDomain = true; + override findCookies( + domain: string, + path: string, + allowSpecialUseDomain?: boolean, + ): Promise + override findCookies( + domain: string, + path: string, + allowSpecialUseDomain?: boolean, + callback?: Callback, + ): void + override findCookies( + domain: string, + path: string, + allowSpecialUseDomain: boolean | Callback = false, + _callback?: Callback, + ): unknown { + if (typeof allowSpecialUseDomain === 'function') { + allowSpecialUseDomain = true } - const results: any[] = []; + const results: any[] = [] const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback if (!domain) { - return cb(null, []); + return cb(null, []) } - let pathMatcher: (domainIndex: any) => void; + let pathMatcher: (domainIndex: any) => void if (!path) { // null means "all paths" pathMatcher = function matchAll(domainIndex) { for (const curPath in domainIndex) { - const pathIndex = domainIndex[curPath]; + const pathIndex = domainIndex[curPath] for (const key in pathIndex) { - results.push(pathIndex[key]); + results.push(pathIndex[key]) } } - }; + } } else { pathMatcher = function matchRFC(domainIndex) { //NOTE: we should use path-match algorithm from S5.1.4 here //(see : https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/canonical_cookie.cc#L299) - Object.keys(domainIndex).forEach(cookiePath => { + Object.keys(domainIndex).forEach((cookiePath) => { if (pathMatch(path, cookiePath)) { - const pathIndex = domainIndex[cookiePath]; + const pathIndex = domainIndex[cookiePath] for (const key in pathIndex) { - results.push(pathIndex[key]); + results.push(pathIndex[key]) } } - }); - }; + }) + } } - const domains = permuteDomain(domain, allowSpecialUseDomain) || [domain]; - const idx = this.idx; - domains.forEach(curDomain => { - const domainIndex = idx[curDomain]; + const domains = permuteDomain(domain, allowSpecialUseDomain) || [domain] + const idx = this.idx + domains.forEach((curDomain) => { + const domainIndex = idx[curDomain] if (!domainIndex) { - return; + return } - pathMatcher(domainIndex); - }); + pathMatcher(domainIndex) + }) - cb(null, results); + cb(null, results) return promiseCallback.promise } override putCookie(cookie: Cookie): Promise - override putCookie(cookie: Cookie, callback: Callback): void; + override putCookie(cookie: Cookie, callback: Callback): void override putCookie(cookie: Cookie, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -162,26 +196,48 @@ export class MemoryCookieStore extends Store { pathEntry[key] = cookie - cb(null, undefined); + cb(null, undefined) return promiseCallback.promise } override updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise - override updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; - override updateCookie(_oldCookie: Cookie, newCookie: Cookie, callback?: Callback): unknown { + override updateCookie( + oldCookie: Cookie, + newCookie: Cookie, + callback: Callback, + ): void + override updateCookie( + _oldCookie: Cookie, + newCookie: Cookie, + callback?: Callback, + ): unknown { // this seems wrong but it stops typescript from complaining and all the test pass... - callback = callback ?? function() {} + callback = callback ?? function () {} // updateCookie() may avoid updating cookies that are identical. For example, // lastAccessed may not be important to some stores and an equality // comparison could exclude that field. - return this.putCookie(newCookie, callback); + return this.putCookie(newCookie, callback) } - override removeCookie(domain: string, path: string, key: string): Promise - override removeCookie(domain: string, path: string, key: string, callback: Callback): void - override removeCookie(domain: string, path: string, key: string, _callback?: Callback): unknown { + override removeCookie( + domain: string, + path: string, + key: string, + ): Promise + override removeCookie( + domain: string, + path: string, + key: string, + callback: Callback, + ): void + override removeCookie( + domain: string, + path: string, + key: string, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -196,26 +252,34 @@ export class MemoryCookieStore extends Store { } } - cb(null, undefined); + cb(null, undefined) return promiseCallback.promise } override removeCookies(domain: string, path: string): Promise - override removeCookies(domain: string, path: string, callback: Callback): void - override removeCookies(domain: string, path: string, _callback?: Callback): unknown { + override removeCookies( + domain: string, + path: string, + callback: Callback, + ): void + override removeCookies( + domain: string, + path: string, + _callback?: Callback, + ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback const domainEntry = this.idx[domain] if (domainEntry) { if (path) { - delete domainEntry[path]; + delete domainEntry[path] } else { - delete this.idx[domain]; + delete this.idx[domain] } } - cb(null); + cb(null) return promiseCallback.promise } @@ -225,9 +289,9 @@ export class MemoryCookieStore extends Store { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - this.idx = {}; + this.idx = {} - cb(null); + cb(null) return promiseCallback.promise } @@ -237,78 +301,78 @@ export class MemoryCookieStore extends Store { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - const cookies: Cookie[] = []; - const idx = this.idx; + const cookies: Cookie[] = [] + const idx = this.idx - const domains = Object.keys(idx); - domains.forEach(domain => { + const domains = Object.keys(idx) + domains.forEach((domain) => { const domainEntry = idx[domain] ?? {} - const paths = Object.keys(domainEntry); - paths.forEach(path => { + const paths = Object.keys(domainEntry) + paths.forEach((path) => { const pathEntry = domainEntry[path] ?? {} - const keys = Object.keys(pathEntry); - keys.forEach(key => { + const keys = Object.keys(pathEntry) + keys.forEach((key) => { const keyEntry = pathEntry[key] if (keyEntry != null) { - cookies.push(keyEntry); + cookies.push(keyEntry) } - }); - }); - }); + }) + }) + }) // Sort by creationIndex so deserializing retains the creation order. // When implementing your own store, this SHOULD retain the order too cookies.sort((a, b) => { - return (a.creationIndex || 0) - (b.creationIndex || 0); - }); + return (a.creationIndex || 0) - (b.creationIndex || 0) + }) - cb(null, cookies); + cb(null, cookies) return promiseCallback.promise } } -export function inspectFallback(val: { [x: string]: any; }) { - const domains = Object.keys(val); +export function inspectFallback(val: { [x: string]: any }) { + const domains = Object.keys(val) if (domains.length === 0) { - return "{}"; + return '{}' } - let result = "{\n"; + let result = '{\n' Object.keys(val).forEach((domain, i) => { - result += formatDomain(domain, val[domain]); + result += formatDomain(domain, val[domain]) if (i < domains.length - 1) { - result += ","; + result += ',' } - result += "\n"; - }); - result += "}"; - return result; + result += '\n' + }) + result += '}' + return result } -function formatDomain(domainName: string, domainValue: { [x: string]: any; }) { - const indent = " "; - let result = `${indent}'${domainName}': {\n`; +function formatDomain(domainName: string, domainValue: { [x: string]: any }) { + const indent = ' ' + let result = `${indent}'${domainName}': {\n` Object.keys(domainValue).forEach((path, i, paths) => { - result += formatPath(path, domainValue[path]); + result += formatPath(path, domainValue[path]) if (i < paths.length - 1) { - result += ","; + result += ',' } - result += "\n"; - }); - result += `${indent}}`; - return result; + result += '\n' + }) + result += `${indent}}` + return result } -function formatPath(pathName: string, pathValue: { [x: string]: any; }) { - const indent = " "; - let result = `${indent}'${pathName}': {\n`; +function formatPath(pathName: string, pathValue: { [x: string]: any }) { + const indent = ' ' + let result = `${indent}'${pathName}': {\n` Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { - const cookie = pathValue[cookieName]; - result += ` ${cookieName}: ${cookie.inspect()}`; + const cookie = pathValue[cookieName] + result += ` ${cookieName}: ${cookie.inspect()}` if (i < cookieNames.length - 1) { - result += ","; + result += ',' } - result += "\n"; - }); - result += `${indent}}`; - return result; + result += '\n' + }) + result += `${indent}}` + return result } diff --git a/lib/pathMatch.ts b/lib/pathMatch.ts index 926c885c..36245607 100644 --- a/lib/pathMatch.ts +++ b/lib/pathMatch.ts @@ -36,24 +36,27 @@ export function pathMatch(reqPath: string, cookiePath: string): boolean { // "o The cookie-path and the request-path are identical." if (cookiePath === reqPath) { - return true; + return true } - const idx = reqPath.indexOf(cookiePath); + const idx = reqPath.indexOf(cookiePath) if (idx === 0) { // "o The cookie-path is a prefix of the request-path, and the last // character of the cookie-path is %x2F ("/")." - if (cookiePath[cookiePath.length - 1] === "/") { - return true; + if (cookiePath[cookiePath.length - 1] === '/') { + return true } // " o The cookie-path is a prefix of the request-path, and the first // character of the request-path that is not included in the cookie- path // is a %x2F ("/") character." - if (new RegExp(`^${cookiePath}`).test(reqPath) && reqPath[cookiePath.length] === "/") { - return true; + if ( + new RegExp(`^${cookiePath}`).test(reqPath) && + reqPath[cookiePath.length] === '/' + ) { + return true } } - return false; + return false } diff --git a/lib/permuteDomain.ts b/lib/permuteDomain.ts index e1edde58..151b3576 100644 --- a/lib/permuteDomain.ts +++ b/lib/permuteDomain.ts @@ -28,36 +28,39 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; -const pubsuffix = require("./pubsuffix-psl"); +'use strict' +const pubsuffix = require('./pubsuffix-psl') // Gives the permutation of all possible domainMatch()es of a given domain. The // array is in shortest-to-longest order. Handy for indexing. -export function permuteDomain(domain: string, allowSpecialUseDomain?: boolean): string[] | null { +export function permuteDomain( + domain: string, + allowSpecialUseDomain?: boolean, +): string[] | null { const pubSuf = pubsuffix.getPublicSuffix(domain, { - allowSpecialUseDomain: allowSpecialUseDomain - }); + allowSpecialUseDomain: allowSpecialUseDomain, + }) if (!pubSuf) { - return null; + return null } if (pubSuf == domain) { - return [domain]; + return [domain] } // Nuke trailing dot - if (domain.slice(-1) == ".") { - domain = domain.slice(0, -1); + if (domain.slice(-1) == '.') { + domain = domain.slice(0, -1) } - const prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com" - const parts = prefix.split(".").reverse(); - let cur = pubSuf; - const permutations = [cur]; + const prefix = domain.slice(0, -(pubSuf.length + 1)) // ".example.com" + const parts = prefix.split('.').reverse() + let cur = pubSuf + const permutations = [cur] while (parts.length) { - cur = `${parts.shift()}.${cur}`; - permutations.push(cur); + cur = `${parts.shift()}.${cur}` + permutations.push(cur) } - return permutations; + return permutations } diff --git a/lib/pubsuffix-psl.ts b/lib/pubsuffix-psl.ts index dc7fb4c2..2cef28a3 100644 --- a/lib/pubsuffix-psl.ts +++ b/lib/pubsuffix-psl.ts @@ -28,55 +28,60 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; +'use strict' import * as psl from 'psl' // RFC 6761 -const SPECIAL_USE_DOMAINS = [ - "local", - "example", - "invalid", - "localhost", - "test" -]; +const SPECIAL_USE_DOMAINS = ['local', 'example', 'invalid', 'localhost', 'test'] -const SPECIAL_TREATMENT_DOMAINS = ["localhost", "invalid"]; +const SPECIAL_TREATMENT_DOMAINS = ['localhost', 'invalid'] type GetPublicSuffixOptions = { - allowSpecialUseDomain?: boolean | undefined; - ignoreError?: boolean | undefined; + allowSpecialUseDomain?: boolean | undefined + ignoreError?: boolean | undefined } const defaultGetPublicSuffixOptions: GetPublicSuffixOptions = { allowSpecialUseDomain: false, - ignoreError: false + ignoreError: false, } -export function getPublicSuffix(domain: string, options: GetPublicSuffixOptions = {}): string | null { +export function getPublicSuffix( + domain: string, + options: GetPublicSuffixOptions = {}, +): string | null { options = { ...defaultGetPublicSuffixOptions, ...options } - const domainParts = domain.split("."); - const topLevelDomain = domainParts[domainParts.length - 1]; - const allowSpecialUseDomain = !!options.allowSpecialUseDomain; - const ignoreError = !!options.ignoreError; + const domainParts = domain.split('.') + const topLevelDomain = domainParts[domainParts.length - 1] + const allowSpecialUseDomain = !!options.allowSpecialUseDomain + const ignoreError = !!options.ignoreError - if (allowSpecialUseDomain && typeof topLevelDomain === 'string' && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { + if ( + allowSpecialUseDomain && + typeof topLevelDomain === 'string' && + SPECIAL_USE_DOMAINS.includes(topLevelDomain) + ) { if (domainParts.length > 1) { - const secondLevelDomain = domainParts[domainParts.length - 2]; + const secondLevelDomain = domainParts[domainParts.length - 2] // In aforementioned example, the eTLD/pubSuf will be apple.localhost - return `${secondLevelDomain}.${topLevelDomain}`; + return `${secondLevelDomain}.${topLevelDomain}` } else if (SPECIAL_TREATMENT_DOMAINS.includes(topLevelDomain)) { // For a single word special use domain, e.g. 'localhost' or 'invalid', per RFC 6761, // "Application software MAY recognize {localhost/invalid} names as special, or // MAY pass them to name resolution APIs as they would for other domain names." - return `${topLevelDomain}`; + return `${topLevelDomain}` } } - if (!ignoreError && typeof topLevelDomain === 'string' && SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { + if ( + !ignoreError && + typeof topLevelDomain === 'string' && + SPECIAL_USE_DOMAINS.includes(topLevelDomain) + ) { throw new Error( - `Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.` - ); + `Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, + ) } - return psl.get(domain); + return psl.get(domain) } diff --git a/lib/store.ts b/lib/store.ts index 9b949844..1aada6ca 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -28,70 +28,123 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -"use strict"; +'use strict' /*jshint unused:false */ -import type {Callback, Cookie} from "./cookie"; +import type { Callback, Cookie } from './cookie' export class Store { - synchronous: boolean; + synchronous: boolean constructor() { - this.synchronous = false; + this.synchronous = false } - findCookie(domain: string | null, path: string | null, key: string | undefined): Promise - findCookie(domain: string | null, path: string | null, key: string | undefined, callback: Callback): void - findCookie(_domain: string | null, _path: string | null, _key: string | undefined, _callback?: Callback): unknown { - throw new Error("findCookie is not implemented"); + findCookie( + domain: string | null, + path: string | null, + key: string | undefined, + ): Promise + findCookie( + domain: string | null, + path: string | null, + key: string | undefined, + callback: Callback, + ): void + findCookie( + _domain: string | null, + _path: string | null, + _key: string | undefined, + _callback?: Callback, + ): unknown { + throw new Error('findCookie is not implemented') } - findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean): Promise - findCookies(domain: string | null, path: string | null, allowSpecialUseDomain?: boolean, callback?: Callback): void - findCookies(_domain: string | null, _path: string | null, _allowSpecialUseDomain: boolean | Callback = false, _callback?: Callback): unknown { - throw new Error("findCookies is not implemented"); + findCookies( + domain: string | null, + path: string | null, + allowSpecialUseDomain?: boolean, + ): Promise + findCookies( + domain: string | null, + path: string | null, + allowSpecialUseDomain?: boolean, + callback?: Callback, + ): void + findCookies( + _domain: string | null, + _path: string | null, + _allowSpecialUseDomain: boolean | Callback = false, + _callback?: Callback, + ): unknown { + throw new Error('findCookies is not implemented') } putCookie(cookie: Cookie): Promise - putCookie(cookie: Cookie, callback: Callback): void; + putCookie(cookie: Cookie, callback: Callback): void putCookie(_cookie: Cookie, _callback?: Callback): unknown { - throw new Error("putCookie is not implemented"); + throw new Error('putCookie is not implemented') } - updateCookie(oldCookie: Cookie, newCookie: Cookie): Promise - updateCookie(oldCookie: Cookie, newCookie: Cookie, callback: Callback): void; - updateCookie(_oldCookie: Cookie, _newCookie: Cookie, _callback?: Callback): unknown { + updateCookie( + oldCookie: Cookie, + newCookie: Cookie, + callback: Callback, + ): void + updateCookie( + _oldCookie: Cookie, + _newCookie: Cookie, + _callback?: Callback, + ): unknown { // recommended default implementation: // return this.putCookie(newCookie, cb); - throw new Error("updateCookie is not implemented"); + throw new Error('updateCookie is not implemented') } - removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined): Promise - removeCookie(domain: string | null | undefined, path: string | null | undefined, key: string | null | undefined, callback: Callback): void - removeCookie(_domain: string | null | undefined, _path: string | null | undefined, _key: string | null | undefined, _callback?: Callback): unknown { - throw new Error("removeCookie is not implemented"); + removeCookie( + domain: string | null | undefined, + path: string | null | undefined, + key: string | null | undefined, + ): Promise + removeCookie( + domain: string | null | undefined, + path: string | null | undefined, + key: string | null | undefined, + callback: Callback, + ): void + removeCookie( + _domain: string | null | undefined, + _path: string | null | undefined, + _key: string | null | undefined, + _callback?: Callback, + ): unknown { + throw new Error('removeCookie is not implemented') } removeCookies(domain: string, path: string): Promise removeCookies(domain: string, path: string, callback: Callback): void - removeCookies(_domain: string, _path: string, _callback?: Callback): unknown { - throw new Error("removeCookies is not implemented"); + removeCookies( + _domain: string, + _path: string, + _callback?: Callback, + ): unknown { + throw new Error('removeCookies is not implemented') } removeAllCookies(): Promise removeAllCookies(callback: Callback): void removeAllCookies(_callback?: Callback): unknown { - throw new Error("removeAllCookies is not implemented"); + throw new Error('removeAllCookies is not implemented') } getAllCookies(): Promise getAllCookies(callback: Callback): void getAllCookies(_callback?: Callback): unknown { throw new Error( - "getAllCookies is not implemented (therefore jar cannot be serialized)" - ); + 'getAllCookies is not implemented (therefore jar cannot be serialized)', + ) } } -exports.Store = Store; +exports.Store = Store diff --git a/lib/utilHelper.ts b/lib/utilHelper.ts index e662c4c2..8fcc5529 100644 --- a/lib/utilHelper.ts +++ b/lib/utilHelper.ts @@ -1,39 +1,44 @@ function requireUtil() { try { // eslint-disable-next-line no-restricted-modules - return require("util"); + return require('util') } catch (e) { - return null; + return null } } // for v10.12.0+ function lookupCustomInspectSymbol() { - return Symbol.for("nodejs.util.inspect.custom"); + return Symbol.for('nodejs.util.inspect.custom') } // for older node environments -function tryReadingCustomSymbolFromUtilInspect(options: { requireUtil?: any; }) { - const _requireUtil = options.requireUtil || requireUtil; - const util = _requireUtil(); - return util ? util.inspect.custom : null; +function tryReadingCustomSymbolFromUtilInspect(options: { requireUtil?: any }) { + const _requireUtil = options.requireUtil || requireUtil + const util = _requireUtil() + return util ? util.inspect.custom : null } -export function getUtilInspect(fallback: (value: any) => any, options: { requireUtil?: any; } = {}) { - const _requireUtil = options.requireUtil || requireUtil; - const util = _requireUtil(); +export function getUtilInspect( + fallback: (value: any) => any, + options: { requireUtil?: any } = {}, +) { + const _requireUtil = options.requireUtil || requireUtil + const util = _requireUtil() return function inspect(value: any, showHidden?: any, depth?: any) { - return util ? util.inspect(value, showHidden, depth) : fallback(value); - }; + return util ? util.inspect(value, showHidden, depth) : fallback(value) + } } -export function getCustomInspectSymbol(options: { lookupCustomInspectSymbol?: any; requireUtil?: any; } = {}) { +export function getCustomInspectSymbol( + options: { lookupCustomInspectSymbol?: any; requireUtil?: any } = {}, +) { const _lookupCustomInspectSymbol = - options.lookupCustomInspectSymbol || lookupCustomInspectSymbol; + options.lookupCustomInspectSymbol || lookupCustomInspectSymbol // get custom inspect symbol for node environments return ( _lookupCustomInspectSymbol() || tryReadingCustomSymbolFromUtilInspect(options) - ); + ) } diff --git a/lib/validators.ts b/lib/validators.ts index c0b1a276..5c67e5df 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -25,23 +25,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ************************************************************************************ */ -"use strict"; +'use strict' /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ export function isFunction(data: any): boolean { - return typeof data === 'function'; + return typeof data === 'function' } export function isNonEmptyString(data: any): boolean { - return isString(data) && data !== ''; + return isString(data) && data !== '' } export function isDate(data: any): boolean { - return isInstanceStrict(data, Date) && isInteger(data.getTime()); + return isInstanceStrict(data, Date) && isInteger(data.getTime()) } export function isEmptyString(data: any): boolean { - return data === '' || (data instanceof String && data.toString() === ''); + return data === '' || (data instanceof String && data.toString() === '') } export function isString(data: any): boolean { @@ -49,42 +49,42 @@ export function isString(data: any): boolean { } export function isObject(data: any): boolean { - return toString.call(data) === '[object Object]'; + return toString.call(data) === '[object Object]' } export function isInstanceStrict(data: any, prototype: Function): boolean { try { - return data instanceof prototype; + return data instanceof prototype } catch (error) { - return false; + return false } } export function isInteger(data: any): boolean { - return typeof data === 'number' && data % 1 === 0; + return typeof data === 'number' && data % 1 === 0 } /* End validation functions */ export function validate(bool: boolean, cb?: any, options?: any): void { if (!isFunction(cb)) { - options = cb; - cb = null; + options = cb + cb = null } - if (!isObject(options)) options = { Error: "Failed Check" }; + if (!isObject(options)) options = { Error: 'Failed Check' } if (!bool) { if (cb) { - cb(new ParameterError(options)); + cb(new ParameterError(options)) } else { - throw new ParameterError(options); + throw new ParameterError(options) } } } export class ParameterError extends Error { constructor(...params: any[]) { - super(...params); + super(...params) if (Object.setPrototypeOf) { - Object.setPrototypeOf(this, ParameterError.prototype); + Object.setPrototypeOf(this, ParameterError.prototype) } } } diff --git a/test/ietf_data/dates/bsd-examples.json b/test/ietf_data/dates/bsd-examples.json index bc43160d..eafd8936 100644 --- a/test/ietf_data/dates/bsd-examples.json +++ b/test/ietf_data/dates/bsd-examples.json @@ -2,166 +2,220 @@ { "test": "Sat, 15-Apr-17 21:01:22 GMT", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Thu, 19-Apr-2007 16:00:00 GMT", "expected": "Thu, 19 Apr 2007 16:00:00 GMT" - }, { + }, + { "test": "Wed, 25 Apr 2007 21:02:13 GMT", "expected": "Wed, 25 Apr 2007 21:02:13 GMT" - }, { + }, + { "test": "Thu, 19/Apr\\2007 16:00:00 GMT", "expected": "Thu, 19 Apr 2007 16:00:00 GMT" - }, { + }, + { "test": "Fri, 1 Jan 2010 01:01:50 GMT", "expected": "Fri, 01 Jan 2010 01:01:50 GMT" - }, { + }, + { "test": "Wednesday, 1-Jan-2003 00:00:00 GMT", "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { + }, + { "test": ", 1-Jan-2003 00:00:00 GMT", "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { + }, + { "test": " 1-Jan-2003 00:00:00 GMT", "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { + }, + { "test": "1-Jan-2003 00:00:00 GMT", "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { + }, + { "test": "Wed,18-Apr-07 22:50:12 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "WillyWonka , 18-Apr-07 22:50:12 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "WillyWonka , 18-Apr-07 22:50:12", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "WillyWonka , 18-apr-07 22:50:12", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Mon, 18-Apr-1977 22:50:13 GMT", "expected": "Mon, 18 Apr 1977 22:50:13 GMT" - }, { + }, + { "test": "Mon, 18-Apr-77 22:50:13 GMT", "expected": "Mon, 18 Apr 1977 22:50:13 GMT" - }, { + }, + { "test": "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Partyday, 18- April-07 22:50:12", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Partyday, 18 - Apri-07 22:50:12", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Wednes, 1-Januar-2003 00:00:00 GMT", "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 GMT-2", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 GMT BLAH", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 GMT-0400", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 DST", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 -0400", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 (hello there)", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 11:22:33", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 ::00 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 boink:z 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 91:22:33 21:01:22", "expected": null - }, { + }, + { "test": "Thu Apr 18 22:50:12 2007 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "22:50:12 Thu Apr 18 2007 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Thu 22:50:12 Apr 18 2007 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Thu Apr 22:50:12 18 2007 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Thu Apr 18 22:50:12 2007 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Thu Apr 18 2007 22:50:12 GMT", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Thu Apr 18 2007 GMT 22:50:12", "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { + }, + { "test": "Sat, 15-Apr-17 21:01:22 GMT", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15-Sat, Apr-17 21:01:22 GMT", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15-Sat, Apr 21:01:22 GMT 17", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15-Sat, Apr 21:01:22 GMT 2017", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15 Apr 21:01:22 2017", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15 17 Apr 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Apr 15 17 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "Apr 15 21:01:22 17", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "2017 April 15 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "15 April 2017 21:01:22", "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { + }, + { "test": "98 April 17 21:01:22", "expected": null - }, { + }, + { "test": "Thu, 012-Aug-2008 20:49:07 GMT", "expected": null - }, { + }, + { "test": "Thu, 12-Aug-31841 20:49:07 GMT", "expected": null - }, { + }, + { "test": "Thu, 12-Aug-9999999999 20:49:07 GMT", "expected": null - }, { + }, + { "test": "Thu, 999999999999-Aug-2007 20:49:07 GMT", "expected": null - }, { + }, + { "test": "Thu, 12-Aug-2007 20:61:99999999999 GMT", "expected": null - }, { + }, + { "test": "IAintNoDateFool", "expected": null } diff --git a/test/ietf_data/dates/examples.json b/test/ietf_data/dates/examples.json index 61e674d4..192d60f3 100644 --- a/test/ietf_data/dates/examples.json +++ b/test/ietf_data/dates/examples.json @@ -2,46 +2,60 @@ { "test": "Mon, 10-Dec-2007 17:02:24 GMT", "expected": "Mon, 10 Dec 2007 17:02:24 GMT" - }, { + }, + { "test": "Wed, 09 Dec 2009 16:27:23 GMT", "expected": "Wed, 09 Dec 2009 16:27:23 GMT" - }, { + }, + { "test": "Thursday, 01-Jan-1970 00:00:00 GMT", "expected": "Thu, 01 Jan 1970 00:00:00 GMT" - }, { + }, + { "test": "Mon Dec 10 16:32:30 2007 GMT", "expected": "Mon, 10 Dec 2007 16:32:30 GMT" - }, { + }, + { "test": "Wednesday, 01-Jan-10 00:00:00 GMT", "expected": "Fri, 01 Jan 2010 00:00:00 GMT" - }, { + }, + { "test": "Mon, 10-Dec-07 20:35:03 GMT", "expected": "Mon, 10 Dec 2007 20:35:03 GMT" - }, { + }, + { "test": "Wed, 1 Jan 2020 00:00:00 GMT", "expected": "Wed, 01 Jan 2020 00:00:00 GMT" - }, { + }, + { "test": "Saturday, 8-Dec-2012 21:24:09 GMT", "expected": "Sat, 08 Dec 2012 21:24:09 GMT" - }, { + }, + { "test": "Thu, 31 Dec 23:55:55 2037 GMT", "expected": "Thu, 31 Dec 2037 23:55:55 GMT" - }, { + }, + { "test": "Sun, 9 Dec 2012 13:42:05 GMT", "expected": "Sun, 09 Dec 2012 13:42:05 GMT" - }, { + }, + { "test": "Wed Dec 12 2007 08:44:07 GMT-0500 (EST)", "expected": "Wed, 12 Dec 2007 08:44:07 GMT" - }, { + }, + { "test": "Mon, 01-Jan-2011 00: 00:00 GMT", "expected": null - }, { + }, + { "test": "Sun, 1-Jan-1995 00:00:00 GMT", "expected": "Sun, 01 Jan 1995 00:00:00 GMT" - }, { + }, + { "test": "Wednesday, 01-Jan-10 0:0:00 GMT", "expected": "Fri, 01 Jan 2010 00:00:00 GMT" - }, { + }, + { "test": "Thu, 10 Dec 2009 13:57:2 GMT", "expected": "Thu, 10 Dec 2009 13:57:02 GMT" } diff --git a/test/ietf_data/parser.json b/test/ietf_data/parser.json index 166de9f5..77720ae0 100644 --- a/test/ietf_data/parser.json +++ b/test/ietf_data/parser.json @@ -1,21 +1,13 @@ [ { "test": "0001", - "received": [ - "foo=bar" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0002", - "received": [ - "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0003", @@ -23,101 +15,61 @@ "foo=bar; Expires=Fri, 07 Aug 2007 08:04:19 GMT", "foo2=bar2; Expires=Fri, 07 Aug 9999 08:04:19 GMT" ], - "sent": [ - { "name": "foo2", "value": "bar2" } - ] + "sent": [{ "name": "foo2", "value": "bar2" }] }, { "test": "0004", - "received": [ - "foo" - ], + "received": ["foo"], "sent": [] }, { "test": "0005", - "received": [ - "foo=bar; max-age=10000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; max-age=10000;"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0006", - "received": [ - "foo=bar; max-age=0;" - ], + "received": ["foo=bar; max-age=0;"], "sent": [] }, { "test": "0007", - "received": [ - "foo=bar; version=1;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; version=1;"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0008", - "received": [ - "foo=bar; version=1000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; version=1000;"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0009", - "received": [ - "foo=bar; customvalue=1000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; customvalue=1000;"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0010", - "received": [ - "foo=bar; secure;" - ], + "received": ["foo=bar; secure;"], "sent": [] }, { "test": "0011", - "received": [ - "foo=bar; customvalue=\"1000 or more\";" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; customvalue=\"1000 or more\";"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0012", - "received": [ - "foo=bar; customvalue=\"no trailing semicolon\"" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; customvalue=\"no trailing semicolon\""], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "0013", - "received": [ - "foo=bar", - "foo=qux" - ], - "sent": [ - { "name": "foo", "value": "qux" } - ] + "received": ["foo=bar", "foo=qux"], + "sent": [{ "name": "foo", "value": "qux" }] }, { "test": "0014", - "received": [ - "foo1=bar", - "foo2=qux" - ], + "received": ["foo1=bar", "foo2=qux"], "sent": [ { "name": "foo1", "value": "bar" }, { "name": "foo2", "value": "qux" } @@ -125,10 +77,7 @@ }, { "test": "0015", - "received": [ - "a=b", - "z=y" - ], + "received": ["a=b", "z=y"], "sent": [ { "name": "a", "value": "b" }, { "name": "z", "value": "y" } @@ -136,10 +85,7 @@ }, { "test": "0016", - "received": [ - "z=y", - "a=b" - ], + "received": ["z=y", "a=b"], "sent": [ { "name": "z", "value": "y" }, { "name": "a", "value": "b" } @@ -147,38 +93,22 @@ }, { "test": "0017", - "received": [ - "z=y, a=b" - ], - "sent": [ - { "name": "z", "value": "y, a=b" } - ] + "received": ["z=y, a=b"], + "sent": [{ "name": "z", "value": "y, a=b" }] }, { "test": "0018", - "received": [ - "z=y; foo=bar, a=b" - ], - "sent": [ - { "name": "z", "value": "y" } - ] + "received": ["z=y; foo=bar, a=b"], + "sent": [{ "name": "z", "value": "y" }] }, { "test": "0019", - "received": [ - "foo=b;max-age=3600, c=d;path=/" - ], - "sent": [ - { "name": "foo", "value": "b" } - ] + "received": ["foo=b;max-age=3600, c=d;path=/"], + "sent": [{ "name": "foo", "value": "b" }] }, { "test": "0020", - "received": [ - "a=b", - "=", - "c=d" - ], + "received": ["a=b", "=", "c=d"], "sent": [ { "name": "a", "value": "b" }, { "name": "c", "value": "d" } @@ -186,11 +116,7 @@ }, { "test": "0021", - "received": [ - "a=b", - "=x", - "c=d" - ], + "received": ["a=b", "=x", "c=d"], "sent": [ { "name": "a", "value": "b" }, { "name": "c", "value": "d" } @@ -198,11 +124,7 @@ }, { "test": "0022", - "received": [ - "a=b", - "x=", - "c=d" - ], + "received": ["a=b", "x=", "c=d"], "sent": [ { "name": "a", "value": "b" }, { "name": "x", "value": "" }, @@ -211,246 +133,162 @@ }, { "test": "0023", - "received": [ - "foo", - "" - ], + "received": ["foo", ""], "sent": [] }, { "test": "0024", - "received": [ - "foo", - "=" - ], + "received": ["foo", "="], "sent": [] }, { "test": "0025", - "received": [ - "foo", - "; bar" - ], + "received": ["foo", "; bar"], "sent": [] }, { "test": "0026", - "received": [ - "foo", - " " - ], + "received": ["foo", " "], "sent": [] }, { "test": "0027", - "received": [ - "foo", - "bar" - ], + "received": ["foo", "bar"], "sent": [] }, { "test": "0028", - "received": [ - "foo", - "\t" - ], + "received": ["foo", "\t"], "sent": [] }, { "test": "ATTRIBUTE0001", - "received": [ - "foo=bar; Secure" - ], + "received": ["foo=bar; Secure"], "sent": [] }, { "test": "ATTRIBUTE0002", - "received": [ - "foo=bar; seCURe" - ], + "received": ["foo=bar; seCURe"], "sent": [] }, { "test": "ATTRIBUTE0003", - "received": [ - "foo=bar; \"Secure\"" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; \"Secure\""], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0004", - "received": [ - "foo=bar; Secure=" - ], + "received": ["foo=bar; Secure="], "sent": [] }, { "test": "ATTRIBUTE0005", - "received": [ - "foo=bar; Secure=aaaa" - ], + "received": ["foo=bar; Secure=aaaa"], "sent": [] }, { "test": "ATTRIBUTE0006", - "received": [ - "foo=bar; Secure qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Secure qux"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0007", - "received": [ - "foo=bar; Secure =aaaaa" - ], + "received": ["foo=bar; Secure =aaaaa"], "sent": [] }, { "test": "ATTRIBUTE0008", - "received": [ - "foo=bar; Secure= aaaaa" - ], + "received": ["foo=bar; Secure= aaaaa"], "sent": [] }, { "test": "ATTRIBUTE0009", - "received": [ - "foo=bar; Secure; qux" - ], + "received": ["foo=bar; Secure; qux"], "sent": [] }, { "test": "ATTRIBUTE0010", - "received": [ - "foo=bar; Secure;qux" - ], + "received": ["foo=bar; Secure;qux"], "sent": [] }, { "test": "ATTRIBUTE0011", - "received": [ - "foo=bar; Secure ; qux" - ], + "received": ["foo=bar; Secure ; qux"], "sent": [] }, { "test": "ATTRIBUTE0012", - "received": [ - "foo=bar; Secure" - ], + "received": ["foo=bar; Secure"], "sent": [] }, { "test": "ATTRIBUTE0013", - "received": [ - "foo=bar; Secure ;" - ], + "received": ["foo=bar; Secure ;"], "sent": [] }, { "test": "ATTRIBUTE0014", - "received": [ - "foo=bar; Path" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Path"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0015", - "received": [ - "foo=bar; Path=" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Path="], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0016", - "received": [ - "foo=bar; Path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Path=/"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0017", - "received": [ - "foo=bar; Path=/qux" - ], + "received": ["foo=bar; Path=/qux"], "sent": [] }, { "test": "ATTRIBUTE0018", - "received": [ - "foo=bar; Path =/qux" - ], + "received": ["foo=bar; Path =/qux"], "sent": [] }, { "test": "ATTRIBUTE0019", - "received": [ - "foo=bar; Path= /qux" - ], + "received": ["foo=bar; Path= /qux"], "sent": [] }, { "test": "ATTRIBUTE0020", - "received": [ - "foo=bar; Path=/qux ; taz" - ], + "received": ["foo=bar; Path=/qux ; taz"], "sent": [] }, { "test": "ATTRIBUTE0021", - "received": [ - "foo=bar; Path=/qux; Path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Path=/qux; Path=/"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0022", - "received": [ - "foo=bar; Path=/; Path=/qux" - ], + "received": ["foo=bar; Path=/; Path=/qux"], "sent": [] }, { "test": "ATTRIBUTE0023", - "received": [ - "foo=bar; Path=/qux; Path=/cookie-parser-result" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Path=/qux; Path=/cookie-parser-result"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "ATTRIBUTE0024", - "received": [ - "foo=bar; Path=/cookie-parser-result; Path=/qux" - ], + "received": ["foo=bar; Path=/cookie-parser-result; Path=/qux"], "sent": [] }, { "test": "ATTRIBUTE0025", - "received": [ - "foo=bar; qux; Secure" - ], + "received": ["foo=bar; qux; Secure"], "sent": [] }, { "test": "ATTRIBUTE0026", - "received": [ - "foo=bar; qux=\"aaa;bbb\"; Secure" - ], + "received": ["foo=bar; qux=\"aaa;bbb\"; Secure"], "sent": [] }, { @@ -459,7 +297,10 @@ "foo=\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" ], "sent": [ - { "name": "foo", "value": "\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } + { + "name": "foo", + "value": "\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + } ] }, { @@ -468,7 +309,10 @@ "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" ], "sent": [ - { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } + { + "name": "\u6625\u8282\u56de", + "value": "\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + } ] }, { @@ -477,7 +321,10 @@ "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0; \u5b8c\u5168\u624b\u518c" ], "sent": [ - { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0" } + { + "name": "\u6625\u8282\u56de", + "value": "\u5bb6\u8def\u00b7\u6625\u8fd0" + } ] }, { @@ -486,161 +333,104 @@ "foo=\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" ], "sent": [ - { "name": "foo", "value": "\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" } + { + "name": "foo", + "value": "\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" + } ] }, { "test": "CHROMIUM0001", - "received": [ - "a=b" - ], - "sent": [ - { "name": "a", "value": "b" } - ] + "received": ["a=b"], + "sent": [{ "name": "a", "value": "b" }] }, { "test": "CHROMIUM0002", - "received": [ - "aBc=\"zzz \" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \"" } - ] + "received": ["aBc=\"zzz \" ;"], + "sent": [{ "name": "aBc", "value": "\"zzz \"" }] }, { "test": "CHROMIUM0003", - "received": [ - "aBc=\"zzz \" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \"" } - ] + "received": ["aBc=\"zzz \" ;"], + "sent": [{ "name": "aBc", "value": "\"zzz \"" }] }, { "test": "CHROMIUM0004", - "received": [ - "aBc=\"zz;pp\" ; ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zz" } - ] + "received": ["aBc=\"zz;pp\" ; ;"], + "sent": [{ "name": "aBc", "value": "\"zz" }] }, { "test": "CHROMIUM0005", - "received": [ - "aBc=\"zz ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zz" } - ] + "received": ["aBc=\"zz ;"], + "sent": [{ "name": "aBc", "value": "\"zz" }] }, { "test": "CHROMIUM0006", - "received": [ - "aBc=\"zzz \" \"ppp\" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \" \"ppp\"" } - ] + "received": ["aBc=\"zzz \" \"ppp\" ;"], + "sent": [{ "name": "aBc", "value": "\"zzz \" \"ppp\"" }] }, { "test": "CHROMIUM0007", - "received": [ - "aBc=\"zzz \" \"ppp\" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \" \"ppp\"" } - ] + "received": ["aBc=\"zzz \" \"ppp\" ;"], + "sent": [{ "name": "aBc", "value": "\"zzz \" \"ppp\"" }] }, { "test": "CHROMIUM0008", - "received": [ - "aBc=A\"B ;" - ], - "sent": [ - { "name": "aBc", "value": "A\"B" } - ] + "received": ["aBc=A\"B ;"], + "sent": [{ "name": "aBc", "value": "A\"B" }] }, { "test": "CHROMIUM0009", - "received": [ - "BLAHHH; path=/;" - ], + "received": ["BLAHHH; path=/;"], "sent": [] }, { "test": "CHROMIUM0010", - "received": [ - "\"BLA\\\"HHH\"; path=/;" - ], + "received": ["\"BLA\\\"HHH\"; path=/;"], "sent": [] }, { "test": "CHROMIUM0011", - "received": [ - "a=\"B" - ], - "sent": [ - { "name": "a", "value": "\"B" } - ] + "received": ["a=\"B"], + "sent": [{ "name": "a", "value": "\"B" }] }, { "test": "CHROMIUM0012", - "received": [ - "=ABC" - ], + "received": ["=ABC"], "sent": [] }, { "test": "CHROMIUM0013", - "received": [ - "ABC=; path = /" - ], - "sent": [ - { "name": "ABC", "value": "" } - ] + "received": ["ABC=; path = /"], + "sent": [{ "name": "ABC", "value": "" }] }, { "test": "CHROMIUM0014", - "received": [ - " A = BC ;foo;;; bar" - ], - "sent": [ - { "name": "A", "value": "BC" } - ] + "received": [" A = BC ;foo;;; bar"], + "sent": [{ "name": "A", "value": "BC" }] }, { "test": "CHROMIUM0015", - "received": [ - " A=== BC ;foo;;; bar" - ], - "sent": [ - { "name": "A", "value": "== BC" } - ] + "received": [" A=== BC ;foo;;; bar"], + "sent": [{ "name": "A", "value": "== BC" }] }, { "test": "CHROMIUM0016", "received": [ "foo=\"zohNumRKgI0oxyhSsV3Z7D\" ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " ], - "sent": [ - { "name": "foo", "value": "\"zohNumRKgI0oxyhSsV3Z7D\"" } - ] + "sent": [{ "name": "foo", "value": "\"zohNumRKgI0oxyhSsV3Z7D\"" }] }, { "test": "CHROMIUM0017", "received": [ "foo=zohNumRKgI0oxyhSsV3Z7D ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " ], - "sent": [ - { "name": "foo", "value": "zohNumRKgI0oxyhSsV3Z7D" } - ] + "sent": [{ "name": "foo", "value": "zohNumRKgI0oxyhSsV3Z7D" }] }, { "test": "CHROMIUM0018", - "received": [ - " " - ], + "received": [" "], "sent": [] }, { @@ -649,78 +439,51 @@ "a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ], "sent": [ - { "name": "a", "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } + { + "name": "a", + "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } ] }, { "test": "CHROMIUM0021", - "received": [ - "" - ], + "received": [""], "sent": [] }, { "test": "COMMA0001", - "received": [ - "foo=bar, baz=qux" - ], - "sent": [ - { "name": "foo", "value": "bar, baz=qux" } - ] + "received": ["foo=bar, baz=qux"], + "sent": [{ "name": "foo", "value": "bar, baz=qux" }] }, { "test": "COMMA0002", - "received": [ - "foo=\"bar, baz=qux\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar, baz=qux\"" } - ] + "received": ["foo=\"bar, baz=qux\""], + "sent": [{ "name": "foo", "value": "\"bar, baz=qux\"" }] }, { "test": "COMMA0003", - "received": [ - "foo=bar; b,az=qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; b,az=qux"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "COMMA0004", - "received": [ - "foo=bar; baz=q,ux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; baz=q,ux"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "COMMA0005", - "received": [ - "foo=bar; Max-Age=50,399" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Max-Age=50,399"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "COMMA0006", - "received": [ - "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "COMMA0007", - "received": [ - "foo=bar; Expires=Fri 07 Aug 9999 08:04:19 GMT, baz=qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; Expires=Fri 07 Aug 9999 08:04:19 GMT, baz=qux"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DISABLED_CHROMIUM0020", @@ -731,187 +494,125 @@ }, { "test": "DISABLED_CHROMIUM0022", - "received": [ - "AAA=BB\u0000ZYX" - ], - "sent": [ - { "name": "AAA", "value": "BB" } - ] + "received": ["AAA=BB\u0000ZYX"], + "sent": [{ "name": "AAA", "value": "BB" }] }, { "test": "DISABLED_CHROMIUM0023", - "received": [ - "AAA=BB\rZYX" - ], - "sent": [ - { "name": "AAA", "value": "BB" } - ] + "received": ["AAA=BB\rZYX"], + "sent": [{ "name": "AAA", "value": "BB" }] }, { "test": "DISABLED_PATH0029", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/bar" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/bar"], "sent-to": "/cookie-parser-result/f%6Fo/bar?disabled-path0029", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0001", - "received": [ - "foo=bar; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0001", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0002", - "received": [ - "foo=bar; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0002", "sent": [] }, { "test": "DOMAIN0003", - "received": [ - "foo=bar; domain=.home.example.org" - ], + "received": ["foo=bar; domain=.home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0003", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0004", - "received": [ - "foo=bar; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.org"], "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0004", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0005", - "received": [ - "foo=bar; domain=.home.example.org" - ], + "received": ["foo=bar; domain=.home.example.org"], "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0005", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0006", - "received": [ - "foo=bar; domain=.home.example.org" - ], + "received": ["foo=bar; domain=.home.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0006", "sent": [] }, { "test": "DOMAIN0007", - "received": [ - "foo=bar; domain=sibling.example.org" - ], + "received": ["foo=bar; domain=sibling.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0007", "sent": [] }, { "test": "DOMAIN0008", - "received": [ - "foo=bar; domain=.example.org" - ], + "received": ["foo=bar; domain=.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0008", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0009", - "received": [ - "foo=bar; domain=example.org" - ], + "received": ["foo=bar; domain=example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0009", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0010", - "received": [ - "foo=bar; domain=..home.example.org" - ], + "received": ["foo=bar; domain=..home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0010", "sent": [] }, { "test": "DOMAIN0011", - "received": [ - "foo=bar; domain=home..example.org" - ], + "received": ["foo=bar; domain=home..example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0011", "sent": [] }, { "test": "DOMAIN0012", - "received": [ - "foo=bar; domain= .home.example.org" - ], + "received": ["foo=bar; domain= .home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0012", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0013", - "received": [ - "foo=bar; domain= . home.example.org" - ], + "received": ["foo=bar; domain= . home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0013", "sent": [] }, { "test": "DOMAIN0014", - "received": [ - "foo=bar; domain=home.example.org." - ], + "received": ["foo=bar; domain=home.example.org."], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0014", "sent": [] }, { "test": "DOMAIN0015", - "received": [ - "foo=bar; domain=home.example.org.." - ], + "received": ["foo=bar; domain=home.example.org.."], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0015", "sent": [] }, { "test": "DOMAIN0016", - "received": [ - "foo=bar; domain=home.example.org ." - ], + "received": ["foo=bar; domain=home.example.org ."], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0016", "sent": [] }, { "test": "DOMAIN0017", - "received": [ - "foo=bar; domain=.org" - ], + "received": ["foo=bar; domain=.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0017", "sent": [] }, { "test": "DOMAIN0018", - "received": [ - "foo=bar; domain=.org." - ], + "received": ["foo=bar; domain=.org."], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0018", "sent": [] }, @@ -941,9 +642,7 @@ }, { "test": "DOMAIN0021", - "received": [ - "foo=bar; domain=\"home.example.org\"" - ], + "received": ["foo=bar; domain=\"home.example.org\""], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0021", "sent": [] }, @@ -973,93 +672,63 @@ }, { "test": "DOMAIN0024", - "received": [ - "foo=bar; domain=.example.org; domain=home.example.org" - ], + "received": ["foo=bar; domain=.example.org; domain=home.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0024", "sent": [] }, { "test": "DOMAIN0025", - "received": [ - "foo=bar; domain=home.example.org; domain=.example.org" - ], + "received": ["foo=bar; domain=home.example.org; domain=.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0025", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0026", - "received": [ - "foo=bar; domain=home.eXaMpLe.org" - ], + "received": ["foo=bar; domain=home.eXaMpLe.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0026", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0027", - "received": [ - "foo=bar; domain=home.example.org:8888" - ], + "received": ["foo=bar; domain=home.example.org:8888"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0027", "sent": [] }, { "test": "DOMAIN0028", - "received": [ - "foo=bar; domain=subdomain.home.example.org" - ], + "received": ["foo=bar; domain=subdomain.home.example.org"], "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0028", "sent": [] }, { "test": "DOMAIN0029", - "received": [ - "foo=bar" - ], + "received": ["foo=bar"], "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0029", "sent": [] }, { "test": "DOMAIN0031", - "received": [ - "foo=bar; domain=home.example.org; domain=.example.org" - ], + "received": ["foo=bar; domain=home.example.org; domain=.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0031", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0033", - "received": [ - "foo=bar; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.org"], "sent-to": "http://hoMe.eXaMplE.org:8888/cookie-parser-result?domain0033", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0034", - "received": [ - "foo=bar; domain=home.example.org; domain=home.example.com" - ], + "received": ["foo=bar; domain=home.example.org; domain=home.example.com"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0034", "sent": [] }, { "test": "DOMAIN0035", - "received": [ - "foo=bar; domain=home.example.com; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.com; domain=home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0035", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0036", @@ -1067,9 +736,7 @@ "foo=bar; domain=home.example.org; domain=home.example.com; domain=home.example.org" ], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0036", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0037", @@ -1081,109 +748,72 @@ }, { "test": "DOMAIN0038", - "received": [ - "foo=bar; domain=home.example.org; domain=home.example.org" - ], + "received": ["foo=bar; domain=home.example.org; domain=home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0038", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0039", - "received": [ - "foo=bar; domain=home.example.org; domain=example.org" - ], + "received": ["foo=bar; domain=home.example.org; domain=example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0039", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0040", - "received": [ - "foo=bar; domain=example.org; domain=home.example.org" - ], + "received": ["foo=bar; domain=example.org; domain=home.example.org"], "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0040", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "DOMAIN0041", - "received": [ - "foo=bar; domain=.sibling.example.org" - ], + "received": ["foo=bar; domain=.sibling.example.org"], "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0041", "sent": [] }, { "test": "DOMAIN0042", - "received": [ - "foo=bar; domain=.sibling.home.example.org" - ], + "received": ["foo=bar; domain=.sibling.home.example.org"], "sent-to": "http://sibling.home.example.org:8888/cookie-parser-result?domain0042", "sent": [] }, { "test": "MOZILLA0001", - "received": [ - "foo=bar; max-age=-1" - ], + "received": ["foo=bar; max-age=-1"], "sent": [] }, { "test": "MOZILLA0002", - "received": [ - "foo=bar; max-age=0" - ], + "received": ["foo=bar; max-age=0"], "sent": [] }, { "test": "MOZILLA0003", - "received": [ - "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" - ], + "received": ["foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT"], "sent": [] }, { "test": "MOZILLA0004", - "received": [ - "foo=bar; max-age=60" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; max-age=60"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "MOZILLA0005", - "received": [ - "foo=bar; max-age=-20" - ], + "received": ["foo=bar; max-age=-20"], "sent": [] }, { "test": "MOZILLA0006", - "received": [ - "foo=bar; max-age=60" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; max-age=60"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "MOZILLA0007", - "received": [ - "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" - ], + "received": ["foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT"], "sent": [] }, { "test": "MOZILLA0008", - "received": [ - "foo=bar; max-age=60", - "foo1=bar; max-age=60" - ], + "received": ["foo=bar; max-age=60", "foo1=bar; max-age=60"], "sent": [ { "name": "foo", "value": "bar" }, { "name": "foo1", "value": "bar" } @@ -1196,9 +826,7 @@ "foo1=bar; max-age=60", "foo=differentvalue; max-age=0" ], - "sent": [ - { "name": "foo1", "value": "bar" } - ] + "sent": [{ "name": "foo1", "value": "bar" }] }, { "test": "MOZILLA0010", @@ -1208,9 +836,7 @@ "foo=differentvalue; max-age=0", "foo2=evendifferentvalue; max-age=0" ], - "sent": [ - { "name": "foo1", "value": "bar" } - ] + "sent": [{ "name": "foo1", "value": "bar" }] }, { "test": "MOZILLA0011", @@ -1225,370 +851,219 @@ "test=\"fubar! = foo;bar\\\";\" parser; max-age=6", "five; max-age=2.63," ], - "sent": [ - { "name": "test", "value": "\"fubar! = foo" } - ] + "sent": [{ "name": "test", "value": "\"fubar! = foo" }] }, { "test": "MOZILLA0013", - "received": [ - "test=kill; max-age=0", - "five; max-age=0" - ], + "received": ["test=kill; max-age=0", "five; max-age=0"], "sent": [] }, { "test": "MOZILLA0014", - "received": [ - "six" - ], + "received": ["six"], "sent": [] }, { "test": "MOZILLA0015", - "received": [ - "six", - "seven" - ], + "received": ["six", "seven"], "sent": [] }, { "test": "MOZILLA0016", - "received": [ - "six", - "seven", - " =eight" - ], + "received": ["six", "seven", " =eight"], "sent": [] }, { "test": "MOZILLA0017", - "received": [ - "six", - "seven", - " =eight", - "test=six" - ], - "sent": [ - { "name": "test", "value": "six" } - ] + "received": ["six", "seven", " =eight", "test=six"], + "sent": [{ "name": "test", "value": "six" }] }, { "test": "NAME0001", - "received": [ - "a=bar" - ], - "sent": [ - { "name": "a", "value": "bar" } - ] + "received": ["a=bar"], + "sent": [{ "name": "a", "value": "bar" }] }, { "test": "NAME0002", - "received": [ - "1=bar" - ], - "sent": [ - { "name": "1", "value": "bar" } - ] + "received": ["1=bar"], + "sent": [{ "name": "1", "value": "bar" }] }, { "test": "NAME0003", - "received": [ - "$=bar" - ], - "sent": [ - { "name": "$", "value": "bar" } - ] + "received": ["$=bar"], + "sent": [{ "name": "$", "value": "bar" }] }, { "test": "NAME0004", - "received": [ - "!a=bar" - ], - "sent": [ - { "name": "!a", "value": "bar" } - ] + "received": ["!a=bar"], + "sent": [{ "name": "!a", "value": "bar" }] }, { "test": "NAME0005", - "received": [ - "@a=bar" - ], - "sent": [ - { "name": "@a", "value": "bar" } - ] + "received": ["@a=bar"], + "sent": [{ "name": "@a", "value": "bar" }] }, { "test": "NAME0006", - "received": [ - "#a=bar" - ], - "sent": [ - { "name": "#a", "value": "bar" } - ] + "received": ["#a=bar"], + "sent": [{ "name": "#a", "value": "bar" }] }, { "test": "NAME0007", - "received": [ - "$a=bar" - ], - "sent": [ - { "name": "$a", "value": "bar" } - ] + "received": ["$a=bar"], + "sent": [{ "name": "$a", "value": "bar" }] }, { "test": "NAME0008", - "received": [ - "%a=bar" - ], - "sent": [ - { "name": "%a", "value": "bar" } - ] + "received": ["%a=bar"], + "sent": [{ "name": "%a", "value": "bar" }] }, { "test": "NAME0009", - "received": [ - "^a=bar" - ], - "sent": [ - { "name": "^a", "value": "bar" } - ] + "received": ["^a=bar"], + "sent": [{ "name": "^a", "value": "bar" }] }, { "test": "NAME0010", - "received": [ - "&a=bar" - ], - "sent": [ - { "name": "&a", "value": "bar" } - ] + "received": ["&a=bar"], + "sent": [{ "name": "&a", "value": "bar" }] }, { "test": "NAME0011", - "received": [ - "*a=bar" - ], - "sent": [ - { "name": "*a", "value": "bar" } - ] + "received": ["*a=bar"], + "sent": [{ "name": "*a", "value": "bar" }] }, { "test": "NAME0012", - "received": [ - "(a=bar" - ], - "sent": [ - { "name": "(a", "value": "bar" } - ] + "received": ["(a=bar"], + "sent": [{ "name": "(a", "value": "bar" }] }, { "test": "NAME0013", - "received": [ - ")a=bar" - ], - "sent": [ - { "name": ")a", "value": "bar" } - ] + "received": [")a=bar"], + "sent": [{ "name": ")a", "value": "bar" }] }, { "test": "NAME0014", - "received": [ - "-a=bar" - ], - "sent": [ - { "name": "-a", "value": "bar" } - ] + "received": ["-a=bar"], + "sent": [{ "name": "-a", "value": "bar" }] }, { "test": "NAME0015", - "received": [ - "_a=bar" - ], - "sent": [ - { "name": "_a", "value": "bar" } - ] + "received": ["_a=bar"], + "sent": [{ "name": "_a", "value": "bar" }] }, { "test": "NAME0016", - "received": [ - "+=bar" - ], - "sent": [ - { "name": "+", "value": "bar" } - ] + "received": ["+=bar"], + "sent": [{ "name": "+", "value": "bar" }] }, { "test": "NAME0017", - "received": [ - "=a=bar" - ], + "received": ["=a=bar"], "sent": [] }, { "test": "NAME0018", - "received": [ - "a =bar" - ], - "sent": [ - { "name": "a", "value": "bar" } - ] + "received": ["a =bar"], + "sent": [{ "name": "a", "value": "bar" }] }, { "test": "NAME0019", - "received": [ - "\"a=bar" - ], - "sent": [ - { "name": "\"a", "value": "bar" } - ] + "received": ["\"a=bar"], + "sent": [{ "name": "\"a", "value": "bar" }] }, { "test": "NAME0020", - "received": [ - "\"a=b\"=bar" - ], - "sent": [ - { "name": "\"a", "value": "b\"=bar" } - ] + "received": ["\"a=b\"=bar"], + "sent": [{ "name": "\"a", "value": "b\"=bar" }] }, { "test": "NAME0021", - "received": [ - "\"a=b\"=bar", - "\"a=qux" - ], - "sent": [ - { "name": "\"a", "value": "qux" } - ] + "received": ["\"a=b\"=bar", "\"a=qux"], + "sent": [{ "name": "\"a", "value": "qux" }] }, { "test": "NAME0022", - "received": [ - " foo=bar" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": [" foo=bar"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "NAME0023", - "received": [ - "foo;bar=baz" - ], + "received": ["foo;bar=baz"], "sent": [] }, { "test": "NAME0024", - "received": [ - "$Version=1; foo=bar" - ], - "sent": [ - { "name": "$Version", "value": "1" } - ] + "received": ["$Version=1; foo=bar"], + "sent": [{ "name": "$Version", "value": "1" }] }, { "test": "NAME0025", - "received": [ - "===a=bar" - ], + "received": ["===a=bar"], "sent": [] }, { "test": "NAME0026", - "received": [ - "foo=bar " - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar "], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "NAME0027", - "received": [ - "foo=bar ;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar ;"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "NAME0028", - "received": [ - "=a" - ], + "received": ["=a"], "sent": [] }, { "test": "NAME0029", - "received": [ - "=" - ], + "received": ["="], "sent": [] }, { "test": "NAME0030", - "received": [ - "foo bar=baz" - ], - "sent": [ - { "name": "foo bar", "value": "baz" } - ] + "received": ["foo bar=baz"], + "sent": [{ "name": "foo bar", "value": "baz" }] }, { "test": "NAME0031", - "received": [ - "\"foo;bar\"=baz" - ], + "received": ["\"foo;bar\"=baz"], "sent": [] }, { "test": "NAME0032", - "received": [ - "\"foo\\\"bar;baz\"=qux" - ], + "received": ["\"foo\\\"bar;baz\"=qux"], "sent": [] }, { "test": "NAME0033", - "received": [ - "=foo=bar", - "aaa" - ], + "received": ["=foo=bar", "aaa"], "sent": [] }, { "test": "OPTIONAL_DOMAIN0030", - "received": [ - "foo=bar; domain=" - ], + "received": ["foo=bar; domain="], "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0030", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "OPTIONAL_DOMAIN0041", - "received": [ - "foo=bar; domain=example.org; domain=" - ], + "received": ["foo=bar; domain=example.org; domain="], "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0041", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "OPTIONAL_DOMAIN0042", - "received": [ - "foo=bar; domain=foo.example.org; domain=" - ], + "received": ["foo=bar; domain=foo.example.org; domain="], "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0042", "sent": [] }, { "test": "OPTIONAL_DOMAIN0043", - "received": [ - "foo=bar; domain=foo.example.org; domain=" - ], + "received": ["foo=bar; domain=foo.example.org; domain="], "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?optional-domain0043", "sent": [] }, @@ -1612,10 +1087,7 @@ }, { "test": "PATH0001", - "received": [ - "a=b; path=/", - "x=y; path=/cookie-parser-result" - ], + "received": ["a=b; path=/", "x=y; path=/cookie-parser-result"], "sent": [ { "name": "x", "value": "y" }, { "name": "a", "value": "b" } @@ -1623,10 +1095,7 @@ }, { "test": "PATH0002", - "received": [ - "a=b; path=/cookie-parser-result", - "x=y; path=/" - ], + "received": ["a=b; path=/cookie-parser-result", "x=y; path=/"], "sent": [ { "name": "a", "value": "b" }, { "name": "x", "value": "y" } @@ -1634,10 +1103,7 @@ }, { "test": "PATH0003", - "received": [ - "x=y; path=/", - "a=b; path=/cookie-parser-result" - ], + "received": ["x=y; path=/", "a=b; path=/cookie-parser-result"], "sent": [ { "name": "a", "value": "b" }, { "name": "x", "value": "y" } @@ -1645,10 +1111,7 @@ }, { "test": "PATH0004", - "received": [ - "x=y; path=/cookie-parser-result", - "a=b; path=/" - ], + "received": ["x=y; path=/cookie-parser-result", "a=b; path=/"], "sent": [ { "name": "x", "value": "y" }, { "name": "a", "value": "b" } @@ -1656,211 +1119,138 @@ }, { "test": "PATH0005", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo"], "sent": [] }, { "test": "PATH0006", - "received": [ - "foo=bar", - "foo=qux; path=/cookie-parser-result/foo" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar", "foo=qux; path=/cookie-parser-result/foo"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0007", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo"], "sent-to": "/cookie-parser-result/foo?path0007", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0008", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo"], "sent-to": "/cookie-parser-result/bar?path0008", "sent": [] }, { "test": "PATH0009", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux"], "sent-to": "/cookie-parser-result/foo?path0009", "sent": [] }, { "test": "PATH0010", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux"], "sent-to": "/cookie-parser-result/foo/qux?path0010", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0011", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux"], "sent-to": "/cookie-parser-result/bar/qux?path0011", "sent": [] }, { "test": "PATH0012", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux"], "sent-to": "/cookie-parser-result/foo/baz?path0012", "sent": [] }, { "test": "PATH0013", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux/"], "sent-to": "/cookie-parser-result/foo/baz?path0013", "sent": [] }, { "test": "PATH0014", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux/"], "sent-to": "/cookie-parser-result/foo/qux?path0014", "sent": [] }, { "test": "PATH0015", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux/"], "sent-to": "/cookie-parser-result/foo/qux/?path0015", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0016", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/"], "sent-to": "/cookie-parser-result/foo/qux?path0016", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0017", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/"], "sent-to": "/cookie-parser-result/foo//qux?path0017", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0018", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/"], "sent-to": "/cookie-parser-result/fooqux?path0018", "sent": [] }, { "test": "PATH0019", - "received": [ - "foo=bar; path" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; path"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0020", - "received": [ - "foo=bar; path=" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; path="], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0021", - "received": [ - "foo=bar; path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; path=/"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0022", - "received": [ - "foo=bar; path= /" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; path= /"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0023", - "received": [ - "foo=bar; Path=/cookie-PARSER-result" - ], + "received": ["foo=bar; Path=/cookie-PARSER-result"], "sent": [] }, { "test": "PATH0024", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux?" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux?"], "sent-to": "/cookie-parser-result/foo/qux?path0024", "sent": [] }, { "test": "PATH0025", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux#" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux#"], "sent-to": "/cookie-parser-result/foo/qux?path0025", "sent": [] }, { "test": "PATH0026", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux;" - ], + "received": ["foo=bar; path=/cookie-parser-result/foo/qux;"], "sent-to": "/cookie-parser-result/foo/qux?path0026", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0027", - "received": [ - "foo=bar; path=\"/cookie-parser-result/foo/qux;\"" - ], + "received": ["foo=bar; path=\"/cookie-parser-result/foo/qux;\""], "sent-to": "/cookie-parser-result/foo/qux?path0027", - "sent": [ - { "name": "foo", "value": "bar" } - ] + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0028", - "received": [ - "foo=bar; path=/cookie-parser-result/f%6Fo/bar" - ], + "received": ["foo=bar; path=/cookie-parser-result/f%6Fo/bar"], "sent-to": "/cookie-parser-result/foo/bar?path0028", "sent": [] }, @@ -1870,24 +1260,16 @@ "a=b; \tpath\t=\t/cookie-parser-result\t", "x=y; \tpath\t=\t/book\t" ], - "sent": [ - { "name": "a", "value": "b" } - ] + "sent": [{ "name": "a", "value": "b" }] }, { "test": "PATH0030", - "received": [ - "foo=bar; path=/dog; path=" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo=bar; path=/dog; path="], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "PATH0031", - "received": [ - "foo=bar; path=; path=/dog" - ], + "received": ["foo=bar; path=; path=/dog"], "sent": [] }, { @@ -1904,56 +1286,32 @@ }, { "test": "VALUE0001", - "received": [ - "foo= bar" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["foo= bar"], + "sent": [{ "name": "foo", "value": "bar" }] }, { "test": "VALUE0002", - "received": [ - "foo=\"bar\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar\"" } - ] + "received": ["foo=\"bar\""], + "sent": [{ "name": "foo", "value": "\"bar\"" }] }, { "test": "VALUE0003", - "received": [ - "foo=\" bar \"" - ], - "sent": [ - { "name": "foo", "value": "\" bar \"" } - ] + "received": ["foo=\" bar \""], + "sent": [{ "name": "foo", "value": "\" bar \"" }] }, { "test": "VALUE0004", - "received": [ - "foo=\"bar;baz\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar" } - ] + "received": ["foo=\"bar;baz\""], + "sent": [{ "name": "foo", "value": "\"bar" }] }, { "test": "VALUE0005", - "received": [ - "foo=\"bar=baz\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar=baz\"" } - ] + "received": ["foo=\"bar=baz\""], + "sent": [{ "name": "foo", "value": "\"bar=baz\"" }] }, { "test": "VALUE0006", - "received": [ - "\tfoo\t=\tbar\t \t;\tttt" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + "received": ["\tfoo\t=\tbar\t \t;\tttt"], + "sent": [{ "name": "foo", "value": "bar" }] } ] From c42cf1ca2b82a8f5dbe07839e01e82eb499a73ef Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Tue, 28 Mar 2023 09:14:49 -0300 Subject: [PATCH 18/33] changes from linter --- .eslintrc.json | 61 ++++++++++++++++++++++---- .prettierignore | 4 +- jest.config.ts | 4 +- lib/__tests__/cookieJar.spec.ts | 13 ++++-- lib/__tests__/cookiePrefixes.spec.ts | 4 +- lib/__tests__/date.spec.ts | 2 +- lib/__tests__/jarSerialization.spec.ts | 2 +- lib/cookie.ts | 44 ++++++++++++------- lib/memstore.ts | 1 + lib/permuteDomain.ts | 4 +- lib/store.ts | 1 - lib/validators.ts | 31 ++++++------- lib/version.ts | 4 +- package.json | 19 ++++---- 14 files changed, 127 insertions(+), 67 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d85ba378..4ece7bc5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,15 +6,60 @@ "prettier" ], "rules": { + // our parser is written with control characters "no-control-regex": "off", - "@typescript-eslint/ban-ts-comment": "off", - "no-restricted-modules": ["error", - // we can't rely on node standard modules in "native" or "browser" environments - // - exceptions: - // "punycode": since it's a package.json dependency - "assert", "buffer", "child_process", "cluster", "crypto", "dgram", "dns", "domain", "events", "freelist", "fs", - "http", "https", "module", "net", "os", "path", "querystring", "readline", "repl", "smalloc", "stream", - "string_decoder", "sys", "timers", "tls", "tracing", "tty", "url", "util", "vm", "zlib" + + // this is only needed because `createPromiseCallback` uses arguments to support the callback-style api + // we should strongly consider dropping the callback and sync api variants (in say v6) to reduce the + // surface area and complexity of tough-cookie + "prefer-rest-params": "warn", + "@typescript-eslint/no-unused-vars": "warn", + + // used by callSync which needs to use Function because it's used by several apis + "@typescript-eslint/ban-types": "warn", + + // there are several areas that still have ts-ignore comments, let's try to eliminate those + "@typescript-eslint/ban-ts-comment": "warn", + + // i'd like to clean up all usages of any but the current api is just too loose + "@typescript-eslint/no-explicit-any": "warn", + + // we can't rely on node standard modules in "native" or "browser" environments + // - except "punycode" (since it's a package.json dependency) + "no-restricted-modules": [ + "error", + "assert", + "buffer", + "child_process", + "cluster", + "crypto", + "dgram", + "dns", + "domain", + "events", + "freelist", + "fs", + "http", + "https", + "module", + "net", + "os", + "path", + "querystring", + "readline", + "repl", + "smalloc", + "stream", + "string_decoder", + "sys", + "timers", + "tls", + "tracing", + "tty", + "url", + "util", + "vm", + "zlib" ] } } diff --git a/.prettierignore b/.prettierignore index 9726f453..ecfc81b1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ -dist/*.js -test/*.js +dist +test diff --git a/jest.config.ts b/jest.config.ts index 76952882..bd1104f5 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,11 +1,11 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -import type { JestConfigWithTsJest } from "ts-jest"; +import type { JestConfigWithTsJest } from 'ts-jest' const config: JestConfigWithTsJest = { preset: 'ts-jest', testEnvironment: 'node', testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$', - testPathIgnorePatterns: ["dist"], + testPathIgnorePatterns: ['dist'], maxWorkers: 1, globals: { '*.ts': ['ts-jest', { isolatedModules: false }], diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 4f8a8afd..cb63ad77 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,14 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store } from "../cookie"; +import { + Cookie, + CookieJar, + MemoryCookieStore, + ParameterError, + SerializedCookieJar, + Store, +} from '../cookie' const { objectContaining, assertions } = expect jest.useFakeTimers() @@ -135,7 +142,7 @@ describe('CookieJar', () => { '=a;domain=example.com', '=b;domain=example.com', 'c=d;domain=example.com', - ].map(Cookie.parse) + ].map((value) => Cookie.parse(value)) expect(cookies).toEqual([ undefined, undefined, @@ -965,7 +972,7 @@ describe('CookieJar', () => { prefixSecurity: 'silent', rejectPublicSuffixes: true, storeType: 'MemoryCookieStore', - version: 'tough-cookie@4.1.2', + version: 'tough-cookie@5.0.0', } expect(data).toEqual(expected) }, diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index e68f0dfc..9a1c82df 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -3,8 +3,8 @@ import { CookieJar, PrefixSecurityEnum } from '../cookie' const { objectContaining } = expect let cookieJar: CookieJar -let insecureUrl = 'http://www.example.com' -let secureUrl = 'https://www.example.com' +const insecureUrl = 'http://www.example.com' +const secureUrl = 'https://www.example.com' describe('When `prefixSecurity` is enabled for `CookieJar`', () => { describe('silent', () => { diff --git a/lib/__tests__/date.spec.ts b/lib/__tests__/date.spec.ts index 0252da8c..a3c554e7 100644 --- a/lib/__tests__/date.spec.ts +++ b/lib/__tests__/date.spec.ts @@ -37,7 +37,7 @@ type EquivalenceDateParsingTestCase = { [key: string]: string } -const { parseDate } = require('../cookie') +import { parseDate } from '../cookie' const dateTests: DateParsingTestCase = { 'Wed, 09 Jun 2021 10:18:14 GMT': true, diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index fe3f9353..c0d39e06 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -37,7 +37,7 @@ jest.useFakeTimers() describe('cookieJar serialization', () => { it('should use the expected version', () => { - expect(version).toBe('4.1.2') + expect(version).toBe('5.0.0') }) it('should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`', () => { diff --git a/lib/cookie.ts b/lib/cookie.ts index 9da0a0f8..1f9667e6 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -29,16 +29,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import * as punycode from "punycode/"; -import { parse as urlParse } from "url"; -import * as pubsuffix from "./pubsuffix-psl"; -import { Store } from "./store"; -import { MemoryCookieStore } from "./memstore"; -import { pathMatch } from "./pathMatch"; -import * as validators from "./validators"; -import VERSION from "./version"; -import { permuteDomain } from "./permuteDomain"; -import { getCustomInspectSymbol } from "./utilHelper"; +import * as punycode from 'punycode/' +import { parse as urlParse } from 'url' +import * as pubsuffix from './pubsuffix-psl' +import { Store } from './store' +import { MemoryCookieStore } from './memstore' +import { pathMatch } from './pathMatch' +import * as validators from './validators' +import { version } from './version' +import { permuteDomain } from './permuteDomain' +import { getCustomInspectSymbol } from './utilHelper' // From RFC6265 S4.1.1 // note that it excludes \x3B ";" @@ -527,7 +527,10 @@ function parseCookiePair(cookiePair: string, looseMode: boolean) { return c } -function parse(str: string, options: any = {}): Cookie | undefined | null { +function parse( + str: string, + options?: ParseCookieOptions, +): Cookie | undefined | null { if (validators.isEmptyString(str) || !validators.isString(str)) { return null } @@ -537,7 +540,7 @@ function parse(str: string, options: any = {}): Cookie | undefined | null { // We use a regex to parse the "name-value-pair" part of S5.2 const firstSemi = str.indexOf(';') // S5.2 step 1 const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi) - const c = parseCookiePair(cookiePair, !!options.loose) + const c = parseCookiePair(cookiePair, options?.loose ?? false) if (!c) { return undefined } @@ -659,8 +662,7 @@ function parse(str: string, options: any = {}): Cookie | undefined | null { break case 'samesite': // RFC6265bis-02 S5.3.7 - const enforcement = av_value ? av_value.toLowerCase() : '' - switch (enforcement) { + switch (av_value ? av_value.toLowerCase() : '') { case 'strict': c.sameSite = 'strict' break @@ -1159,7 +1161,10 @@ export class Cookie { return this.canonicalizedDomain() } - static parse(cookieString: string, options?: {}): Cookie | undefined | null { + static parse( + cookieString: string, + options?: ParseCookieOptions, + ): Cookie | undefined | null { return parse(cookieString, options) } @@ -1709,6 +1714,7 @@ export class CookieJar { // deferred from S5.3 // non-RFC: allow retention of expired cookies by choice if (expireCheck && c.expiryTime() <= now) { + // eslint-disable-next-line @typescript-eslint/no-empty-function store.removeCookie(c.domain, c.path, c.key, () => {}) // result ignored return false } @@ -1882,7 +1888,7 @@ export class CookieJar { // The version of tough-cookie that serialized this jar. Generally a good // practice since future versions can make data import decisions based on // known past behavior. When/if this matters, use `semver`. - version: `tough-cookie@${VERSION}`, + version: `tough-cookie@${version}`, // add the store type, to make humans happy: storeType: type, @@ -2180,7 +2186,7 @@ export class CookieJar { const getPublicSuffix = pubsuffix.getPublicSuffix const ParameterError = validators.ParameterError -export { VERSION as version } +export { version as version } export { Store as Store } export { MemoryCookieStore as MemoryCookieStore } export { parseDate as parseDate } @@ -2214,6 +2220,10 @@ type GetCookiesOptions = { sort?: boolean | undefined } +type ParseCookieOptions = { + loose?: boolean | undefined +} + interface PromiseCallback { promise: Promise callback: ( diff --git a/lib/memstore.ts b/lib/memstore.ts index d4a99420..e5f978cc 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -213,6 +213,7 @@ export class MemoryCookieStore extends Store { callback?: Callback, ): unknown { // this seems wrong but it stops typescript from complaining and all the test pass... + // eslint-disable-next-line @typescript-eslint/no-empty-function callback = callback ?? function () {} // updateCookie() may avoid updating cookies that are identical. For example, diff --git a/lib/permuteDomain.ts b/lib/permuteDomain.ts index 151b3576..e73a29f9 100644 --- a/lib/permuteDomain.ts +++ b/lib/permuteDomain.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ 'use strict' -const pubsuffix = require('./pubsuffix-psl') +import { getPublicSuffix } from './pubsuffix-psl' // Gives the permutation of all possible domainMatch()es of a given domain. The // array is in shortest-to-longest order. Handy for indexing. @@ -38,7 +38,7 @@ export function permuteDomain( domain: string, allowSpecialUseDomain?: boolean, ): string[] | null { - const pubSuf = pubsuffix.getPublicSuffix(domain, { + const pubSuf = getPublicSuffix(domain, { allowSpecialUseDomain: allowSpecialUseDomain, }) diff --git a/lib/store.ts b/lib/store.ts index 1aada6ca..a0eaaea9 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -29,7 +29,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ 'use strict' -/*jshint unused:false */ import type { Callback, Cookie } from './cookie' diff --git a/lib/validators.ts b/lib/validators.ts index 5c67e5df..0cde8973 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -28,51 +28,46 @@ SOFTWARE. 'use strict' /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ -export function isFunction(data: any): boolean { +export function isFunction(data: unknown): boolean { return typeof data === 'function' } -export function isNonEmptyString(data: any): boolean { +export function isNonEmptyString(data: unknown): boolean { return isString(data) && data !== '' } -export function isDate(data: any): boolean { - return isInstanceStrict(data, Date) && isInteger(data.getTime()) +export function isDate(data: unknown): boolean { + if (data instanceof Date) { + return isInteger(data.getTime()) + } + return false } -export function isEmptyString(data: any): boolean { +export function isEmptyString(data: unknown): boolean { return data === '' || (data instanceof String && data.toString() === '') } -export function isString(data: any): boolean { +export function isString(data: unknown): boolean { return typeof data === 'string' || data instanceof String } -export function isObject(data: any): boolean { +export function isObject(data: unknown): boolean { return toString.call(data) === '[object Object]' } -export function isInstanceStrict(data: any, prototype: Function): boolean { - try { - return data instanceof prototype - } catch (error) { - return false - } -} - -export function isInteger(data: any): boolean { +export function isInteger(data: unknown): boolean { return typeof data === 'number' && data % 1 === 0 } /* End validation functions */ -export function validate(bool: boolean, cb?: any, options?: any): void { +export function validate(bool: boolean, cb?: unknown, options?: unknown): void { if (!isFunction(cb)) { options = cb cb = null } if (!isObject(options)) options = { Error: 'Failed Check' } if (!bool) { - if (cb) { + if (typeof cb === 'function') { cb(new ParameterError(options)) } else { throw new ParameterError(options) diff --git a/lib/version.ts b/lib/version.ts index 98c9a1d0..aa8cf9b4 100644 --- a/lib/version.ts +++ b/lib/version.ts @@ -1,2 +1,2 @@ -// TODO: this should be sourced from package.json (maybe?) -export default '4.1.2' +// Generated by genversion. +export const version = '5.0.0' diff --git a/package.json b/package.json index 7facbaf2..ffdeae66 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "RFC6265", "RFC2965" ], - "version": "4.1.2", + "version": "5.0.0", "homepage": "https://github.com/salesforce/tough-cookie", "repository": { "type": "git", @@ -82,12 +82,15 @@ "dist" ], "scripts": { - "version": "genversion lib/version.js && git add lib/version.js", - "test": "jest && tsc && vows test/*_test.js", - "cover": "jest --coverage", - "lint": "eslint --fix lib", - "prettier": "prettier --write '**/*.{json,ts,yaml,md}'", - "format": "npm run eslint -- --fix" + "build": "npm run version && tsc", + "lint": "eslint --quiet --fix lib", + "lint:everything": "eslint --fix lib", + "format": "prettier --write '**/*.{json,ts,yaml,md}'", + "prepack": "npm run version", + "test": "jest", + "test:coverage": "jest --coverage", + "test:legacy": "npm run build && vows test/*_test.js", + "version": "genversion --es6 lib/version.ts" }, "engines": { "node": ">=6" @@ -102,7 +105,7 @@ "@typescript-eslint/eslint-plugin": "^5.57.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "genversion": "^2.1.0", + "genversion": "^3.1.1", "jest": "^29.5.0", "prettier": "^2.8.7", "ts-jest": "^29.0.5", From 2bc6217f980d15f69d0cbdc36be31bda33d2424c Mon Sep 17 00:00:00 2001 From: Will Harney <62956339+wjhsf@users.noreply.github.com> Date: Mon, 17 Apr 2023 13:35:18 -0400 Subject: [PATCH 19/33] [v5] Update config (#269) * Update config to support ES6 classes. Targeting ES5 causes the "fixes issue #145" test in cookieJar.spec.ts to fail. Changing the target to the next newest (ES6) requires adding "moduleResolution" to the tsconfig, which then causes issues with using `import` in our TypeScript, but `require` in the legacy vows tests. To support both, we have to bump to a newer version of TypeScript to use the new "node16" module/moduleResolution. (Note: I didn't jump to the _latest_ TypeScript to try to support older projects, as well.) * Avoid unnecessarily running compiled test files. * Fix flaky test. * Avoid hacky reliance on inherited property of global object. * Avoid needing to use `resolveJsonModule`. * Remove config that's just the default. See: https://huafu.github.io/ts-jest/user/config/isolatedModules * Update tsconfig to target node 16 and only compile ./lib * Chang `dist/lib` imports to just `dist`. * add granularity to npm scripts --- jest.config.js | 7 +- lib/__tests__/data/dates/bsd-examples.ts | 168 ++ lib/__tests__/data/dates/examples.ts | 48 + lib/__tests__/data/parser.ts | 1959 ++++++++++++++++++++++ lib/__tests__/ietf.spec.ts | 6 +- lib/validators.ts | 11 +- package.json | 13 +- test/node_util_fallback_test.js | 7 +- tsconfig.json | 35 +- 9 files changed, 2216 insertions(+), 38 deletions(-) create mode 100644 lib/__tests__/data/dates/bsd-examples.ts create mode 100644 lib/__tests__/data/dates/examples.ts create mode 100644 lib/__tests__/data/parser.ts diff --git a/jest.config.js b/jest.config.js index d286cbbb..5fb0fc7b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,9 +4,6 @@ module.exports = { testEnvironment: "node", testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", maxWorkers: 1, - globals: { - "ts-jest": { - isolatedModules: false - } - } + rootDir: './lib/', + testPathIgnorePatterns: ['./lib/__tests__/data/'] }; diff --git a/lib/__tests__/data/dates/bsd-examples.ts b/lib/__tests__/data/dates/bsd-examples.ts new file mode 100644 index 00000000..8ba721a9 --- /dev/null +++ b/lib/__tests__/data/dates/bsd-examples.ts @@ -0,0 +1,168 @@ +export default [ + { + "test": "Sat, 15-Apr-17 21:01:22 GMT", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Thu, 19-Apr-2007 16:00:00 GMT", + "expected": "Thu, 19 Apr 2007 16:00:00 GMT" + }, { + "test": "Wed, 25 Apr 2007 21:02:13 GMT", + "expected": "Wed, 25 Apr 2007 21:02:13 GMT" + }, { + "test": "Thu, 19/Apr\\2007 16:00:00 GMT", + "expected": "Thu, 19 Apr 2007 16:00:00 GMT" + }, { + "test": "Fri, 1 Jan 2010 01:01:50 GMT", + "expected": "Fri, 01 Jan 2010 01:01:50 GMT" + }, { + "test": "Wednesday, 1-Jan-2003 00:00:00 GMT", + "expected": "Wed, 01 Jan 2003 00:00:00 GMT" + }, { + "test": ", 1-Jan-2003 00:00:00 GMT", + "expected": "Wed, 01 Jan 2003 00:00:00 GMT" + }, { + "test": " 1-Jan-2003 00:00:00 GMT", + "expected": "Wed, 01 Jan 2003 00:00:00 GMT" + }, { + "test": "1-Jan-2003 00:00:00 GMT", + "expected": "Wed, 01 Jan 2003 00:00:00 GMT" + }, { + "test": "Wed,18-Apr-07 22:50:12 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "WillyWonka , 18-Apr-07 22:50:12 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "WillyWonka , 18-Apr-07 22:50:12", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "WillyWonka , 18-apr-07 22:50:12", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Mon, 18-Apr-1977 22:50:13 GMT", + "expected": "Mon, 18 Apr 1977 22:50:13 GMT" + }, { + "test": "Mon, 18-Apr-77 22:50:13 GMT", + "expected": "Mon, 18 Apr 1977 22:50:13 GMT" + }, { + "test": "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Partyday, 18- April-07 22:50:12", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Partyday, 18 - Apri-07 22:50:12", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Wednes, 1-Januar-2003 00:00:00 GMT", + "expected": "Wed, 01 Jan 2003 00:00:00 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 GMT-2", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 GMT BLAH", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 GMT-0400", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 DST", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 -0400", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 (hello there)", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 11:22:33", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 ::00 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 boink:z 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Sat, 15-Apr-17 91:22:33 21:01:22", + "expected": null + }, { + "test": "Thu Apr 18 22:50:12 2007 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "22:50:12 Thu Apr 18 2007 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Thu 22:50:12 Apr 18 2007 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Thu Apr 22:50:12 18 2007 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Thu Apr 18 22:50:12 2007 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Thu Apr 18 2007 22:50:12 GMT", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Thu Apr 18 2007 GMT 22:50:12", + "expected": "Wed, 18 Apr 2007 22:50:12 GMT" + }, { + "test": "Sat, 15-Apr-17 21:01:22 GMT", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15-Sat, Apr-17 21:01:22 GMT", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15-Sat, Apr 21:01:22 GMT 17", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15-Sat, Apr 21:01:22 GMT 2017", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15 Apr 21:01:22 2017", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15 17 Apr 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Apr 15 17 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "Apr 15 21:01:22 17", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "2017 April 15 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "15 April 2017 21:01:22", + "expected": "Sat, 15 Apr 2017 21:01:22 GMT" + }, { + "test": "98 April 17 21:01:22", + "expected": null + }, { + "test": "Thu, 012-Aug-2008 20:49:07 GMT", + "expected": null + }, { + "test": "Thu, 12-Aug-31841 20:49:07 GMT", + "expected": null + }, { + "test": "Thu, 12-Aug-9999999999 20:49:07 GMT", + "expected": null + }, { + "test": "Thu, 999999999999-Aug-2007 20:49:07 GMT", + "expected": null + }, { + "test": "Thu, 12-Aug-2007 20:61:99999999999 GMT", + "expected": null + }, { + "test": "IAintNoDateFool", + "expected": null + } +] diff --git a/lib/__tests__/data/dates/examples.ts b/lib/__tests__/data/dates/examples.ts new file mode 100644 index 00000000..eba15640 --- /dev/null +++ b/lib/__tests__/data/dates/examples.ts @@ -0,0 +1,48 @@ +export default [ + { + "test": "Mon, 10-Dec-2007 17:02:24 GMT", + "expected": "Mon, 10 Dec 2007 17:02:24 GMT" + }, { + "test": "Wed, 09 Dec 2009 16:27:23 GMT", + "expected": "Wed, 09 Dec 2009 16:27:23 GMT" + }, { + "test": "Thursday, 01-Jan-1970 00:00:00 GMT", + "expected": "Thu, 01 Jan 1970 00:00:00 GMT" + }, { + "test": "Mon Dec 10 16:32:30 2007 GMT", + "expected": "Mon, 10 Dec 2007 16:32:30 GMT" + }, { + "test": "Wednesday, 01-Jan-10 00:00:00 GMT", + "expected": "Fri, 01 Jan 2010 00:00:00 GMT" + }, { + "test": "Mon, 10-Dec-07 20:35:03 GMT", + "expected": "Mon, 10 Dec 2007 20:35:03 GMT" + }, { + "test": "Wed, 1 Jan 2020 00:00:00 GMT", + "expected": "Wed, 01 Jan 2020 00:00:00 GMT" + }, { + "test": "Saturday, 8-Dec-2012 21:24:09 GMT", + "expected": "Sat, 08 Dec 2012 21:24:09 GMT" + }, { + "test": "Thu, 31 Dec 23:55:55 2037 GMT", + "expected": "Thu, 31 Dec 2037 23:55:55 GMT" + }, { + "test": "Sun, 9 Dec 2012 13:42:05 GMT", + "expected": "Sun, 09 Dec 2012 13:42:05 GMT" + }, { + "test": "Wed Dec 12 2007 08:44:07 GMT-0500 (EST)", + "expected": "Wed, 12 Dec 2007 08:44:07 GMT" + }, { + "test": "Mon, 01-Jan-2011 00: 00:00 GMT", + "expected": null + }, { + "test": "Sun, 1-Jan-1995 00:00:00 GMT", + "expected": "Sun, 01 Jan 1995 00:00:00 GMT" + }, { + "test": "Wednesday, 01-Jan-10 0:0:00 GMT", + "expected": "Fri, 01 Jan 2010 00:00:00 GMT" + }, { + "test": "Thu, 10 Dec 2009 13:57:2 GMT", + "expected": "Thu, 10 Dec 2009 13:57:02 GMT" + } +] diff --git a/lib/__tests__/data/parser.ts b/lib/__tests__/data/parser.ts new file mode 100644 index 00000000..4d67a075 --- /dev/null +++ b/lib/__tests__/data/parser.ts @@ -0,0 +1,1959 @@ +export default [ + { + "test": "0001", + "received": [ + "foo=bar" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0002", + "received": [ + "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0003", + "received": [ + "foo=bar; Expires=Fri, 07 Aug 2007 08:04:19 GMT", + "foo2=bar2; Expires=Fri, 07 Aug 9999 08:04:19 GMT" + ], + "sent": [ + { "name": "foo2", "value": "bar2" } + ] + }, + { + "test": "0004", + "received": [ + "foo" + ], + "sent": [] + }, + { + "test": "0005", + "received": [ + "foo=bar; max-age=10000;" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0006", + "received": [ + "foo=bar; max-age=0;" + ], + "sent": [] + }, + { + "test": "0007", + "received": [ + "foo=bar; version=1;" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0008", + "received": [ + "foo=bar; version=1000;" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0009", + "received": [ + "foo=bar; customvalue=1000;" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0010", + "received": [ + "foo=bar; secure;" + ], + "sent": [] + }, + { + "test": "0011", + "received": [ + "foo=bar; customvalue=\"1000 or more\";" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0012", + "received": [ + "foo=bar; customvalue=\"no trailing semicolon\"" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "0013", + "received": [ + "foo=bar", + "foo=qux" + ], + "sent": [ + { "name": "foo", "value": "qux" } + ] + }, + { + "test": "0014", + "received": [ + "foo1=bar", + "foo2=qux" + ], + "sent": [ + { "name": "foo1", "value": "bar" }, + { "name": "foo2", "value": "qux" } + ] + }, + { + "test": "0015", + "received": [ + "a=b", + "z=y" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "z", "value": "y" } + ] + }, + { + "test": "0016", + "received": [ + "z=y", + "a=b" + ], + "sent": [ + { "name": "z", "value": "y" }, + { "name": "a", "value": "b" } + ] + }, + { + "test": "0017", + "received": [ + "z=y, a=b" + ], + "sent": [ + { "name": "z", "value": "y, a=b" } + ] + }, + { + "test": "0018", + "received": [ + "z=y; foo=bar, a=b" + ], + "sent": [ + { "name": "z", "value": "y" } + ] + }, + { + "test": "0019", + "received": [ + "foo=b;max-age=3600, c=d;path=/" + ], + "sent": [ + { "name": "foo", "value": "b" } + ] + }, + { + "test": "0020", + "received": [ + "a=b", + "=", + "c=d" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "c", "value": "d" } + ] + }, + { + "test": "0021", + "received": [ + "a=b", + "=x", + "c=d" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "c", "value": "d" } + ] + }, + { + "test": "0022", + "received": [ + "a=b", + "x=", + "c=d" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "x", "value": "" }, + { "name": "c", "value": "d" } + ] + }, + { + "test": "0023", + "received": [ + "foo", + "" + ], + "sent": [] + }, + { + "test": "0024", + "received": [ + "foo", + "=" + ], + "sent": [] + }, + { + "test": "0025", + "received": [ + "foo", + "; bar" + ], + "sent": [] + }, + { + "test": "0026", + "received": [ + "foo", + " " + ], + "sent": [] + }, + { + "test": "0027", + "received": [ + "foo", + "bar" + ], + "sent": [] + }, + { + "test": "0028", + "received": [ + "foo", + "\t" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0001", + "received": [ + "foo=bar; Secure" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0002", + "received": [ + "foo=bar; seCURe" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0003", + "received": [ + "foo=bar; \"Secure\"" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0004", + "received": [ + "foo=bar; Secure=" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0005", + "received": [ + "foo=bar; Secure=aaaa" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0006", + "received": [ + "foo=bar; Secure qux" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0007", + "received": [ + "foo=bar; Secure =aaaaa" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0008", + "received": [ + "foo=bar; Secure= aaaaa" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0009", + "received": [ + "foo=bar; Secure; qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0010", + "received": [ + "foo=bar; Secure;qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0011", + "received": [ + "foo=bar; Secure ; qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0012", + "received": [ + "foo=bar; Secure" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0013", + "received": [ + "foo=bar; Secure ;" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0014", + "received": [ + "foo=bar; Path" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0015", + "received": [ + "foo=bar; Path=" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0016", + "received": [ + "foo=bar; Path=/" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0017", + "received": [ + "foo=bar; Path=/qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0018", + "received": [ + "foo=bar; Path =/qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0019", + "received": [ + "foo=bar; Path= /qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0020", + "received": [ + "foo=bar; Path=/qux ; taz" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0021", + "received": [ + "foo=bar; Path=/qux; Path=/" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0022", + "received": [ + "foo=bar; Path=/; Path=/qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0023", + "received": [ + "foo=bar; Path=/qux; Path=/cookie-parser-result" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "ATTRIBUTE0024", + "received": [ + "foo=bar; Path=/cookie-parser-result; Path=/qux" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0025", + "received": [ + "foo=bar; qux; Secure" + ], + "sent": [] + }, + { + "test": "ATTRIBUTE0026", + "received": [ + "foo=bar; qux=\"aaa;bbb\"; Secure" + ], + "sent": [] + }, + { + "test": "CHARSET0001", + "received": [ + "foo=\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + ], + "sent": [ + { "name": "foo", "value": "\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } + ] + }, + { + "test": "CHARSET0002", + "received": [ + "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + ], + "sent": [ + { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } + ] + }, + { + "test": "CHARSET0003", + "received": [ + "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0; \u5b8c\u5168\u624b\u518c" + ], + "sent": [ + { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0" } + ] + }, + { + "test": "CHARSET0004", + "received": [ + "foo=\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" + ], + "sent": [ + { "name": "foo", "value": "\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" } + ] + }, + { + "test": "CHROMIUM0001", + "received": [ + "a=b" + ], + "sent": [ + { "name": "a", "value": "b" } + ] + }, + { + "test": "CHROMIUM0002", + "received": [ + "aBc=\"zzz \" ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zzz \"" } + ] + }, + { + "test": "CHROMIUM0003", + "received": [ + "aBc=\"zzz \" ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zzz \"" } + ] + }, + { + "test": "CHROMIUM0004", + "received": [ + "aBc=\"zz;pp\" ; ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zz" } + ] + }, + { + "test": "CHROMIUM0005", + "received": [ + "aBc=\"zz ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zz" } + ] + }, + { + "test": "CHROMIUM0006", + "received": [ + "aBc=\"zzz \" \"ppp\" ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zzz \" \"ppp\"" } + ] + }, + { + "test": "CHROMIUM0007", + "received": [ + "aBc=\"zzz \" \"ppp\" ;" + ], + "sent": [ + { "name": "aBc", "value": "\"zzz \" \"ppp\"" } + ] + }, + { + "test": "CHROMIUM0008", + "received": [ + "aBc=A\"B ;" + ], + "sent": [ + { "name": "aBc", "value": "A\"B" } + ] + }, + { + "test": "CHROMIUM0009", + "received": [ + "BLAHHH; path=/;" + ], + "sent": [] + }, + { + "test": "CHROMIUM0010", + "received": [ + "\"BLA\\\"HHH\"; path=/;" + ], + "sent": [] + }, + { + "test": "CHROMIUM0011", + "received": [ + "a=\"B" + ], + "sent": [ + { "name": "a", "value": "\"B" } + ] + }, + { + "test": "CHROMIUM0012", + "received": [ + "=ABC" + ], + "sent": [] + }, + { + "test": "CHROMIUM0013", + "received": [ + "ABC=; path = /" + ], + "sent": [ + { "name": "ABC", "value": "" } + ] + }, + { + "test": "CHROMIUM0014", + "received": [ + " A = BC ;foo;;; bar" + ], + "sent": [ + { "name": "A", "value": "BC" } + ] + }, + { + "test": "CHROMIUM0015", + "received": [ + " A=== BC ;foo;;; bar" + ], + "sent": [ + { "name": "A", "value": "== BC" } + ] + }, + { + "test": "CHROMIUM0016", + "received": [ + "foo=\"zohNumRKgI0oxyhSsV3Z7D\" ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " + ], + "sent": [ + { "name": "foo", "value": "\"zohNumRKgI0oxyhSsV3Z7D\"" } + ] + }, + { + "test": "CHROMIUM0017", + "received": [ + "foo=zohNumRKgI0oxyhSsV3Z7D ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " + ], + "sent": [ + { "name": "foo", "value": "zohNumRKgI0oxyhSsV3Z7D" } + ] + }, + { + "test": "CHROMIUM0018", + "received": [ + " " + ], + "sent": [] + }, + { + "test": "CHROMIUM0019", + "received": [ + "a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + "sent": [ + { "name": "a", "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } + ] + }, + { + "test": "CHROMIUM0021", + "received": [ + "" + ], + "sent": [] + }, + { + "test": "COMMA0001", + "received": [ + "foo=bar, baz=qux" + ], + "sent": [ + { "name": "foo", "value": "bar, baz=qux" } + ] + }, + { + "test": "COMMA0002", + "received": [ + "foo=\"bar, baz=qux\"" + ], + "sent": [ + { "name": "foo", "value": "\"bar, baz=qux\"" } + ] + }, + { + "test": "COMMA0003", + "received": [ + "foo=bar; b,az=qux" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "COMMA0004", + "received": [ + "foo=bar; baz=q,ux" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "COMMA0005", + "received": [ + "foo=bar; Max-Age=50,399" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "COMMA0006", + "received": [ + "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "COMMA0007", + "received": [ + "foo=bar; Expires=Fri 07 Aug 9999 08:04:19 GMT, baz=qux" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DISABLED_CHROMIUM0020", + "received": [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + "sent": [] + }, + { + "test": "DISABLED_CHROMIUM0022", + "received": [ + "AAA=BB\u0000ZYX" + ], + "sent": [ + { "name": "AAA", "value": "BB" } + ] + }, + { + "test": "DISABLED_CHROMIUM0023", + "received": [ + "AAA=BB\rZYX" + ], + "sent": [ + { "name": "AAA", "value": "BB" } + ] + }, + { + "test": "DISABLED_PATH0029", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/bar" + ], + "sent-to": "/cookie-parser-result/f%6Fo/bar?disabled-path0029", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0001", + "received": [ + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0001", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0002", + "received": [ + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0002", + "sent": [] + }, + { + "test": "DOMAIN0003", + "received": [ + "foo=bar; domain=.home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0003", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0004", + "received": [ + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0004", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0005", + "received": [ + "foo=bar; domain=.home.example.org" + ], + "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0005", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0006", + "received": [ + "foo=bar; domain=.home.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0006", + "sent": [] + }, + { + "test": "DOMAIN0007", + "received": [ + "foo=bar; domain=sibling.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0007", + "sent": [] + }, + { + "test": "DOMAIN0008", + "received": [ + "foo=bar; domain=.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0008", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0009", + "received": [ + "foo=bar; domain=example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0009", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0010", + "received": [ + "foo=bar; domain=..home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0010", + "sent": [] + }, + { + "test": "DOMAIN0011", + "received": [ + "foo=bar; domain=home..example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0011", + "sent": [] + }, + { + "test": "DOMAIN0012", + "received": [ + "foo=bar; domain= .home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0012", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0013", + "received": [ + "foo=bar; domain= . home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0013", + "sent": [] + }, + { + "test": "DOMAIN0014", + "received": [ + "foo=bar; domain=home.example.org." + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0014", + "sent": [] + }, + { + "test": "DOMAIN0015", + "received": [ + "foo=bar; domain=home.example.org.." + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0015", + "sent": [] + }, + { + "test": "DOMAIN0016", + "received": [ + "foo=bar; domain=home.example.org ." + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0016", + "sent": [] + }, + { + "test": "DOMAIN0017", + "received": [ + "foo=bar; domain=.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0017", + "sent": [] + }, + { + "test": "DOMAIN0018", + "received": [ + "foo=bar; domain=.org." + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0018", + "sent": [] + }, + { + "test": "DOMAIN0019", + "received": [ + "foo=bar; domain=home.example.org", + "foo2=bar2; domain=.home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0019", + "sent": [ + { "name": "foo", "value": "bar" }, + { "name": "foo2", "value": "bar2" } + ] + }, + { + "test": "DOMAIN0020", + "received": [ + "foo2=bar2; domain=.home.example.org", + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0020", + "sent": [ + { "name": "foo2", "value": "bar2" }, + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0021", + "received": [ + "foo=bar; domain=\"home.example.org\"" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0021", + "sent": [] + }, + { + "test": "DOMAIN0022", + "received": [ + "foo=bar; domain=home.example.org", + "foo2=bar2; domain=.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0022", + "sent": [ + { "name": "foo", "value": "bar" }, + { "name": "foo2", "value": "bar2" } + ] + }, + { + "test": "DOMAIN0023", + "received": [ + "foo2=bar2; domain=.example.org", + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0023", + "sent": [ + { "name": "foo2", "value": "bar2" }, + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0024", + "received": [ + "foo=bar; domain=.example.org; domain=home.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0024", + "sent": [] + }, + { + "test": "DOMAIN0025", + "received": [ + "foo=bar; domain=home.example.org; domain=.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0025", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0026", + "received": [ + "foo=bar; domain=home.eXaMpLe.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0026", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0027", + "received": [ + "foo=bar; domain=home.example.org:8888" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0027", + "sent": [] + }, + { + "test": "DOMAIN0028", + "received": [ + "foo=bar; domain=subdomain.home.example.org" + ], + "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0028", + "sent": [] + }, + { + "test": "DOMAIN0029", + "received": [ + "foo=bar" + ], + "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0029", + "sent": [] + }, + { + "test": "DOMAIN0031", + "received": [ + "foo=bar; domain=home.example.org; domain=.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0031", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0033", + "received": [ + "foo=bar; domain=home.example.org" + ], + "sent-to": "http://hoMe.eXaMplE.org:8888/cookie-parser-result?domain0033", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0034", + "received": [ + "foo=bar; domain=home.example.org; domain=home.example.com" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0034", + "sent": [] + }, + { + "test": "DOMAIN0035", + "received": [ + "foo=bar; domain=home.example.com; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0035", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0036", + "received": [ + "foo=bar; domain=home.example.org; domain=home.example.com; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0036", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0037", + "received": [ + "foo=bar; domain=home.example.com; domain=home.example.org; domain=home.example.com" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0037", + "sent": [] + }, + { + "test": "DOMAIN0038", + "received": [ + "foo=bar; domain=home.example.org; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0038", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0039", + "received": [ + "foo=bar; domain=home.example.org; domain=example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0039", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0040", + "received": [ + "foo=bar; domain=example.org; domain=home.example.org" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0040", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "DOMAIN0041", + "received": [ + "foo=bar; domain=.sibling.example.org" + ], + "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0041", + "sent": [] + }, + { + "test": "DOMAIN0042", + "received": [ + "foo=bar; domain=.sibling.home.example.org" + ], + "sent-to": "http://sibling.home.example.org:8888/cookie-parser-result?domain0042", + "sent": [] + }, + { + "test": "MOZILLA0001", + "received": [ + "foo=bar; max-age=-1" + ], + "sent": [] + }, + { + "test": "MOZILLA0002", + "received": [ + "foo=bar; max-age=0" + ], + "sent": [] + }, + { + "test": "MOZILLA0003", + "received": [ + "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" + ], + "sent": [] + }, + { + "test": "MOZILLA0004", + "received": [ + "foo=bar; max-age=60" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "MOZILLA0005", + "received": [ + "foo=bar; max-age=-20" + ], + "sent": [] + }, + { + "test": "MOZILLA0006", + "received": [ + "foo=bar; max-age=60" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "MOZILLA0007", + "received": [ + "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" + ], + "sent": [] + }, + { + "test": "MOZILLA0008", + "received": [ + "foo=bar; max-age=60", + "foo1=bar; max-age=60" + ], + "sent": [ + { "name": "foo", "value": "bar" }, + { "name": "foo1", "value": "bar" } + ] + }, + { + "test": "MOZILLA0009", + "received": [ + "foo=bar; max-age=60", + "foo1=bar; max-age=60", + "foo=differentvalue; max-age=0" + ], + "sent": [ + { "name": "foo1", "value": "bar" } + ] + }, + { + "test": "MOZILLA0010", + "received": [ + "foo=bar; max-age=60", + "foo1=bar; max-age=60", + "foo=differentvalue; max-age=0", + "foo2=evendifferentvalue; max-age=0" + ], + "sent": [ + { "name": "foo1", "value": "bar" } + ] + }, + { + "test": "MOZILLA0011", + "received": [ + "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;" + ], + "sent": [] + }, + { + "test": "MOZILLA0012", + "received": [ + "test=\"fubar! = foo;bar\\\";\" parser; max-age=6", + "five; max-age=2.63," + ], + "sent": [ + { "name": "test", "value": "\"fubar! = foo" } + ] + }, + { + "test": "MOZILLA0013", + "received": [ + "test=kill; max-age=0", + "five; max-age=0" + ], + "sent": [] + }, + { + "test": "MOZILLA0014", + "received": [ + "six" + ], + "sent": [] + }, + { + "test": "MOZILLA0015", + "received": [ + "six", + "seven" + ], + "sent": [] + }, + { + "test": "MOZILLA0016", + "received": [ + "six", + "seven", + " =eight" + ], + "sent": [] + }, + { + "test": "MOZILLA0017", + "received": [ + "six", + "seven", + " =eight", + "test=six" + ], + "sent": [ + { "name": "test", "value": "six" } + ] + }, + { + "test": "NAME0001", + "received": [ + "a=bar" + ], + "sent": [ + { "name": "a", "value": "bar" } + ] + }, + { + "test": "NAME0002", + "received": [ + "1=bar" + ], + "sent": [ + { "name": "1", "value": "bar" } + ] + }, + { + "test": "NAME0003", + "received": [ + "$=bar" + ], + "sent": [ + { "name": "$", "value": "bar" } + ] + }, + { + "test": "NAME0004", + "received": [ + "!a=bar" + ], + "sent": [ + { "name": "!a", "value": "bar" } + ] + }, + { + "test": "NAME0005", + "received": [ + "@a=bar" + ], + "sent": [ + { "name": "@a", "value": "bar" } + ] + }, + { + "test": "NAME0006", + "received": [ + "#a=bar" + ], + "sent": [ + { "name": "#a", "value": "bar" } + ] + }, + { + "test": "NAME0007", + "received": [ + "$a=bar" + ], + "sent": [ + { "name": "$a", "value": "bar" } + ] + }, + { + "test": "NAME0008", + "received": [ + "%a=bar" + ], + "sent": [ + { "name": "%a", "value": "bar" } + ] + }, + { + "test": "NAME0009", + "received": [ + "^a=bar" + ], + "sent": [ + { "name": "^a", "value": "bar" } + ] + }, + { + "test": "NAME0010", + "received": [ + "&a=bar" + ], + "sent": [ + { "name": "&a", "value": "bar" } + ] + }, + { + "test": "NAME0011", + "received": [ + "*a=bar" + ], + "sent": [ + { "name": "*a", "value": "bar" } + ] + }, + { + "test": "NAME0012", + "received": [ + "(a=bar" + ], + "sent": [ + { "name": "(a", "value": "bar" } + ] + }, + { + "test": "NAME0013", + "received": [ + ")a=bar" + ], + "sent": [ + { "name": ")a", "value": "bar" } + ] + }, + { + "test": "NAME0014", + "received": [ + "-a=bar" + ], + "sent": [ + { "name": "-a", "value": "bar" } + ] + }, + { + "test": "NAME0015", + "received": [ + "_a=bar" + ], + "sent": [ + { "name": "_a", "value": "bar" } + ] + }, + { + "test": "NAME0016", + "received": [ + "+=bar" + ], + "sent": [ + { "name": "+", "value": "bar" } + ] + }, + { + "test": "NAME0017", + "received": [ + "=a=bar" + ], + "sent": [] + }, + { + "test": "NAME0018", + "received": [ + "a =bar" + ], + "sent": [ + { "name": "a", "value": "bar" } + ] + }, + { + "test": "NAME0019", + "received": [ + "\"a=bar" + ], + "sent": [ + { "name": "\"a", "value": "bar" } + ] + }, + { + "test": "NAME0020", + "received": [ + "\"a=b\"=bar" + ], + "sent": [ + { "name": "\"a", "value": "b\"=bar" } + ] + }, + { + "test": "NAME0021", + "received": [ + "\"a=b\"=bar", + "\"a=qux" + ], + "sent": [ + { "name": "\"a", "value": "qux" } + ] + }, + { + "test": "NAME0022", + "received": [ + " foo=bar" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "NAME0023", + "received": [ + "foo;bar=baz" + ], + "sent": [] + }, + { + "test": "NAME0024", + "received": [ + "$Version=1; foo=bar" + ], + "sent": [ + { "name": "$Version", "value": "1" } + ] + }, + { + "test": "NAME0025", + "received": [ + "===a=bar" + ], + "sent": [] + }, + { + "test": "NAME0026", + "received": [ + "foo=bar " + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "NAME0027", + "received": [ + "foo=bar ;" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "NAME0028", + "received": [ + "=a" + ], + "sent": [] + }, + { + "test": "NAME0029", + "received": [ + "=" + ], + "sent": [] + }, + { + "test": "NAME0030", + "received": [ + "foo bar=baz" + ], + "sent": [ + { "name": "foo bar", "value": "baz" } + ] + }, + { + "test": "NAME0031", + "received": [ + "\"foo;bar\"=baz" + ], + "sent": [] + }, + { + "test": "NAME0032", + "received": [ + "\"foo\\\"bar;baz\"=qux" + ], + "sent": [] + }, + { + "test": "NAME0033", + "received": [ + "=foo=bar", + "aaa" + ], + "sent": [] + }, + { + "test": "OPTIONAL_DOMAIN0030", + "received": [ + "foo=bar; domain=" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0030", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "OPTIONAL_DOMAIN0041", + "received": [ + "foo=bar; domain=example.org; domain=" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0041", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "OPTIONAL_DOMAIN0042", + "received": [ + "foo=bar; domain=foo.example.org; domain=" + ], + "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0042", + "sent": [] + }, + { + "test": "OPTIONAL_DOMAIN0043", + "received": [ + "foo=bar; domain=foo.example.org; domain=" + ], + "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?optional-domain0043", + "sent": [] + }, + { + "test": "ORDERING0001", + "received": [ + "key=val0;", + "key=val1; path=/cookie-parser-result", + "key=val2; path=/", + "key=val3; path=/bar", + "key=val4; domain=.example.org", + "key=val5; domain=.example.org; path=/cookie-parser-result/foo" + ], + "sent-to": "/cookie-parser-result/foo/baz?ordering0001", + "sent": [ + { "name": "key", "value": "val5" }, + { "name": "key", "value": "val1" }, + { "name": "key", "value": "val2" }, + { "name": "key", "value": "val4" } + ] + }, + { + "test": "PATH0001", + "received": [ + "a=b; path=/", + "x=y; path=/cookie-parser-result" + ], + "sent": [ + { "name": "x", "value": "y" }, + { "name": "a", "value": "b" } + ] + }, + { + "test": "PATH0002", + "received": [ + "a=b; path=/cookie-parser-result", + "x=y; path=/" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "x", "value": "y" } + ] + }, + { + "test": "PATH0003", + "received": [ + "x=y; path=/", + "a=b; path=/cookie-parser-result" + ], + "sent": [ + { "name": "a", "value": "b" }, + { "name": "x", "value": "y" } + ] + }, + { + "test": "PATH0004", + "received": [ + "x=y; path=/cookie-parser-result", + "a=b; path=/" + ], + "sent": [ + { "name": "x", "value": "y" }, + { "name": "a", "value": "b" } + ] + }, + { + "test": "PATH0005", + "received": [ + "foo=bar; path=/cookie-parser-result/foo" + ], + "sent": [] + }, + { + "test": "PATH0006", + "received": [ + "foo=bar", + "foo=qux; path=/cookie-parser-result/foo" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0007", + "received": [ + "foo=bar; path=/cookie-parser-result/foo" + ], + "sent-to": "/cookie-parser-result/foo?path0007", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0008", + "received": [ + "foo=bar; path=/cookie-parser-result/foo" + ], + "sent-to": "/cookie-parser-result/bar?path0008", + "sent": [] + }, + { + "test": "PATH0009", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux" + ], + "sent-to": "/cookie-parser-result/foo?path0009", + "sent": [] + }, + { + "test": "PATH0010", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0010", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0011", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux" + ], + "sent-to": "/cookie-parser-result/bar/qux?path0011", + "sent": [] + }, + { + "test": "PATH0012", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux" + ], + "sent-to": "/cookie-parser-result/foo/baz?path0012", + "sent": [] + }, + { + "test": "PATH0013", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux/" + ], + "sent-to": "/cookie-parser-result/foo/baz?path0013", + "sent": [] + }, + { + "test": "PATH0014", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux/" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0014", + "sent": [] + }, + { + "test": "PATH0015", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux/" + ], + "sent-to": "/cookie-parser-result/foo/qux/?path0015", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0016", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0016", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0017", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/" + ], + "sent-to": "/cookie-parser-result/foo//qux?path0017", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0018", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/" + ], + "sent-to": "/cookie-parser-result/fooqux?path0018", + "sent": [] + }, + { + "test": "PATH0019", + "received": [ + "foo=bar; path" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0020", + "received": [ + "foo=bar; path=" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0021", + "received": [ + "foo=bar; path=/" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0022", + "received": [ + "foo=bar; path= /" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0023", + "received": [ + "foo=bar; Path=/cookie-PARSER-result" + ], + "sent": [] + }, + { + "test": "PATH0024", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux?" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0024", + "sent": [] + }, + { + "test": "PATH0025", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux#" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0025", + "sent": [] + }, + { + "test": "PATH0026", + "received": [ + "foo=bar; path=/cookie-parser-result/foo/qux;" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0026", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0027", + "received": [ + "foo=bar; path=\"/cookie-parser-result/foo/qux;\"" + ], + "sent-to": "/cookie-parser-result/foo/qux?path0027", + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0028", + "received": [ + "foo=bar; path=/cookie-parser-result/f%6Fo/bar" + ], + "sent-to": "/cookie-parser-result/foo/bar?path0028", + "sent": [] + }, + { + "test": "PATH0029", + "received": [ + "a=b; \tpath\t=\t/cookie-parser-result\t", + "x=y; \tpath\t=\t/book\t" + ], + "sent": [ + { "name": "a", "value": "b" } + ] + }, + { + "test": "PATH0030", + "received": [ + "foo=bar; path=/dog; path=" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "PATH0031", + "received": [ + "foo=bar; path=; path=/dog" + ], + "sent": [] + }, + { + "test": "PATH0032", + "received": [ + "foo=bar; path=/cookie-parser-result", + "foo=qux; path=/cookie-parser-result/" + ], + "sent-to": "/cookie-parser-result/dog?path0032", + "sent": [ + { "name": "foo", "value": "qux" }, + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "VALUE0001", + "received": [ + "foo= bar" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + }, + { + "test": "VALUE0002", + "received": [ + "foo=\"bar\"" + ], + "sent": [ + { "name": "foo", "value": "\"bar\"" } + ] + }, + { + "test": "VALUE0003", + "received": [ + "foo=\" bar \"" + ], + "sent": [ + { "name": "foo", "value": "\" bar \"" } + ] + }, + { + "test": "VALUE0004", + "received": [ + "foo=\"bar;baz\"" + ], + "sent": [ + { "name": "foo", "value": "\"bar" } + ] + }, + { + "test": "VALUE0005", + "received": [ + "foo=\"bar=baz\"" + ], + "sent": [ + { "name": "foo", "value": "\"bar=baz\"" } + ] + }, + { + "test": "VALUE0006", + "received": [ + "\tfoo\t=\tbar\t \t;\tttt" + ], + "sent": [ + { "name": "foo", "value": "bar" } + ] + } +] diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 2582b486..aca72b53 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -31,9 +31,9 @@ import {CookieJar, parseDate} from '../cookie' import url from 'url' -import parserData from '../../test/ietf_data/parser.json' -import bsdExampleDates from '../../test/ietf_data/dates/bsd-examples.json' -import exampleDates from '../../test/ietf_data/dates/examples.json' +import parserData from './data/parser' +import bsdExampleDates from './data/dates/bsd-examples' +import exampleDates from './data/dates/examples' describe('IETF http state tests', () => { describe('Set/get cookie tests', () => { diff --git a/lib/validators.ts b/lib/validators.ts index c0b1a276..b7a65a22 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -27,6 +27,8 @@ SOFTWARE. ************************************************************************************ */ "use strict"; +const toString = Object.prototype.toString + /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ export function isFunction(data: any): boolean { return typeof data === 'function'; @@ -80,11 +82,4 @@ export function validate(bool: boolean, cb?: any, options?: any): void { } } -export class ParameterError extends Error { - constructor(...params: any[]) { - super(...params); - if (Object.setPrototypeOf) { - Object.setPrototypeOf(this, ParameterError.prototype); - } - } -} +export class ParameterError extends Error {} diff --git a/package.json b/package.json index 3eac1c56..d85a9c32 100644 --- a/package.json +++ b/package.json @@ -82,20 +82,25 @@ "dist" ], "scripts": { + "build": "npm run clean && tsc", + "clean": "rm -rf dist", "version": "genversion lib/version.js && git add lib/version.js", - "test": "jest && tsc && vows test/*_test.js", + "test": "npm run test:ts && npm run test:legacy", + "test:ts": "jest", + "test:legacy": "npm run build -- --declaration false && vows test/*_test.js", + "typecheck": "tsc --noEmit", "cover": "jest --coverage", "eslint": "eslint --env node --ext .js .", "prettier": "prettier '**/*.{json,ts,yaml,md}'", "format": "npm run eslint -- --fix" }, "engines": { - "node": ">=6" + "node": ">=16" }, "devDependencies": { "@types/jest": "^27.4.0", - "@types/punycode": "^2.1.0", "@types/psl": "^1.1.0", + "@types/punycode": "^2.1.0", "async": "^2.6.2", "eslint": "^5.16.0", "eslint-config-prettier": "^4.2.0", @@ -105,7 +110,7 @@ "nyc": "^14.0.0", "prettier": "^1.17.0", "ts-jest": "^27.1.3", - "typescript": "4.5.5", + "typescript": "4.7.4", "vows": "^0.8.2" }, "dependencies": { diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 558cc36a..7f0e7a4d 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -89,7 +89,12 @@ vows "util usage in Cookie": { "custom inspect for Cookie still works": function() { const cookie = Cookie.parse("a=1; Domain=example.com; Path=/"); - assert.equal(cookie.inspect(), util.inspect(cookie)); + // The custom inspect uses Date.now(), so the two invocations cannot be directly compared, + // as "cAge" will not necessarily be the same value (sometimes 0ms, sometimes 1ms). + // assert.equal(cookie.inspect(), util.inspect(cookie)); + const expected = /^Cookie="a=1; Domain=example\.com; Path=\/; hostOnly=\?; aAge=\?; cAge=\dms"$/ + assert.match(cookie.inspect(), expected) + assert.match(util.inspect(cookie), expected) } }, "util usage in MemoryCookie": { diff --git a/tsconfig.json b/tsconfig.json index 1c859b64..5b5858c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,27 @@ { "compilerOptions": { - "experimentalDecorators": true, - "outDir": "./dist", - "allowJs": true, - "target": "es5", - "resolveJsonModule": true, - "esModuleInterop": true, + /* Language and Environment */ + "target": "ES2021", + "lib": ["ES2021"], + /* Modules */ + "module": "Node16", + "rootDir": "./lib/", + /* Emit */ "declaration": true, + "outDir": "./dist/", + "importsNotUsedAsValues": "error", + /* Interop Constraints */ + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + /* Type Checking */ "strict": true, - "noImplicitAny": true, - "noImplicitThis": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, - "importsNotUsedAsValues": "error", + "noImplicitOverride": true, "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": true - }, - "include": ["./lib/**/*"], - "exclude": ["./lib/**/*.spec.ts"] + "allowUnreachableCode": false + } } From 04d4dae7d9a77e83682ac1986faeb80b1c910521 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 17 Apr 2023 23:41:18 -0300 Subject: [PATCH 20/33] Run validations on v5 branch --- .github/workflows/integration.yaml | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index d6d1e8f4..49464b14 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -2,9 +2,9 @@ name: Actions-CI on: push: - branches: [master] + branches: [master, v5] pull_request: - branches: [master] + branches: [master, v5] jobs: build: @@ -23,3 +23,5 @@ jobs: - name: Install dependencies and test run: npm install - run: npm test + - run: npm eslint + - run: npm prettier diff --git a/package.json b/package.json index 824e7162..8d3ffa67 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "test:legacy": "npm run build -- --declaration false && vows test/*_test.js", "typecheck": "tsc --noEmit", "cover": "jest --coverage", - "eslint": "eslint --env node --ext .js .", + "eslint": "eslint --env node --ext .ts .", "prettier": "prettier '**/*.{json,ts,yaml,md}'", "format": "npm run eslint -- --fix" }, From a1528ae4d178907b3f0f0c708c0f34862fddd4be Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 17 Apr 2023 23:42:53 -0300 Subject: [PATCH 21/33] Run validations on v5 branch --- .github/workflows/integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 49464b14..547e3389 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -23,5 +23,5 @@ jobs: - name: Install dependencies and test run: npm install - run: npm test - - run: npm eslint - - run: npm prettier + - run: npm run eslint + - run: npm run prettier From 44bf4c7eb2b9bbe40b90f07be83cbb604fdeb932 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 17 Apr 2023 23:59:12 -0300 Subject: [PATCH 22/33] Run validations on v5 branch --- .github/workflows/integration.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 547e3389..a1e439b2 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -12,14 +12,15 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x] + node-version: [latest, lts, lts/-1] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + cache: npm - name: Install dependencies and test run: npm install - run: npm test From a31f2dcd98fa13cf748f2b906f483490c561fa3d Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Tue, 18 Apr 2023 00:00:44 -0300 Subject: [PATCH 23/33] Run validations on v5 branch --- .github/workflows/integration.yaml | 2 +- .gitignore | 1 - package-lock.json | 11504 +++++++++++++++++++++++++++ 3 files changed, 11505 insertions(+), 2 deletions(-) create mode 100644 package-lock.json diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index a1e439b2..d339d48a 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [latest, lts, lts/-1] + node-version: [latest, lts/*, lts/-1] steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index df02b1cc..a0fc24a8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ npm-debug.log docker-compose.override.yml .nyc_output coverage -package-lock.json .npm .config .bash_history diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..fc3f5781 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11504 @@ +{ + "name": "tough-cookie", + "version": "5.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tough-cookie", + "version": "5.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "url-parse": "^1.5.3" + }, + "devDependencies": { + "@types/jest": "^29", + "@types/psl": "^1", + "@types/punycode": "^2", + "@types/url-parse": "^1.4.8", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "@typescript-eslint/parser": "^5.57.0", + "async": "2.6.4", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "genversion": "^3.1.1", + "jest": "^29.5.0", + "prettier": "^2.8.7", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.9.5", + "vows": "^0.8.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", + "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/psl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.0.tgz", + "integrity": "sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==", + "dev": true + }, + "node_modules/@types/punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/url-parse": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.8.tgz", + "integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", + "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/type-utils": "5.58.0", + "@typescript-eslint/utils": "5.58.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz", + "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz", + "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz", + "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/utils": "5.58.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz", + "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz", + "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz", + "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz", + "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.58.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001470", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", + "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.341", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz", + "integrity": "sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz", + "integrity": "sha1-13ONpn48XwVcJNPhmqGu7QY8PoM=", + "dev": true, + "dependencies": { + "parents": "^1.0.1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/genversion": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/genversion/-/genversion-3.1.1.tgz", + "integrity": "sha512-/H861PMsihhjgX2qqhTN8egM11V04imhA+3JRFY3jjPua2Sy1NqaqqQPjSP8rdM9jZoKpFhVj9g3Fs9XPCjBYQ==", + "dev": true, + "dependencies": { + "commander": "^7.2.0", + "find-package": "^1.0.0" + }, + "bin": { + "genversion": "bin/genversion.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "dependencies": { + "path-platform": "~0.11.15" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", + "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vows": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.8.3.tgz", + "integrity": "sha512-PVIxa/ovXhrw5gA3mz6M+ZF3PHlqX4tutR2p/y9NWPAaFVKcWBE8b2ktfr0opQM/qFmcOVWKjSCJVjnYOvjXhw==", + "dev": true, + "dependencies": { + "diff": "^4.0.1", + "eyes": "~0.1.6", + "glob": "^7.1.2" + }, + "bin": { + "vows": "bin/vows" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true + }, + "@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "requires": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", + "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/node": { + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "@types/psl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.0.tgz", + "integrity": "sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==", + "dev": true + }, + "@types/punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g==", + "dev": true + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/url-parse": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.8.tgz", + "integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", + "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/type-utils": "5.58.0", + "@typescript-eslint/utils": "5.58.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz", + "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz", + "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz", + "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/utils": "5.58.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz", + "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz", + "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz", + "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz", + "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.58.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001470", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", + "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.341", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz", + "integrity": "sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true + }, + "espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz", + "integrity": "sha1-13ONpn48XwVcJNPhmqGu7QY8PoM=", + "dev": true, + "requires": { + "parents": "^1.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "genversion": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/genversion/-/genversion-3.1.1.tgz", + "integrity": "sha512-/H861PMsihhjgX2qqhTN8egM11V04imhA+3JRFY3jjPua2Sy1NqaqqQPjSP8rdM9jZoKpFhVj9g3Fs9XPCjBYQ==", + "dev": true, + "requires": { + "commander": "^7.2.0", + "find-package": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + }, + "pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.0.tgz", + "integrity": "sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "vows": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.8.3.tgz", + "integrity": "sha512-PVIxa/ovXhrw5gA3mz6M+ZF3PHlqX4tutR2p/y9NWPAaFVKcWBE8b2ktfr0opQM/qFmcOVWKjSCJVjnYOvjXhw==", + "dev": true, + "requires": { + "diff": "^4.0.1", + "eyes": "~0.1.6", + "glob": "^7.1.2" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} From df809b9e51f48f977877357405915a5460f90f89 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Tue, 18 Apr 2023 13:53:54 -0400 Subject: [PATCH 24/33] Extend prettier plugin, rather than config. --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 527b0a64..8ed11807 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended", //"plugin:@typescript-eslint/recommended-requiring-type-checking", - "prettier" + "plugin:prettier/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { From 8215a6e6d5c7ae078a3de894953922034bb12f40 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Tue, 18 Apr 2023 13:54:06 -0400 Subject: [PATCH 25/33] Apply lint fixes. --- lib/__tests__/cookieJar.spec.ts | 30 +- lib/__tests__/cookiePrefixes.spec.ts | 2 +- lib/__tests__/cookieSorting.spec.ts | 8 +- lib/__tests__/cookieToAndFromJson.spec.ts | 10 +- lib/__tests__/cookieToString.spec.ts | 4 +- lib/__tests__/data/dates/bsd-examples.ts | 384 ++-- lib/__tests__/data/dates/examples.ts | 104 +- lib/__tests__/data/parser.ts | 2342 ++++++++------------- lib/__tests__/ietf.spec.ts | 16 +- lib/__tests__/jarSerialization.spec.ts | 432 ++-- lib/__tests__/nodeUtilFallback.spec.ts | 10 +- lib/__tests__/parse.spec.ts | 4 +- lib/__tests__/regression.spec.ts | 2 +- lib/__tests__/removeAll.spec.ts | 71 +- lib/__tests__/sameSite.spec.ts | 2 +- lib/cookie.ts | 20 +- lib/utilHelper.ts | 1 - 17 files changed, 1462 insertions(+), 1980 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 53c170d0..17ca724e 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,14 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store } from "../cookie"; +import { + Cookie, + CookieJar, + MemoryCookieStore, + ParameterError, + SerializedCookieJar, + Store, +} from '../cookie' jest.useFakeTimers() @@ -66,7 +73,10 @@ describe('CookieJar', () => { cookie = await cookieJar.setCookie('foo=bar', 'http://example.com') }, syncStyle() { - const result = cookieJar.setCookieSync('foo=bar', 'http://example.com') + const result = cookieJar.setCookieSync( + 'foo=bar', + 'http://example.com', + ) if (result == null) { throw new Error('Result should not have been undefined') } @@ -368,7 +378,7 @@ describe('CookieJar', () => { cookies = cookieJar.getCookiesSync('http://example.com') }, }, - ()=> { + () => { expect(cookies).toEqual([ expect.objectContaining({ key: 'foo', @@ -947,7 +957,7 @@ describe('CookieJar', () => { syncStyle() { const result = cookieJar.serializeSync() if (!result) { - throw new Error("This should have been undefined") + throw new Error('This should have been undefined') } data = result }, @@ -1118,7 +1128,7 @@ describe('loose mode', () => { const cookieJar = new CookieJar(null, { looseMode: true }) const cookieJarAsJson = cookieJar.toJSON() if (!cookieJarAsJson) { - throw new Error("This should not have been undefined") + throw new Error('This should not have been undefined') } const clonedCookieJar = CookieJar.fromJSON(cookieJarAsJson) await clonedCookieJar.setCookie('FooBar', 'http://www.foonet.net') @@ -1249,7 +1259,7 @@ describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( ) } catch (e) { if (!(e instanceof Error)) { - throw new Error("This should be an error instance") + throw new Error('This should be an error instance') } expect(e.message).toBe( `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, @@ -1300,7 +1310,7 @@ describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( ) } catch (e) { if (!(e instanceof Error)) { - throw new Error("This should be an error instance") + throw new Error('This should be an error instance') } expect(e.message).toBe( `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, @@ -1348,7 +1358,7 @@ describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( ) } catch (e) { if (!(e instanceof Error)) { - throw new Error("This should be an error instance") + throw new Error('This should be an error instance') } expect(e.message).toBe( `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, @@ -1370,7 +1380,7 @@ describe.each(['local', 'example', 'invalid', 'localhost', 'test'])( ) } catch (e) { if (!(e instanceof Error)) { - throw new Error("This should be an error instance") + throw new Error('This should be an error instance') } expect(e.message).toBe( `Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`, @@ -1424,7 +1434,7 @@ function createCookie( ): Cookie { const cookie = Cookie.parse(cookieString) if (!cookie) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } if (options?.hostOnly) { cookie.hostOnly = options.hostOnly diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index b3d5cc46..e68b316a 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -1,4 +1,4 @@ -import { CookieJar, PrefixSecurityEnum } from "../cookie"; +import { CookieJar, PrefixSecurityEnum } from '../cookie' let cookieJar: CookieJar const insecureUrl = 'http://www.example.com' diff --git a/lib/__tests__/cookieSorting.spec.ts b/lib/__tests__/cookieSorting.spec.ts index a5291540..cd2cc4f1 100644 --- a/lib/__tests__/cookieSorting.spec.ts +++ b/lib/__tests__/cookieSorting.spec.ts @@ -9,7 +9,9 @@ describe('Cookie sorting', () => { const cookie2 = new Cookie() expect(typeof cookie1.creationIndex).toBe('number') expect(typeof cookie2.creationIndex).toBe('number') - expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex as number) + expect(cookie1.creationIndex).toBeLessThan( + cookie2.creationIndex as number, + ) }) it('should set the creation index during construction when creation time is provided', () => { @@ -19,7 +21,9 @@ describe('Cookie sorting', () => { expect(cookie1.creation).toEqual(cookie2.creation) expect(typeof cookie1.creationIndex).toBe('number') expect(typeof cookie2.creationIndex).toBe('number') - expect(cookie1.creationIndex).toBeLessThan(cookie2.creationIndex as number) + expect(cookie1.creationIndex).toBeLessThan( + cookie2.creationIndex as number, + ) }) it('should leave the creation index alone during setCookie', async () => { diff --git a/lib/__tests__/cookieToAndFromJson.spec.ts b/lib/__tests__/cookieToAndFromJson.spec.ts index bc88b68b..a29004ec 100644 --- a/lib/__tests__/cookieToAndFromJson.spec.ts +++ b/lib/__tests__/cookieToAndFromJson.spec.ts @@ -1,4 +1,4 @@ -import { Cookie } from "../cookie"; +import { Cookie } from '../cookie' jest.useFakeTimers() @@ -8,7 +8,7 @@ describe('Cookie.toJSON()', () => { 'alpha=beta; Domain=example.com; Path=/foo; Expires=Tue, 19 Jan 2038 03:14:07 GMT; HttpOnly', ) if (!cookie) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } expect(cookie.toJSON()).toEqual({ creation: new Date().toISOString(), @@ -44,8 +44,8 @@ describe('Cookie.fromJSON()', () => { path: '/foo', value: 'beta', lastAccessed: new Date(2000000000123), - }, - )) + }), + ) }) it('should be able to handle a null value deserialization', () => { @@ -60,7 +60,7 @@ describe('Cookie.fromJSON()', () => { }) const cookie = Cookie.fromJSON(json) if (!cookie) { - throw new Error("This should not be null") + throw new Error('This should not be null') } expect(cookie.expires).toBe('Infinity') expect(cookie.creation).toBe('Infinity') diff --git a/lib/__tests__/cookieToString.spec.ts b/lib/__tests__/cookieToString.spec.ts index 78142c5c..1b9dc46a 100644 --- a/lib/__tests__/cookieToString.spec.ts +++ b/lib/__tests__/cookieToString.spec.ts @@ -1,10 +1,10 @@ -import { Cookie } from "../cookie"; +import { Cookie } from '../cookie' describe('Cookie.toString()', () => { const parse = (cookieString: string): Cookie => { const cookie = Cookie.parse(cookieString) if (!cookie) { - throw new Error("This should have parsed") + throw new Error('This should have parsed') } return cookie } diff --git a/lib/__tests__/data/dates/bsd-examples.ts b/lib/__tests__/data/dates/bsd-examples.ts index 8ba721a9..a7daf2fb 100644 --- a/lib/__tests__/data/dates/bsd-examples.ts +++ b/lib/__tests__/data/dates/bsd-examples.ts @@ -1,168 +1,222 @@ export default [ { - "test": "Sat, 15-Apr-17 21:01:22 GMT", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Thu, 19-Apr-2007 16:00:00 GMT", - "expected": "Thu, 19 Apr 2007 16:00:00 GMT" - }, { - "test": "Wed, 25 Apr 2007 21:02:13 GMT", - "expected": "Wed, 25 Apr 2007 21:02:13 GMT" - }, { - "test": "Thu, 19/Apr\\2007 16:00:00 GMT", - "expected": "Thu, 19 Apr 2007 16:00:00 GMT" - }, { - "test": "Fri, 1 Jan 2010 01:01:50 GMT", - "expected": "Fri, 01 Jan 2010 01:01:50 GMT" - }, { - "test": "Wednesday, 1-Jan-2003 00:00:00 GMT", - "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { - "test": ", 1-Jan-2003 00:00:00 GMT", - "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { - "test": " 1-Jan-2003 00:00:00 GMT", - "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { - "test": "1-Jan-2003 00:00:00 GMT", - "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { - "test": "Wed,18-Apr-07 22:50:12 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "WillyWonka , 18-Apr-07 22:50:12 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "WillyWonka , 18-Apr-07 22:50:12", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "WillyWonka , 18-apr-07 22:50:12", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Mon, 18-Apr-1977 22:50:13 GMT", - "expected": "Mon, 18 Apr 1977 22:50:13 GMT" - }, { - "test": "Mon, 18-Apr-77 22:50:13 GMT", - "expected": "Mon, 18 Apr 1977 22:50:13 GMT" - }, { - "test": "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Partyday, 18- April-07 22:50:12", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Partyday, 18 - Apri-07 22:50:12", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Wednes, 1-Januar-2003 00:00:00 GMT", - "expected": "Wed, 01 Jan 2003 00:00:00 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 GMT-2", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 GMT BLAH", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 GMT-0400", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 DST", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 -0400", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 (hello there)", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 11:22:33", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 ::00 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 boink:z 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Sat, 15-Apr-17 91:22:33 21:01:22", - "expected": null - }, { - "test": "Thu Apr 18 22:50:12 2007 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "22:50:12 Thu Apr 18 2007 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Thu 22:50:12 Apr 18 2007 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Thu Apr 22:50:12 18 2007 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Thu Apr 18 22:50:12 2007 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Thu Apr 18 2007 22:50:12 GMT", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Thu Apr 18 2007 GMT 22:50:12", - "expected": "Wed, 18 Apr 2007 22:50:12 GMT" - }, { - "test": "Sat, 15-Apr-17 21:01:22 GMT", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15-Sat, Apr-17 21:01:22 GMT", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15-Sat, Apr 21:01:22 GMT 17", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15-Sat, Apr 21:01:22 GMT 2017", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15 Apr 21:01:22 2017", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15 17 Apr 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Apr 15 17 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "Apr 15 21:01:22 17", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "2017 April 15 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "15 April 2017 21:01:22", - "expected": "Sat, 15 Apr 2017 21:01:22 GMT" - }, { - "test": "98 April 17 21:01:22", - "expected": null - }, { - "test": "Thu, 012-Aug-2008 20:49:07 GMT", - "expected": null - }, { - "test": "Thu, 12-Aug-31841 20:49:07 GMT", - "expected": null - }, { - "test": "Thu, 12-Aug-9999999999 20:49:07 GMT", - "expected": null - }, { - "test": "Thu, 999999999999-Aug-2007 20:49:07 GMT", - "expected": null - }, { - "test": "Thu, 12-Aug-2007 20:61:99999999999 GMT", - "expected": null - }, { - "test": "IAintNoDateFool", - "expected": null - } + test: 'Sat, 15-Apr-17 21:01:22 GMT', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Thu, 19-Apr-2007 16:00:00 GMT', + expected: 'Thu, 19 Apr 2007 16:00:00 GMT', + }, + { + test: 'Wed, 25 Apr 2007 21:02:13 GMT', + expected: 'Wed, 25 Apr 2007 21:02:13 GMT', + }, + { + test: 'Thu, 19/Apr\\2007 16:00:00 GMT', + expected: 'Thu, 19 Apr 2007 16:00:00 GMT', + }, + { + test: 'Fri, 1 Jan 2010 01:01:50 GMT', + expected: 'Fri, 01 Jan 2010 01:01:50 GMT', + }, + { + test: 'Wednesday, 1-Jan-2003 00:00:00 GMT', + expected: 'Wed, 01 Jan 2003 00:00:00 GMT', + }, + { + test: ', 1-Jan-2003 00:00:00 GMT', + expected: 'Wed, 01 Jan 2003 00:00:00 GMT', + }, + { + test: ' 1-Jan-2003 00:00:00 GMT', + expected: 'Wed, 01 Jan 2003 00:00:00 GMT', + }, + { + test: '1-Jan-2003 00:00:00 GMT', + expected: 'Wed, 01 Jan 2003 00:00:00 GMT', + }, + { + test: 'Wed,18-Apr-07 22:50:12 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'WillyWonka , 18-Apr-07 22:50:12 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'WillyWonka , 18-Apr-07 22:50:12', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'WillyWonka , 18-apr-07 22:50:12', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Mon, 18-Apr-1977 22:50:13 GMT', + expected: 'Mon, 18 Apr 1977 22:50:13 GMT', + }, + { + test: 'Mon, 18-Apr-77 22:50:13 GMT', + expected: 'Mon, 18 Apr 1977 22:50:13 GMT', + }, + { + test: '"Sat, 15-Apr-17\\"21:01:22\\"GMT"', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Partyday, 18- April-07 22:50:12', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Partyday, 18 - Apri-07 22:50:12', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Wednes, 1-Januar-2003 00:00:00 GMT', + expected: 'Wed, 01 Jan 2003 00:00:00 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 GMT-2', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 GMT BLAH', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 GMT-0400', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 DST', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 -0400', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 (hello there)', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 11:22:33', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 ::00 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 boink:z 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Sat, 15-Apr-17 91:22:33 21:01:22', + expected: null, + }, + { + test: 'Thu Apr 18 22:50:12 2007 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: '22:50:12 Thu Apr 18 2007 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Thu 22:50:12 Apr 18 2007 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Thu Apr 22:50:12 18 2007 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Thu Apr 18 22:50:12 2007 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Thu Apr 18 2007 22:50:12 GMT', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Thu Apr 18 2007 GMT 22:50:12', + expected: 'Wed, 18 Apr 2007 22:50:12 GMT', + }, + { + test: 'Sat, 15-Apr-17 21:01:22 GMT', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15-Sat, Apr-17 21:01:22 GMT', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15-Sat, Apr 21:01:22 GMT 17', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15-Sat, Apr 21:01:22 GMT 2017', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15 Apr 21:01:22 2017', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15 17 Apr 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Apr 15 17 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: 'Apr 15 21:01:22 17', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '2017 April 15 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '15 April 2017 21:01:22', + expected: 'Sat, 15 Apr 2017 21:01:22 GMT', + }, + { + test: '98 April 17 21:01:22', + expected: null, + }, + { + test: 'Thu, 012-Aug-2008 20:49:07 GMT', + expected: null, + }, + { + test: 'Thu, 12-Aug-31841 20:49:07 GMT', + expected: null, + }, + { + test: 'Thu, 12-Aug-9999999999 20:49:07 GMT', + expected: null, + }, + { + test: 'Thu, 999999999999-Aug-2007 20:49:07 GMT', + expected: null, + }, + { + test: 'Thu, 12-Aug-2007 20:61:99999999999 GMT', + expected: null, + }, + { + test: 'IAintNoDateFool', + expected: null, + }, ] diff --git a/lib/__tests__/data/dates/examples.ts b/lib/__tests__/data/dates/examples.ts index eba15640..2ce3b762 100644 --- a/lib/__tests__/data/dates/examples.ts +++ b/lib/__tests__/data/dates/examples.ts @@ -1,48 +1,62 @@ export default [ { - "test": "Mon, 10-Dec-2007 17:02:24 GMT", - "expected": "Mon, 10 Dec 2007 17:02:24 GMT" - }, { - "test": "Wed, 09 Dec 2009 16:27:23 GMT", - "expected": "Wed, 09 Dec 2009 16:27:23 GMT" - }, { - "test": "Thursday, 01-Jan-1970 00:00:00 GMT", - "expected": "Thu, 01 Jan 1970 00:00:00 GMT" - }, { - "test": "Mon Dec 10 16:32:30 2007 GMT", - "expected": "Mon, 10 Dec 2007 16:32:30 GMT" - }, { - "test": "Wednesday, 01-Jan-10 00:00:00 GMT", - "expected": "Fri, 01 Jan 2010 00:00:00 GMT" - }, { - "test": "Mon, 10-Dec-07 20:35:03 GMT", - "expected": "Mon, 10 Dec 2007 20:35:03 GMT" - }, { - "test": "Wed, 1 Jan 2020 00:00:00 GMT", - "expected": "Wed, 01 Jan 2020 00:00:00 GMT" - }, { - "test": "Saturday, 8-Dec-2012 21:24:09 GMT", - "expected": "Sat, 08 Dec 2012 21:24:09 GMT" - }, { - "test": "Thu, 31 Dec 23:55:55 2037 GMT", - "expected": "Thu, 31 Dec 2037 23:55:55 GMT" - }, { - "test": "Sun, 9 Dec 2012 13:42:05 GMT", - "expected": "Sun, 09 Dec 2012 13:42:05 GMT" - }, { - "test": "Wed Dec 12 2007 08:44:07 GMT-0500 (EST)", - "expected": "Wed, 12 Dec 2007 08:44:07 GMT" - }, { - "test": "Mon, 01-Jan-2011 00: 00:00 GMT", - "expected": null - }, { - "test": "Sun, 1-Jan-1995 00:00:00 GMT", - "expected": "Sun, 01 Jan 1995 00:00:00 GMT" - }, { - "test": "Wednesday, 01-Jan-10 0:0:00 GMT", - "expected": "Fri, 01 Jan 2010 00:00:00 GMT" - }, { - "test": "Thu, 10 Dec 2009 13:57:2 GMT", - "expected": "Thu, 10 Dec 2009 13:57:02 GMT" - } + test: 'Mon, 10-Dec-2007 17:02:24 GMT', + expected: 'Mon, 10 Dec 2007 17:02:24 GMT', + }, + { + test: 'Wed, 09 Dec 2009 16:27:23 GMT', + expected: 'Wed, 09 Dec 2009 16:27:23 GMT', + }, + { + test: 'Thursday, 01-Jan-1970 00:00:00 GMT', + expected: 'Thu, 01 Jan 1970 00:00:00 GMT', + }, + { + test: 'Mon Dec 10 16:32:30 2007 GMT', + expected: 'Mon, 10 Dec 2007 16:32:30 GMT', + }, + { + test: 'Wednesday, 01-Jan-10 00:00:00 GMT', + expected: 'Fri, 01 Jan 2010 00:00:00 GMT', + }, + { + test: 'Mon, 10-Dec-07 20:35:03 GMT', + expected: 'Mon, 10 Dec 2007 20:35:03 GMT', + }, + { + test: 'Wed, 1 Jan 2020 00:00:00 GMT', + expected: 'Wed, 01 Jan 2020 00:00:00 GMT', + }, + { + test: 'Saturday, 8-Dec-2012 21:24:09 GMT', + expected: 'Sat, 08 Dec 2012 21:24:09 GMT', + }, + { + test: 'Thu, 31 Dec 23:55:55 2037 GMT', + expected: 'Thu, 31 Dec 2037 23:55:55 GMT', + }, + { + test: 'Sun, 9 Dec 2012 13:42:05 GMT', + expected: 'Sun, 09 Dec 2012 13:42:05 GMT', + }, + { + test: 'Wed Dec 12 2007 08:44:07 GMT-0500 (EST)', + expected: 'Wed, 12 Dec 2007 08:44:07 GMT', + }, + { + test: 'Mon, 01-Jan-2011 00: 00:00 GMT', + expected: null, + }, + { + test: 'Sun, 1-Jan-1995 00:00:00 GMT', + expected: 'Sun, 01 Jan 1995 00:00:00 GMT', + }, + { + test: 'Wednesday, 01-Jan-10 0:0:00 GMT', + expected: 'Fri, 01 Jan 2010 00:00:00 GMT', + }, + { + test: 'Thu, 10 Dec 2009 13:57:2 GMT', + expected: 'Thu, 10 Dec 2009 13:57:02 GMT', + }, ] diff --git a/lib/__tests__/data/parser.ts b/lib/__tests__/data/parser.ts index 4d67a075..7618a7bf 100644 --- a/lib/__tests__/data/parser.ts +++ b/lib/__tests__/data/parser.ts @@ -1,1959 +1,1333 @@ export default [ { - "test": "0001", - "received": [ - "foo=bar" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0001', + received: ['foo=bar'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0002", - "received": [ - "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0002', + received: ['foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0003", - "received": [ - "foo=bar; Expires=Fri, 07 Aug 2007 08:04:19 GMT", - "foo2=bar2; Expires=Fri, 07 Aug 9999 08:04:19 GMT" + test: '0003', + received: [ + 'foo=bar; Expires=Fri, 07 Aug 2007 08:04:19 GMT', + 'foo2=bar2; Expires=Fri, 07 Aug 9999 08:04:19 GMT', ], - "sent": [ - { "name": "foo2", "value": "bar2" } - ] + sent: [{ name: 'foo2', value: 'bar2' }], }, { - "test": "0004", - "received": [ - "foo" - ], - "sent": [] + test: '0004', + received: ['foo'], + sent: [], }, { - "test": "0005", - "received": [ - "foo=bar; max-age=10000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0005', + received: ['foo=bar; max-age=10000;'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0006", - "received": [ - "foo=bar; max-age=0;" - ], - "sent": [] + test: '0006', + received: ['foo=bar; max-age=0;'], + sent: [], }, { - "test": "0007", - "received": [ - "foo=bar; version=1;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0007', + received: ['foo=bar; version=1;'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0008", - "received": [ - "foo=bar; version=1000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0008', + received: ['foo=bar; version=1000;'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0009", - "received": [ - "foo=bar; customvalue=1000;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0009', + received: ['foo=bar; customvalue=1000;'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0010", - "received": [ - "foo=bar; secure;" - ], - "sent": [] + test: '0010', + received: ['foo=bar; secure;'], + sent: [], }, { - "test": "0011", - "received": [ - "foo=bar; customvalue=\"1000 or more\";" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0011', + received: ['foo=bar; customvalue="1000 or more";'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0012", - "received": [ - "foo=bar; customvalue=\"no trailing semicolon\"" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: '0012', + received: ['foo=bar; customvalue="no trailing semicolon"'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "0013", - "received": [ - "foo=bar", - "foo=qux" - ], - "sent": [ - { "name": "foo", "value": "qux" } - ] + test: '0013', + received: ['foo=bar', 'foo=qux'], + sent: [{ name: 'foo', value: 'qux' }], }, { - "test": "0014", - "received": [ - "foo1=bar", - "foo2=qux" + test: '0014', + received: ['foo1=bar', 'foo2=qux'], + sent: [ + { name: 'foo1', value: 'bar' }, + { name: 'foo2', value: 'qux' }, ], - "sent": [ - { "name": "foo1", "value": "bar" }, - { "name": "foo2", "value": "qux" } - ] }, { - "test": "0015", - "received": [ - "a=b", - "z=y" + test: '0015', + received: ['a=b', 'z=y'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'z', value: 'y' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "z", "value": "y" } - ] }, { - "test": "0016", - "received": [ - "z=y", - "a=b" + test: '0016', + received: ['z=y', 'a=b'], + sent: [ + { name: 'z', value: 'y' }, + { name: 'a', value: 'b' }, ], - "sent": [ - { "name": "z", "value": "y" }, - { "name": "a", "value": "b" } - ] }, { - "test": "0017", - "received": [ - "z=y, a=b" - ], - "sent": [ - { "name": "z", "value": "y, a=b" } - ] + test: '0017', + received: ['z=y, a=b'], + sent: [{ name: 'z', value: 'y, a=b' }], }, { - "test": "0018", - "received": [ - "z=y; foo=bar, a=b" - ], - "sent": [ - { "name": "z", "value": "y" } - ] + test: '0018', + received: ['z=y; foo=bar, a=b'], + sent: [{ name: 'z', value: 'y' }], }, { - "test": "0019", - "received": [ - "foo=b;max-age=3600, c=d;path=/" - ], - "sent": [ - { "name": "foo", "value": "b" } - ] + test: '0019', + received: ['foo=b;max-age=3600, c=d;path=/'], + sent: [{ name: 'foo', value: 'b' }], }, { - "test": "0020", - "received": [ - "a=b", - "=", - "c=d" + test: '0020', + received: ['a=b', '=', 'c=d'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'c', value: 'd' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "c", "value": "d" } - ] }, { - "test": "0021", - "received": [ - "a=b", - "=x", - "c=d" + test: '0021', + received: ['a=b', '=x', 'c=d'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'c', value: 'd' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "c", "value": "d" } - ] }, { - "test": "0022", - "received": [ - "a=b", - "x=", - "c=d" + test: '0022', + received: ['a=b', 'x=', 'c=d'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'x', value: '' }, + { name: 'c', value: 'd' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "x", "value": "" }, - { "name": "c", "value": "d" } - ] }, { - "test": "0023", - "received": [ - "foo", - "" - ], - "sent": [] + test: '0023', + received: ['foo', ''], + sent: [], }, { - "test": "0024", - "received": [ - "foo", - "=" - ], - "sent": [] + test: '0024', + received: ['foo', '='], + sent: [], }, { - "test": "0025", - "received": [ - "foo", - "; bar" - ], - "sent": [] + test: '0025', + received: ['foo', '; bar'], + sent: [], }, { - "test": "0026", - "received": [ - "foo", - " " - ], - "sent": [] + test: '0026', + received: ['foo', ' '], + sent: [], }, { - "test": "0027", - "received": [ - "foo", - "bar" - ], - "sent": [] + test: '0027', + received: ['foo', 'bar'], + sent: [], }, { - "test": "0028", - "received": [ - "foo", - "\t" - ], - "sent": [] + test: '0028', + received: ['foo', '\t'], + sent: [], }, { - "test": "ATTRIBUTE0001", - "received": [ - "foo=bar; Secure" - ], - "sent": [] + test: 'ATTRIBUTE0001', + received: ['foo=bar; Secure'], + sent: [], }, { - "test": "ATTRIBUTE0002", - "received": [ - "foo=bar; seCURe" - ], - "sent": [] + test: 'ATTRIBUTE0002', + received: ['foo=bar; seCURe'], + sent: [], }, { - "test": "ATTRIBUTE0003", - "received": [ - "foo=bar; \"Secure\"" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0003', + received: ['foo=bar; "Secure"'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0004", - "received": [ - "foo=bar; Secure=" - ], - "sent": [] + test: 'ATTRIBUTE0004', + received: ['foo=bar; Secure='], + sent: [], }, { - "test": "ATTRIBUTE0005", - "received": [ - "foo=bar; Secure=aaaa" - ], - "sent": [] + test: 'ATTRIBUTE0005', + received: ['foo=bar; Secure=aaaa'], + sent: [], }, { - "test": "ATTRIBUTE0006", - "received": [ - "foo=bar; Secure qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0006', + received: ['foo=bar; Secure qux'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0007", - "received": [ - "foo=bar; Secure =aaaaa" - ], - "sent": [] + test: 'ATTRIBUTE0007', + received: ['foo=bar; Secure =aaaaa'], + sent: [], }, { - "test": "ATTRIBUTE0008", - "received": [ - "foo=bar; Secure= aaaaa" - ], - "sent": [] + test: 'ATTRIBUTE0008', + received: ['foo=bar; Secure= aaaaa'], + sent: [], }, { - "test": "ATTRIBUTE0009", - "received": [ - "foo=bar; Secure; qux" - ], - "sent": [] + test: 'ATTRIBUTE0009', + received: ['foo=bar; Secure; qux'], + sent: [], }, { - "test": "ATTRIBUTE0010", - "received": [ - "foo=bar; Secure;qux" - ], - "sent": [] + test: 'ATTRIBUTE0010', + received: ['foo=bar; Secure;qux'], + sent: [], }, { - "test": "ATTRIBUTE0011", - "received": [ - "foo=bar; Secure ; qux" - ], - "sent": [] + test: 'ATTRIBUTE0011', + received: ['foo=bar; Secure ; qux'], + sent: [], }, { - "test": "ATTRIBUTE0012", - "received": [ - "foo=bar; Secure" - ], - "sent": [] + test: 'ATTRIBUTE0012', + received: ['foo=bar; Secure'], + sent: [], }, { - "test": "ATTRIBUTE0013", - "received": [ - "foo=bar; Secure ;" - ], - "sent": [] + test: 'ATTRIBUTE0013', + received: ['foo=bar; Secure ;'], + sent: [], }, { - "test": "ATTRIBUTE0014", - "received": [ - "foo=bar; Path" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0014', + received: ['foo=bar; Path'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0015", - "received": [ - "foo=bar; Path=" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0015', + received: ['foo=bar; Path='], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0016", - "received": [ - "foo=bar; Path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0016', + received: ['foo=bar; Path=/'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0017", - "received": [ - "foo=bar; Path=/qux" - ], - "sent": [] + test: 'ATTRIBUTE0017', + received: ['foo=bar; Path=/qux'], + sent: [], }, { - "test": "ATTRIBUTE0018", - "received": [ - "foo=bar; Path =/qux" - ], - "sent": [] + test: 'ATTRIBUTE0018', + received: ['foo=bar; Path =/qux'], + sent: [], }, { - "test": "ATTRIBUTE0019", - "received": [ - "foo=bar; Path= /qux" - ], - "sent": [] + test: 'ATTRIBUTE0019', + received: ['foo=bar; Path= /qux'], + sent: [], }, { - "test": "ATTRIBUTE0020", - "received": [ - "foo=bar; Path=/qux ; taz" - ], - "sent": [] + test: 'ATTRIBUTE0020', + received: ['foo=bar; Path=/qux ; taz'], + sent: [], }, { - "test": "ATTRIBUTE0021", - "received": [ - "foo=bar; Path=/qux; Path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0021', + received: ['foo=bar; Path=/qux; Path=/'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0022", - "received": [ - "foo=bar; Path=/; Path=/qux" - ], - "sent": [] + test: 'ATTRIBUTE0022', + received: ['foo=bar; Path=/; Path=/qux'], + sent: [], }, { - "test": "ATTRIBUTE0023", - "received": [ - "foo=bar; Path=/qux; Path=/cookie-parser-result" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'ATTRIBUTE0023', + received: ['foo=bar; Path=/qux; Path=/cookie-parser-result'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "ATTRIBUTE0024", - "received": [ - "foo=bar; Path=/cookie-parser-result; Path=/qux" - ], - "sent": [] + test: 'ATTRIBUTE0024', + received: ['foo=bar; Path=/cookie-parser-result; Path=/qux'], + sent: [], }, { - "test": "ATTRIBUTE0025", - "received": [ - "foo=bar; qux; Secure" - ], - "sent": [] + test: 'ATTRIBUTE0025', + received: ['foo=bar; qux; Secure'], + sent: [], }, { - "test": "ATTRIBUTE0026", - "received": [ - "foo=bar; qux=\"aaa;bbb\"; Secure" - ], - "sent": [] + test: 'ATTRIBUTE0026', + received: ['foo=bar; qux="aaa;bbb"; Secure'], + sent: [], }, { - "test": "CHARSET0001", - "received": [ - "foo=\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + test: 'CHARSET0001', + received: [ + 'foo=\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c', + ], + sent: [ + { + name: 'foo', + value: + '\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c', + }, ], - "sent": [ - { "name": "foo", "value": "\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } - ] }, { - "test": "CHARSET0002", - "received": [ - "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" + test: 'CHARSET0002', + received: [ + '\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c', + ], + sent: [ + { + name: '\u6625\u8282\u56de', + value: '\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c', + }, ], - "sent": [ - { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c" } - ] }, { - "test": "CHARSET0003", - "received": [ - "\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0; \u5b8c\u5168\u624b\u518c" + test: 'CHARSET0003', + received: [ + '\u6625\u8282\u56de=\u5bb6\u8def\u00b7\u6625\u8fd0; \u5b8c\u5168\u624b\u518c', + ], + sent: [ + { name: '\u6625\u8282\u56de', value: '\u5bb6\u8def\u00b7\u6625\u8fd0' }, ], - "sent": [ - { "name": "\u6625\u8282\u56de", "value": "\u5bb6\u8def\u00b7\u6625\u8fd0" } - ] }, { - "test": "CHARSET0004", - "received": [ - "foo=\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" + test: 'CHARSET0004', + received: [ + 'foo="\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c"', + ], + sent: [ + { + name: 'foo', + value: + '"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c"', + }, ], - "sent": [ - { "name": "foo", "value": "\"\u6625\u8282\u56de\u5bb6\u8def\u00b7\u6625\u8fd0\u5b8c\u5168\u624b\u518c\"" } - ] }, { - "test": "CHROMIUM0001", - "received": [ - "a=b" - ], - "sent": [ - { "name": "a", "value": "b" } - ] + test: 'CHROMIUM0001', + received: ['a=b'], + sent: [{ name: 'a', value: 'b' }], }, { - "test": "CHROMIUM0002", - "received": [ - "aBc=\"zzz \" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \"" } - ] + test: 'CHROMIUM0002', + received: ['aBc="zzz " ;'], + sent: [{ name: 'aBc', value: '"zzz "' }], }, { - "test": "CHROMIUM0003", - "received": [ - "aBc=\"zzz \" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \"" } - ] + test: 'CHROMIUM0003', + received: ['aBc="zzz " ;'], + sent: [{ name: 'aBc', value: '"zzz "' }], }, { - "test": "CHROMIUM0004", - "received": [ - "aBc=\"zz;pp\" ; ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zz" } - ] + test: 'CHROMIUM0004', + received: ['aBc="zz;pp" ; ;'], + sent: [{ name: 'aBc', value: '"zz' }], }, { - "test": "CHROMIUM0005", - "received": [ - "aBc=\"zz ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zz" } - ] + test: 'CHROMIUM0005', + received: ['aBc="zz ;'], + sent: [{ name: 'aBc', value: '"zz' }], }, { - "test": "CHROMIUM0006", - "received": [ - "aBc=\"zzz \" \"ppp\" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \" \"ppp\"" } - ] + test: 'CHROMIUM0006', + received: ['aBc="zzz " "ppp" ;'], + sent: [{ name: 'aBc', value: '"zzz " "ppp"' }], }, { - "test": "CHROMIUM0007", - "received": [ - "aBc=\"zzz \" \"ppp\" ;" - ], - "sent": [ - { "name": "aBc", "value": "\"zzz \" \"ppp\"" } - ] + test: 'CHROMIUM0007', + received: ['aBc="zzz " "ppp" ;'], + sent: [{ name: 'aBc', value: '"zzz " "ppp"' }], }, { - "test": "CHROMIUM0008", - "received": [ - "aBc=A\"B ;" - ], - "sent": [ - { "name": "aBc", "value": "A\"B" } - ] + test: 'CHROMIUM0008', + received: ['aBc=A"B ;'], + sent: [{ name: 'aBc', value: 'A"B' }], }, { - "test": "CHROMIUM0009", - "received": [ - "BLAHHH; path=/;" - ], - "sent": [] + test: 'CHROMIUM0009', + received: ['BLAHHH; path=/;'], + sent: [], }, { - "test": "CHROMIUM0010", - "received": [ - "\"BLA\\\"HHH\"; path=/;" - ], - "sent": [] + test: 'CHROMIUM0010', + received: ['"BLA\\"HHH"; path=/;'], + sent: [], }, { - "test": "CHROMIUM0011", - "received": [ - "a=\"B" - ], - "sent": [ - { "name": "a", "value": "\"B" } - ] + test: 'CHROMIUM0011', + received: ['a="B'], + sent: [{ name: 'a', value: '"B' }], }, { - "test": "CHROMIUM0012", - "received": [ - "=ABC" - ], - "sent": [] + test: 'CHROMIUM0012', + received: ['=ABC'], + sent: [], }, { - "test": "CHROMIUM0013", - "received": [ - "ABC=; path = /" - ], - "sent": [ - { "name": "ABC", "value": "" } - ] + test: 'CHROMIUM0013', + received: ['ABC=; path = /'], + sent: [{ name: 'ABC', value: '' }], }, { - "test": "CHROMIUM0014", - "received": [ - " A = BC ;foo;;; bar" - ], - "sent": [ - { "name": "A", "value": "BC" } - ] + test: 'CHROMIUM0014', + received: [' A = BC ;foo;;; bar'], + sent: [{ name: 'A', value: 'BC' }], }, { - "test": "CHROMIUM0015", - "received": [ - " A=== BC ;foo;;; bar" - ], - "sent": [ - { "name": "A", "value": "== BC" } - ] + test: 'CHROMIUM0015', + received: [' A=== BC ;foo;;; bar'], + sent: [{ name: 'A', value: '== BC' }], }, { - "test": "CHROMIUM0016", - "received": [ - "foo=\"zohNumRKgI0oxyhSsV3Z7D\" ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " + test: 'CHROMIUM0016', + received: [ + 'foo="zohNumRKgI0oxyhSsV3Z7D" ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; ', ], - "sent": [ - { "name": "foo", "value": "\"zohNumRKgI0oxyhSsV3Z7D\"" } - ] + sent: [{ name: 'foo', value: '"zohNumRKgI0oxyhSsV3Z7D"' }], }, { - "test": "CHROMIUM0017", - "received": [ - "foo=zohNumRKgI0oxyhSsV3Z7D ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; " + test: 'CHROMIUM0017', + received: [ + 'foo=zohNumRKgI0oxyhSsV3Z7D ; expires=Sun, 18-Apr-2027 21:06:29 GMT ; path=/ ; ', ], - "sent": [ - { "name": "foo", "value": "zohNumRKgI0oxyhSsV3Z7D" } - ] + sent: [{ name: 'foo', value: 'zohNumRKgI0oxyhSsV3Z7D' }], }, { - "test": "CHROMIUM0018", - "received": [ - " " - ], - "sent": [] + test: 'CHROMIUM0018', + received: [' '], + sent: [], }, { - "test": "CHROMIUM0019", - "received": [ - "a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + test: 'CHROMIUM0019', + received: [ + 'a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + ], + sent: [ + { + name: 'a', + value: + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + }, ], - "sent": [ - { "name": "a", "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } - ] }, { - "test": "CHROMIUM0021", - "received": [ - "" - ], - "sent": [] + test: 'CHROMIUM0021', + received: [''], + sent: [], }, { - "test": "COMMA0001", - "received": [ - "foo=bar, baz=qux" - ], - "sent": [ - { "name": "foo", "value": "bar, baz=qux" } - ] + test: 'COMMA0001', + received: ['foo=bar, baz=qux'], + sent: [{ name: 'foo', value: 'bar, baz=qux' }], }, { - "test": "COMMA0002", - "received": [ - "foo=\"bar, baz=qux\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar, baz=qux\"" } - ] + test: 'COMMA0002', + received: ['foo="bar, baz=qux"'], + sent: [{ name: 'foo', value: '"bar, baz=qux"' }], }, { - "test": "COMMA0003", - "received": [ - "foo=bar; b,az=qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'COMMA0003', + received: ['foo=bar; b,az=qux'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "COMMA0004", - "received": [ - "foo=bar; baz=q,ux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'COMMA0004', + received: ['foo=bar; baz=q,ux'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "COMMA0005", - "received": [ - "foo=bar; Max-Age=50,399" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'COMMA0005', + received: ['foo=bar; Max-Age=50,399'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "COMMA0006", - "received": [ - "foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'COMMA0006', + received: ['foo=bar; Expires=Fri, 07 Aug 9999 08:04:19 GMT'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "COMMA0007", - "received": [ - "foo=bar; Expires=Fri 07 Aug 9999 08:04:19 GMT, baz=qux" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'COMMA0007', + received: ['foo=bar; Expires=Fri 07 Aug 9999 08:04:19 GMT, baz=qux'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DISABLED_CHROMIUM0020", - "received": [ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + test: 'DISABLED_CHROMIUM0020', + received: [ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', ], - "sent": [] + sent: [], }, { - "test": "DISABLED_CHROMIUM0022", - "received": [ - "AAA=BB\u0000ZYX" - ], - "sent": [ - { "name": "AAA", "value": "BB" } - ] + test: 'DISABLED_CHROMIUM0022', + received: ['AAA=BB\u0000ZYX'], + sent: [{ name: 'AAA', value: 'BB' }], }, { - "test": "DISABLED_CHROMIUM0023", - "received": [ - "AAA=BB\rZYX" - ], - "sent": [ - { "name": "AAA", "value": "BB" } - ] + test: 'DISABLED_CHROMIUM0023', + received: ['AAA=BB\rZYX'], + sent: [{ name: 'AAA', value: 'BB' }], }, { - "test": "DISABLED_PATH0029", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/bar" - ], - "sent-to": "/cookie-parser-result/f%6Fo/bar?disabled-path0029", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DISABLED_PATH0029', + received: ['foo=bar; path=/cookie-parser-result/foo/bar'], + 'sent-to': '/cookie-parser-result/f%6Fo/bar?disabled-path0029', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0001", - "received": [ - "foo=bar; domain=home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0001", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0001', + received: ['foo=bar; domain=home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0001', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0002", - "received": [ - "foo=bar; domain=home.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0002", - "sent": [] + test: 'DOMAIN0002', + received: ['foo=bar; domain=home.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0002', + sent: [], }, { - "test": "DOMAIN0003", - "received": [ - "foo=bar; domain=.home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0003", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0003', + received: ['foo=bar; domain=.home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0003', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0004", - "received": [ - "foo=bar; domain=home.example.org" - ], - "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0004", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0004', + received: ['foo=bar; domain=home.example.org'], + 'sent-to': + 'http://subdomain.home.example.org:8888/cookie-parser-result?domain0004', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0005", - "received": [ - "foo=bar; domain=.home.example.org" - ], - "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0005", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0005', + received: ['foo=bar; domain=.home.example.org'], + 'sent-to': + 'http://subdomain.home.example.org:8888/cookie-parser-result?domain0005', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0006", - "received": [ - "foo=bar; domain=.home.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0006", - "sent": [] + test: 'DOMAIN0006', + received: ['foo=bar; domain=.home.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0006', + sent: [], }, { - "test": "DOMAIN0007", - "received": [ - "foo=bar; domain=sibling.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0007", - "sent": [] + test: 'DOMAIN0007', + received: ['foo=bar; domain=sibling.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0007', + sent: [], }, { - "test": "DOMAIN0008", - "received": [ - "foo=bar; domain=.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0008", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0008', + received: ['foo=bar; domain=.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0008', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0009", - "received": [ - "foo=bar; domain=example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0009", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0009', + received: ['foo=bar; domain=example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0009', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0010", - "received": [ - "foo=bar; domain=..home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0010", - "sent": [] + test: 'DOMAIN0010', + received: ['foo=bar; domain=..home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0010', + sent: [], }, { - "test": "DOMAIN0011", - "received": [ - "foo=bar; domain=home..example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0011", - "sent": [] + test: 'DOMAIN0011', + received: ['foo=bar; domain=home..example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0011', + sent: [], }, { - "test": "DOMAIN0012", - "received": [ - "foo=bar; domain= .home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0012", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0012', + received: ['foo=bar; domain= .home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0012', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0013", - "received": [ - "foo=bar; domain= . home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0013", - "sent": [] + test: 'DOMAIN0013', + received: ['foo=bar; domain= . home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0013', + sent: [], }, { - "test": "DOMAIN0014", - "received": [ - "foo=bar; domain=home.example.org." - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0014", - "sent": [] + test: 'DOMAIN0014', + received: ['foo=bar; domain=home.example.org.'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0014', + sent: [], }, { - "test": "DOMAIN0015", - "received": [ - "foo=bar; domain=home.example.org.." - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0015", - "sent": [] + test: 'DOMAIN0015', + received: ['foo=bar; domain=home.example.org..'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0015', + sent: [], }, { - "test": "DOMAIN0016", - "received": [ - "foo=bar; domain=home.example.org ." - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0016", - "sent": [] + test: 'DOMAIN0016', + received: ['foo=bar; domain=home.example.org .'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0016', + sent: [], }, { - "test": "DOMAIN0017", - "received": [ - "foo=bar; domain=.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0017", - "sent": [] + test: 'DOMAIN0017', + received: ['foo=bar; domain=.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0017', + sent: [], }, { - "test": "DOMAIN0018", - "received": [ - "foo=bar; domain=.org." - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0018", - "sent": [] + test: 'DOMAIN0018', + received: ['foo=bar; domain=.org.'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0018', + sent: [], }, { - "test": "DOMAIN0019", - "received": [ - "foo=bar; domain=home.example.org", - "foo2=bar2; domain=.home.example.org" + test: 'DOMAIN0019', + received: [ + 'foo=bar; domain=home.example.org', + 'foo2=bar2; domain=.home.example.org', + ], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0019', + sent: [ + { name: 'foo', value: 'bar' }, + { name: 'foo2', value: 'bar2' }, ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0019", - "sent": [ - { "name": "foo", "value": "bar" }, - { "name": "foo2", "value": "bar2" } - ] }, { - "test": "DOMAIN0020", - "received": [ - "foo2=bar2; domain=.home.example.org", - "foo=bar; domain=home.example.org" + test: 'DOMAIN0020', + received: [ + 'foo2=bar2; domain=.home.example.org', + 'foo=bar; domain=home.example.org', + ], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0020', + sent: [ + { name: 'foo2', value: 'bar2' }, + { name: 'foo', value: 'bar' }, ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0020", - "sent": [ - { "name": "foo2", "value": "bar2" }, - { "name": "foo", "value": "bar" } - ] }, { - "test": "DOMAIN0021", - "received": [ - "foo=bar; domain=\"home.example.org\"" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0021", - "sent": [] + test: 'DOMAIN0021', + received: ['foo=bar; domain="home.example.org"'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0021', + sent: [], }, { - "test": "DOMAIN0022", - "received": [ - "foo=bar; domain=home.example.org", - "foo2=bar2; domain=.example.org" + test: 'DOMAIN0022', + received: [ + 'foo=bar; domain=home.example.org', + 'foo2=bar2; domain=.example.org', + ], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0022', + sent: [ + { name: 'foo', value: 'bar' }, + { name: 'foo2', value: 'bar2' }, ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0022", - "sent": [ - { "name": "foo", "value": "bar" }, - { "name": "foo2", "value": "bar2" } - ] }, { - "test": "DOMAIN0023", - "received": [ - "foo2=bar2; domain=.example.org", - "foo=bar; domain=home.example.org" + test: 'DOMAIN0023', + received: [ + 'foo2=bar2; domain=.example.org', + 'foo=bar; domain=home.example.org', + ], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0023', + sent: [ + { name: 'foo2', value: 'bar2' }, + { name: 'foo', value: 'bar' }, ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0023", - "sent": [ - { "name": "foo2", "value": "bar2" }, - { "name": "foo", "value": "bar" } - ] }, { - "test": "DOMAIN0024", - "received": [ - "foo=bar; domain=.example.org; domain=home.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0024", - "sent": [] + test: 'DOMAIN0024', + received: ['foo=bar; domain=.example.org; domain=home.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0024', + sent: [], }, { - "test": "DOMAIN0025", - "received": [ - "foo=bar; domain=home.example.org; domain=.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0025", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0025', + received: ['foo=bar; domain=home.example.org; domain=.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0025', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0026", - "received": [ - "foo=bar; domain=home.eXaMpLe.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0026", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0026', + received: ['foo=bar; domain=home.eXaMpLe.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0026', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0027", - "received": [ - "foo=bar; domain=home.example.org:8888" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0027", - "sent": [] + test: 'DOMAIN0027', + received: ['foo=bar; domain=home.example.org:8888'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0027', + sent: [], }, { - "test": "DOMAIN0028", - "received": [ - "foo=bar; domain=subdomain.home.example.org" - ], - "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0028", - "sent": [] + test: 'DOMAIN0028', + received: ['foo=bar; domain=subdomain.home.example.org'], + 'sent-to': + 'http://subdomain.home.example.org:8888/cookie-parser-result?domain0028', + sent: [], }, { - "test": "DOMAIN0029", - "received": [ - "foo=bar" - ], - "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?domain0029", - "sent": [] + test: 'DOMAIN0029', + received: ['foo=bar'], + 'sent-to': + 'http://subdomain.home.example.org:8888/cookie-parser-result?domain0029', + sent: [], }, { - "test": "DOMAIN0031", - "received": [ - "foo=bar; domain=home.example.org; domain=.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0031", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0031', + received: ['foo=bar; domain=home.example.org; domain=.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0031', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0033", - "received": [ - "foo=bar; domain=home.example.org" - ], - "sent-to": "http://hoMe.eXaMplE.org:8888/cookie-parser-result?domain0033", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0033', + received: ['foo=bar; domain=home.example.org'], + 'sent-to': 'http://hoMe.eXaMplE.org:8888/cookie-parser-result?domain0033', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0034", - "received": [ - "foo=bar; domain=home.example.org; domain=home.example.com" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0034", - "sent": [] + test: 'DOMAIN0034', + received: ['foo=bar; domain=home.example.org; domain=home.example.com'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0034', + sent: [], }, { - "test": "DOMAIN0035", - "received": [ - "foo=bar; domain=home.example.com; domain=home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0035", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0035', + received: ['foo=bar; domain=home.example.com; domain=home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0035', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0036", - "received": [ - "foo=bar; domain=home.example.org; domain=home.example.com; domain=home.example.org" + test: 'DOMAIN0036', + received: [ + 'foo=bar; domain=home.example.org; domain=home.example.com; domain=home.example.org', ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0036", - "sent": [ - { "name": "foo", "value": "bar" } - ] + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0036', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0037", - "received": [ - "foo=bar; domain=home.example.com; domain=home.example.org; domain=home.example.com" + test: 'DOMAIN0037', + received: [ + 'foo=bar; domain=home.example.com; domain=home.example.org; domain=home.example.com', ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0037", - "sent": [] + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0037', + sent: [], }, { - "test": "DOMAIN0038", - "received": [ - "foo=bar; domain=home.example.org; domain=home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0038", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0038', + received: ['foo=bar; domain=home.example.org; domain=home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0038', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0039", - "received": [ - "foo=bar; domain=home.example.org; domain=example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0039", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0039', + received: ['foo=bar; domain=home.example.org; domain=example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0039', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0040", - "received": [ - "foo=bar; domain=example.org; domain=home.example.org" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?domain0040", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'DOMAIN0040', + received: ['foo=bar; domain=example.org; domain=home.example.org'], + 'sent-to': 'http://home.example.org:8888/cookie-parser-result?domain0040', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "DOMAIN0041", - "received": [ - "foo=bar; domain=.sibling.example.org" - ], - "sent-to": "http://sibling.example.org:8888/cookie-parser-result?domain0041", - "sent": [] + test: 'DOMAIN0041', + received: ['foo=bar; domain=.sibling.example.org'], + 'sent-to': + 'http://sibling.example.org:8888/cookie-parser-result?domain0041', + sent: [], }, { - "test": "DOMAIN0042", - "received": [ - "foo=bar; domain=.sibling.home.example.org" - ], - "sent-to": "http://sibling.home.example.org:8888/cookie-parser-result?domain0042", - "sent": [] + test: 'DOMAIN0042', + received: ['foo=bar; domain=.sibling.home.example.org'], + 'sent-to': + 'http://sibling.home.example.org:8888/cookie-parser-result?domain0042', + sent: [], }, { - "test": "MOZILLA0001", - "received": [ - "foo=bar; max-age=-1" - ], - "sent": [] + test: 'MOZILLA0001', + received: ['foo=bar; max-age=-1'], + sent: [], }, { - "test": "MOZILLA0002", - "received": [ - "foo=bar; max-age=0" - ], - "sent": [] + test: 'MOZILLA0002', + received: ['foo=bar; max-age=0'], + sent: [], }, { - "test": "MOZILLA0003", - "received": [ - "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" - ], - "sent": [] + test: 'MOZILLA0003', + received: ['foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT'], + sent: [], }, { - "test": "MOZILLA0004", - "received": [ - "foo=bar; max-age=60" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'MOZILLA0004', + received: ['foo=bar; max-age=60'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "MOZILLA0005", - "received": [ - "foo=bar; max-age=-20" - ], - "sent": [] + test: 'MOZILLA0005', + received: ['foo=bar; max-age=-20'], + sent: [], }, { - "test": "MOZILLA0006", - "received": [ - "foo=bar; max-age=60" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'MOZILLA0006', + received: ['foo=bar; max-age=60'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "MOZILLA0007", - "received": [ - "foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT" - ], - "sent": [] + test: 'MOZILLA0007', + received: ['foo=bar; expires=Thu, 10 Apr 1980 16:33:12 GMT'], + sent: [], }, { - "test": "MOZILLA0008", - "received": [ - "foo=bar; max-age=60", - "foo1=bar; max-age=60" + test: 'MOZILLA0008', + received: ['foo=bar; max-age=60', 'foo1=bar; max-age=60'], + sent: [ + { name: 'foo', value: 'bar' }, + { name: 'foo1', value: 'bar' }, ], - "sent": [ - { "name": "foo", "value": "bar" }, - { "name": "foo1", "value": "bar" } - ] }, { - "test": "MOZILLA0009", - "received": [ - "foo=bar; max-age=60", - "foo1=bar; max-age=60", - "foo=differentvalue; max-age=0" + test: 'MOZILLA0009', + received: [ + 'foo=bar; max-age=60', + 'foo1=bar; max-age=60', + 'foo=differentvalue; max-age=0', ], - "sent": [ - { "name": "foo1", "value": "bar" } - ] + sent: [{ name: 'foo1', value: 'bar' }], }, { - "test": "MOZILLA0010", - "received": [ - "foo=bar; max-age=60", - "foo1=bar; max-age=60", - "foo=differentvalue; max-age=0", - "foo2=evendifferentvalue; max-age=0" + test: 'MOZILLA0010', + received: [ + 'foo=bar; max-age=60', + 'foo1=bar; max-age=60', + 'foo=differentvalue; max-age=0', + 'foo2=evendifferentvalue; max-age=0', ], - "sent": [ - { "name": "foo1", "value": "bar" } - ] + sent: [{ name: 'foo1', value: 'bar' }], }, { - "test": "MOZILLA0011", - "received": [ - "test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;" + test: 'MOZILLA0011', + received: [ + 'test=parser; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;', ], - "sent": [] + sent: [], }, { - "test": "MOZILLA0012", - "received": [ - "test=\"fubar! = foo;bar\\\";\" parser; max-age=6", - "five; max-age=2.63," + test: 'MOZILLA0012', + received: [ + 'test="fubar! = foo;bar\\";" parser; max-age=6', + 'five; max-age=2.63,', ], - "sent": [ - { "name": "test", "value": "\"fubar! = foo" } - ] + sent: [{ name: 'test', value: '"fubar! = foo' }], }, { - "test": "MOZILLA0013", - "received": [ - "test=kill; max-age=0", - "five; max-age=0" - ], - "sent": [] + test: 'MOZILLA0013', + received: ['test=kill; max-age=0', 'five; max-age=0'], + sent: [], }, { - "test": "MOZILLA0014", - "received": [ - "six" - ], - "sent": [] + test: 'MOZILLA0014', + received: ['six'], + sent: [], }, { - "test": "MOZILLA0015", - "received": [ - "six", - "seven" - ], - "sent": [] + test: 'MOZILLA0015', + received: ['six', 'seven'], + sent: [], }, { - "test": "MOZILLA0016", - "received": [ - "six", - "seven", - " =eight" - ], - "sent": [] + test: 'MOZILLA0016', + received: ['six', 'seven', ' =eight'], + sent: [], }, { - "test": "MOZILLA0017", - "received": [ - "six", - "seven", - " =eight", - "test=six" - ], - "sent": [ - { "name": "test", "value": "six" } - ] + test: 'MOZILLA0017', + received: ['six', 'seven', ' =eight', 'test=six'], + sent: [{ name: 'test', value: 'six' }], }, { - "test": "NAME0001", - "received": [ - "a=bar" - ], - "sent": [ - { "name": "a", "value": "bar" } - ] + test: 'NAME0001', + received: ['a=bar'], + sent: [{ name: 'a', value: 'bar' }], }, { - "test": "NAME0002", - "received": [ - "1=bar" - ], - "sent": [ - { "name": "1", "value": "bar" } - ] + test: 'NAME0002', + received: ['1=bar'], + sent: [{ name: '1', value: 'bar' }], }, { - "test": "NAME0003", - "received": [ - "$=bar" - ], - "sent": [ - { "name": "$", "value": "bar" } - ] + test: 'NAME0003', + received: ['$=bar'], + sent: [{ name: '$', value: 'bar' }], }, { - "test": "NAME0004", - "received": [ - "!a=bar" - ], - "sent": [ - { "name": "!a", "value": "bar" } - ] + test: 'NAME0004', + received: ['!a=bar'], + sent: [{ name: '!a', value: 'bar' }], }, { - "test": "NAME0005", - "received": [ - "@a=bar" - ], - "sent": [ - { "name": "@a", "value": "bar" } - ] + test: 'NAME0005', + received: ['@a=bar'], + sent: [{ name: '@a', value: 'bar' }], }, { - "test": "NAME0006", - "received": [ - "#a=bar" - ], - "sent": [ - { "name": "#a", "value": "bar" } - ] + test: 'NAME0006', + received: ['#a=bar'], + sent: [{ name: '#a', value: 'bar' }], }, { - "test": "NAME0007", - "received": [ - "$a=bar" - ], - "sent": [ - { "name": "$a", "value": "bar" } - ] + test: 'NAME0007', + received: ['$a=bar'], + sent: [{ name: '$a', value: 'bar' }], }, { - "test": "NAME0008", - "received": [ - "%a=bar" - ], - "sent": [ - { "name": "%a", "value": "bar" } - ] + test: 'NAME0008', + received: ['%a=bar'], + sent: [{ name: '%a', value: 'bar' }], }, { - "test": "NAME0009", - "received": [ - "^a=bar" - ], - "sent": [ - { "name": "^a", "value": "bar" } - ] + test: 'NAME0009', + received: ['^a=bar'], + sent: [{ name: '^a', value: 'bar' }], }, { - "test": "NAME0010", - "received": [ - "&a=bar" - ], - "sent": [ - { "name": "&a", "value": "bar" } - ] + test: 'NAME0010', + received: ['&a=bar'], + sent: [{ name: '&a', value: 'bar' }], }, { - "test": "NAME0011", - "received": [ - "*a=bar" - ], - "sent": [ - { "name": "*a", "value": "bar" } - ] + test: 'NAME0011', + received: ['*a=bar'], + sent: [{ name: '*a', value: 'bar' }], }, { - "test": "NAME0012", - "received": [ - "(a=bar" - ], - "sent": [ - { "name": "(a", "value": "bar" } - ] + test: 'NAME0012', + received: ['(a=bar'], + sent: [{ name: '(a', value: 'bar' }], }, { - "test": "NAME0013", - "received": [ - ")a=bar" - ], - "sent": [ - { "name": ")a", "value": "bar" } - ] + test: 'NAME0013', + received: [')a=bar'], + sent: [{ name: ')a', value: 'bar' }], }, { - "test": "NAME0014", - "received": [ - "-a=bar" - ], - "sent": [ - { "name": "-a", "value": "bar" } - ] + test: 'NAME0014', + received: ['-a=bar'], + sent: [{ name: '-a', value: 'bar' }], }, { - "test": "NAME0015", - "received": [ - "_a=bar" - ], - "sent": [ - { "name": "_a", "value": "bar" } - ] + test: 'NAME0015', + received: ['_a=bar'], + sent: [{ name: '_a', value: 'bar' }], }, { - "test": "NAME0016", - "received": [ - "+=bar" - ], - "sent": [ - { "name": "+", "value": "bar" } - ] + test: 'NAME0016', + received: ['+=bar'], + sent: [{ name: '+', value: 'bar' }], }, { - "test": "NAME0017", - "received": [ - "=a=bar" - ], - "sent": [] + test: 'NAME0017', + received: ['=a=bar'], + sent: [], }, { - "test": "NAME0018", - "received": [ - "a =bar" - ], - "sent": [ - { "name": "a", "value": "bar" } - ] + test: 'NAME0018', + received: ['a =bar'], + sent: [{ name: 'a', value: 'bar' }], }, { - "test": "NAME0019", - "received": [ - "\"a=bar" - ], - "sent": [ - { "name": "\"a", "value": "bar" } - ] + test: 'NAME0019', + received: ['"a=bar'], + sent: [{ name: '"a', value: 'bar' }], }, { - "test": "NAME0020", - "received": [ - "\"a=b\"=bar" - ], - "sent": [ - { "name": "\"a", "value": "b\"=bar" } - ] + test: 'NAME0020', + received: ['"a=b"=bar'], + sent: [{ name: '"a', value: 'b"=bar' }], }, { - "test": "NAME0021", - "received": [ - "\"a=b\"=bar", - "\"a=qux" - ], - "sent": [ - { "name": "\"a", "value": "qux" } - ] + test: 'NAME0021', + received: ['"a=b"=bar', '"a=qux'], + sent: [{ name: '"a', value: 'qux' }], }, { - "test": "NAME0022", - "received": [ - " foo=bar" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'NAME0022', + received: [' foo=bar'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "NAME0023", - "received": [ - "foo;bar=baz" - ], - "sent": [] + test: 'NAME0023', + received: ['foo;bar=baz'], + sent: [], }, { - "test": "NAME0024", - "received": [ - "$Version=1; foo=bar" - ], - "sent": [ - { "name": "$Version", "value": "1" } - ] + test: 'NAME0024', + received: ['$Version=1; foo=bar'], + sent: [{ name: '$Version', value: '1' }], }, { - "test": "NAME0025", - "received": [ - "===a=bar" - ], - "sent": [] + test: 'NAME0025', + received: ['===a=bar'], + sent: [], }, { - "test": "NAME0026", - "received": [ - "foo=bar " - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'NAME0026', + received: ['foo=bar '], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "NAME0027", - "received": [ - "foo=bar ;" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'NAME0027', + received: ['foo=bar ;'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "NAME0028", - "received": [ - "=a" - ], - "sent": [] + test: 'NAME0028', + received: ['=a'], + sent: [], }, { - "test": "NAME0029", - "received": [ - "=" - ], - "sent": [] + test: 'NAME0029', + received: ['='], + sent: [], }, { - "test": "NAME0030", - "received": [ - "foo bar=baz" - ], - "sent": [ - { "name": "foo bar", "value": "baz" } - ] + test: 'NAME0030', + received: ['foo bar=baz'], + sent: [{ name: 'foo bar', value: 'baz' }], }, { - "test": "NAME0031", - "received": [ - "\"foo;bar\"=baz" - ], - "sent": [] + test: 'NAME0031', + received: ['"foo;bar"=baz'], + sent: [], }, { - "test": "NAME0032", - "received": [ - "\"foo\\\"bar;baz\"=qux" - ], - "sent": [] + test: 'NAME0032', + received: ['"foo\\"bar;baz"=qux'], + sent: [], }, { - "test": "NAME0033", - "received": [ - "=foo=bar", - "aaa" - ], - "sent": [] + test: 'NAME0033', + received: ['=foo=bar', 'aaa'], + sent: [], }, { - "test": "OPTIONAL_DOMAIN0030", - "received": [ - "foo=bar; domain=" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0030", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'OPTIONAL_DOMAIN0030', + received: ['foo=bar; domain='], + 'sent-to': + 'http://home.example.org:8888/cookie-parser-result?optional-domain0030', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "OPTIONAL_DOMAIN0041", - "received": [ - "foo=bar; domain=example.org; domain=" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0041", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'OPTIONAL_DOMAIN0041', + received: ['foo=bar; domain=example.org; domain='], + 'sent-to': + 'http://home.example.org:8888/cookie-parser-result?optional-domain0041', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "OPTIONAL_DOMAIN0042", - "received": [ - "foo=bar; domain=foo.example.org; domain=" - ], - "sent-to": "http://home.example.org:8888/cookie-parser-result?optional-domain0042", - "sent": [] + test: 'OPTIONAL_DOMAIN0042', + received: ['foo=bar; domain=foo.example.org; domain='], + 'sent-to': + 'http://home.example.org:8888/cookie-parser-result?optional-domain0042', + sent: [], }, { - "test": "OPTIONAL_DOMAIN0043", - "received": [ - "foo=bar; domain=foo.example.org; domain=" - ], - "sent-to": "http://subdomain.home.example.org:8888/cookie-parser-result?optional-domain0043", - "sent": [] - }, - { - "test": "ORDERING0001", - "received": [ - "key=val0;", - "key=val1; path=/cookie-parser-result", - "key=val2; path=/", - "key=val3; path=/bar", - "key=val4; domain=.example.org", - "key=val5; domain=.example.org; path=/cookie-parser-result/foo" + test: 'OPTIONAL_DOMAIN0043', + received: ['foo=bar; domain=foo.example.org; domain='], + 'sent-to': + 'http://subdomain.home.example.org:8888/cookie-parser-result?optional-domain0043', + sent: [], + }, + { + test: 'ORDERING0001', + received: [ + 'key=val0;', + 'key=val1; path=/cookie-parser-result', + 'key=val2; path=/', + 'key=val3; path=/bar', + 'key=val4; domain=.example.org', + 'key=val5; domain=.example.org; path=/cookie-parser-result/foo', ], - "sent-to": "/cookie-parser-result/foo/baz?ordering0001", - "sent": [ - { "name": "key", "value": "val5" }, - { "name": "key", "value": "val1" }, - { "name": "key", "value": "val2" }, - { "name": "key", "value": "val4" } - ] - }, - { - "test": "PATH0001", - "received": [ - "a=b; path=/", - "x=y; path=/cookie-parser-result" + 'sent-to': '/cookie-parser-result/foo/baz?ordering0001', + sent: [ + { name: 'key', value: 'val5' }, + { name: 'key', value: 'val1' }, + { name: 'key', value: 'val2' }, + { name: 'key', value: 'val4' }, ], - "sent": [ - { "name": "x", "value": "y" }, - { "name": "a", "value": "b" } - ] }, { - "test": "PATH0002", - "received": [ - "a=b; path=/cookie-parser-result", - "x=y; path=/" + test: 'PATH0001', + received: ['a=b; path=/', 'x=y; path=/cookie-parser-result'], + sent: [ + { name: 'x', value: 'y' }, + { name: 'a', value: 'b' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "x", "value": "y" } - ] }, { - "test": "PATH0003", - "received": [ - "x=y; path=/", - "a=b; path=/cookie-parser-result" + test: 'PATH0002', + received: ['a=b; path=/cookie-parser-result', 'x=y; path=/'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'x', value: 'y' }, ], - "sent": [ - { "name": "a", "value": "b" }, - { "name": "x", "value": "y" } - ] }, { - "test": "PATH0004", - "received": [ - "x=y; path=/cookie-parser-result", - "a=b; path=/" + test: 'PATH0003', + received: ['x=y; path=/', 'a=b; path=/cookie-parser-result'], + sent: [ + { name: 'a', value: 'b' }, + { name: 'x', value: 'y' }, ], - "sent": [ - { "name": "x", "value": "y" }, - { "name": "a", "value": "b" } - ] }, { - "test": "PATH0005", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" + test: 'PATH0004', + received: ['x=y; path=/cookie-parser-result', 'a=b; path=/'], + sent: [ + { name: 'x', value: 'y' }, + { name: 'a', value: 'b' }, ], - "sent": [] }, { - "test": "PATH0006", - "received": [ - "foo=bar", - "foo=qux; path=/cookie-parser-result/foo" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0005', + received: ['foo=bar; path=/cookie-parser-result/foo'], + sent: [], }, { - "test": "PATH0007", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" - ], - "sent-to": "/cookie-parser-result/foo?path0007", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0006', + received: ['foo=bar', 'foo=qux; path=/cookie-parser-result/foo'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0008", - "received": [ - "foo=bar; path=/cookie-parser-result/foo" - ], - "sent-to": "/cookie-parser-result/bar?path0008", - "sent": [] + test: 'PATH0007', + received: ['foo=bar; path=/cookie-parser-result/foo'], + 'sent-to': '/cookie-parser-result/foo?path0007', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0009", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], - "sent-to": "/cookie-parser-result/foo?path0009", - "sent": [] + test: 'PATH0008', + received: ['foo=bar; path=/cookie-parser-result/foo'], + 'sent-to': '/cookie-parser-result/bar?path0008', + sent: [], }, { - "test": "PATH0010", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0010", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0009', + received: ['foo=bar; path=/cookie-parser-result/foo/qux'], + 'sent-to': '/cookie-parser-result/foo?path0009', + sent: [], }, { - "test": "PATH0011", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], - "sent-to": "/cookie-parser-result/bar/qux?path0011", - "sent": [] + test: 'PATH0010', + received: ['foo=bar; path=/cookie-parser-result/foo/qux'], + 'sent-to': '/cookie-parser-result/foo/qux?path0010', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0012", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux" - ], - "sent-to": "/cookie-parser-result/foo/baz?path0012", - "sent": [] + test: 'PATH0011', + received: ['foo=bar; path=/cookie-parser-result/foo/qux'], + 'sent-to': '/cookie-parser-result/bar/qux?path0011', + sent: [], }, { - "test": "PATH0013", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], - "sent-to": "/cookie-parser-result/foo/baz?path0013", - "sent": [] + test: 'PATH0012', + received: ['foo=bar; path=/cookie-parser-result/foo/qux'], + 'sent-to': '/cookie-parser-result/foo/baz?path0012', + sent: [], }, { - "test": "PATH0014", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0014", - "sent": [] + test: 'PATH0013', + received: ['foo=bar; path=/cookie-parser-result/foo/qux/'], + 'sent-to': '/cookie-parser-result/foo/baz?path0013', + sent: [], }, { - "test": "PATH0015", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux/" - ], - "sent-to": "/cookie-parser-result/foo/qux/?path0015", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0014', + received: ['foo=bar; path=/cookie-parser-result/foo/qux/'], + 'sent-to': '/cookie-parser-result/foo/qux?path0014', + sent: [], }, { - "test": "PATH0016", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0016", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0015', + received: ['foo=bar; path=/cookie-parser-result/foo/qux/'], + 'sent-to': '/cookie-parser-result/foo/qux/?path0015', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0017", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], - "sent-to": "/cookie-parser-result/foo//qux?path0017", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0016', + received: ['foo=bar; path=/cookie-parser-result/foo/'], + 'sent-to': '/cookie-parser-result/foo/qux?path0016', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0018", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/" - ], - "sent-to": "/cookie-parser-result/fooqux?path0018", - "sent": [] + test: 'PATH0017', + received: ['foo=bar; path=/cookie-parser-result/foo/'], + 'sent-to': '/cookie-parser-result/foo//qux?path0017', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0019", - "received": [ - "foo=bar; path" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0018', + received: ['foo=bar; path=/cookie-parser-result/foo/'], + 'sent-to': '/cookie-parser-result/fooqux?path0018', + sent: [], }, { - "test": "PATH0020", - "received": [ - "foo=bar; path=" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0019', + received: ['foo=bar; path'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0021", - "received": [ - "foo=bar; path=/" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0020', + received: ['foo=bar; path='], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0022", - "received": [ - "foo=bar; path= /" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0021', + received: ['foo=bar; path=/'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0023", - "received": [ - "foo=bar; Path=/cookie-PARSER-result" - ], - "sent": [] + test: 'PATH0022', + received: ['foo=bar; path= /'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0024", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux?" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0024", - "sent": [] + test: 'PATH0023', + received: ['foo=bar; Path=/cookie-PARSER-result'], + sent: [], }, { - "test": "PATH0025", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux#" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0025", - "sent": [] + test: 'PATH0024', + received: ['foo=bar; path=/cookie-parser-result/foo/qux?'], + 'sent-to': '/cookie-parser-result/foo/qux?path0024', + sent: [], }, { - "test": "PATH0026", - "received": [ - "foo=bar; path=/cookie-parser-result/foo/qux;" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0026", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0025', + received: ['foo=bar; path=/cookie-parser-result/foo/qux#'], + 'sent-to': '/cookie-parser-result/foo/qux?path0025', + sent: [], }, { - "test": "PATH0027", - "received": [ - "foo=bar; path=\"/cookie-parser-result/foo/qux;\"" - ], - "sent-to": "/cookie-parser-result/foo/qux?path0027", - "sent": [ - { "name": "foo", "value": "bar" } - ] + test: 'PATH0026', + received: ['foo=bar; path=/cookie-parser-result/foo/qux;'], + 'sent-to': '/cookie-parser-result/foo/qux?path0026', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0028", - "received": [ - "foo=bar; path=/cookie-parser-result/f%6Fo/bar" - ], - "sent-to": "/cookie-parser-result/foo/bar?path0028", - "sent": [] + test: 'PATH0027', + received: ['foo=bar; path="/cookie-parser-result/foo/qux;"'], + 'sent-to': '/cookie-parser-result/foo/qux?path0027', + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0029", - "received": [ - "a=b; \tpath\t=\t/cookie-parser-result\t", - "x=y; \tpath\t=\t/book\t" - ], - "sent": [ - { "name": "a", "value": "b" } - ] + test: 'PATH0028', + received: ['foo=bar; path=/cookie-parser-result/f%6Fo/bar'], + 'sent-to': '/cookie-parser-result/foo/bar?path0028', + sent: [], }, { - "test": "PATH0030", - "received": [ - "foo=bar; path=/dog; path=" + test: 'PATH0029', + received: [ + 'a=b; \tpath\t=\t/cookie-parser-result\t', + 'x=y; \tpath\t=\t/book\t', ], - "sent": [ - { "name": "foo", "value": "bar" } - ] + sent: [{ name: 'a', value: 'b' }], }, { - "test": "PATH0031", - "received": [ - "foo=bar; path=; path=/dog" - ], - "sent": [] + test: 'PATH0030', + received: ['foo=bar; path=/dog; path='], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "PATH0032", - "received": [ - "foo=bar; path=/cookie-parser-result", - "foo=qux; path=/cookie-parser-result/" - ], - "sent-to": "/cookie-parser-result/dog?path0032", - "sent": [ - { "name": "foo", "value": "qux" }, - { "name": "foo", "value": "bar" } - ] + test: 'PATH0031', + received: ['foo=bar; path=; path=/dog'], + sent: [], }, { - "test": "VALUE0001", - "received": [ - "foo= bar" + test: 'PATH0032', + received: [ + 'foo=bar; path=/cookie-parser-result', + 'foo=qux; path=/cookie-parser-result/', + ], + 'sent-to': '/cookie-parser-result/dog?path0032', + sent: [ + { name: 'foo', value: 'qux' }, + { name: 'foo', value: 'bar' }, ], - "sent": [ - { "name": "foo", "value": "bar" } - ] }, { - "test": "VALUE0002", - "received": [ - "foo=\"bar\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar\"" } - ] + test: 'VALUE0001', + received: ['foo= bar'], + sent: [{ name: 'foo', value: 'bar' }], }, { - "test": "VALUE0003", - "received": [ - "foo=\" bar \"" - ], - "sent": [ - { "name": "foo", "value": "\" bar \"" } - ] + test: 'VALUE0002', + received: ['foo="bar"'], + sent: [{ name: 'foo', value: '"bar"' }], }, { - "test": "VALUE0004", - "received": [ - "foo=\"bar;baz\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar" } - ] + test: 'VALUE0003', + received: ['foo=" bar "'], + sent: [{ name: 'foo', value: '" bar "' }], }, { - "test": "VALUE0005", - "received": [ - "foo=\"bar=baz\"" - ], - "sent": [ - { "name": "foo", "value": "\"bar=baz\"" } - ] + test: 'VALUE0004', + received: ['foo="bar;baz"'], + sent: [{ name: 'foo', value: '"bar' }], }, { - "test": "VALUE0006", - "received": [ - "\tfoo\t=\tbar\t \t;\tttt" - ], - "sent": [ - { "name": "foo", "value": "bar" } - ] - } + test: 'VALUE0005', + received: ['foo="bar=baz"'], + sent: [{ name: 'foo', value: '"bar=baz"' }], + }, + { + test: 'VALUE0006', + received: ['\tfoo\t=\tbar\t \t;\tttt'], + sent: [{ name: 'foo', value: 'bar' }], + }, ] diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 84c0f34b..1a058097 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -29,11 +29,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { CookieJar, parseDate } from "../cookie"; -import url from "url"; -import parserData from "./data/parser"; -import bsdExampleDates from "./data/dates/bsd-examples"; -import exampleDates from "./data/dates/examples"; +import { CookieJar, parseDate } from '../cookie' +import url from 'url' +import parserData from './data/parser' +import bsdExampleDates from './data/dates/bsd-examples' +import exampleDates from './data/dates/examples' describe('IETF http state tests', () => { describe('Set/get cookie tests', () => { @@ -58,7 +58,7 @@ describe('IETF http state tests', () => { actual.forEach((actualCookie, idx) => { const expectedCookie = expected[idx] if (!expectedCookie) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } expect(actualCookie.key).toBe(expectedCookie.name) expect(actualCookie.value).toBe(expectedCookie.value) @@ -73,7 +73,7 @@ describe('IETF http state tests', () => { if (expected) { const date = parseDate(test) if (!date) { - throw new Error("This should have parsed") + throw new Error('This should have parsed') } expect(date.toUTCString()).toBe(expected) } else { @@ -88,7 +88,7 @@ describe('IETF http state tests', () => { if (expected) { const date = parseDate(test) if (!date) { - throw new Error("This should have parsed") + throw new Error('This should have parsed') } expect(date.toUTCString()).toBe(expected) } else { diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 506d8b1e..f3cea642 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -29,297 +29,307 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { Cookie, CookieJar, MemoryCookieStore, SerializedCookie, SerializedCookieJar, Store, version } from "../cookie"; +import { + Cookie, + CookieJar, + MemoryCookieStore, + SerializedCookie, + SerializedCookieJar, + Store, + version, +} from '../cookie' + +jest.useFakeTimers() + +describe('cookieJar serialization', () => { + it('should use the expected version', () => { + expect(version).toBe('5.0.0') + }) -jest.useFakeTimers(); + it('should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`', () => { + expect(Cookie.serializableProperties).toEqual([ + 'key', + 'value', + 'expires', + 'maxAge', + 'domain', + 'path', + 'secure', + 'httpOnly', + 'extensions', + 'hostOnly', + 'pathIsDefault', + 'creation', + 'lastAccessed', + 'sameSite', + ]) + }) -describe("cookieJar serialization", () => { - it("should use the expected version", () => { - expect(version).toBe("5.0.0"); - }); + describe('a store without `getAllCookies`', () => { + it('cannot call toJSON', () => { + const store = new Store() + store.synchronous = true - it("should provide the list of serialized properties available for a Cookie with `Cookie.serializableProperties`", () => { - expect(Cookie.serializableProperties).toEqual([ - "key", - "value", - "expires", - "maxAge", - "domain", - "path", - "secure", - "httpOnly", - "extensions", - "hostOnly", - "pathIsDefault", - "creation", - "lastAccessed", - "sameSite" - ]); - }); - - describe("a store without `getAllCookies`", () => { - it("cannot call toJSON", () => { - const store = new Store(); - store.synchronous = true; - - const jar = new CookieJar(store); + const jar = new CookieJar(store) expect(() => jar.toJSON()).toThrowError( - "getAllCookies is not implemented (therefore jar cannot be serialized)" - ); - }); - }); - - describe("for async stores", () => { - it("cannot call toJSON", () => { - const store = new MemoryCookieStore(); - store.synchronous = false; - const jar = new CookieJar(store); + 'getAllCookies is not implemented (therefore jar cannot be serialized)', + ) + }) + }) + + describe('for async stores', () => { + it('cannot call toJSON', () => { + const store = new MemoryCookieStore() + store.synchronous = false + const jar = new CookieJar(store) expect(() => jar.toJSON()).toThrowError( - "CookieJar store is not synchronous; use async API instead." - ); - }); - }); + 'CookieJar store is not synchronous; use async API instead.', + ) + }) + }) - describe("with a small store", () => { - let jar: CookieJar; + describe('with a small store', () => { + let jar: CookieJar beforeEach(async () => { - jar = new CookieJar(); + jar = new CookieJar() // domain cookie with custom extension await jar.setCookie( - "sid=one; domain=example.com; path=/; fubar", - "http://example.com/" - ); + 'sid=one; domain=example.com; path=/; fubar', + 'http://example.com/', + ) await jar.setCookie( - "sid=two; domain=example.net; path=/; fubar", - "http://example.net/" - ); - }); + 'sid=two; domain=example.net; path=/; fubar', + 'http://example.net/', + ) + }) - it("should serialize synchronously", () => { - const serializedJar = jar.serializeSync(); + it('should serialize synchronously', () => { + const serializedJar = jar.serializeSync() if (!serializedJar) { - throw new Error("This should not be undefined"); + throw new Error('This should not be undefined') } - expectDataToMatchSerializationSchema(serializedJar); - expect(serializedJar.cookies.length).toBe(2); - }); + expectDataToMatchSerializationSchema(serializedJar) + expect(serializedJar.cookies.length).toBe(2) + }) - it("should deserialize synchronously", () => { - const serializedJar = jar.serializeSync(); + it('should deserialize synchronously', () => { + const serializedJar = jar.serializeSync() if (!serializedJar) { - throw new Error("This should not be undefined"); + throw new Error('This should not be undefined') } - const deserializedJar = CookieJar.deserializeSync(serializedJar); - expect(jar.store).toEqual(deserializedJar.store); - }); - - it("should serialize asynchronously", async () => { - const serializedJar = await jar.serialize(); - expectDataToMatchSerializationSchema(serializedJar); - expect(serializedJar.cookies.length).toBe(2); - }); - - it("should deserialize asynchronously", async () => { - const serializedJar = await jar.serialize(); - const deserializedJar = await CookieJar.deserialize(serializedJar); - expect(jar.store).toEqual(deserializedJar.store); - }); - }); - - describe("with a small store for cloning", () => { - let jar: CookieJar; + const deserializedJar = CookieJar.deserializeSync(serializedJar) + expect(jar.store).toEqual(deserializedJar.store) + }) + + it('should serialize asynchronously', async () => { + const serializedJar = await jar.serialize() + expectDataToMatchSerializationSchema(serializedJar) + expect(serializedJar.cookies.length).toBe(2) + }) + + it('should deserialize asynchronously', async () => { + const serializedJar = await jar.serialize() + const deserializedJar = await CookieJar.deserialize(serializedJar) + expect(jar.store).toEqual(deserializedJar.store) + }) + }) + + describe('with a small store for cloning', () => { + let jar: CookieJar beforeEach(async () => { - jar = new CookieJar(); + jar = new CookieJar() // domain cookie with custom extension await jar.setCookie( - "sid=three; domain=example.com; path=/; cloner", - "http://example.com/" - ); + 'sid=three; domain=example.com; path=/; cloner', + 'http://example.com/', + ) await jar.setCookie( - "sid=four; domain=example.net; path=/; cloner", - "http://example.net/" - ); - }); - - it("should contain the same contents when cloned asynchronously", async () => { - const clonedJar = await jar.clone(new MemoryCookieStore()); - expect(clonedJar.store).toEqual(jar.store); - }); - - it("should contain the same contents when cloned synchronously", () => { - const clonedJar = jar.cloneSync(new MemoryCookieStore()); + 'sid=four; domain=example.net; path=/; cloner', + 'http://example.net/', + ) + }) + + it('should contain the same contents when cloned asynchronously', async () => { + const clonedJar = await jar.clone(new MemoryCookieStore()) + expect(clonedJar.store).toEqual(jar.store) + }) + + it('should contain the same contents when cloned synchronously', () => { + const clonedJar = jar.cloneSync(new MemoryCookieStore()) if (!clonedJar) { - throw new Error("This should not be undefined"); + throw new Error('This should not be undefined') } - expect(clonedJar.store).toEqual(jar.store); - }); + expect(clonedJar.store).toEqual(jar.store) + }) - it("should raise an error when attempting to synchronously clone to an async store", () => { - const newStore = new MemoryCookieStore(); - newStore.synchronous = false; + it('should raise an error when attempting to synchronously clone to an async store', () => { + const newStore = new MemoryCookieStore() + newStore.synchronous = false expect(() => jar.cloneSync(newStore)).toThrowError( - "CookieJar clone destination store is not synchronous; use async API instead." - ); - }); - }); + 'CookieJar clone destination store is not synchronous; use async API instead.', + ) + }) + }) - describe("with a moderately-sized store", () => { - let jar: CookieJar; - let expires: Date; + describe('with a moderately-sized store', () => { + let jar: CookieJar + let expires: Date beforeEach(async () => { - expires = new Date(Date.now() + 86400000); + expires = new Date(Date.now() + 86400000) - jar = new CookieJar(); + jar = new CookieJar() // Do paths first since the MemoryCookieStore index is domain at the top // level. This should cause the preservation of creation order in // getAllCookies to be exercised. - const paths = ["/", "/foo", "/foo/bar"]; - const domains = ["example.com", "www.example.com", "example.net"]; + const paths = ['/', '/foo', '/foo/bar'] + const domains = ['example.com', 'www.example.com', 'example.net'] for await (const path of paths) { for await (const domain of domains) { - const key = "key"; - const value = JSON.stringify({ path, domain }); - const cookie = new Cookie({ expires, domain, path, key, value }); - await jar.setCookie(cookie, `http://${domain}/`); + const key = 'key' + const value = JSON.stringify({ path, domain }) + const cookie = new Cookie({ expires, domain, path, key, value }) + await jar.setCookie(cookie, `http://${domain}/`) } } // corner cases const cornerCases = [ - { expires: "Infinity", key: "infExp", value: "infExp" }, - { maxAge: 3600, key: "max", value: "max" }, + { expires: 'Infinity', key: 'infExp', value: 'infExp' }, + { maxAge: 3600, key: 'max', value: 'max' }, { expires, - key: "flags", - value: "flags", + key: 'flags', + value: 'flags', secure: true, - httpOnly: true + httpOnly: true, }, { expires, - key: "honly", - value: "honly", + key: 'honly', + value: 'honly', hostOnly: true, - domain: "www.example.org" - } - ]; + domain: 'www.example.org', + }, + ] for await (const cornerCase of cornerCases) { - const domain = cornerCase.domain ?? "example.org"; - const path = "/"; - const cookie = new Cookie({ ...cornerCase, domain, path }); - await jar.setCookie(cookie, "https://www.example.org/", { - ignoreError: true - }); + const domain = cornerCase.domain ?? 'example.org' + const path = '/' + const cookie = new Cookie({ ...cornerCase, domain, path }) + await jar.setCookie(cookie, 'https://www.example.org/', { + ignoreError: true, + }) } - }); - - it("should have the expected metadata", async () => { - const serializedJar = await jar.serialize(); - expect(serializedJar.version).toBe(`tough-cookie@${version}`); - expect(serializedJar.rejectPublicSuffixes).toBe(true); - expect(serializedJar.storeType).toBe("MemoryCookieStore"); - }); - - it("should contain the expected serialized cookies", async () => { - const serializedJar = await jar.serialize(); - expect(serializedJar.cookies.length).toBe(13); - expectDataToMatchSerializationSchema(serializedJar); + }) + + it('should have the expected metadata', async () => { + const serializedJar = await jar.serialize() + expect(serializedJar.version).toBe(`tough-cookie@${version}`) + expect(serializedJar.rejectPublicSuffixes).toBe(true) + expect(serializedJar.storeType).toBe('MemoryCookieStore') + }) + + it('should contain the expected serialized cookies', async () => { + const serializedJar = await jar.serialize() + expect(serializedJar.cookies.length).toBe(13) + expectDataToMatchSerializationSchema(serializedJar) serializedJar.cookies.forEach((serializedCookie) => { - if (serializedCookie.key === "key") { - const parsedValue = JSON.parse(serializedCookie.value ?? "{}") as { - domain?: string, + if (serializedCookie.key === 'key') { + const parsedValue = JSON.parse(serializedCookie.value ?? '{}') as { + domain?: string path?: string - }; - expect(typeof parsedValue.domain).toBe("string"); - expect(typeof parsedValue.path).toBe("string"); + } + expect(typeof parsedValue.domain).toBe('string') + expect(typeof parsedValue.path).toBe('string') } if ( - serializedCookie.key === "infExp" || - serializedCookie.key === "max" + serializedCookie.key === 'infExp' || + serializedCookie.key === 'max' ) { - expect(serializedCookie.expires).toBeFalsy(); + expect(serializedCookie.expires).toBeFalsy() } else { - expect(serializedCookie.expires).toBe(expires.toISOString()); + expect(serializedCookie.expires).toBe(expires.toISOString()) } - if (serializedCookie.key === "max") { - expect(serializedCookie.maxAge).toBe(3600); + if (serializedCookie.key === 'max') { + expect(serializedCookie.maxAge).toBe(3600) } else { - expect(serializedCookie.maxAge).toBeUndefined(); + expect(serializedCookie.maxAge).toBeUndefined() } - if (serializedCookie.key === "flags") { - expect(serializedCookie.secure).toBe(true); - expect(serializedCookie.httpOnly).toBe(true); + if (serializedCookie.key === 'flags') { + expect(serializedCookie.secure).toBe(true) + expect(serializedCookie.httpOnly).toBe(true) } else { - expect(serializedCookie.secure).toBeUndefined(); - expect(serializedCookie.httpOnly).toBeUndefined(); + expect(serializedCookie.secure).toBeUndefined() + expect(serializedCookie.httpOnly).toBeUndefined() } - expect(serializedCookie.hostOnly).toBe(serializedCookie.key === "honly"); + expect(serializedCookie.hostOnly).toBe(serializedCookie.key === 'honly') - expect(serializedCookie.creation).toBe(new Date().toISOString()); - expect(serializedCookie.lastAccessed).toBe(new Date().toISOString()); - }); - }); + expect(serializedCookie.creation).toBe(new Date().toISOString()) + expect(serializedCookie.lastAccessed).toBe(new Date().toISOString()) + }) + }) - it("should be the same when deserialized", async () => { - const serializedJar = await jar.serialize(); - const deserializedJar = await CookieJar.deserialize(serializedJar); - expect(deserializedJar.store).toEqual(jar.store); + it('should be the same when deserialized', async () => { + const serializedJar = await jar.serialize() + const deserializedJar = await CookieJar.deserialize(serializedJar) + expect(deserializedJar.store).toEqual(jar.store) - const cookies = await deserializedJar.getCookies("http://example.org/"); + const cookies = await deserializedJar.getCookies('http://example.org/') expect(cookies).toEqual([ expect.objectContaining({ - key: "infExp", - expires: "Infinity" + key: 'infExp', + expires: 'Infinity', }), expect.objectContaining({ - key: "max" - }) - ]); - expect((cookies[0] as Cookie).TTL(Date.now())).toBe(Infinity); - expect((cookies[1] as Cookie).TTL(Date.now())).toBe(3600 * 1000); - }); - }); -}); - -function expectDataToMatchSerializationSchema(serializedJar: SerializedCookieJar) { - expect(serializedJar).not.toBeNull(); - expect(serializedJar).toBeInstanceOf(Object); - expect(serializedJar.version).toBe(`tough-cookie@${version}`); - expect(serializedJar.storeType).toBe("MemoryCookieStore"); - expect(serializedJar.rejectPublicSuffixes).toBe(true); - expect(serializedJar.cookies).toBeInstanceOf(Array); - serializedJar.cookies.forEach((cookie) => validateSerializedCookie(cookie)); + key: 'max', + }), + ]) + expect((cookies[0] as Cookie).TTL(Date.now())).toBe(Infinity) + expect((cookies[1] as Cookie).TTL(Date.now())).toBe(3600 * 1000) + }) + }) +}) + +function expectDataToMatchSerializationSchema( + serializedJar: SerializedCookieJar, +) { + expect(serializedJar).not.toBeNull() + expect(serializedJar).toBeInstanceOf(Object) + expect(serializedJar.version).toBe(`tough-cookie@${version}`) + expect(serializedJar.storeType).toBe('MemoryCookieStore') + expect(serializedJar.rejectPublicSuffixes).toBe(true) + expect(serializedJar.cookies).toBeInstanceOf(Array) + serializedJar.cookies.forEach((cookie) => validateSerializedCookie(cookie)) } const serializedCookiePropTypes: { [key: string]: string } = { - key: "string", - value: "string", - expires: "isoDate", // if "Infinity" it's supposed to be missing - maxAge: "intOrInf", - domain: "string", - path: "string", - secure: "boolean", - httpOnly: "boolean", - extensions: "array", // of strings, technically - hostOnly: "boolean", - pathIsDefault: "boolean", - creation: "isoDate", - lastAccessed: "isoDate", - sameSite: "string" -}; + key: 'string', + value: 'string', + expires: 'isoDate', // if "Infinity" it's supposed to be missing + maxAge: 'intOrInf', + domain: 'string', + path: 'string', + secure: 'boolean', + httpOnly: 'boolean', + extensions: 'array', // of strings, technically + hostOnly: 'boolean', + pathIsDefault: 'boolean', + creation: 'isoDate', + lastAccessed: 'isoDate', + sameSite: 'string', +} function validateSerializedCookie(cookie: SerializedCookie) { expect(typeof cookie).toBe('object') diff --git a/lib/__tests__/nodeUtilFallback.spec.ts b/lib/__tests__/nodeUtilFallback.spec.ts index d5cba2df..a5966e57 100644 --- a/lib/__tests__/nodeUtilFallback.spec.ts +++ b/lib/__tests__/nodeUtilFallback.spec.ts @@ -1,7 +1,7 @@ -import { getCustomInspectSymbol, getUtilInspect } from "../utilHelper"; -import util from "util"; -import { Cookie, CookieJar, MemoryCookieStore } from "../cookie"; -import { inspectFallback } from "../memstore"; +import { getCustomInspectSymbol, getUtilInspect } from '../utilHelper' +import util from 'util' +import { Cookie, CookieJar, MemoryCookieStore } from '../cookie' +import { inspectFallback } from '../memstore' jest.useFakeTimers() @@ -49,7 +49,7 @@ describe('Node util module fallback for non-node environments', () => { it('custom inspect for Cookie still works', () => { const cookie = Cookie.parse('a=1; Domain=example.com; Path=/') if (!cookie) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } expect(cookie.inspect()).toEqual(util.inspect(cookie)) }) diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts index 2461ae51..bb6f117d 100644 --- a/lib/__tests__/parse.spec.ts +++ b/lib/__tests__/parse.spec.ts @@ -1,5 +1,5 @@ -import { Cookie } from "../cookie"; -import { performance } from "node:perf_hooks"; +import { Cookie } from '../cookie' +import { performance } from 'node:perf_hooks' describe('Cookie.parse', () => { it.each([ diff --git a/lib/__tests__/regression.spec.ts b/lib/__tests__/regression.spec.ts index 390d4580..5fd06556 100644 --- a/lib/__tests__/regression.spec.ts +++ b/lib/__tests__/regression.spec.ts @@ -1,4 +1,4 @@ -import { Cookie, CookieJar } from "../cookie"; +import { Cookie, CookieJar } from '../cookie' const url = 'http://www.example.com' diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts index 5c336267..3389cb0d 100644 --- a/lib/__tests__/removeAll.spec.ts +++ b/lib/__tests__/removeAll.spec.ts @@ -1,6 +1,12 @@ -import { Callback, Cookie, CookieJar, MemoryCookieStore, Store } from "../cookie"; -import spyOn = jest.spyOn; -import SpyInstance = jest.SpyInstance; +import { + Callback, + Cookie, + CookieJar, + MemoryCookieStore, + Store, +} from '../cookie' +import spyOn = jest.spyOn +import SpyInstance = jest.SpyInstance const url = 'http://example.com/index.html' @@ -34,17 +40,21 @@ describe('store removeAllCookies API', () => { store, 'removeCookie', ) - spy.mockImplementationOnce((domain: string, path: string, key: string, callback: Callback) => - _removeCookie.call(store, domain, path, key, callback), + spy.mockImplementationOnce( + (domain: string, path: string, key: string, callback: Callback) => + _removeCookie.call(store, domain, path, key, callback), ) - spy.mockImplementationOnce((domain: string, path: string, key: string, callback: Callback) => - _removeCookie.call(store, domain, path, key, callback), + spy.mockImplementationOnce( + (domain: string, path: string, key: string, callback: Callback) => + _removeCookie.call(store, domain, path, key, callback), ) - spy.mockImplementationOnce((domain: string, path: string, key: string, callback: Callback) => - _removeCookie.call(store, domain, path, key, callback), + spy.mockImplementationOnce( + (domain: string, path: string, key: string, callback: Callback) => + _removeCookie.call(store, domain, path, key, callback), ) - spy.mockImplementationOnce((_domain, _path, _key, callback: Callback) => - callback(new Error('something happened 4')), + spy.mockImplementationOnce( + (_domain, _path, _key, callback: Callback) => + callback(new Error('something happened 4')), ) await expect(jar.removeAllCookies()).rejects.toThrowError( @@ -73,14 +83,21 @@ describe('store removeAllCookies API', () => { store, 'removeCookie', ) - spy.mockImplementation((domain: string, path: string, key: string, callback: Callback) => { - if (spy.mock.calls.length % 2 === 1) { - return callback( - new Error(`something happened ${spy.mock.calls.length}`), - ) - } - return _removeCookie.call(store, domain, path, key, callback) - }) + spy.mockImplementation( + ( + domain: string, + path: string, + key: string, + callback: Callback, + ) => { + if (spy.mock.calls.length % 2 === 1) { + return callback( + new Error(`something happened ${spy.mock.calls.length}`), + ) + } + return _removeCookie.call(store, domain, path, key, callback) + }, + ) await expect(jar.removeAllCookies()).rejects.toThrowError( 'something happened 1', @@ -145,7 +162,7 @@ class StoreWithoutRemoveAll extends Store { callback?: Callback, ): unknown { if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return callback(undefined, undefined) } @@ -168,7 +185,7 @@ class StoreWithoutRemoveAll extends Store { callback?: Callback, ): unknown { if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return callback(undefined, []) } @@ -179,7 +196,7 @@ class StoreWithoutRemoveAll extends Store { this.stats.put++ this.cookies.push(cookie) if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return callback(undefined) } @@ -189,7 +206,7 @@ class StoreWithoutRemoveAll extends Store { override getAllCookies(callback?: Callback): unknown { this.stats.getAll++ if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return callback(undefined, this.cookies.slice()) } @@ -213,7 +230,7 @@ class StoreWithoutRemoveAll extends Store { ): unknown { this.stats.remove++ if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return callback(undefined) } @@ -236,7 +253,7 @@ class MemoryStoreExtension extends MemoryCookieStore { override getAllCookies(callback?: Callback): unknown { this.stats.getAll++ if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return super.getAllCookies(callback) } @@ -260,7 +277,7 @@ class MemoryStoreExtension extends MemoryCookieStore { ): unknown { this.stats.remove++ if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return super.removeCookie(domain, path, key, callback) } @@ -270,7 +287,7 @@ class MemoryStoreExtension extends MemoryCookieStore { override removeAllCookies(callback?: Callback): unknown { this.stats.removeAll++ if (!callback) { - throw new Error("This should not be undefined") + throw new Error('This should not be undefined') } return super.removeAllCookies(callback) } diff --git a/lib/__tests__/sameSite.spec.ts b/lib/__tests__/sameSite.spec.ts index a2221341..ce1540ad 100644 --- a/lib/__tests__/sameSite.spec.ts +++ b/lib/__tests__/sameSite.spec.ts @@ -1,4 +1,4 @@ -import { Cookie, CookieJar } from "../cookie"; +import { Cookie, CookieJar } from '../cookie' const url = 'http://example.com/index.html' diff --git a/lib/cookie.ts b/lib/cookie.ts index cc17e23c..92c4eaef 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -29,16 +29,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import * as punycode from "punycode/"; -import urlParse from "url-parse"; -import * as pubsuffix from "./pubsuffix-psl"; -import { Store } from "./store"; -import { MemoryCookieStore } from "./memstore"; -import { pathMatch } from "./pathMatch"; -import * as validators from "./validators"; -import { version } from "./version"; -import { permuteDomain } from "./permuteDomain"; -import { getCustomInspectSymbol } from "./utilHelper"; +import * as punycode from 'punycode/' +import urlParse from 'url-parse' +import * as pubsuffix from './pubsuffix-psl' +import { Store } from './store' +import { MemoryCookieStore } from './memstore' +import { pathMatch } from './pathMatch' +import * as validators from './validators' +import { version } from './version' +import { permuteDomain } from './permuteDomain' +import { getCustomInspectSymbol } from './utilHelper' // From RFC6265 S4.1.1 // note that it excludes \x3B ";" diff --git a/lib/utilHelper.ts b/lib/utilHelper.ts index 8fcc5529..ee86c1fe 100644 --- a/lib/utilHelper.ts +++ b/lib/utilHelper.ts @@ -1,6 +1,5 @@ function requireUtil() { try { - // eslint-disable-next-line no-restricted-modules return require('util') } catch (e) { return null From 8584a01eb82f59a4898d6a958aa6969809f1817d Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 19 Jun 2023 13:49:59 -0300 Subject: [PATCH 26/33] Add fix from https://github.com/salesforce/tough-cookie/pull/283 --- lib/__tests__/cookieJar.spec.ts | 20 +++++++++++++++++++- lib/memstore.ts | 22 +++++++++++----------- test/cookie_jar_test.js | 25 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 5f23f4a5..b5f4c4a7 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import {Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store} from '../cookie' +import { Cookie, CookieJar, MemoryCookieStore, ParameterError, SerializedCookieJar, Store } from "../cookie"; const { objectContaining, assertions } = expect jest.useFakeTimers() @@ -1008,6 +1008,24 @@ it('should fix issue #197 - CookieJar().setCookie throws an error when empty coo )).rejects.toThrowError('Cookie failed to parse') }) +it('should fix issue #282 - Prototype pollution when setting a cookie with the domain __proto__', async () => { + const jar = new CookieJar(undefined, { + rejectPublicSuffixes: false + }); + // try to pollute the prototype + jar.setCookieSync( + "Slonser=polluted; Domain=__proto__; Path=/notauth", + "https://__proto__/admin" + ); + jar.setCookieSync( + "Auth=Lol; Domain=google.com; Path=/notauth", + "https://google.com/" + ); + + const pollutedObject = {}; + expect('/notauth' in pollutedObject).toBe(false); +}) + // special use domains under a sub-domain describe.each([ "local", diff --git a/lib/memstore.ts b/lib/memstore.ts index 156a8882..0ac169e2 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -29,9 +29,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ "use strict"; -import {Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain} from "./cookie"; -import {Store} from './store' -import {getCustomInspectSymbol, getUtilInspect} from './utilHelper' +import { Callback, Cookie, createPromiseCallback, pathMatch, permuteDomain } from "./cookie"; +import { Store } from "./store"; +import { getCustomInspectSymbol, getUtilInspect } from "./utilHelper"; export class MemoryCookieStore extends Store { override synchronous: boolean; @@ -46,7 +46,7 @@ export class MemoryCookieStore extends Store { constructor() { super(); this.synchronous = true; - this.idx = {}; + this.idx = Object.create(null); const customInspectSymbol = getCustomInspectSymbol(); if (customInspectSymbol) { // @ts-ignore @@ -154,10 +154,10 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - const domainEntry: { [key: string]: any } = this.idx[domain] ?? {} + const domainEntry: { [key: string]: any } = this.idx[domain] ?? Object.create(null) this.idx[domain] = domainEntry - const pathEntry: { [key: string]: any } = domainEntry[path] ?? {} + const pathEntry: { [key: string]: any } = domainEntry[path] ?? Object.create(null) domainEntry[path] = pathEntry pathEntry[key] = cookie @@ -225,7 +225,7 @@ export class MemoryCookieStore extends Store { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - this.idx = {}; + this.idx = Object.create(null); cb(null); return promiseCallback.promise @@ -270,9 +270,9 @@ export class MemoryCookieStore extends Store { export function inspectFallback(val: { [x: string]: any; }) { const domains = Object.keys(val); if (domains.length === 0) { - return "{}"; + return "[Object: null prototype] {}"; } - let result = "{\n"; + let result = "[Object: null prototype] {\n"; Object.keys(val).forEach((domain, i) => { result += formatDomain(domain, val[domain]); if (i < domains.length - 1) { @@ -286,7 +286,7 @@ export function inspectFallback(val: { [x: string]: any; }) { function formatDomain(domainName: string, domainValue: { [x: string]: any; }) { const indent = " "; - let result = `${indent}'${domainName}': {\n`; + let result = `${indent}'${domainName}': [Object: null prototype] {\n`; Object.keys(domainValue).forEach((path, i, paths) => { result += formatPath(path, domainValue[path]); if (i < paths.length - 1) { @@ -300,7 +300,7 @@ function formatDomain(domainName: string, domainValue: { [x: string]: any; }) { function formatPath(pathName: string, pathValue: { [x: string]: any; }) { const indent = " "; - let result = `${indent}'${pathName}': {\n`; + let result = `${indent}'${pathName}': [Object: null prototype] {\n`; Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { const cookie = pathValue[cookieName]; result += ` ${cookieName}: ${cookie.inspect()}`; diff --git a/test/cookie_jar_test.js b/test/cookie_jar_test.js index ed13d850..a4191d22 100644 --- a/test/cookie_jar_test.js +++ b/test/cookie_jar_test.js @@ -810,4 +810,29 @@ vows } } }) + .addBatch({ + "Issue #282 - Prototype pollution": { + "when setting a cookie with the domain __proto__": { + topic: function() { + const jar = new tough.CookieJar(undefined, { + rejectPublicSuffixes: false + }); + // try to pollute the prototype + jar.setCookieSync( + "Slonser=polluted; Domain=__proto__; Path=/notauth", + "https://__proto__/admin" + ); + jar.setCookieSync( + "Auth=Lol; Domain=google.com; Path=/notauth", + "https://google.com/" + ); + this.callback(); + }, + "results in a cookie that is not affected by the attempted prototype pollution": function() { + const pollutedObject = {}; + assert(pollutedObject["/notauth"] === undefined); + } + } + } + }) .export(module); From b87dc38a2476af7b3096e95aed4874cb851fb092 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 19 Jun 2023 14:59:41 -0300 Subject: [PATCH 27/33] Fix prettier errors --- lib/memstore.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/memstore.ts b/lib/memstore.ts index 63ba464a..419680cc 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -188,10 +188,12 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - const domainEntry: { [key: string]: any } = this.idx[domain] ?? Object.create(null) + const domainEntry: { [key: string]: any } = + this.idx[domain] ?? Object.create(null) this.idx[domain] = domainEntry - const pathEntry: { [key: string]: any } = domainEntry[path] ?? Object.create(null) + const pathEntry: { [key: string]: any } = + domainEntry[path] ?? Object.create(null) domainEntry[path] = pathEntry pathEntry[key] = cookie @@ -335,9 +337,9 @@ export class MemoryCookieStore extends Store { export function inspectFallback(val: { [x: string]: any }) { const domains = Object.keys(val) if (domains.length === 0) { - return "[Object: null prototype] {}" + return '[Object: null prototype] {}' } - let result = "[Object: null prototype] {\n"; + let result = '[Object: null prototype] {\n' Object.keys(val).forEach((domain, i) => { result += formatDomain(domain, val[domain]) if (i < domains.length - 1) { From f08b28993ca24e4e67756196c88cc973fd2a2ef2 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 23 Jun 2023 15:13:49 -0400 Subject: [PATCH 28/33] Remove some @ts-ignore directives. --- lib/__tests__/cookieJar.spec.ts | 26 ++++++++++---------------- lib/__tests__/defaultPath.spec.ts | 1 - lib/__tests__/domainMatch.spec.ts | 1 - lib/cookie.ts | 22 ++++++++-------------- lib/store.ts | 10 +++++++--- 5 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 5281f9e8..4a56c6a9 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -160,8 +160,9 @@ describe('CookieJar', () => { }) it('should set a timestamp when storing or retrieving a cookie', async () => { - // @ts-ignore - cookie = Cookie.parse('a=b; Domain=example.com; Path=/') + // We know that we're passing a valid cookie, so we can use the non-null assertion + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + cookie = Cookie.parse('a=b; Domain=example.com; Path=/')! const t0 = new Date() expect(cookie).toEqual( @@ -1007,7 +1008,6 @@ describe('CookieJar', () => { }) it('should remove all from matching domain', async () => { - // @ts-ignore await cookieJar.store.removeCookies('example.com', null) const exampleCookies = await cookieJar.store.findCookies( @@ -1145,11 +1145,8 @@ describe('loose mode', () => { it('should fix issue #132', async () => { const cookieJar = new CookieJar() await expect( - cookieJar.setCookie( - // @ts-ignore - { key: 'x', value: 'y' }, - 'http://example.com/', - ), + // @ts-expect-error test case is explicitly testing invalid input + cookieJar.setCookie({ key: 'x', value: 'y' }, 'http://example.com/'), ).rejects.toThrowError( 'First argument to setCookie must be a Cookie object or string', ) @@ -1177,15 +1174,12 @@ it('should fix issue #144', async () => { ]) }) -it('should fix issue #145 - missing 2nd url parameter', async () => { - expect.assertions(1) +it('should fix issue #145 - missing 2nd url parameter', () => { const cookieJar = new CookieJar() - try { - // @ts-ignore - await cookieJar.setCookie('x=y; Domain=example.com; Path=/') - } catch (e) { - expect(e).toBeInstanceOf(ParameterError) - } + expect( + // @ts-expect-error test case explicitly violates the expected function signature + () => cookieJar.setCookie('x=y; Domain=example.com; Path=/'), + ).toThrow(ParameterError) }) it('should fix issue #197 - CookieJar().setCookie throws an error when empty cookie is passed', async () => { diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts index 8f279a18..178aaee9 100644 --- a/lib/__tests__/defaultPath.spec.ts +++ b/lib/__tests__/defaultPath.spec.ts @@ -24,7 +24,6 @@ describe('defaultPath', () => { output: '/', }, ])('defaultPath("$input") => $output', ({ input, output }) => { - // @ts-ignore expect(defaultPath(input)).toBe(output) }) }) diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index b9e14a2d..9b6e0585 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -70,7 +70,6 @@ describe('domainMatch', () => { ['com', 'com', true], // "are identical" rule ['NOTATLD', 'notaTLD', true], // "are identical" rule (after canonicalization) ])('domainMatch(%s, %s) => %s', (string, domain, expectedValue) => { - // @ts-ignore expect(domainMatch(string, domain)).toBe(expectedValue) }) }) diff --git a/lib/cookie.ts b/lib/cookie.ts index 92c4eaef..50e90209 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -383,8 +383,8 @@ function canonicalDomain(str: string | null) { // S5.1.3 Domain Matching function domainMatch( - str?: string, - domStr?: string, + str?: string | null, + domStr?: string | null, canonicalize?: boolean, ): boolean | null { if (str == null || domStr == null) { @@ -456,7 +456,7 @@ function domainMatch( * * Assumption: the path (and not query part or absolute uri) is passed in. */ -function defaultPath(path?: string): string { +function defaultPath(path?: string | null): string { // "2. If the uri-path is empty or if the first character of the uri-path is not // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. if (!path || path.substr(0, 1) !== '/') { @@ -757,9 +757,7 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { } const c = new Cookie() - for (let i = 0; i < Cookie.serializableProperties.length; i++) { - const prop = Cookie.serializableProperties[i] - // @ts-ignore + for (const prop of Cookie.serializableProperties) { if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) { continue // leave as prototype default } @@ -771,7 +769,6 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(obj[prop]) } } else { - // @ts-ignore c[prop] = obj[prop] } } @@ -926,7 +923,6 @@ export class Cookie { const obj: SerializedCookie = {} for (const prop of Cookie.serializableProperties) { - // @ts-ignore if (this[prop] === cookieDefaults[prop]) { continue // leave as prototype default } @@ -957,9 +953,7 @@ export class Cookie { : maxAge } } else { - // @ts-ignore if (this[prop] !== cookieDefaults[prop]) { - // @ts-ignore obj[prop] = this[prop] } } @@ -983,6 +977,7 @@ export class Cookie { ) { return false } + // @ts-ignore if (this.maxAge != null && this.maxAge <= 0) { return false // "Max-Age=" non-zero-digit *DIGIT } @@ -1105,7 +1100,6 @@ export class Cookie { return Infinity } - // @ts-ignore if (typeof expires === 'string') { expires = parseDate(expires) } @@ -1181,12 +1175,12 @@ export class Cookie { strict: 3, lax: 2, none: 1, - } + } as const static sameSiteCanonical = { strict: 'Strict', lax: 'Lax', - } + } as const static serializableProperties = [ 'key', @@ -1203,7 +1197,7 @@ export class Cookie { 'creation', 'lastAccessed', 'sameSite', - ] + ] as const } function getNormalizedPrefixSecurity(prefixSecurity: string) { diff --git a/lib/store.ts b/lib/store.ts index a0eaaea9..6aebb3ea 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -121,11 +121,15 @@ export class Store { throw new Error('removeCookie is not implemented') } - removeCookies(domain: string, path: string): Promise - removeCookies(domain: string, path: string, callback: Callback): void + removeCookies(domain: string, path: string | null): Promise + removeCookies( + domain: string, + path: string | null, + callback: Callback, + ): void removeCookies( _domain: string, - _path: string, + _path: string | null, _callback?: Callback, ): unknown { throw new Error('removeCookies is not implemented') From bae5ae80ca849a4eef1d866a97cc1736939036eb Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 6 Jul 2023 10:23:20 -0300 Subject: [PATCH 29/33] Changes to support full eslint rule configurations These are the remaining changes required to enable `plugin:@typescript-eslint/recommended-requiring-type-checking` as well as removing our other temporary rule overrides (`@typescript-eslint/ban-types`, `@typescript-eslint/no-explicit-any`, and `@typescript-eslint/no-unused-vars`) --- .eslintrc.json | 13 +- lib/__tests__/cookieJar.spec.ts | 10 +- lib/__tests__/jarSerialization.spec.ts | 73 ++-- lib/__tests__/nodeUtilFallback.spec.ts | 15 +- lib/cookie.ts | 488 ++++++++++++++++--------- lib/memstore.ts | 231 ++++++++---- lib/permuteDomain.ts | 9 +- lib/pubsuffix-psl.ts | 12 +- lib/store.ts | 3 +- lib/utilHelper.ts | 67 +++- lib/validators.ts | 20 +- test/.eslintrc.json | 5 - test/node_util_fallback_test.js | 13 +- 13 files changed, 614 insertions(+), 345 deletions(-) delete mode 100644 test/.eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json index 8ed11807..b7fc4a21 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,7 @@ "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", - //"plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:prettier/recommended" ], "parser": "@typescript-eslint/parser", @@ -16,16 +16,9 @@ // this is only needed because `createPromiseCallback` uses arguments to support the callback-style api // we should strongly consider dropping the callback and sync api variants (in say v6) to reduce the // surface area and complexity of tough-cookie - "prefer-rest-params": "warn", - "@typescript-eslint/no-unused-vars": "warn", - - // used by callSync which needs to use Function because it's used by several apis - "@typescript-eslint/ban-types": "warn", + "prefer-rest-params": "off", // there are several areas that still have ts-ignore comments, let's try to eliminate those - "@typescript-eslint/ban-ts-comment": "warn", - - // i'd like to clean up all usages of any but the current api is just too loose - "@typescript-eslint/no-explicit-any": "warn" + "@typescript-eslint/ban-ts-comment": "warn" } } diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 5281f9e8..0a5b091c 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -412,8 +412,12 @@ describe('CookieJar', () => { }) it('should set all the cookies', async () => { - const { cookies } = await cookieJar.serialize() - expect(cookies).toHaveLength(6) + try { + const { cookies } = await cookieJar.serialize() + expect(cookies).toHaveLength(6) + } catch (e) { + console.log(e) + } }) it('should scope cookies by path as the default behavior', async () => { @@ -1195,7 +1199,7 @@ it('should fix issue #197 - CookieJar().setCookie throws an error when empty coo ).rejects.toThrowError('Cookie failed to parse') }) -it('should fix issue #282 - Prototype pollution when setting a cookie with the domain __proto__', async () => { +it('should fix issue #282 - Prototype pollution when setting a cookie with the domain __proto__', () => { const jar = new CookieJar(undefined, { rejectPublicSuffixes: false, }) diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index f3cea642..78be6267 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -201,33 +201,52 @@ describe('cookieJar serialization', () => { } // corner cases - const cornerCases = [ - { expires: 'Infinity', key: 'infExp', value: 'infExp' }, - { maxAge: 3600, key: 'max', value: 'max' }, - { - expires, - key: 'flags', - value: 'flags', - secure: true, - httpOnly: true, - }, - { - expires, - key: 'honly', - value: 'honly', - hostOnly: true, - domain: 'www.example.org', - }, - ] - - for await (const cornerCase of cornerCases) { - const domain = cornerCase.domain ?? 'example.org' - const path = '/' - const cookie = new Cookie({ ...cornerCase, domain, path }) - await jar.setCookie(cookie, 'https://www.example.org/', { - ignoreError: true, - }) - } + const corner_case_1 = new Cookie({ + expires: 'Infinity', + key: 'infExp', + value: 'infExp', + domain: 'example.org', + path: '/', + }) + await jar.setCookie(corner_case_1, 'https://www.example.org/', { + ignoreError: true, + }) + + const corner_case_2 = new Cookie({ + maxAge: 3600, + key: 'max', + value: 'max', + domain: 'example.org', + path: '/', + }) + await jar.setCookie(corner_case_2, 'https://www.example.org/', { + ignoreError: true, + }) + + const corner_case_3 = new Cookie({ + expires, + key: 'flags', + value: 'flags', + secure: true, + httpOnly: true, + domain: 'example.org', + path: '/', + }) + await jar.setCookie(corner_case_3, 'https://www.example.org/', { + ignoreError: true, + }) + + const corner_case_4 = new Cookie({ + expires, + key: 'honly', + value: 'honly', + hostOnly: true, + domain: 'www.example.org', + path: '/', + }) + await jar.setCookie(corner_case_4, 'https://www.example.org/', { + ignoreError: true, + }) }) it('should have the expected metadata', async () => { diff --git a/lib/__tests__/nodeUtilFallback.spec.ts b/lib/__tests__/nodeUtilFallback.spec.ts index a5966e57..8a959cdb 100644 --- a/lib/__tests__/nodeUtilFallback.spec.ts +++ b/lib/__tests__/nodeUtilFallback.spec.ts @@ -13,22 +13,13 @@ describe('Node util module fallback for non-node environments', () => { ) }) - it('should not be null in a node environment when custom inspect symbol cannot be retrieved (< node v10.12.0', () => { + it('should not be null in a non-node environment since we create the symbol if it does not exist', () => { expect( getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null, + requireUtil: () => undefined, }), ).toEqual(Symbol.for('nodejs.util.inspect.custom') || util.inspect.custom) }) - - it('should be null in a non-node environment since `util` features cannot be relied on', () => { - expect( - getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null, - requireUtil: () => null, - }), - ).toBeNull() - }) }) describe('getUtilInspect', () => { @@ -39,7 +30,7 @@ describe('Node util module fallback for non-node environments', () => { it('should use fallback inspect function in a non-node environment', () => { const inspect = getUtilInspect(() => 'fallback', { - requireUtil: () => null, + requireUtil: () => undefined, }) expect(inspect('util.inspect')).toEqual('fallback') }) diff --git a/lib/cookie.ts b/lib/cookie.ts index 92c4eaef..946a7043 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -77,7 +77,6 @@ const MONTH_TO_NUM = { } const MAX_TIME = 2147483647000 // 31-bit max -const MIN_TIME = 0 // 31-bit min const SAME_SITE_CONTEXT_VAL_ERR = 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"' @@ -441,11 +440,7 @@ function domainMatch( } /* " * The string is a host name (i.e., not an IP address)." */ - if (IP_REGEX_LOWERCASE.test(_str)) { - return false // it's an IP address - } - - return true + return !IP_REGEX_LOWERCASE.test(_str) } // RFC6265 S5.1.4 Paths and Path-Match @@ -729,26 +724,16 @@ function isHostPrefixConditionMet(cookie: Cookie) { ) } -// avoid the V8 deoptimization monster! -function jsonParse(str: string) { - let obj - try { - obj = JSON.parse(str) - } catch (e) { - return e - } - return obj -} - -function fromJSON(str: string | SerializedCookie | null | undefined) { +function fromJSON(str: string | SerializedCookie | null | undefined | unknown) { if (!str || validators.isEmptyString(str)) { return null } - let obj + let obj: unknown if (typeof str === 'string') { - obj = jsonParse(str) - if (obj instanceof Error) { + try { + obj = JSON.parse(str) + } catch (e) { return null } } else { @@ -757,24 +742,76 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { } const c = new Cookie() - for (let i = 0; i < Cookie.serializableProperties.length; i++) { - const prop = Cookie.serializableProperties[i] - // @ts-ignore - if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) { - continue // leave as prototype default - } + Cookie.serializableProperties.forEach((prop) => { + if (obj && typeof obj === 'object' && inOperator(prop, obj)) { + const val = obj[prop] + if (val === undefined) { + return + } - if (prop === 'expires' || prop === 'creation' || prop === 'lastAccessed') { - if (obj[prop] === null) { - c[prop] = null - } else { - c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(obj[prop]) + if (inOperator(prop, cookieDefaults) && val === cookieDefaults[prop]) { + return + } + + switch (prop) { + case 'key': + case 'value': + case 'sameSite': + if (typeof val === 'string') { + c[prop] = val + } + break + case 'expires': + case 'creation': + case 'lastAccessed': + if ( + typeof val === 'number' || + typeof val === 'string' || + val instanceof Date + ) { + c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(val) + } else if (val === null) { + c[prop] = null + } + break + case 'maxAge': + if ( + typeof val === 'number' || + val === 'Infinity' || + val === '-Infinity' + ) { + c[prop] = val + } + break + case 'domain': + case 'path': + if (typeof val === 'string' || val === null) { + c[prop] = val + } + break + case 'secure': + case 'httpOnly': + if (typeof val === 'boolean') { + c[prop] = val + } + break + case 'extensions': + if ( + Array.isArray(val) && + val.every((item) => typeof item === 'string') + ) { + c[prop] = val + } + break + case 'hostOnly': + case 'pathIsDefault': + if (typeof val === 'boolean' || val === null) { + c[prop] = val + } + break } - } else { - // @ts-ignore - c[prop] = obj[prop] } - } + }) return c } @@ -791,7 +828,7 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { function cookieCompare(a: Cookie, b: Cookie) { validators.validate(validators.isObject(a), a) validators.validate(validators.isObject(b), b) - let cmp = 0 + let cmp: number // descending for length: b CMP a const aPathLen = a.path ? a.path.length : 0 @@ -872,6 +909,24 @@ const cookieDefaults = { sameSite: undefined, } +export type CreateCookieOptions = { + key?: string | undefined + value?: string | undefined + expires?: Date | 'Infinity' | null | undefined + maxAge?: number | 'Infinity' | '-Infinity' | undefined + domain?: string | null | undefined + path?: string | null | undefined + secure?: boolean | undefined + httpOnly?: boolean | undefined + extensions?: string[] | null | undefined + creation?: Date | 'Infinity' | null + creationIndex?: number | undefined + hostOnly?: boolean | null | undefined + pathIsDefault?: boolean | null | undefined + lastAccessed?: Date | 'Infinity' | null | undefined + sameSite?: string | undefined +} + export class Cookie { key: string | undefined value: string | undefined @@ -889,11 +944,16 @@ export class Cookie { lastAccessed: Date | 'Infinity' | null | undefined sameSite: string | undefined - constructor(options: any = {}) { + constructor(options: CreateCookieOptions = {}) { + // supports inspect if that feature is available in the environment const customInspectSymbol = getCustomInspectSymbol() if (customInspectSymbol) { - // @ts-ignore - this[customInspectSymbol] = this.inspect + Object.defineProperty(this, customInspectSymbol, { + value: this.inspect.bind(this), + enumerable: false, + writable: false, + configurable: false, + }) } Object.assign(this, cookieDefaults, options) @@ -910,7 +970,7 @@ export class Cookie { inspect() { const now = Date.now() - const hostOnly = this.hostOnly != null ? this.hostOnly : '?' + const hostOnly = this.hostOnly != null ? this.hostOnly.toString() : '?' const createAge = this.creation && this.creation !== 'Infinity' ? `${now - this.creation.getTime()}ms` @@ -926,8 +986,11 @@ export class Cookie { const obj: SerializedCookie = {} for (const prop of Cookie.serializableProperties) { - // @ts-ignore - if (this[prop] === cookieDefaults[prop]) { + if ( + inOperator(prop, this) && + inOperator(prop, cookieDefaults) && + this[prop] === cookieDefaults[prop] + ) { continue // leave as prototype default } @@ -936,19 +999,22 @@ export class Cookie { prop === 'creation' || prop === 'lastAccessed' ) { - if (this[prop] == null) { - obj[prop] = null - } else { - const value = this[prop] - if (value === 'Infinity') { - obj[prop] = value + if (inOperator(prop, this)) { + if (this[prop] == null) { + obj[prop] = null } else { - obj[prop] = value && value.toISOString() + const value = this[prop] + if (value === 'Infinity') { + obj[prop] = value + } else if (value instanceof Date) { + obj[prop] = value.toISOString() + } else { + obj[prop] = value + } } } - } else if (prop === 'maxAge') { + } else if (prop === 'maxAge' && inOperator(prop, this)) { const maxAge = this[prop] - if (maxAge != null) { // again, intentionally not === obj[prop] = @@ -957,9 +1023,11 @@ export class Cookie { : maxAge } } else { - // @ts-ignore - if (this[prop] !== cookieDefaults[prop]) { - // @ts-ignore + if ( + inOperator(prop, this) && + inOperator(prop, cookieDefaults) && + this[prop] !== cookieDefaults[prop] + ) { obj[prop] = this[prop] } } @@ -1023,14 +1091,11 @@ export class Cookie { } cookieString() { - let val = this.value - if (val == null) { - val = '' - } - if (this.key === '') { - return val + const val = this.value ?? '' + if (this.key) { + return `${this.key}=${val}` } - return `${this.key}=${val}` + return val } // gives Set-Cookie header format @@ -1040,8 +1105,6 @@ export class Cookie { if (this.expires != 'Infinity') { if (this.expires instanceof Date) { str += `; Expires=${formatDate(this.expires)}` - } else { - str += `; Expires=${this.expires}` } } @@ -1100,22 +1163,17 @@ export class Cookie { return this.maxAge <= 0 ? 0 : this.maxAge * 1000 } - let expires = this.expires + const expires = this.expires if (expires === 'Infinity') { return Infinity } - // @ts-ignore - if (typeof expires === 'string') { - expires = parseDate(expires) - } - return (expires?.getTime() ?? now) - (now || Date.now()) } // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() // elsewhere) - expiryTime(now?: Date): number { + expiryTime(now?: Date): number | undefined { if (this.maxAge != null) { const relativeTo = now || this.creation || new Date() const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity @@ -1130,21 +1188,7 @@ export class Cookie { return Infinity } - // @ts-ignore - return this.expires.getTime() - } - - // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() - // elsewhere), except it returns a Date - expiryDate(now: Date) { - const millisec = this.expiryTime(now) - if (millisec == Infinity) { - return new Date(MAX_TIME) - } else if (millisec == -Infinity) { - return new Date(MIN_TIME) - } else { - return new Date(millisec) - } + return this.expires ? this.expires.getTime() : undefined } // This replaces the "persistent-flag" parts of S5.3 step 3 @@ -1237,10 +1281,7 @@ const defaultGetCookieOptions: GetCookiesOptions = { } export function createPromiseCallback(args: IArguments): PromiseCallback { - let callback: ( - error: Error | null | undefined, - result: T | undefined, - ) => Promise + let callback: (error: Error | null | undefined, result: T | undefined) => void let resolve: (result: T | undefined) => void let reject: (error: Error | null) => void @@ -1249,33 +1290,46 @@ export function createPromiseCallback(args: IArguments): PromiseCallback { reject = _reject }) - if (typeof args[args.length - 1] === 'function') { - const cb = args[args.length - 1] + const cb = args[args.length - 1] as unknown + if (typeof cb === 'function') { callback = (err, result) => { try { cb(err, result) } catch (e) { - reject(e instanceof Error ? e : new Error(`${e}`)) + reject(e instanceof Error ? e : new Error()) } - return promise } } else { callback = (err, result) => { try { err ? reject(err) : resolve(result) } catch (e) { - reject(e instanceof Error ? e : new Error(`${e}`)) + reject(e instanceof Error ? e : new Error()) } - return promise } } return { promise, callback, + resolve: (value: T | undefined) => { + callback(null, value) + return promise + }, + reject: (error: Error | null | undefined) => { + callback(error, undefined) + return promise + }, } } +export type CreateCookieJarOptions = { + rejectPublicSuffixes?: boolean | undefined + looseMode?: boolean | undefined + prefixSecurity?: 'strict' | 'silent' | 'unsafe-disabled' | undefined + allowSpecialUseDomain?: boolean | undefined +} + export class CookieJar { readonly store: Store private readonly rejectPublicSuffixes: boolean @@ -1283,22 +1337,23 @@ export class CookieJar { private readonly allowSpecialUseDomain: boolean readonly prefixSecurity: string - constructor(store?: any, options: any = { rejectPublicSuffixes: true }) { + constructor( + store?: Store | null | undefined, + options?: CreateCookieJarOptions | boolean, + ) { if (typeof options === 'boolean') { options = { rejectPublicSuffixes: options } } - validators.validate(validators.isObject(options), options) - this.rejectPublicSuffixes = options.rejectPublicSuffixes - this.enableLooseMode = !!options.looseMode - this.allowSpecialUseDomain = - typeof options.allowSpecialUseDomain === 'boolean' - ? options.allowSpecialUseDomain - : true - this.store = store || new MemoryCookieStore() - this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity) + this.rejectPublicSuffixes = options?.rejectPublicSuffixes ?? true + this.enableLooseMode = options?.looseMode ?? false + this.allowSpecialUseDomain = options?.allowSpecialUseDomain ?? true + this.prefixSecurity = getNormalizedPrefixSecurity( + options?.prefixSecurity ?? 'silent', + ) + this.store = store ?? new MemoryCookieStore() } - private callSync(fn: Function): T | undefined { + private callSync(fn: (callback: Callback) => void): T | undefined { if (!this.store.synchronous) { throw new Error( 'CookieJar store is not synchronous; use async API instead.', @@ -1306,7 +1361,7 @@ export class CookieJar { } let syncErr: Error | undefined let syncResult: T | undefined = undefined - fn.call(this, (error: Error, result: T) => { + fn.call(this, (error, result) => { syncErr = error syncResult = result }) @@ -1353,7 +1408,7 @@ export class CookieJar { let err if (validators.isFunction(url)) { - return cb(new Error('No URL was specified')) + return promiseCallback.reject(new Error('No URL was specified')) } const context = getCookieContext(url) @@ -1369,7 +1424,7 @@ export class CookieJar { cookie instanceof String && cookie.length == 0 ) { - return cb(null) + return promiseCallback.reject(null) } const host = canonicalDomain(context.hostname) @@ -1379,7 +1434,7 @@ export class CookieJar { if (options?.sameSiteContext) { sameSiteContext = checkSameSiteContext(options.sameSiteContext) if (!sameSiteContext) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } } @@ -1388,7 +1443,7 @@ export class CookieJar { const parsedCookie = Cookie.parse(cookie.toString(), { loose: loose }) if (!parsedCookie) { err = new Error('Cookie failed to parse') - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } cookie = parsedCookie } else if (!(cookie instanceof Cookie)) { @@ -1397,7 +1452,7 @@ export class CookieJar { err = new Error( 'First argument to setCookie must be a Cookie object or string', ) - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } // S5.3 step 2 @@ -1421,16 +1476,16 @@ export class CookieJar { if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { // e.g. "com" err = new Error('Cookie has domain set to a public suffix') - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } } catch (err) { if (options?.ignoreError) { - return cb(null) + return promiseCallback.reject(null) } else { if (err instanceof Error) { - return cb(err) + return promiseCallback.reject(err) } else { - return cb(null) + return promiseCallback.reject(null) } } } @@ -1442,9 +1497,11 @@ export class CookieJar { !domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false) ) { err = new Error( - `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}`, + `Cookie not in this host's domain. Cookie:${ + cookie.cdomain() ?? 'null' + } Request:${host ?? 'null'}`, ) - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } if (cookie.hostOnly == null) { @@ -1470,7 +1527,7 @@ export class CookieJar { // S5.3 step 10 if (options?.http === false && cookie.httpOnly) { err = new Error("Cookie is HttpOnly and this isn't an HTTP API") - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } // 6252bis-02 S5.4 Step 13 & 14: @@ -1485,7 +1542,7 @@ export class CookieJar { // abort these steps and ignore the newly created cookie entirely." if (sameSiteContext === 'none') { err = new Error('Cookie is SameSite but this is a cross-origin request') - return cb(options?.ignoreError ? null : err) + return promiseCallback.reject(options?.ignoreError ? null : err) } } @@ -1509,7 +1566,7 @@ export class CookieJar { "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'" } if (errorFound) { - return cb( + return promiseCallback.reject( options?.ignoreError || ignoreErrorForPrefixSecurity ? null : new Error(errorMsg), @@ -1625,6 +1682,7 @@ export class CookieJar { getCookies( url: string, options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1649,11 +1707,11 @@ export class CookieJar { if (options?.sameSiteContext) { const sameSiteContext = checkSameSiteContext(options.sameSiteContext) if (sameSiteContext == null) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext] if (!sameSiteLevel) { - return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) } } @@ -1716,7 +1774,8 @@ export class CookieJar { // deferred from S5.3 // non-RFC: allow retention of expired cookies by choice - if (expireCheck && c.expiryTime() <= now) { + const expiryTime = c.expiryTime() + if (expireCheck && expiryTime && expiryTime <= now) { // eslint-disable-next-line @typescript-eslint/no-empty-function store.removeCookie(c.domain, c.path, c.key, () => {}) // result ignored return false @@ -1782,6 +1841,7 @@ export class CookieJar { getCookieString( url: string, options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1837,6 +1897,7 @@ export class CookieJar { getSetCookieStrings( url: string, options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1864,7 +1925,10 @@ export class CookieJar { this.getCookies(url, options, next) return promiseCallback.promise } - getSetCookieStringsSync(url: string, options: any = {}): string[] { + getSetCookieStringsSync( + url: string, + options: GetCookiesOptions = {}, + ): string[] { return ( this.callSync( this.getSetCookieStrings.bind(this, url, options), @@ -1875,6 +1939,7 @@ export class CookieJar { serialize(callback: Callback): void serialize(): Promise serialize(callback?: Callback): unknown + // eslint-disable-next-line @typescript-eslint/no-unused-vars serialize(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -1912,7 +1977,7 @@ export class CookieJar { typeof this.store.getAllCookies === 'function' ) ) { - return cb( + return promiseCallback.reject( new Error( 'store does not support getAllCookies and cannot be serialized', ), @@ -1921,17 +1986,18 @@ export class CookieJar { this.store.getAllCookies((err, cookies) => { if (err) { - return cb(err) + promiseCallback.callback(err) + return } if (cookies == null) { - return cb(undefined, serialized) + promiseCallback.callback(undefined, serialized) + return } serialized.cookies = cookies.map((cookie) => { // convert to serialized 'raw' cookies - const serializedCookie = - cookie instanceof Cookie ? cookie.toJSON() : cookie + const serializedCookie = cookie.toJSON() // Remove the index so new ones get assigned during deserialization delete serializedCookie.creationIndex @@ -1939,13 +2005,15 @@ export class CookieJar { return serializedCookie }) - return cb(null, serialized) + promiseCallback.callback(undefined, serialized) }) return promiseCallback.promise } serializeSync(): SerializedCookieJar | undefined { - return this.callSync(this.serialize.bind(this)) + return this.callSync((callback) => { + this.serialize(callback) + }) } toJSON() { @@ -1953,40 +2021,56 @@ export class CookieJar { } // use the class method CookieJar.deserialize instead of calling this directly - _importCookies(serialized: { cookies: any }, cb: Callback) { - let cookies = serialized.cookies - if (!cookies || !Array.isArray(cookies)) { - return cb(new Error('serialized jar has no cookies array'), undefined) + _importCookies(serialized: unknown, callback: Callback) { + let cookies: unknown[] | undefined = undefined + + if ( + serialized && + typeof serialized === 'object' && + inOperator('cookies', serialized) && + Array.isArray(serialized.cookies) + ) { + cookies = serialized.cookies } + + if (!cookies) { + return callback( + new Error('serialized jar has no cookies array'), + undefined, + ) + } + cookies = cookies.slice() // do not modify the original const putNext = (err?: Error): void => { if (err) { - return cb(err, undefined) + return callback(err, undefined) } - if (!cookies.length) { - return cb(err, this) - } + if (Array.isArray(cookies)) { + if (!cookies.length) { + return callback(err, this) + } - let cookie - try { - cookie = fromJSON(cookies.shift()) - } catch (e) { - return cb(e instanceof Error ? e : new Error(`${e}`), undefined) - } + let cookie + try { + cookie = fromJSON(cookies.shift()) + } catch (e) { + return callback(e instanceof Error ? e : new Error(), undefined) + } - if (cookie === null) { - return putNext(undefined) // skip this cookie - } + if (cookie === null) { + return putNext(undefined) // skip this cookie + } - this.store.putCookie(cookie, putNext) + this.store.putCookie(cookie, putNext) + } } putNext() } - _importCookiesSync(serialized: any): void { + _importCookiesSync(serialized: unknown): void { this.callSync(this._importCookies.bind(this, serialized)) } @@ -1996,6 +2080,7 @@ export class CookieJar { clone(newStore: Store): Promise clone( newStore?: Store | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { if (typeof newStore === 'function') { @@ -2007,7 +2092,7 @@ export class CookieJar { this.serialize((err, serialized) => { if (err) { - return cb(err) + return promiseCallback.reject(err) } return CookieJar.deserialize(serialized ?? '', newStore, cb) }) @@ -2020,7 +2105,7 @@ export class CookieJar { newStore && typeof newStore !== 'function' ? this.clone.bind(this, newStore) : this.clone.bind(this) - return this.callSync(cloneFn) + return this.callSync((callback) => cloneFn(callback)) } cloneSync(newStore?: Store): CookieJar | undefined { @@ -2038,6 +2123,7 @@ export class CookieJar { removeAllCookies(callback: ErrorCallback): void removeAllCookies(): Promise removeAllCookies(callback?: ErrorCallback): unknown + // eslint-disable-next-line @typescript-eslint/no-unused-vars removeAllCookies(_callback?: ErrorCallback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -2099,7 +2185,7 @@ export class CookieJar { return promiseCallback.promise } removeAllCookiesSync(): void { - return this.callSync(this.removeAllCookies.bind(this)) + return this.callSync((callback) => this.removeAllCookies(callback)) } static deserialize( @@ -2124,6 +2210,7 @@ export class CookieJar { static deserialize( strOrObj: string | object, store?: Store | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { if (typeof store === 'function') { @@ -2131,29 +2218,51 @@ export class CookieJar { } const promiseCallback = createPromiseCallback(arguments) - const cb = promiseCallback.callback - let serialized + let serialized: unknown if (typeof strOrObj === 'string') { - serialized = jsonParse(strOrObj) - if (serialized instanceof Error) { - return cb(serialized, undefined) + try { + serialized = JSON.parse(strOrObj) + } catch (e) { + return promiseCallback.reject(e instanceof Error ? e : new Error()) } } else { serialized = strOrObj } + const readSerializedProperty = (property: string): unknown | undefined => { + return serialized && + typeof serialized === 'object' && + inOperator(property, serialized) + ? serialized[property] + : undefined + } + + const readSerializedBoolean = (property: string): boolean | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'boolean' ? value : undefined + } + + const readSerializedString = (property: string): string | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'string' ? value : undefined + } + const jar = new CookieJar(store, { - rejectPublicSuffixes: serialized.rejectPublicSuffixes, - looseMode: serialized.enableLooseMode, - allowSpecialUseDomain: serialized.allowSpecialUseDomain, - prefixSecurity: serialized.prefixSecurity, + rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), + looseMode: readSerializedBoolean('enableLooseMode'), + allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), + prefixSecurity: getNormalizedPrefixSecurity( + readSerializedString('prefixSecurity') ?? 'silent', + ), }) + jar._importCookies(serialized, (err) => { if (err) { - return cb(err, undefined) + promiseCallback.callback(err) + return } - return cb(undefined, jar) + promiseCallback.callback(undefined, jar) }) return promiseCallback.promise @@ -2163,11 +2272,34 @@ export class CookieJar { strOrObj: string | SerializedCookieJar, store?: Store, ): CookieJar { - const serialized = + const serialized: unknown = typeof strOrObj === 'string' ? JSON.parse(strOrObj) : strOrObj + + const readSerializedProperty = (property: string): unknown | undefined => { + return serialized && + typeof serialized === 'object' && + inOperator(property, serialized) + ? serialized[property] + : undefined + } + + const readSerializedBoolean = (property: string): boolean | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'boolean' ? value : undefined + } + + const readSerializedString = (property: string): string | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'string' ? value : undefined + } + const jar = new CookieJar(store, { - rejectPublicSuffixes: serialized.rejectPublicSuffixes, - looseMode: serialized.enableLooseMode, + rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), + looseMode: readSerializedBoolean('enableLooseMode'), + allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), + prefixSecurity: getNormalizedPrefixSecurity( + readSerializedString('prefixSecurity') ?? 'silent', + ), }) // catch this mistake early: @@ -2229,24 +2361,23 @@ type ParseCookieOptions = { interface PromiseCallback { promise: Promise - callback: ( - error: Error | undefined | null, - result?: T, - ) => Promise + callback: (error: Error | undefined | null, result?: T) => void + resolve: (value: T | undefined) => Promise + reject: (error: Error | undefined | null) => Promise } export interface SerializedCookieJar { version: string storeType: string | null rejectPublicSuffixes: boolean - [key: string]: any + [key: string]: unknown cookies: SerializedCookie[] } export interface SerializedCookie { key?: string value?: string - [key: string]: any + [key: string]: unknown } export type Callback = ( @@ -2254,3 +2385,10 @@ export type Callback = ( result: T | undefined, ) => void export type ErrorCallback = (error: Error) => void + +function inOperator( + k: K, + o: T, +): o is T & Record { + return k in o +} diff --git a/lib/memstore.ts b/lib/memstore.ts index 419680cc..02156d3d 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -39,24 +39,30 @@ import { import { Store } from './store' import { getCustomInspectSymbol, getUtilInspect } from './utilHelper' -export class MemoryCookieStore extends Store { - override synchronous: boolean - idx: { - [domain: string]: { - [path: string]: { - [key: string]: Cookie - } +export type MemoryCookieStoreIndex = { + [domain: string]: { + [path: string]: { + [key: string]: Cookie } } +} + +export class MemoryCookieStore extends Store { + override synchronous: boolean + idx: MemoryCookieStoreIndex constructor() { super() this.synchronous = true - this.idx = Object.create(null) + this.idx = Object.create(null) as MemoryCookieStoreIndex const customInspectSymbol = getCustomInspectSymbol() if (customInspectSymbol) { - // @ts-ignore - this[customInspectSymbol] = this.inspect + Object.defineProperty(this, customInspectSymbol, { + value: this.inspect.bind(this), + enumerable: false, + writable: false, + configurable: false, + }) } } @@ -80,27 +86,28 @@ export class MemoryCookieStore extends Store { domain: string | null, path: string | null, key: string | undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback if (domain == null || path == null) { - return cb(null, undefined) + return promiseCallback.resolve(undefined) } const domainEntry = this.idx[domain] if (!domainEntry) { - return cb(null, undefined) + return promiseCallback.resolve(undefined) } const pathEntry = domainEntry[path] if (!pathEntry) { - return cb(null, undefined) + return promiseCallback.resolve(undefined) } if (key == null) { - return cb(null, null) + return promiseCallback.resolve(null) } cb(null, pathEntry[key] || null) @@ -122,28 +129,34 @@ export class MemoryCookieStore extends Store { domain: string, path: string, allowSpecialUseDomain: boolean | Callback = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { if (typeof allowSpecialUseDomain === 'function') { allowSpecialUseDomain = true } - const results: any[] = [] + const results: Cookie[] = [] const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback if (!domain) { - return cb(null, []) + return promiseCallback.resolve([]) } - let pathMatcher: (domainIndex: any) => void + let pathMatcher: ( + domainIndex: { [p: string]: { [p: string]: Cookie } } | undefined, + ) => void if (!path) { // null means "all paths" pathMatcher = function matchAll(domainIndex) { for (const curPath in domainIndex) { const pathIndex = domainIndex[curPath] for (const key in pathIndex) { - results.push(pathIndex[key]) + const value = pathIndex[key] + if (value) { + results.push(value) + } } } } @@ -151,14 +164,17 @@ export class MemoryCookieStore extends Store { pathMatcher = function matchRFC(domainIndex) { //NOTE: we should use path-match algorithm from S5.1.4 here //(see : https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/canonical_cookie.cc#L299) - Object.keys(domainIndex).forEach((cookiePath) => { + for (const cookiePath in domainIndex) { if (pathMatch(path, cookiePath)) { const pathIndex = domainIndex[cookiePath] for (const key in pathIndex) { - results.push(pathIndex[key]) + const value = pathIndex[key] + if (value) { + results.push(value) + } } } - }) + } } } @@ -178,6 +194,7 @@ export class MemoryCookieStore extends Store { override putCookie(cookie: Cookie): Promise override putCookie(cookie: Cookie, callback: Callback): void + // eslint-disable-next-line @typescript-eslint/no-unused-vars override putCookie(cookie: Cookie, _callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -188,12 +205,23 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - const domainEntry: { [key: string]: any } = - this.idx[domain] ?? Object.create(null) + const domainEntry: { + [path: string]: { + [key: string]: Cookie + } + } = + this.idx[domain] ?? + (Object.create(null) as { + [path: string]: { + [key: string]: Cookie + } + }) + this.idx[domain] = domainEntry - const pathEntry: { [key: string]: any } = - domainEntry[path] ?? Object.create(null) + const pathEntry: { [key: string]: Cookie } = + domainEntry[path] ?? (Object.create(null) as { [key: string]: Cookie }) + domainEntry[path] = pathEntry pathEntry[key] = cookie @@ -239,6 +267,7 @@ export class MemoryCookieStore extends Store { domain: string, path: string, key: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -268,6 +297,7 @@ export class MemoryCookieStore extends Store { override removeCookies( domain: string, path: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _callback?: Callback, ): unknown { const promiseCallback = createPromiseCallback(arguments) @@ -288,11 +318,12 @@ export class MemoryCookieStore extends Store { override removeAllCookies(): Promise override removeAllCookies(callback: Callback): void + // eslint-disable-next-line @typescript-eslint/no-unused-vars override removeAllCookies(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - this.idx = Object.create(null) + this.idx = Object.create(null) as MemoryCookieStoreIndex cb(null) return promiseCallback.promise @@ -300,6 +331,7 @@ export class MemoryCookieStore extends Store { override getAllCookies(): Promise override getAllCookies(callback: Callback): void + // eslint-disable-next-line @typescript-eslint/no-unused-vars override getAllCookies(_callback?: Callback): unknown { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback @@ -334,48 +366,119 @@ export class MemoryCookieStore extends Store { } } -export function inspectFallback(val: { [x: string]: any }) { - const domains = Object.keys(val) - if (domains.length === 0) { - return '[Object: null prototype] {}' +export function inspectFallback(val: unknown): string { + if (val === null) { + return 'null' + } + + if (val === undefined) { + return 'undefined' } - let result = '[Object: null prototype] {\n' - Object.keys(val).forEach((domain, i) => { - result += formatDomain(domain, val[domain]) - if (i < domains.length - 1) { - result += ',' + + if (typeof val === 'string') { + return `'${val}'` + } + + if (typeof val === 'object') { + const domains = Object.keys(val) + if (domains.length === 0) { + return '[Object: null prototype] {}' } - result += '\n' - }) - result += '}' - return result + let result = '[Object: null prototype] {\n' + Object.keys(val).forEach((domain, i) => { + if (inOperator(domain, val)) { + result += formatDomain(domain, val[domain]) + if (i < domains.length - 1) { + result += ',' + } + result += '\n' + } + }) + result += '}' + return result + } + + return val.toString() } -function formatDomain(domainName: string, domainValue: { [x: string]: any }) { - const indent = ' ' - let result = `${indent}'${domainName}': [Object: null prototype] {\n` - Object.keys(domainValue).forEach((path, i, paths) => { - result += formatPath(path, domainValue[path]) - if (i < paths.length - 1) { - result += ',' - } - result += '\n' - }) - result += `${indent}}` - return result +function formatDomain(domainName: string, domainValue: unknown) { + if (domainValue === null) { + return 'null' + } + + if (domainValue === undefined) { + return 'undefined' + } + + if (typeof domainValue === 'string') { + return `'${domainValue}'` + } + + if (typeof domainValue === 'object') { + const indent = ' ' + let result = `${indent}'${domainName}': [Object: null prototype] {\n` + Object.keys(domainValue).forEach((path, i, paths) => { + if (inOperator(path, domainValue)) { + result += formatPath(path, domainValue[path]) + if (i < paths.length - 1) { + result += ',' + } + result += '\n' + } + }) + result += `${indent}}` + return result + } + + return domainValue.toString() } -function formatPath(pathName: string, pathValue: { [x: string]: any }) { - const indent = ' ' - let result = `${indent}'${pathName}': [Object: null prototype] {\n` - Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { - const cookie = pathValue[cookieName] - result += ` ${cookieName}: ${cookie.inspect()}` - if (i < cookieNames.length - 1) { - result += ',' - } - result += '\n' - }) - result += `${indent}}` - return result +function formatPath(pathName: string, pathValue: unknown) { + if (pathValue === null) { + return 'null' + } + + if (pathValue === undefined) { + return 'undefined' + } + + if (typeof pathValue === 'string') { + return `'${pathValue}'` + } + + if (typeof pathValue === 'object') { + const indent = ' ' + let result = `${indent}'${pathName}': [Object: null prototype] {\n` + Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { + if (inOperator(cookieName, pathValue)) { + const cookie = pathValue[cookieName] + if ( + cookie != null && + typeof cookie === 'object' && + inOperator('inspect', cookie) && + typeof cookie.inspect === 'function' + ) { + const inspectedValue: unknown = cookie.inspect() + if (typeof inspectedValue === 'string') { + result += ` ${cookieName}: ${inspectedValue}` + if (i < cookieNames.length - 1) { + result += ',' + } + result += '\n' + } + } + } + }) + result += `${indent}}` + return result + } + + return pathValue.toString() +} + +function inOperator( + k: K, + o: T, +): o is T & Record { + return k in o } diff --git a/lib/permuteDomain.ts b/lib/permuteDomain.ts index e73a29f9..630436ee 100644 --- a/lib/permuteDomain.ts +++ b/lib/permuteDomain.ts @@ -59,8 +59,13 @@ export function permuteDomain( let cur = pubSuf const permutations = [cur] while (parts.length) { - cur = `${parts.shift()}.${cur}` - permutations.push(cur) + const part = parts.shift() + if (part !== undefined) { + cur = `${part}.${cur}` + permutations.push(cur) + } else { + throw new Error('domain part value should be a string but was undefined') + } } return permutations } diff --git a/lib/pubsuffix-psl.ts b/lib/pubsuffix-psl.ts index 2cef28a3..0efa9e1b 100644 --- a/lib/pubsuffix-psl.ts +++ b/lib/pubsuffix-psl.ts @@ -58,13 +58,17 @@ export function getPublicSuffix( if ( allowSpecialUseDomain && - typeof topLevelDomain === 'string' && + topLevelDomain !== undefined && SPECIAL_USE_DOMAINS.includes(topLevelDomain) ) { if (domainParts.length > 1) { const secondLevelDomain = domainParts[domainParts.length - 2] - // In aforementioned example, the eTLD/pubSuf will be apple.localhost - return `${secondLevelDomain}.${topLevelDomain}` + if (secondLevelDomain !== undefined) { + // In aforementioned example, the eTLD/pubSuf will be apple.localhost + return `${secondLevelDomain}.${topLevelDomain}` + } else { + throw new Error('expected secondLevelDomain to be a string') + } } else if (SPECIAL_TREATMENT_DOMAINS.includes(topLevelDomain)) { // For a single word special use domain, e.g. 'localhost' or 'invalid', per RFC 6761, // "Application software MAY recognize {localhost/invalid} names as special, or @@ -75,7 +79,7 @@ export function getPublicSuffix( if ( !ignoreError && - typeof topLevelDomain === 'string' && + topLevelDomain !== undefined && SPECIAL_USE_DOMAINS.includes(topLevelDomain) ) { throw new Error( diff --git a/lib/store.ts b/lib/store.ts index a0eaaea9..ba08f42c 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -32,6 +32,7 @@ import type { Callback, Cookie } from './cookie' +/* eslint-disable @typescript-eslint/no-unused-vars */ export class Store { synchronous: boolean @@ -145,5 +146,3 @@ export class Store { ) } } - -exports.Store = Store diff --git a/lib/utilHelper.ts b/lib/utilHelper.ts index ee86c1fe..ead2d745 100644 --- a/lib/utilHelper.ts +++ b/lib/utilHelper.ts @@ -1,43 +1,70 @@ -function requireUtil() { +/** + * It would nice to drop this entirely but, for whatever reason, we expose an + * `inspect` method on `Cookie` and `MemoryCookieStore` that just delegates to + * Node's `util.inspect` functionality. Since that functionality isn't supported + * in non-Node environments (e.g.; React Native) this fallback is here to provide + * equivalent behavior when it is not present. + */ + +import type util from 'node:util' + +type RequireUtil = () => typeof util | undefined +type InspectCompatFunction = ( + object: unknown, + showHidden?: boolean, + depth?: number | null, + color?: boolean, +) => string + +function requireUtil(): typeof util | undefined { try { - return require('util') + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('util') as typeof util } catch (e) { - return null + return undefined } } // for v10.12.0+ -function lookupCustomInspectSymbol() { +function lookupCustomInspectSymbol(): symbol { return Symbol.for('nodejs.util.inspect.custom') } // for older node environments -function tryReadingCustomSymbolFromUtilInspect(options: { requireUtil?: any }) { +function tryReadingCustomSymbolFromUtilInspect(options: { + requireUtil?: RequireUtil +}): typeof util.inspect.custom | undefined { const _requireUtil = options.requireUtil || requireUtil - const util = _requireUtil() - return util ? util.inspect.custom : null + const nodeUtil = _requireUtil() + return nodeUtil ? nodeUtil.inspect.custom : undefined } export function getUtilInspect( - fallback: (value: any) => any, - options: { requireUtil?: any } = {}, -) { + fallback: (value: unknown) => string, + options: { requireUtil?: RequireUtil } = {}, +): InspectCompatFunction { const _requireUtil = options.requireUtil || requireUtil - const util = _requireUtil() - return function inspect(value: any, showHidden?: any, depth?: any) { - return util ? util.inspect(value, showHidden, depth) : fallback(value) + const nodeUtil = _requireUtil() + return function inspect( + object: unknown, + showHidden?: boolean, + depth?: number | null, + color?: boolean, + ): string { + return nodeUtil + ? nodeUtil.inspect(object, showHidden, depth, color) + : fallback(object) } } export function getCustomInspectSymbol( - options: { lookupCustomInspectSymbol?: any; requireUtil?: any } = {}, -) { - const _lookupCustomInspectSymbol = - options.lookupCustomInspectSymbol || lookupCustomInspectSymbol - + options: { + requireUtil?: RequireUtil + } = {}, +): symbol { // get custom inspect symbol for node environments return ( - _lookupCustomInspectSymbol() || - tryReadingCustomSymbolFromUtilInspect(options) + tryReadingCustomSymbolFromUtilInspect(options) || + lookupCustomInspectSymbol() ) } diff --git a/lib/validators.ts b/lib/validators.ts index a99ed49b..5e96941b 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -27,8 +27,6 @@ SOFTWARE. ************************************************************************************ */ 'use strict' -const toString = Object.prototype.toString - /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ export function isFunction(data: unknown): boolean { return typeof data === 'function' @@ -54,7 +52,7 @@ export function isString(data: unknown): boolean { } export function isObject(data: unknown): boolean { - return toString.call(data) === '[object Object]' + return Object.prototype.toString.call(data) === '[object Object]' } export function isInteger(data: unknown): boolean { @@ -62,19 +60,21 @@ export function isInteger(data: unknown): boolean { } /* End validation functions */ -export function validate(bool: boolean, cb?: unknown, options?: unknown): void { +export function validate( + bool: boolean, + cb?: unknown, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _deprecated?: unknown, +): void { if (!isFunction(cb)) { - options = cb cb = null } - if (!isObject(options)) options = { Error: 'Failed Check' } + if (!bool) { if (typeof cb === 'function') { - // @ts-ignore - cb(new ParameterError(options)) + cb(new ParameterError('Failed Check')) } else { - // @ts-ignore - throw new ParameterError(options) + throw new ParameterError('Failed Check') } } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index e4149bbb..00000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-restricted-modules": "off" - } -} diff --git a/test/node_util_fallback_test.js b/test/node_util_fallback_test.js index 7f0e7a4d..7f2a8a57 100644 --- a/test/node_util_fallback_test.js +++ b/test/node_util_fallback_test.js @@ -59,19 +59,10 @@ vows "should not be null in a node environment when custom inspect symbol cannot be retrieved (< node v10.12.0)": function() { assert.equal( getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null + lookupCustomInspectSymbol: () => undefined }), Symbol.for("nodejs.util.inspect.custom") || util.inspect.custom ); - }, - "should be null in a non-node environment since 'util' features cannot be relied on": function() { - assert.equal( - getCustomInspectSymbol({ - lookupCustomInspectSymbol: () => null, - requireUtil: () => null - }), - null - ); } }, getUtilInspect: { @@ -81,7 +72,7 @@ vows }, "should use fallback inspect function in a non-node environment": function() { const inspect = getUtilInspect(() => "fallback", { - requireUtil: () => null + requireUtil: () => undefined }); assert.equal(inspect("util.inspect"), "fallback"); } From afea889c8703e0485440ebe672355449ce3aaae5 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 17 Jul 2023 14:34:14 -0300 Subject: [PATCH 30/33] remove debug code --- lib/__tests__/cookieJar.spec.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 2094ac8a..9fc503a8 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -413,12 +413,8 @@ describe('CookieJar', () => { }) it('should set all the cookies', async () => { - try { - const { cookies } = await cookieJar.serialize() - expect(cookies).toHaveLength(6) - } catch (e) { - console.log(e) - } + const { cookies } = await cookieJar.serialize() + expect(cookies).toHaveLength(6) }) it('should scope cookies by path as the default behavior', async () => { From e07f193ecf87462f2895546aa6487a10b6bb426f Mon Sep 17 00:00:00 2001 From: Will Harney <62956339+wjhsf@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:21:07 -0400 Subject: [PATCH 31/33] Clean up validate function. (#275) * Clean up validate function and add descriptions to validators. * Fix type error. * Add @types/node * Fix validator implementation and add safe toString function. --- lib/cookie.ts | 25 ++++++++++------- lib/utils.ts | 19 +++++++++++++ lib/validators.ts | 69 ++++++++++++++++++++++++++++++----------------- package-lock.json | 13 ++++----- package.json | 5 ++-- 5 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 lib/utils.ts diff --git a/lib/cookie.ts b/lib/cookie.ts index 50e90209..6f0abb67 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -39,6 +39,7 @@ import * as validators from './validators' import { version } from './version' import { permuteDomain } from './permuteDomain' import { getCustomInspectSymbol } from './utilHelper' +import { ErrorCallback, safeToString } from './utils' // From RFC6265 S4.1.1 // note that it excludes \x3B ";" @@ -357,7 +358,7 @@ function parseDate(str: string | undefined | null): Date | undefined { } function formatDate(date: Date) { - validators.validate(validators.isDate(date), date) + validators.validate(validators.isDate(date), safeToString(date)) return date.toUTCString() } @@ -699,7 +700,7 @@ function parse( * @returns boolean */ function isSecurePrefixConditionMet(cookie: Cookie) { - validators.validate(validators.isObject(cookie), cookie) + validators.validate(validators.isObject(cookie), safeToString(cookie)) const startsWithSecurePrefix = typeof cookie.key === 'string' && cookie.key.startsWith('__Secure-') return !startsWithSecurePrefix || cookie.secure @@ -786,8 +787,8 @@ function fromJSON(str: string | SerializedCookie | null | undefined) { */ function cookieCompare(a: Cookie, b: Cookie) { - validators.validate(validators.isObject(a), a) - validators.validate(validators.isObject(b), b) + validators.validate(validators.isObject(a), safeToString(a)) + validators.validate(validators.isObject(b), safeToString(b)) let cmp = 0 // descending for length: b CMP a @@ -977,8 +978,11 @@ export class Cookie { ) { return false } - // @ts-ignore - if (this.maxAge != null && this.maxAge <= 0) { + if ( + this.maxAge != null && + this.maxAge !== 'Infinity' && + (this.maxAge === '-Infinity' || this.maxAge <= 0) + ) { return false // "Max-Age=" non-zero-digit *DIGIT } if (this.path != null && !PATH_VALUE.test(this.path)) { @@ -1343,7 +1347,11 @@ export class CookieJar { const promiseCallback = createPromiseCallback(arguments) const cb = promiseCallback.callback - validators.validate(validators.isNonEmptyString(url), callback, options) + validators.validate( + validators.isNonEmptyString(url), + callback, + safeToString(options), + ) let err if (validators.isFunction(url)) { @@ -1629,7 +1637,7 @@ export class CookieJar { if (typeof options === 'function' || options === undefined) { options = defaultGetCookieOptions } - validators.validate(validators.isObject(options), cb, options) + validators.validate(validators.isObject(options), cb, safeToString(options)) validators.validate(validators.isFunction(cb), cb) const host = canonicalDomain(context.hostname) @@ -2247,4 +2255,3 @@ export type Callback = ( error: Error | undefined, result: T | undefined, ) => void -export type ErrorCallback = (error: Error) => void diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 00000000..a66e5a37 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,19 @@ +/** Signature for a callback function that expects an error to be passed. */ +export type ErrorCallback = (error: Error, result?: never) => void + +/** Unbound `Object.prototype.toString`. */ +const unboundToString = Object.prototype.toString + +/** Wrapped `Object.prototype.toString`, so that you don't need to remember to use `.call()`. */ +export const objectToString = (obj: unknown) => unboundToString.call(obj) + +/** Safely converts any value to string, using the value's own `toString` when available. */ +export const safeToString = (val: unknown) => { + // Ideally, we'd just use String() for everything, but it breaks if `toString` is missing (mostly + // values with no prototype), so we have to use Object#toString as a fallback. + if (val === undefined || val === null || typeof val.toString === 'function') { + return String(val) + } else { + return objectToString(val) + } +} diff --git a/lib/validators.ts b/lib/validators.ts index a99ed49b..35db4755 100644 --- a/lib/validators.ts +++ b/lib/validators.ts @@ -25,58 +25,79 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ************************************************************************************ */ -'use strict' -const toString = Object.prototype.toString +import { ErrorCallback, objectToString, safeToString } from './utils' /* Validation functions copied from check-types package - https://www.npmjs.com/package/check-types */ -export function isFunction(data: unknown): boolean { + +/** Determines whether the argument is a function. */ +export function isFunction(data: unknown): data is Function { return typeof data === 'function' } +/** Determines whether the argument is a non-empty string. */ export function isNonEmptyString(data: unknown): boolean { return isString(data) && data !== '' } +/** Determines whether the argument is a *valid* Date. */ export function isDate(data: unknown): boolean { - if (data instanceof Date) { - return isInteger(data.getTime()) - } - return false + return isInstanceStrict(data, Date) && isInteger(data.getTime()) } +/** Determines whether the argument is the empty string. */ export function isEmptyString(data: unknown): boolean { return data === '' || (data instanceof String && data.toString() === '') } +/** Determines whether the argument is a string. */ export function isString(data: unknown): boolean { return typeof data === 'string' || data instanceof String } +/** Determines whether the string representation of the argument is "[object Object]". */ export function isObject(data: unknown): boolean { - return toString.call(data) === '[object Object]' + return objectToString(data) === '[object Object]' } +/** Determines whether the first argument is an instance of the second. */ +export function isInstanceStrict( + data: unknown, + Constructor: T, +): data is T['prototype'] { + try { + return data instanceof Constructor + } catch { + return false + } +} + +/** Determines whether the argument is an integer. */ export function isInteger(data: unknown): boolean { return typeof data === 'number' && data % 1 === 0 } -/* End validation functions */ -export function validate(bool: boolean, cb?: unknown, options?: unknown): void { - if (!isFunction(cb)) { - options = cb - cb = null - } - if (!isObject(options)) options = { Error: 'Failed Check' } - if (!bool) { - if (typeof cb === 'function') { - // @ts-ignore - cb(new ParameterError(options)) - } else { - // @ts-ignore - throw new ParameterError(options) - } - } +/* -- End validation functions -- */ + +/** + * When the first argument is false, an error is created with the given message. If a callback is + * provided, the error is passed to the callback, otherwise the error is thrown. + */ +export function validate( + bool: boolean, + cbOrMessage?: ErrorCallback | string, + message?: string, +): void { + if (bool) return // Validation passes + const cb = isFunction(cbOrMessage) ? cbOrMessage : null + let options = isFunction(cbOrMessage) ? message : cbOrMessage + // The default message prior to v5 was '[object Object]' due to a bug, and the message is kept + // for backwards compatibility. + if (!isObject(options)) options = '[object Object]' + + const err = new ParameterError(safeToString(options)) + if (cb) cb(err) + else throw err } export class ParameterError extends Error {} diff --git a/package-lock.json b/package-lock.json index fc3f5781..167bf701 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@types/jest": "^29", + "@types/node": "^16.18.23", "@types/psl": "^1", "@types/punycode": "^2", "@types/url-parse": "^1.4.8", @@ -1660,9 +1661,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", - "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "version": "16.18.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", + "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", "dev": true }, "node_modules/@types/prettier": { @@ -7896,9 +7897,9 @@ "dev": true }, "@types/node": { - "version": "18.15.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", - "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "version": "16.18.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", + "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", "dev": true }, "@types/prettier": { diff --git a/package.json b/package.json index 8d3ffa67..85d0e571 100644 --- a/package.json +++ b/package.json @@ -100,13 +100,14 @@ }, "devDependencies": { "@types/jest": "^29", + "@types/node": "^16.18.23", "@types/psl": "^1", "@types/punycode": "^2", "@types/url-parse": "^1.4.8", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "@typescript-eslint/parser": "^5.57.0", "async": "2.6.4", "eslint": "^8.36.0", - "@typescript-eslint/parser": "^5.57.0", - "@typescript-eslint/eslint-plugin": "^5.57.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "genversion": "^3.1.1", From bc07ba722601972be28260be423702291282046a Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Thu, 27 Jul 2023 12:42:06 -0300 Subject: [PATCH 32/33] Addressing review comments --- .eslintrc.json | 2 +- lib/__tests__/jarSerialization.spec.ts | 74 ++++++++++---------------- lib/cookie.ts | 26 ++++----- lib/memstore.ts | 55 +++++-------------- lib/permuteDomain.ts | 11 ++-- lib/pubsuffix-psl.ts | 11 ++-- lib/store.ts | 6 ++- 7 files changed, 67 insertions(+), 118 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4e5d723e..67e01125 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,6 +16,6 @@ // this is only needed because `createPromiseCallback` uses arguments to support the callback-style api // we should strongly consider dropping the callback and sync api variants (in say v6) to reduce the // surface area and complexity of tough-cookie - "prefer-rest-params": "off" + "prefer-rest-params": "warn" } } diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 78be6267..9650048a 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -201,52 +201,34 @@ describe('cookieJar serialization', () => { } // corner cases - const corner_case_1 = new Cookie({ - expires: 'Infinity', - key: 'infExp', - value: 'infExp', - domain: 'example.org', - path: '/', - }) - await jar.setCookie(corner_case_1, 'https://www.example.org/', { - ignoreError: true, - }) - - const corner_case_2 = new Cookie({ - maxAge: 3600, - key: 'max', - value: 'max', - domain: 'example.org', - path: '/', - }) - await jar.setCookie(corner_case_2, 'https://www.example.org/', { - ignoreError: true, - }) - - const corner_case_3 = new Cookie({ - expires, - key: 'flags', - value: 'flags', - secure: true, - httpOnly: true, - domain: 'example.org', - path: '/', - }) - await jar.setCookie(corner_case_3, 'https://www.example.org/', { - ignoreError: true, - }) - - const corner_case_4 = new Cookie({ - expires, - key: 'honly', - value: 'honly', - hostOnly: true, - domain: 'www.example.org', - path: '/', - }) - await jar.setCookie(corner_case_4, 'https://www.example.org/', { - ignoreError: true, - }) + const cornerCases = [ + { expires: 'Infinity', key: 'infExp', value: 'infExp' }, + { maxAge: 3600, key: 'max', value: 'max' }, + { + expires, + key: 'flags', + value: 'flags', + secure: true, + httpOnly: true, + }, + { + expires, + key: 'honly', + value: 'honly', + hostOnly: true, + domain: 'www.example.org', + }, + ] as const + + for (const cornerCase of cornerCases) { + const domain = + 'domain' in cornerCase ? cornerCase.domain : 'example.org' + const path = '/' + const cookie = new Cookie({ ...cornerCase, domain, path }) + await jar.setCookie(cookie, 'https://www.example.org/', { + ignoreError: true, + }) + } }) it('should have the expected metadata', async () => { diff --git a/lib/cookie.ts b/lib/cookie.ts index 6d10c882..7d873043 100644 --- a/lib/cookie.ts +++ b/lib/cookie.ts @@ -911,20 +911,20 @@ const cookieDefaults = { } export type CreateCookieOptions = { - key?: string | undefined - value?: string | undefined - expires?: Date | 'Infinity' | null | undefined - maxAge?: number | 'Infinity' | '-Infinity' | undefined - domain?: string | null | undefined - path?: string | null | undefined - secure?: boolean | undefined - httpOnly?: boolean | undefined - extensions?: string[] | null | undefined + key?: string + value?: string + expires?: Date | 'Infinity' | null + maxAge?: number | 'Infinity' | '-Infinity' + domain?: string | null + path?: string | null + secure?: boolean + httpOnly?: boolean + extensions?: string[] | null creation?: Date | 'Infinity' | null - creationIndex?: number | undefined - hostOnly?: boolean | null | undefined - pathIsDefault?: boolean | null | undefined - lastAccessed?: Date | 'Infinity' | null | undefined + creationIndex?: number + hostOnly?: boolean | null + pathIsDefault?: boolean | null + lastAccessed?: Date | 'Infinity' | null sameSite?: string | undefined } diff --git a/lib/memstore.ts b/lib/memstore.ts index 02156d3d..05d959e0 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -145,7 +145,7 @@ export class MemoryCookieStore extends Store { } let pathMatcher: ( - domainIndex: { [p: string]: { [p: string]: Cookie } } | undefined, + domainIndex: MemoryCookieStoreIndex[string] | undefined, ) => void if (!path) { // null means "all paths" @@ -205,22 +205,15 @@ export class MemoryCookieStore extends Store { return promiseCallback.promise } - const domainEntry: { - [path: string]: { - [key: string]: Cookie - } - } = + const domainEntry = this.idx[domain] ?? - (Object.create(null) as { - [path: string]: { - [key: string]: Cookie - } - }) + (Object.create(null) as MemoryCookieStoreIndex[string]) this.idx[domain] = domainEntry - const pathEntry: { [key: string]: Cookie } = - domainEntry[path] ?? (Object.create(null) as { [key: string]: Cookie }) + const pathEntry = + domainEntry[path] ?? + (Object.create(null) as MemoryCookieStoreIndex[string][string]) domainEntry[path] = pathEntry @@ -367,19 +360,11 @@ export class MemoryCookieStore extends Store { } export function inspectFallback(val: unknown): string { - if (val === null) { - return 'null' - } - - if (val === undefined) { - return 'undefined' - } - if (typeof val === 'string') { return `'${val}'` } - if (typeof val === 'object') { + if (val && typeof val === 'object') { const domains = Object.keys(val) if (domains.length === 0) { return '[Object: null prototype] {}' @@ -398,23 +383,15 @@ export function inspectFallback(val: unknown): string { return result } - return val.toString() + return String(val) } function formatDomain(domainName: string, domainValue: unknown) { - if (domainValue === null) { - return 'null' - } - - if (domainValue === undefined) { - return 'undefined' - } - if (typeof domainValue === 'string') { return `'${domainValue}'` } - if (typeof domainValue === 'object') { + if (domainValue && typeof domainValue === 'object') { const indent = ' ' let result = `${indent}'${domainName}': [Object: null prototype] {\n` Object.keys(domainValue).forEach((path, i, paths) => { @@ -430,23 +407,15 @@ function formatDomain(domainName: string, domainValue: unknown) { return result } - return domainValue.toString() + return String(domainValue) } function formatPath(pathName: string, pathValue: unknown) { - if (pathValue === null) { - return 'null' - } - - if (pathValue === undefined) { - return 'undefined' - } - if (typeof pathValue === 'string') { return `'${pathValue}'` } - if (typeof pathValue === 'object') { + if (pathValue && typeof pathValue === 'object') { const indent = ' ' let result = `${indent}'${pathName}': [Object: null prototype] {\n` Object.keys(pathValue).forEach((cookieName, i, cookieNames) => { @@ -473,7 +442,7 @@ function formatPath(pathName: string, pathValue: unknown) { return result } - return pathValue.toString() + return String(pathValue) } function inOperator( diff --git a/lib/permuteDomain.ts b/lib/permuteDomain.ts index 630436ee..0809a5df 100644 --- a/lib/permuteDomain.ts +++ b/lib/permuteDomain.ts @@ -59,13 +59,10 @@ export function permuteDomain( let cur = pubSuf const permutations = [cur] while (parts.length) { - const part = parts.shift() - if (part !== undefined) { - cur = `${part}.${cur}` - permutations.push(cur) - } else { - throw new Error('domain part value should be a string but was undefined') - } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const part = parts.shift()! + cur = `${part}.${cur}` + permutations.push(cur) } return permutations } diff --git a/lib/pubsuffix-psl.ts b/lib/pubsuffix-psl.ts index 0efa9e1b..a1cc5209 100644 --- a/lib/pubsuffix-psl.ts +++ b/lib/pubsuffix-psl.ts @@ -62,13 +62,10 @@ export function getPublicSuffix( SPECIAL_USE_DOMAINS.includes(topLevelDomain) ) { if (domainParts.length > 1) { - const secondLevelDomain = domainParts[domainParts.length - 2] - if (secondLevelDomain !== undefined) { - // In aforementioned example, the eTLD/pubSuf will be apple.localhost - return `${secondLevelDomain}.${topLevelDomain}` - } else { - throw new Error('expected secondLevelDomain to be a string') - } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const secondLevelDomain = domainParts[domainParts.length - 2]! + // In aforementioned example, the eTLD/pubSuf will be apple.localhost + return `${secondLevelDomain}.${topLevelDomain}` } else if (SPECIAL_TREATMENT_DOMAINS.includes(topLevelDomain)) { // For a single word special use domain, e.g. 'localhost' or 'invalid', per RFC 6761, // "Application software MAY recognize {localhost/invalid} names as special, or diff --git a/lib/store.ts b/lib/store.ts index 558bbd72..1f082a24 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -28,11 +28,15 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ + +// disabling this lint on this whole file because Store should be abstract +// but we have implementations in the wild that may not implement all features +/* eslint-disable @typescript-eslint/no-unused-vars */ + 'use strict' import type { Callback, Cookie } from './cookie' -/* eslint-disable @typescript-eslint/no-unused-vars */ export class Store { synchronous: boolean From 3c656604c666075af95e3a8f539be4cebf109a2d Mon Sep 17 00:00:00 2001 From: Will Harney <62956339+wjhsf@users.noreply.github.com> Date: Fri, 1 Sep 2023 15:20:25 -0400 Subject: [PATCH 33/33] Split giant cookie.ts into multiple files. (#296) * Quiet IDE warnings about TS v5 deprecations. * Add eslint warning for files over 500 lines. * Split giant cookie.ts into multiple files. * Update main export. --- .eslintrc.json | 3 +- lib/__tests__/canonicalDomain.spec.ts | 2 +- lib/__tests__/cookie.spec.ts | 2 +- lib/__tests__/cookieJar.spec.ts | 14 +- lib/__tests__/cookiePrefixes.spec.ts | 3 +- lib/__tests__/cookieSorting.spec.ts | 4 +- lib/__tests__/cookieToAndFromJson.spec.ts | 2 +- lib/__tests__/cookieToString.spec.ts | 2 +- lib/__tests__/date.spec.ts | 2 +- lib/__tests__/defaultPath.spec.ts | 2 +- lib/__tests__/domainMatch.spec.ts | 2 +- lib/__tests__/ietf.spec.ts | 3 +- lib/__tests__/jarSerialization.spec.ts | 16 +- lib/__tests__/lifetime.spec.ts | 2 +- lib/__tests__/memoryCookieStore.spec.ts | 3 +- lib/__tests__/nodeUtilFallback.spec.ts | 7 +- lib/__tests__/parse.spec.ts | 2 +- lib/__tests__/pathMatch.spec.ts | 2 +- lib/__tests__/permuteDomain.spec.ts | 2 +- lib/__tests__/permutePath.spec.ts | 3 +- lib/__tests__/regression.spec.ts | 3 +- lib/__tests__/removeAll.spec.ts | 24 +- lib/__tests__/sameSite.spec.ts | 3 +- lib/cookie.ts | 2414 --------------------- lib/cookie/canonicalDomain.ts | 22 + lib/cookie/constants.ts | 36 + lib/cookie/cookie.ts | 733 +++++++ lib/cookie/cookieCompare.ts | 47 + lib/cookie/cookieJar.ts | 1137 ++++++++++ lib/cookie/defaultPath.ts | 30 + lib/cookie/domainMatch.ts | 71 + lib/cookie/formatDate.ts | 8 + lib/cookie/index.ts | 21 + lib/cookie/parseDate.ts | 254 +++ lib/cookie/permutePath.ts | 23 + lib/memstore.ts | 18 +- lib/store.ts | 3 +- lib/utils.ts | 65 + package.json | 4 +- tsconfig.json | 4 +- 40 files changed, 2509 insertions(+), 2489 deletions(-) delete mode 100644 lib/cookie.ts create mode 100644 lib/cookie/canonicalDomain.ts create mode 100644 lib/cookie/constants.ts create mode 100644 lib/cookie/cookie.ts create mode 100644 lib/cookie/cookieCompare.ts create mode 100644 lib/cookie/cookieJar.ts create mode 100644 lib/cookie/defaultPath.ts create mode 100644 lib/cookie/domainMatch.ts create mode 100644 lib/cookie/formatDate.ts create mode 100644 lib/cookie/index.ts create mode 100644 lib/cookie/parseDate.ts create mode 100644 lib/cookie/permutePath.ts diff --git a/.eslintrc.json b/.eslintrc.json index 67e01125..505d7ad0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,6 +16,7 @@ // this is only needed because `createPromiseCallback` uses arguments to support the callback-style api // we should strongly consider dropping the callback and sync api variants (in say v6) to reduce the // surface area and complexity of tough-cookie - "prefer-rest-params": "warn" + "prefer-rest-params": "warn", + "max-lines": ["warn", 500] } } diff --git a/lib/__tests__/canonicalDomain.spec.ts b/lib/__tests__/canonicalDomain.spec.ts index 3e5fba65..caadc82a 100644 --- a/lib/__tests__/canonicalDomain.spec.ts +++ b/lib/__tests__/canonicalDomain.spec.ts @@ -1,4 +1,4 @@ -import { canonicalDomain } from '../cookie' +import { canonicalDomain } from '../cookie/canonicalDomain' // port of tests/domain_and_path_test.js (canonicalDomain tests for domain normalization) describe('canonicalDomain', () => { diff --git a/lib/__tests__/cookie.spec.ts b/lib/__tests__/cookie.spec.ts index a058c769..5f497bf2 100644 --- a/lib/__tests__/cookie.spec.ts +++ b/lib/__tests__/cookie.spec.ts @@ -29,7 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { Cookie } from '../cookie' +import { Cookie } from '../cookie/cookie' jest.useFakeTimers() diff --git a/lib/__tests__/cookieJar.spec.ts b/lib/__tests__/cookieJar.spec.ts index 9fc503a8..52c240f3 100644 --- a/lib/__tests__/cookieJar.spec.ts +++ b/lib/__tests__/cookieJar.spec.ts @@ -29,14 +29,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { - Cookie, - CookieJar, - MemoryCookieStore, - ParameterError, - SerializedCookieJar, - Store, -} from '../cookie' +import { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' +import type { SerializedCookieJar } from '../cookie/constants' +import { MemoryCookieStore } from '../memstore' +import { Store } from '../store' +import { ParameterError } from '../validators' jest.useFakeTimers() diff --git a/lib/__tests__/cookiePrefixes.spec.ts b/lib/__tests__/cookiePrefixes.spec.ts index e68b316a..770f53a9 100644 --- a/lib/__tests__/cookiePrefixes.spec.ts +++ b/lib/__tests__/cookiePrefixes.spec.ts @@ -1,4 +1,5 @@ -import { CookieJar, PrefixSecurityEnum } from '../cookie' +import { PrefixSecurityEnum } from '../cookie/constants' +import { CookieJar } from '../cookie/cookieJar' let cookieJar: CookieJar const insecureUrl = 'http://www.example.com' diff --git a/lib/__tests__/cookieSorting.spec.ts b/lib/__tests__/cookieSorting.spec.ts index cd2cc4f1..99cecdc5 100644 --- a/lib/__tests__/cookieSorting.spec.ts +++ b/lib/__tests__/cookieSorting.spec.ts @@ -1,4 +1,6 @@ -import { Cookie, cookieCompare, CookieJar } from '../cookie' +import { Cookie } from '../cookie/cookie' +import { cookieCompare } from '../cookie/cookieCompare' +import { CookieJar } from '../cookie/cookieJar' jest.useFakeTimers() diff --git a/lib/__tests__/cookieToAndFromJson.spec.ts b/lib/__tests__/cookieToAndFromJson.spec.ts index a29004ec..373a53e1 100644 --- a/lib/__tests__/cookieToAndFromJson.spec.ts +++ b/lib/__tests__/cookieToAndFromJson.spec.ts @@ -1,4 +1,4 @@ -import { Cookie } from '../cookie' +import { Cookie } from '../cookie/cookie' jest.useFakeTimers() diff --git a/lib/__tests__/cookieToString.spec.ts b/lib/__tests__/cookieToString.spec.ts index 1b9dc46a..74e37a2b 100644 --- a/lib/__tests__/cookieToString.spec.ts +++ b/lib/__tests__/cookieToString.spec.ts @@ -1,4 +1,4 @@ -import { Cookie } from '../cookie' +import { Cookie } from '../cookie/cookie' describe('Cookie.toString()', () => { const parse = (cookieString: string): Cookie => { diff --git a/lib/__tests__/date.spec.ts b/lib/__tests__/date.spec.ts index a3c554e7..535ba3f0 100644 --- a/lib/__tests__/date.spec.ts +++ b/lib/__tests__/date.spec.ts @@ -37,7 +37,7 @@ type EquivalenceDateParsingTestCase = { [key: string]: string } -import { parseDate } from '../cookie' +import { parseDate } from '../cookie/parseDate' const dateTests: DateParsingTestCase = { 'Wed, 09 Jun 2021 10:18:14 GMT': true, diff --git a/lib/__tests__/defaultPath.spec.ts b/lib/__tests__/defaultPath.spec.ts index 178aaee9..0fc55bae 100644 --- a/lib/__tests__/defaultPath.spec.ts +++ b/lib/__tests__/defaultPath.spec.ts @@ -1,4 +1,4 @@ -import { defaultPath } from '../cookie' +import { defaultPath } from '../cookie/defaultPath' // port of tests/domain_and_path_test.js (default path tests) describe('defaultPath', () => { diff --git a/lib/__tests__/domainMatch.spec.ts b/lib/__tests__/domainMatch.spec.ts index 9b6e0585..bfc3afbe 100644 --- a/lib/__tests__/domainMatch.spec.ts +++ b/lib/__tests__/domainMatch.spec.ts @@ -1,4 +1,4 @@ -import { domainMatch } from '../cookie' +import { domainMatch } from '../cookie/domainMatch' // port of tests/domain_and_path_test.js (domain match tests) describe('domainMatch', () => { diff --git a/lib/__tests__/ietf.spec.ts b/lib/__tests__/ietf.spec.ts index 1a058097..7d523663 100644 --- a/lib/__tests__/ietf.spec.ts +++ b/lib/__tests__/ietf.spec.ts @@ -29,11 +29,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ -import { CookieJar, parseDate } from '../cookie' import url from 'url' import parserData from './data/parser' import bsdExampleDates from './data/dates/bsd-examples' import exampleDates from './data/dates/examples' +import { CookieJar } from '../cookie/cookieJar' +import { parseDate } from '../cookie/parseDate' describe('IETF http state tests', () => { describe('Set/get cookie tests', () => { diff --git a/lib/__tests__/jarSerialization.spec.ts b/lib/__tests__/jarSerialization.spec.ts index 9650048a..cd9c3a33 100644 --- a/lib/__tests__/jarSerialization.spec.ts +++ b/lib/__tests__/jarSerialization.spec.ts @@ -28,16 +28,12 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - -import { - Cookie, - CookieJar, - MemoryCookieStore, - SerializedCookie, - SerializedCookieJar, - Store, - version, -} from '../cookie' +import { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' +import type { SerializedCookie, SerializedCookieJar } from '../cookie/constants' +import { MemoryCookieStore } from '../memstore' +import { Store } from '../store' +import { version } from '../version' jest.useFakeTimers() diff --git a/lib/__tests__/lifetime.spec.ts b/lib/__tests__/lifetime.spec.ts index 5f2a7aef..025ad26c 100644 --- a/lib/__tests__/lifetime.spec.ts +++ b/lib/__tests__/lifetime.spec.ts @@ -1,4 +1,4 @@ -import { Cookie } from '../cookie' +import { Cookie } from '../cookie/cookie' jest.useFakeTimers() diff --git a/lib/__tests__/memoryCookieStore.spec.ts b/lib/__tests__/memoryCookieStore.spec.ts index fad701eb..242b8588 100644 --- a/lib/__tests__/memoryCookieStore.spec.ts +++ b/lib/__tests__/memoryCookieStore.spec.ts @@ -1,4 +1,5 @@ -import { Cookie, MemoryCookieStore } from '../cookie' +import { Cookie } from '../cookie/cookie' +import { MemoryCookieStore } from '../memstore' describe('MemoryCookieStore', () => { it('should have no static methods', () => { diff --git a/lib/__tests__/nodeUtilFallback.spec.ts b/lib/__tests__/nodeUtilFallback.spec.ts index 8a959cdb..5a977fdf 100644 --- a/lib/__tests__/nodeUtilFallback.spec.ts +++ b/lib/__tests__/nodeUtilFallback.spec.ts @@ -1,7 +1,8 @@ -import { getCustomInspectSymbol, getUtilInspect } from '../utilHelper' import util from 'util' -import { Cookie, CookieJar, MemoryCookieStore } from '../cookie' -import { inspectFallback } from '../memstore' +import { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' +import { MemoryCookieStore, inspectFallback } from '../memstore' +import { getCustomInspectSymbol, getUtilInspect } from '../utilHelper' jest.useFakeTimers() diff --git a/lib/__tests__/parse.spec.ts b/lib/__tests__/parse.spec.ts index bb6f117d..34c679b9 100644 --- a/lib/__tests__/parse.spec.ts +++ b/lib/__tests__/parse.spec.ts @@ -1,4 +1,4 @@ -import { Cookie } from '../cookie' +import { Cookie } from '../cookie/cookie' import { performance } from 'node:perf_hooks' describe('Cookie.parse', () => { diff --git a/lib/__tests__/pathMatch.spec.ts b/lib/__tests__/pathMatch.spec.ts index 1d42f5cd..aa1ee063 100644 --- a/lib/__tests__/pathMatch.spec.ts +++ b/lib/__tests__/pathMatch.spec.ts @@ -1,4 +1,4 @@ -import { pathMatch } from '../cookie' +import { pathMatch } from '../pathMatch' // port of tests/domain_and_path_test.js (path match tests) describe('pathMatch', () => { diff --git a/lib/__tests__/permuteDomain.spec.ts b/lib/__tests__/permuteDomain.spec.ts index 206dcd01..a3479057 100644 --- a/lib/__tests__/permuteDomain.spec.ts +++ b/lib/__tests__/permuteDomain.spec.ts @@ -1,4 +1,4 @@ -import { permuteDomain } from '../cookie' +import { permuteDomain } from '../permuteDomain' // port of tests/domain_and_path_test.js (permute domain tests) describe('permuteDomain', () => { diff --git a/lib/__tests__/permutePath.spec.ts b/lib/__tests__/permutePath.spec.ts index ee05ff34..756dec98 100644 --- a/lib/__tests__/permutePath.spec.ts +++ b/lib/__tests__/permutePath.spec.ts @@ -1,4 +1,5 @@ -import { pathMatch, permutePath } from '../cookie' +import { permutePath } from '../cookie/permutePath' +import { pathMatch } from '../pathMatch' // port of tests/domain_and_path_test.js (permute path tests) describe('permutePath', () => { diff --git a/lib/__tests__/regression.spec.ts b/lib/__tests__/regression.spec.ts index 5fd06556..daf26a2e 100644 --- a/lib/__tests__/regression.spec.ts +++ b/lib/__tests__/regression.spec.ts @@ -1,4 +1,5 @@ -import { Cookie, CookieJar } from '../cookie' +import type { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' const url = 'http://www.example.com' diff --git a/lib/__tests__/removeAll.spec.ts b/lib/__tests__/removeAll.spec.ts index 3389cb0d..1237df7b 100644 --- a/lib/__tests__/removeAll.spec.ts +++ b/lib/__tests__/removeAll.spec.ts @@ -1,12 +1,8 @@ -import { - Callback, - Cookie, - CookieJar, - MemoryCookieStore, - Store, -} from '../cookie' -import spyOn = jest.spyOn -import SpyInstance = jest.SpyInstance +import type { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' +import { MemoryCookieStore } from '../memstore' +import { Store } from '../store' +import type { Callback } from '../utils' const url = 'http://example.com/index.html' @@ -36,10 +32,7 @@ describe('store removeAllCookies API', () => { // replace remove cookie behavior to throw an error on the 4th invocation const _removeCookie = store.removeCookie.bind(store) - const spy: SpyInstance> = spyOn( - store, - 'removeCookie', - ) + const spy = jest.spyOn(store, 'removeCookie') spy.mockImplementationOnce( (domain: string, path: string, key: string, callback: Callback) => _removeCookie.call(store, domain, path, key, callback), @@ -79,10 +72,7 @@ describe('store removeAllCookies API', () => { // replace remove cookie behavior to throw an error on the 4th invocation const _removeCookie = store.removeCookie.bind(store) - const spy: SpyInstance> = spyOn( - store, - 'removeCookie', - ) + const spy = jest.spyOn(store, 'removeCookie') spy.mockImplementation( ( domain: string, diff --git a/lib/__tests__/sameSite.spec.ts b/lib/__tests__/sameSite.spec.ts index ce1540ad..8259f8ae 100644 --- a/lib/__tests__/sameSite.spec.ts +++ b/lib/__tests__/sameSite.spec.ts @@ -1,4 +1,5 @@ -import { Cookie, CookieJar } from '../cookie' +import { Cookie } from '../cookie/cookie' +import { CookieJar } from '../cookie/cookieJar' const url = 'http://example.com/index.html' diff --git a/lib/cookie.ts b/lib/cookie.ts deleted file mode 100644 index 7d873043..00000000 --- a/lib/cookie.ts +++ /dev/null @@ -1,2414 +0,0 @@ -/*! - * Copyright (c) 2015-2020, Salesforce.com, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. Neither the name of Salesforce.com nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -import * as punycode from 'punycode/' -import urlParse from 'url-parse' -import * as pubsuffix from './pubsuffix-psl' -import { Store } from './store' -import { MemoryCookieStore } from './memstore' -import { pathMatch } from './pathMatch' -import * as validators from './validators' -import { version } from './version' -import { permuteDomain } from './permuteDomain' -import { getCustomInspectSymbol } from './utilHelper' -import { ErrorCallback, safeToString } from './utils' - -// From RFC6265 S4.1.1 -// note that it excludes \x3B ";" -const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/ - -// eslint-disable-next-line no-control-regex -const CONTROL_CHARS = /[\x00-\x1F]/ - -// From Chromium // '\r', '\n' and '\0' should be treated as a terminator in -// the "relaxed" mode, see: -// https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60 -const TERMINATORS = ['\n', '\r', '\0'] - -// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"' -// Note ';' is \x3B -const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/ - -// date-time parsing constants (RFC6265 S5.1.1) - -// eslint-disable-next-line no-control-regex -const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/ - -const MONTH_TO_NUM = { - jan: 0, - feb: 1, - mar: 2, - apr: 3, - may: 4, - jun: 5, - jul: 6, - aug: 7, - sep: 8, - oct: 9, - nov: 10, - dec: 11, -} - -const MAX_TIME = 2147483647000 // 31-bit max -const SAME_SITE_CONTEXT_VAL_ERR = - 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"' - -function checkSameSiteContext(value: string) { - validators.validate(validators.isNonEmptyString(value), value) - const context = String(value).toLowerCase() - if (context === 'none' || context === 'lax' || context === 'strict') { - return context - } else { - return null - } -} - -const PrefixSecurityEnum = Object.freeze({ - SILENT: 'silent', - STRICT: 'strict', - DISABLED: 'unsafe-disabled', -}) - -// Dumped from ip-regex@4.0.0, with the following changes: -// * all capturing groups converted to non-capturing -- "(?:)" -// * support for IPv6 Scoped Literal ("%eth1") removed -// * lowercase hexadecimal only -const IP_REGEX_LOWERCASE = - /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/ -const IP_V6_REGEX = ` -\\[?(?: -(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)| -(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)| -(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)| -(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)| -(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)| -(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)| -(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)| -(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)) -)(?:%[0-9a-zA-Z]{1,})?\\]? -` - .replace(/\s*\/\/.*$/gm, '') - .replace(/\n/g, '') - .trim() -const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`) - -/* - * Parses a Natural number (i.e., non-negative integer) with either the - * *DIGIT ( non-digit *OCTET ) - * or - * *DIGIT - * grammar (RFC6265 S5.1.1). - * - * The "trailingOK" boolean controls if the grammar accepts a - * "( non-digit *OCTET )" trailer. - */ -function parseDigits( - token: string, - minDigits: number, - maxDigits: number, - trailingOK: boolean, -) { - let count = 0 - while (count < token.length) { - const c = token.charCodeAt(count) - // "non-digit = %x00-2F / %x3A-FF" - if (c <= 0x2f || c >= 0x3a) { - break - } - count++ - } - - // constrain to a minimum and maximum number of digits. - if (count < minDigits || count > maxDigits) { - return null - } - - if (!trailingOK && count != token.length) { - return null - } - - return parseInt(token.substr(0, count), 10) -} - -function parseTime(token: string) { - const parts = token.split(':') - const result = [0, 0, 0] - - /* RF6256 S5.1.1: - * time = hms-time ( non-digit *OCTET ) - * hms-time = time-field ":" time-field ":" time-field - * time-field = 1*2DIGIT - */ - - if (parts.length !== 3) { - return null - } - - for (let i = 0; i < 3; i++) { - // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be - // followed by "( non-digit *OCTET )" therefore the last time-field can - // have a trailer - const trailingOK = i == 2 - const numPart = parts[i] - if (numPart == null) { - return null - } - const num = parseDigits(numPart, 1, 2, trailingOK) - if (num === null) { - return null - } - result[i] = num - } - - return result -} - -function parseMonth(token: string) { - token = String(token).substr(0, 3).toLowerCase() - switch (token) { - case 'jan': - return MONTH_TO_NUM.jan - case 'feb': - return MONTH_TO_NUM.feb - case 'mar': - return MONTH_TO_NUM.mar - case 'apr': - return MONTH_TO_NUM.apr - case 'may': - return MONTH_TO_NUM.may - case 'jun': - return MONTH_TO_NUM.jun - case 'jul': - return MONTH_TO_NUM.jul - case 'aug': - return MONTH_TO_NUM.aug - case 'sep': - return MONTH_TO_NUM.sep - case 'oct': - return MONTH_TO_NUM.oct - case 'nov': - return MONTH_TO_NUM.nov - case 'dec': - return MONTH_TO_NUM.dec - default: - return null - } -} - -/* - * RFC6265 S5.1.1 date parser (see RFC for full grammar) - */ -function parseDate(str: string | undefined | null): Date | undefined { - if (!str) { - return undefined - } - - /* RFC6265 S5.1.1: - * 2. Process each date-token sequentially in the order the date-tokens - * appear in the cookie-date - */ - const tokens = str.split(DATE_DELIM) - if (!tokens) { - return undefined - } - - let hour = null - let minute = null - let second = null - let dayOfMonth = null - let month = null - let year = null - - for (let i = 0; i < tokens.length; i++) { - const token = (tokens[i] ?? '').trim() - if (!token.length) { - continue - } - - let result - - /* 2.1. If the found-time flag is not set and the token matches the time - * production, set the found-time flag and set the hour- value, - * minute-value, and second-value to the numbers denoted by the digits in - * the date-token, respectively. Skip the remaining sub-steps and continue - * to the next date-token. - */ - if (second === null) { - result = parseTime(token) - if (result) { - hour = result[0] - minute = result[1] - second = result[2] - continue - } - } - - /* 2.2. If the found-day-of-month flag is not set and the date-token matches - * the day-of-month production, set the found-day-of- month flag and set - * the day-of-month-value to the number denoted by the date-token. Skip - * the remaining sub-steps and continue to the next date-token. - */ - if (dayOfMonth === null) { - // "day-of-month = 1*2DIGIT ( non-digit *OCTET )" - result = parseDigits(token, 1, 2, true) - if (result !== null) { - dayOfMonth = result - continue - } - } - - /* 2.3. If the found-month flag is not set and the date-token matches the - * month production, set the found-month flag and set the month-value to - * the month denoted by the date-token. Skip the remaining sub-steps and - * continue to the next date-token. - */ - if (month === null) { - result = parseMonth(token) - if (result !== null) { - month = result - continue - } - } - - /* 2.4. If the found-year flag is not set and the date-token matches the - * year production, set the found-year flag and set the year-value to the - * number denoted by the date-token. Skip the remaining sub-steps and - * continue to the next date-token. - */ - if (year === null) { - // "year = 2*4DIGIT ( non-digit *OCTET )" - result = parseDigits(token, 2, 4, true) - if (result !== null) { - year = result - /* From S5.1.1: - * 3. If the year-value is greater than or equal to 70 and less - * than or equal to 99, increment the year-value by 1900. - * 4. If the year-value is greater than or equal to 0 and less - * than or equal to 69, increment the year-value by 2000. - */ - if (year >= 70 && year <= 99) { - year += 1900 - } else if (year >= 0 && year <= 69) { - year += 2000 - } - } - } - } - - /* RFC 6265 S5.1.1 - * "5. Abort these steps and fail to parse the cookie-date if: - * * at least one of the found-day-of-month, found-month, found- - * year, or found-time flags is not set, - * * the day-of-month-value is less than 1 or greater than 31, - * * the year-value is less than 1601, - * * the hour-value is greater than 23, - * * the minute-value is greater than 59, or - * * the second-value is greater than 59. - * (Note that leap seconds cannot be represented in this syntax.)" - * - * So, in order as above: - */ - if ( - dayOfMonth === null || - month == null || - year == null || - hour == null || - minute == null || - second == null || - dayOfMonth < 1 || - dayOfMonth > 31 || - year < 1601 || - hour > 23 || - minute > 59 || - second > 59 - ) { - return undefined - } - - return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)) -} - -function formatDate(date: Date) { - validators.validate(validators.isDate(date), safeToString(date)) - return date.toUTCString() -} - -// S5.1.2 Canonicalized Host Names -function canonicalDomain(str: string | null) { - if (str == null) { - return null - } - let _str = str.trim().replace(/^\./, '') // S4.1.2.3 & S5.2.3: ignore leading . - - if (IP_V6_REGEX_OBJECT.test(_str)) { - _str = _str.replace('[', '').replace(']', '') - } - - // convert to IDN if any non-ASCII characters - // eslint-disable-next-line no-control-regex - if (punycode && /[^\u0001-\u007f]/.test(_str)) { - _str = punycode.toASCII(_str) - } - - return _str.toLowerCase() -} - -// S5.1.3 Domain Matching -function domainMatch( - str?: string | null, - domStr?: string | null, - canonicalize?: boolean, -): boolean | null { - if (str == null || domStr == null) { - return null - } - - let _str: string | null - let _domStr: string | null - - if (canonicalize !== false) { - _str = canonicalDomain(str) - _domStr = canonicalDomain(domStr) - } else { - _str = str - _domStr = domStr - } - - if (_str == null || _domStr == null) { - return null - } - - /* - * S5.1.3: - * "A string domain-matches a given domain string if at least one of the - * following conditions hold:" - * - * " o The domain string and the string are identical. (Note that both the - * domain string and the string will have been canonicalized to lower case at - * this point)" - */ - if (_str == _domStr) { - return true - } - - /* " o All of the following [three] conditions hold:" */ - - /* "* The domain string is a suffix of the string" */ - const idx = _str.lastIndexOf(domStr) - if (idx <= 0) { - return false // it's a non-match (-1) or prefix (0) - } - - // next, check it's a proper suffix - // e.g., "a.b.c".indexOf("b.c") === 2 - // 5 === 3+2 - if (_str.length !== _domStr.length + idx) { - return false // it's not a suffix - } - - /* " * The last character of the string that is not included in the - * domain string is a %x2E (".") character." */ - if (_str.substr(idx - 1, 1) !== '.') { - return false // doesn't align on "." - } - - /* " * The string is a host name (i.e., not an IP address)." */ - return !IP_REGEX_LOWERCASE.test(_str) -} - -// RFC6265 S5.1.4 Paths and Path-Match - -/* - * "The user agent MUST use an algorithm equivalent to the following algorithm - * to compute the default-path of a cookie:" - * - * Assumption: the path (and not query part or absolute uri) is passed in. - */ -function defaultPath(path?: string | null): string { - // "2. If the uri-path is empty or if the first character of the uri-path is not - // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. - if (!path || path.substr(0, 1) !== '/') { - return '/' - } - - // "3. If the uri-path contains no more than one %x2F ("/") character, output - // %x2F ("/") and skip the remaining step." - if (path === '/') { - return path - } - - const rightSlash = path.lastIndexOf('/') - if (rightSlash === 0) { - return '/' - } - - // "4. Output the characters of the uri-path from the first character up to, - // but not including, the right-most %x2F ("/")." - return path.slice(0, rightSlash) -} - -function trimTerminator(str: string) { - if (validators.isEmptyString(str)) return str - for (let t = 0; t < TERMINATORS.length; t++) { - const terminator = TERMINATORS[t] - const terminatorIdx = terminator ? str.indexOf(terminator) : -1 - if (terminatorIdx !== -1) { - str = str.substr(0, terminatorIdx) - } - } - - return str -} - -function parseCookiePair(cookiePair: string, looseMode: boolean) { - cookiePair = trimTerminator(cookiePair) - validators.validate(validators.isString(cookiePair), cookiePair) - - let firstEq = cookiePair.indexOf('=') - if (looseMode) { - if (firstEq === 0) { - // '=' is immediately at start - cookiePair = cookiePair.substr(1) - firstEq = cookiePair.indexOf('=') // might still need to split on '=' - } - } else { - // non-loose mode - if (firstEq <= 0) { - // no '=' or is at start - return undefined // needs to have non-empty "cookie-name" - } - } - - let cookieName, cookieValue - if (firstEq <= 0) { - cookieName = '' - cookieValue = cookiePair.trim() - } else { - cookieName = cookiePair.substr(0, firstEq).trim() - cookieValue = cookiePair.substr(firstEq + 1).trim() - } - - if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) { - return undefined - } - - const c = new Cookie() - c.key = cookieName - c.value = cookieValue - return c -} - -function parse( - str: string, - options?: ParseCookieOptions, -): Cookie | undefined | null { - if (validators.isEmptyString(str) || !validators.isString(str)) { - return null - } - - str = str.trim() - - // We use a regex to parse the "name-value-pair" part of S5.2 - const firstSemi = str.indexOf(';') // S5.2 step 1 - const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi) - const c = parseCookiePair(cookiePair, options?.loose ?? false) - if (!c) { - return undefined - } - - if (firstSemi === -1) { - return c - } - - // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string - // (including the %x3B (";") in question)." plus later on in the same section - // "discard the first ";" and trim". - const unparsed = str.slice(firstSemi + 1).trim() - - // "If the unparsed-attributes string is empty, skip the rest of these - // steps." - if (unparsed.length === 0) { - return c - } - - /* - * S5.2 says that when looping over the items "[p]rocess the attribute-name - * and attribute-value according to the requirements in the following - * subsections" for every item. Plus, for many of the individual attributes - * in S5.3 it says to use the "attribute-value of the last attribute in the - * cookie-attribute-list". Therefore, in this implementation, we overwrite - * the previous value. - */ - const cookie_avs = unparsed.split(';') - while (cookie_avs.length) { - const av = (cookie_avs.shift() ?? '').trim() - if (av.length === 0) { - // happens if ";;" appears - continue - } - const av_sep = av.indexOf('=') - let av_key, av_value - - if (av_sep === -1) { - av_key = av - av_value = null - } else { - av_key = av.substr(0, av_sep) - av_value = av.substr(av_sep + 1) - } - - av_key = av_key.trim().toLowerCase() - - if (av_value) { - av_value = av_value.trim() - } - - switch (av_key) { - case 'expires': // S5.2.1 - if (av_value) { - const exp = parseDate(av_value) - // "If the attribute-value failed to parse as a cookie date, ignore the - // cookie-av." - if (exp) { - // over and underflow not realistically a concern: V8's getTime() seems to - // store something larger than a 32-bit time_t (even with 32-bit node) - c.expires = exp - } - } - break - - case 'max-age': // S5.2.2 - if (av_value) { - // "If the first character of the attribute-value is not a DIGIT or a "-" - // character ...[or]... If the remainder of attribute-value contains a - // non-DIGIT character, ignore the cookie-av." - if (/^-?[0-9]+$/.test(av_value)) { - const delta = parseInt(av_value, 10) - // "If delta-seconds is less than or equal to zero (0), let expiry-time - // be the earliest representable date and time." - c.setMaxAge(delta) - } - } - break - - case 'domain': // S5.2.3 - // "If the attribute-value is empty, the behavior is undefined. However, - // the user agent SHOULD ignore the cookie-av entirely." - if (av_value) { - // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E - // (".") character." - const domain = av_value.trim().replace(/^\./, '') - if (domain) { - // "Convert the cookie-domain to lower case." - c.domain = domain.toLowerCase() - } - } - break - - case 'path': // S5.2.4 - /* - * "If the attribute-value is empty or if the first character of the - * attribute-value is not %x2F ("/"): - * Let cookie-path be the default-path. - * Otherwise: - * Let cookie-path be the attribute-value." - * - * We'll represent the default-path as null since it depends on the - * context of the parsing. - */ - c.path = av_value && av_value[0] === '/' ? av_value : null - break - - case 'secure': // S5.2.5 - /* - * "If the attribute-name case-insensitively matches the string "Secure", - * the user agent MUST append an attribute to the cookie-attribute-list - * with an attribute-name of Secure and an empty attribute-value." - */ - c.secure = true - break - - case 'httponly': // S5.2.6 -- effectively the same as 'secure' - c.httpOnly = true - break - - case 'samesite': // RFC6265bis-02 S5.3.7 - switch (av_value ? av_value.toLowerCase() : '') { - case 'strict': - c.sameSite = 'strict' - break - case 'lax': - c.sameSite = 'lax' - break - case 'none': - c.sameSite = 'none' - break - default: - c.sameSite = undefined - break - } - break - - default: - c.extensions = c.extensions || [] - c.extensions.push(av) - break - } - } - - return c -} - -/** - * If the cookie-name begins with a case-sensitive match for the - * string "__Secure-", abort these steps and ignore the cookie - * entirely unless the cookie's secure-only-flag is true. - * @param cookie - * @returns boolean - */ -function isSecurePrefixConditionMet(cookie: Cookie) { - validators.validate(validators.isObject(cookie), safeToString(cookie)) - const startsWithSecurePrefix = - typeof cookie.key === 'string' && cookie.key.startsWith('__Secure-') - return !startsWithSecurePrefix || cookie.secure -} - -/** - * If the cookie-name begins with a case-sensitive match for the - * string "__Host-", abort these steps and ignore the cookie - * entirely unless the cookie meets all the following criteria: - * 1. The cookie's secure-only-flag is true. - * 2. The cookie's host-only-flag is true. - * 3. The cookie-attribute-list contains an attribute with an - * attribute-name of "Path", and the cookie's path is "/". - * @param cookie - * @returns boolean - */ -function isHostPrefixConditionMet(cookie: Cookie) { - validators.validate(validators.isObject(cookie)) - const startsWithHostPrefix = - typeof cookie.key === 'string' && cookie.key.startsWith('__Host-') - return ( - !startsWithHostPrefix || - (cookie.secure && - cookie.hostOnly && - cookie.path != null && - cookie.path === '/') - ) -} - -function fromJSON(str: string | SerializedCookie | null | undefined | unknown) { - if (!str || validators.isEmptyString(str)) { - return null - } - - let obj: unknown - if (typeof str === 'string') { - try { - obj = JSON.parse(str) - } catch (e) { - return null - } - } else { - // assume it's an Object - obj = str - } - - const c = new Cookie() - Cookie.serializableProperties.forEach((prop) => { - if (obj && typeof obj === 'object' && inOperator(prop, obj)) { - const val = obj[prop] - if (val === undefined) { - return - } - - if (inOperator(prop, cookieDefaults) && val === cookieDefaults[prop]) { - return - } - - switch (prop) { - case 'key': - case 'value': - case 'sameSite': - if (typeof val === 'string') { - c[prop] = val - } - break - case 'expires': - case 'creation': - case 'lastAccessed': - if ( - typeof val === 'number' || - typeof val === 'string' || - val instanceof Date - ) { - c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(val) - } else if (val === null) { - c[prop] = null - } - break - case 'maxAge': - if ( - typeof val === 'number' || - val === 'Infinity' || - val === '-Infinity' - ) { - c[prop] = val - } - break - case 'domain': - case 'path': - if (typeof val === 'string' || val === null) { - c[prop] = val - } - break - case 'secure': - case 'httpOnly': - if (typeof val === 'boolean') { - c[prop] = val - } - break - case 'extensions': - if ( - Array.isArray(val) && - val.every((item) => typeof item === 'string') - ) { - c[prop] = val - } - break - case 'hostOnly': - case 'pathIsDefault': - if (typeof val === 'boolean' || val === null) { - c[prop] = val - } - break - } - } - }) - - return c -} - -/* Section 5.4 part 2: - * "* Cookies with longer paths are listed before cookies with - * shorter paths. - * - * * Among cookies that have equal-length path fields, cookies with - * earlier creation-times are listed before cookies with later - * creation-times." - */ - -function cookieCompare(a: Cookie, b: Cookie) { - validators.validate(validators.isObject(a), safeToString(a)) - validators.validate(validators.isObject(b), safeToString(b)) - let cmp: number - - // descending for length: b CMP a - const aPathLen = a.path ? a.path.length : 0 - const bPathLen = b.path ? b.path.length : 0 - cmp = bPathLen - aPathLen - if (cmp !== 0) { - return cmp - } - - // ascending for time: a CMP b - const aTime = - a.creation && a.creation instanceof Date ? a.creation.getTime() : MAX_TIME - const bTime = - b.creation && b.creation instanceof Date ? b.creation.getTime() : MAX_TIME - cmp = aTime - bTime - if (cmp !== 0) { - return cmp - } - - // break ties for the same millisecond (precision of JavaScript's clock) - cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0) - - return cmp -} - -// Gives the permutation of all possible pathMatch()es of a given path. The -// array is in longest-to-shortest order. Handy for indexing. -function permutePath(path: string): string[] { - validators.validate(validators.isString(path)) - if (path === '/') { - return ['/'] - } - const permutations = [path] - while (path.length > 1) { - const lindex = path.lastIndexOf('/') - if (lindex === 0) { - break - } - path = path.substr(0, lindex) - permutations.push(path) - } - permutations.push('/') - return permutations -} - -function getCookieContext(url: string | URL) { - if (url instanceof URL && 'query' in url) { - return url - } - - if (typeof url === 'string') { - try { - return urlParse(decodeURI(url)) - } catch { - return urlParse(url) - } - } - - throw new Error('`url` argument is invalid') -} - -const cookieDefaults = { - // the order in which the RFC has them: - key: '', - value: '', - expires: 'Infinity', - maxAge: null, - domain: null, - path: null, - secure: false, - httpOnly: false, - extensions: null, - // set by the CookieJar: - hostOnly: null, - pathIsDefault: null, - creation: null, - lastAccessed: null, - sameSite: undefined, -} - -export type CreateCookieOptions = { - key?: string - value?: string - expires?: Date | 'Infinity' | null - maxAge?: number | 'Infinity' | '-Infinity' - domain?: string | null - path?: string | null - secure?: boolean - httpOnly?: boolean - extensions?: string[] | null - creation?: Date | 'Infinity' | null - creationIndex?: number - hostOnly?: boolean | null - pathIsDefault?: boolean | null - lastAccessed?: Date | 'Infinity' | null - sameSite?: string | undefined -} - -export class Cookie { - key: string | undefined - value: string | undefined - expires: Date | 'Infinity' | null | undefined - maxAge: number | 'Infinity' | '-Infinity' | undefined - domain: string | null | undefined - path: string | null | undefined - secure: boolean | undefined - httpOnly: boolean | undefined - extensions: string[] | null | undefined - creation: Date | 'Infinity' | null - creationIndex: number | undefined - hostOnly: boolean | null | undefined - pathIsDefault: boolean | null | undefined - lastAccessed: Date | 'Infinity' | null | undefined - sameSite: string | undefined - - constructor(options: CreateCookieOptions = {}) { - // supports inspect if that feature is available in the environment - const customInspectSymbol = getCustomInspectSymbol() - if (customInspectSymbol) { - Object.defineProperty(this, customInspectSymbol, { - value: this.inspect.bind(this), - enumerable: false, - writable: false, - configurable: false, - }) - } - - Object.assign(this, cookieDefaults, options) - this.creation = options.creation ?? cookieDefaults.creation ?? new Date() - - // used to break creation ties in cookieCompare(): - Object.defineProperty(this, 'creationIndex', { - configurable: false, - enumerable: false, // important for assert.deepEqual checks - writable: true, - value: ++Cookie.cookiesCreated, - }) - } - - inspect() { - const now = Date.now() - const hostOnly = this.hostOnly != null ? this.hostOnly.toString() : '?' - const createAge = - this.creation && this.creation !== 'Infinity' - ? `${now - this.creation.getTime()}ms` - : '?' - const accessAge = - this.lastAccessed && this.lastAccessed !== 'Infinity' - ? `${now - this.lastAccessed.getTime()}ms` - : '?' - return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"` - } - - toJSON(): SerializedCookie { - const obj: SerializedCookie = {} - - for (const prop of Cookie.serializableProperties) { - const val = this[prop] - - if (val === cookieDefaults[prop]) { - continue // leave as prototype default - } - - switch (prop) { - case 'key': - case 'value': - case 'sameSite': - if (typeof val === 'string') { - obj[prop] = val - } - break - case 'expires': - case 'creation': - case 'lastAccessed': - if ( - typeof val === 'number' || - typeof val === 'string' || - val instanceof Date - ) { - obj[prop] = - val == 'Infinity' ? 'Infinity' : new Date(val).toISOString() - } else if (val === null) { - obj[prop] = null - } - break - case 'maxAge': - if ( - typeof val === 'number' || - val === 'Infinity' || - val === '-Infinity' - ) { - obj[prop] = val - } - break - case 'domain': - case 'path': - if (typeof val === 'string' || val === null) { - obj[prop] = val - } - break - case 'secure': - case 'httpOnly': - if (typeof val === 'boolean') { - obj[prop] = val - } - break - case 'extensions': - if (Array.isArray(val)) { - obj[prop] = val - } - break - case 'hostOnly': - case 'pathIsDefault': - if (typeof val === 'boolean' || val === null) { - obj[prop] = val - } - break - } - } - - return obj - } - - clone() { - return fromJSON(this.toJSON()) - } - - validate() { - if (this.value == null || !COOKIE_OCTETS.test(this.value)) { - return false - } - if ( - this.expires != 'Infinity' && - !(this.expires instanceof Date) && - !parseDate(this.expires) - ) { - return false - } - if (this.maxAge != null && this.maxAge <= 0) { - return false // "Max-Age=" non-zero-digit *DIGIT - } - if (this.path != null && !PATH_VALUE.test(this.path)) { - return false - } - - const cdomain = this.cdomain() - if (cdomain) { - if (cdomain.match(/\.$/)) { - return false // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this - } - const suffix = pubsuffix.getPublicSuffix(cdomain) - if (suffix == null) { - // it's a public suffix - return false - } - } - return true - } - - setExpires(exp: string | Date) { - if (exp instanceof Date) { - this.expires = exp - } else { - this.expires = parseDate(exp) || 'Infinity' - } - } - - setMaxAge(age: number) { - if (age === Infinity) { - this.maxAge = 'Infinity' - } else if (age === -Infinity) { - this.maxAge = '-Infinity' - } else { - this.maxAge = age - } - } - - cookieString() { - const val = this.value ?? '' - if (this.key) { - return `${this.key}=${val}` - } - return val - } - - // gives Set-Cookie header format - toString() { - let str = this.cookieString() - - if (this.expires != 'Infinity') { - if (this.expires instanceof Date) { - str += `; Expires=${formatDate(this.expires)}` - } - } - - if (this.maxAge != null && this.maxAge != Infinity) { - str += `; Max-Age=${this.maxAge}` - } - - if (this.domain && !this.hostOnly) { - str += `; Domain=${this.domain}` - } - if (this.path) { - str += `; Path=${this.path}` - } - - if (this.secure) { - str += '; Secure' - } - if (this.httpOnly) { - str += '; HttpOnly' - } - if (this.sameSite && this.sameSite !== 'none') { - if ( - this.sameSite.toLowerCase() === - Cookie.sameSiteCanonical.lax.toLowerCase() - ) { - str += `; SameSite=${Cookie.sameSiteCanonical.lax}` - } else if ( - this.sameSite.toLowerCase() === - Cookie.sameSiteCanonical.strict.toLowerCase() - ) { - str += `; SameSite=${Cookie.sameSiteCanonical.strict}` - } else { - str += `; SameSite=${this.sameSite}` - } - } - if (this.extensions) { - this.extensions.forEach((ext) => { - str += `; ${ext}` - }) - } - - return str - } - - // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie() - // elsewhere) - // S5.3 says to give the "latest representable date" for which we use Infinity - // For "expired" we use 0 - TTL(now: number = Date.now()): number { - /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires - * attribute, the Max-Age attribute has precedence and controls the - * expiration date of the cookie. - * (Concurs with S5.3 step 3) - */ - if (this.maxAge != null && typeof this.maxAge === 'number') { - return this.maxAge <= 0 ? 0 : this.maxAge * 1000 - } - - const expires = this.expires - if (expires === 'Infinity') { - return Infinity - } - - return (expires?.getTime() ?? now) - (now || Date.now()) - } - - // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() - // elsewhere) - expiryTime(now?: Date): number | undefined { - if (this.maxAge != null) { - const relativeTo = now || this.creation || new Date() - const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity - const age = maxAge <= 0 ? -Infinity : maxAge * 1000 - if (relativeTo === 'Infinity') { - return Infinity - } - return relativeTo.getTime() + age - } - - if (this.expires == 'Infinity') { - return Infinity - } - - return this.expires ? this.expires.getTime() : undefined - } - - // This replaces the "persistent-flag" parts of S5.3 step 3 - isPersistent(): boolean { - return this.maxAge != null || this.expires != 'Infinity' - } - - // Mostly S5.1.2 and S5.2.3: - canonicalizedDomain() { - if (this.domain == null) { - return null - } - return canonicalDomain(this.domain) - } - - cdomain() { - return this.canonicalizedDomain() - } - - static parse( - cookieString: string, - options?: ParseCookieOptions, - ): Cookie | undefined | null { - return parse(cookieString, options) - } - - static fromJSON(jsonString: string | null | undefined): Cookie | null { - return fromJSON(jsonString) - } - - static cookiesCreated = 0 - - static sameSiteLevel = { - strict: 3, - lax: 2, - none: 1, - } as const - - static sameSiteCanonical = { - strict: 'Strict', - lax: 'Lax', - } as const - - static serializableProperties = [ - 'key', - 'value', - 'expires', - 'maxAge', - 'domain', - 'path', - 'secure', - 'httpOnly', - 'extensions', - 'hostOnly', - 'pathIsDefault', - 'creation', - 'lastAccessed', - 'sameSite', - ] as const -} - -function getNormalizedPrefixSecurity(prefixSecurity: string) { - if (prefixSecurity != null) { - const normalizedPrefixSecurity = prefixSecurity.toLowerCase() - /* The three supported options */ - switch (normalizedPrefixSecurity) { - case PrefixSecurityEnum.STRICT: - case PrefixSecurityEnum.SILENT: - case PrefixSecurityEnum.DISABLED: - return normalizedPrefixSecurity - } - } - /* Default is SILENT */ - return PrefixSecurityEnum.SILENT -} - -const defaultSetCookieOptions: SetCookieOptions = { - loose: false, - sameSiteContext: undefined, - ignoreError: false, - http: true, -} - -const defaultGetCookieOptions: GetCookiesOptions = { - http: true, - expire: true, - allPaths: false, - sameSiteContext: undefined, - sort: undefined, -} - -export function createPromiseCallback(args: IArguments): PromiseCallback { - let callback: (error: Error | null | undefined, result: T | undefined) => void - let resolve: (result: T | undefined) => void - let reject: (error: Error | null) => void - - const promise = new Promise((_resolve, _reject) => { - resolve = _resolve - reject = _reject - }) - - const cb = args[args.length - 1] as unknown - if (typeof cb === 'function') { - callback = (err, result) => { - try { - cb(err, result) - } catch (e) { - reject(e instanceof Error ? e : new Error()) - } - } - } else { - callback = (err, result) => { - try { - err ? reject(err) : resolve(result) - } catch (e) { - reject(e instanceof Error ? e : new Error()) - } - } - } - - return { - promise, - callback, - resolve: (value: T | undefined) => { - callback(null, value) - return promise - }, - reject: (error: Error | null | undefined) => { - callback(error, undefined) - return promise - }, - } -} - -export type CreateCookieJarOptions = { - rejectPublicSuffixes?: boolean | undefined - looseMode?: boolean | undefined - prefixSecurity?: 'strict' | 'silent' | 'unsafe-disabled' | undefined - allowSpecialUseDomain?: boolean | undefined -} - -export class CookieJar { - readonly store: Store - private readonly rejectPublicSuffixes: boolean - private readonly enableLooseMode: boolean - private readonly allowSpecialUseDomain: boolean - readonly prefixSecurity: string - - constructor( - store?: Store | null | undefined, - options?: CreateCookieJarOptions | boolean, - ) { - if (typeof options === 'boolean') { - options = { rejectPublicSuffixes: options } - } - this.rejectPublicSuffixes = options?.rejectPublicSuffixes ?? true - this.enableLooseMode = options?.looseMode ?? false - this.allowSpecialUseDomain = options?.allowSpecialUseDomain ?? true - this.prefixSecurity = getNormalizedPrefixSecurity( - options?.prefixSecurity ?? 'silent', - ) - this.store = store ?? new MemoryCookieStore() - } - - private callSync(fn: (callback: Callback) => void): T | undefined { - if (!this.store.synchronous) { - throw new Error( - 'CookieJar store is not synchronous; use async API instead.', - ) - } - let syncErr: Error | undefined - let syncResult: T | undefined = undefined - fn.call(this, (error, result) => { - syncErr = error - syncResult = result - }) - if (syncErr) { - throw syncErr - } - - return syncResult - } - - setCookie( - cookie: string | Cookie, - url: string, - callback: Callback, - ): void - setCookie( - cookie: string | Cookie, - url: string, - options: SetCookieOptions, - callback: Callback, - ): void - setCookie(cookie: string | Cookie, url: string): Promise - setCookie( - cookie: string | Cookie, - url: string, - options: SetCookieOptions, - ): Promise - setCookie( - cookie: string | Cookie, - url: string, - options: SetCookieOptions | Callback, - callback?: Callback, - ): unknown - setCookie( - cookie: string | Cookie, - url: string, - options?: SetCookieOptions | Callback, - callback?: Callback, - ): unknown { - const promiseCallback = createPromiseCallback(arguments) - const cb = promiseCallback.callback - - validators.validate( - validators.isNonEmptyString(url), - callback, - safeToString(options), - ) - let err - - if (typeof url === 'function') { - return promiseCallback.reject(new Error('No URL was specified')) - } - - const context = getCookieContext(url) - if (typeof options === 'function') { - options = defaultSetCookieOptions - } - - validators.validate(typeof cb === 'function', cb) - - if ( - !validators.isNonEmptyString(cookie) && - !validators.isObject(cookie) && - cookie instanceof String && - cookie.length == 0 - ) { - return promiseCallback.reject(null) - } - - const host = canonicalDomain(context.hostname) - const loose = options?.loose || this.enableLooseMode - - let sameSiteContext = null - if (options?.sameSiteContext) { - sameSiteContext = checkSameSiteContext(options.sameSiteContext) - if (!sameSiteContext) { - return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) - } - } - - // S5.3 step 1 - if (typeof cookie === 'string' || cookie instanceof String) { - const parsedCookie = Cookie.parse(cookie.toString(), { loose: loose }) - if (!parsedCookie) { - err = new Error('Cookie failed to parse') - return promiseCallback.reject(options?.ignoreError ? null : err) - } - cookie = parsedCookie - } else if (!(cookie instanceof Cookie)) { - // If you're seeing this error, and are passing in a Cookie object, - // it *might* be a Cookie object from another loaded version of tough-cookie. - err = new Error( - 'First argument to setCookie must be a Cookie object or string', - ) - return promiseCallback.reject(options?.ignoreError ? null : err) - } - - // S5.3 step 2 - const now = options?.now || new Date() // will assign later to save effort in the face of errors - - // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie() - - // S5.3 step 4: NOOP; domain is null by default - - // S5.3 step 5: public suffixes - if (this.rejectPublicSuffixes && cookie.domain) { - try { - const cdomain = cookie.cdomain() - const suffix = - typeof cdomain === 'string' - ? pubsuffix.getPublicSuffix(cdomain, { - allowSpecialUseDomain: this.allowSpecialUseDomain, - ignoreError: options?.ignoreError, - }) - : null - if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { - // e.g. "com" - err = new Error('Cookie has domain set to a public suffix') - return promiseCallback.reject(options?.ignoreError ? null : err) - } - } catch (err) { - if (options?.ignoreError) { - return promiseCallback.reject(null) - } else { - if (err instanceof Error) { - return promiseCallback.reject(err) - } else { - return promiseCallback.reject(null) - } - } - } - } - - // S5.3 step 6: - if (cookie.domain) { - if ( - !domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false) - ) { - err = new Error( - `Cookie not in this host's domain. Cookie:${ - cookie.cdomain() ?? 'null' - } Request:${host ?? 'null'}`, - ) - return promiseCallback.reject(options?.ignoreError ? null : err) - } - - if (cookie.hostOnly == null) { - // don't reset if already set - cookie.hostOnly = false - } - } else { - cookie.hostOnly = true - cookie.domain = host - } - - //S5.2.4 If the attribute-value is empty or if the first character of the - //attribute-value is not %x2F ("/"): - //Let cookie-path be the default-path. - if (!cookie.path || cookie.path[0] !== '/') { - cookie.path = defaultPath(context.pathname ?? undefined) - cookie.pathIsDefault = true - } - - // S5.3 step 8: NOOP; secure attribute - // S5.3 step 9: NOOP; httpOnly attribute - - // S5.3 step 10 - if (options?.http === false && cookie.httpOnly) { - err = new Error("Cookie is HttpOnly and this isn't an HTTP API") - return promiseCallback.reject(options?.ignoreError ? null : err) - } - - // 6252bis-02 S5.4 Step 13 & 14: - if ( - cookie.sameSite !== 'none' && - cookie.sameSite !== undefined && - sameSiteContext - ) { - // "If the cookie's "same-site-flag" is not "None", and the cookie - // is being set from a context whose "site for cookies" is not an - // exact match for request-uri's host's registered domain, then - // abort these steps and ignore the newly created cookie entirely." - if (sameSiteContext === 'none') { - err = new Error('Cookie is SameSite but this is a cross-origin request') - return promiseCallback.reject(options?.ignoreError ? null : err) - } - } - - /* 6265bis-02 S5.4 Steps 15 & 16 */ - const ignoreErrorForPrefixSecurity = - this.prefixSecurity === PrefixSecurityEnum.SILENT - const prefixSecurityDisabled = - this.prefixSecurity === PrefixSecurityEnum.DISABLED - /* If prefix checking is not disabled ...*/ - if (!prefixSecurityDisabled) { - let errorFound = false - let errorMsg - /* Check secure prefix condition */ - if (!isSecurePrefixConditionMet(cookie)) { - errorFound = true - errorMsg = 'Cookie has __Secure prefix but Secure attribute is not set' - } else if (!isHostPrefixConditionMet(cookie)) { - /* Check host prefix condition */ - errorFound = true - errorMsg = - "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'" - } - if (errorFound) { - return promiseCallback.reject( - options?.ignoreError || ignoreErrorForPrefixSecurity - ? null - : new Error(errorMsg), - ) - } - } - - const store = this.store - - if (!store.updateCookie) { - store.updateCookie = function ( - _oldCookie: Cookie, - newCookie: Cookie, - cb?: Callback, - ): Promise { - return this.putCookie(newCookie).then( - () => { - if (cb) { - cb(undefined, undefined) - } - }, - (error: Error) => { - if (cb) { - cb(error, undefined) - } - }, - ) - } - } - - function withCookie( - err: Error | undefined, - oldCookie: Cookie | undefined | null, - ): void { - if (err) { - cb(err) - return - } - - const next = function (err: Error | undefined): void { - if (err || typeof cookie === 'string') { - cb(err) - } else { - cb(null, cookie) - } - } - - if (oldCookie) { - // S5.3 step 11 - "If the cookie store contains a cookie with the same name, - // domain, and path as the newly created cookie:" - if ( - options && - 'http' in options && - options.http === false && - oldCookie.httpOnly - ) { - // step 11.2 - err = new Error("old Cookie is HttpOnly and this isn't an HTTP API") - cb(options.ignoreError ? null : err) - return - } - if (cookie instanceof Cookie) { - cookie.creation = oldCookie.creation - // step 11.3 - cookie.creationIndex = oldCookie.creationIndex - // preserve tie-breaker - cookie.lastAccessed = now - // Step 11.4 (delete cookie) is implied by just setting the new one: - store.updateCookie(oldCookie, cookie, next) // step 12 - } - } else { - if (cookie instanceof Cookie) { - cookie.creation = cookie.lastAccessed = now - store.putCookie(cookie, next) // step 12 - } - } - } - - store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie) - return promiseCallback.promise - } - setCookieSync( - cookie: string | Cookie, - url: string, - options?: SetCookieOptions, - ): Cookie | undefined { - const setCookieFn = this.setCookie.bind( - this, - cookie, - url, - options as SetCookieOptions, - ) - return this.callSync(setCookieFn) - } - - // RFC6365 S5.4 - getCookies(url: string, callback: Callback): void - getCookies( - url: string, - options: GetCookiesOptions | undefined, - callback: Callback, - ): void - getCookies(url: string): Promise - getCookies( - url: string, - options: GetCookiesOptions | undefined, - ): Promise - getCookies( - url: string, - options: GetCookiesOptions | undefined | Callback, - callback?: Callback, - ): unknown - getCookies( - url: string, - options?: GetCookiesOptions | Callback, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _callback?: Callback, - ): unknown { - const promiseCallback = createPromiseCallback(arguments) - const cb = promiseCallback.callback - - validators.validate(validators.isNonEmptyString(url), cb, url) - const context = getCookieContext(url) - if (typeof options === 'function' || options === undefined) { - options = defaultGetCookieOptions - } - validators.validate(validators.isObject(options), cb, safeToString(options)) - validators.validate(typeof cb === 'function', cb) - - const host = canonicalDomain(context.hostname) - const path = context.pathname || '/' - - const secure = - context.protocol && - (context.protocol == 'https:' || context.protocol == 'wss:') - - let sameSiteLevel = 0 - if (options?.sameSiteContext) { - const sameSiteContext = checkSameSiteContext(options.sameSiteContext) - if (sameSiteContext == null) { - return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) - } - sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext] - if (!sameSiteLevel) { - return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) - } - } - - const http = options?.http ?? true - - const now = Date.now() - const expireCheck = options?.expire ?? true - const allPaths = options?.allPaths ?? false - const store = this.store - - function matchingCookie(c: Cookie) { - // "Either: - // The cookie's host-only-flag is true and the canonicalized - // request-host is identical to the cookie's domain. - // Or: - // The cookie's host-only-flag is false and the canonicalized - // request-host domain-matches the cookie's domain." - if (c.hostOnly) { - if (c.domain != host) { - return false - } - } else { - if (!domainMatch(host ?? undefined, c.domain ?? undefined, false)) { - return false - } - } - - // "The request-uri's path path-matches the cookie's path." - if (!allPaths && typeof c.path === 'string' && !pathMatch(path, c.path)) { - return false - } - - // "If the cookie's secure-only-flag is true, then the request-uri's - // scheme must denote a "secure" protocol" - if (c.secure && !secure) { - return false - } - - // "If the cookie's http-only-flag is true, then exclude the cookie if the - // cookie-string is being generated for a "non-HTTP" API" - if (c.httpOnly && !http) { - return false - } - - // RFC6265bis-02 S5.3.7 - if (sameSiteLevel) { - let cookieLevel: number - if (c.sameSite === 'lax') { - cookieLevel = Cookie.sameSiteLevel.lax - } else if (c.sameSite === 'strict') { - cookieLevel = Cookie.sameSiteLevel.strict - } else { - cookieLevel = Cookie.sameSiteLevel.none - } - if (cookieLevel > sameSiteLevel) { - // only allow cookies at or below the request level - return false - } - } - - // deferred from S5.3 - // non-RFC: allow retention of expired cookies by choice - const expiryTime = c.expiryTime() - if (expireCheck && expiryTime && expiryTime <= now) { - // eslint-disable-next-line @typescript-eslint/no-empty-function - store.removeCookie(c.domain, c.path, c.key, () => {}) // result ignored - return false - } - - return true - } - - store.findCookies( - host, - allPaths ? null : path, - this.allowSpecialUseDomain, - (err, cookies): void => { - if (err) { - cb(err) - return - } - - if (cookies == null) { - cb(undefined, []) - return - } - - cookies = cookies.filter(matchingCookie) - - // sorting of S5.4 part 2 - if (options && 'sort' in options && options.sort !== false) { - cookies = cookies.sort(cookieCompare) - } - - // S5.4 part 3 - const now = new Date() - for (const cookie of cookies) { - cookie.lastAccessed = now - } - // TODO persist lastAccessed - - cb(null, cookies) - }, - ) - - return promiseCallback.promise - } - getCookiesSync(url: string, options?: GetCookiesOptions): Cookie[] { - return ( - this.callSync(this.getCookies.bind(this, url, options)) ?? [] - ) - } - - getCookieString( - url: string, - options: GetCookiesOptions, - callback: Callback, - ): void - getCookieString(url: string, callback: Callback): void - getCookieString(url: string): Promise - getCookieString(url: string, options: GetCookiesOptions): Promise - getCookieString( - url: string, - options: GetCookiesOptions | Callback, - callback?: Callback, - ): unknown - getCookieString( - url: string, - options?: GetCookiesOptions | Callback, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _callback?: Callback, - ): unknown { - const promiseCallback = createPromiseCallback(arguments) - - if (typeof options === 'function') { - options = undefined - } - - const next: Callback = function ( - err: Error | undefined, - cookies: Cookie[] | undefined, - ) { - if (err || cookies === undefined) { - promiseCallback.callback(err) - } else { - promiseCallback.callback( - undefined, - cookies - .sort(cookieCompare) - .map((c) => c.cookieString()) - .join('; '), - ) - } - } - - this.getCookies(url, options, next) - return promiseCallback.promise - } - getCookieStringSync(url: string, options?: GetCookiesOptions): string { - return ( - this.callSync( - this.getCookieString.bind(this, url, options as GetCookiesOptions), - ) ?? '' - ) - } - - getSetCookieStrings(url: string, callback: Callback): void - getSetCookieStrings( - url: string, - options: GetCookiesOptions, - callback: Callback, - ): void - getSetCookieStrings(url: string): Promise - getSetCookieStrings( - url: string, - options: GetCookiesOptions, - ): Promise - getSetCookieStrings( - url: string, - options: GetCookiesOptions, - callback?: Callback, - ): unknown - getSetCookieStrings( - url: string, - options?: GetCookiesOptions | Callback, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _callback?: Callback, - ): unknown { - const promiseCallback = createPromiseCallback(arguments) - - if (typeof options === 'function') { - options = undefined - } - - const next: Callback = function ( - err: Error | undefined, - cookies: Cookie[] | undefined, - ) { - if (err || cookies === undefined) { - promiseCallback.callback(err) - } else { - promiseCallback.callback( - null, - cookies.map((c) => { - return c.toString() - }), - ) - } - } - - this.getCookies(url, options, next) - return promiseCallback.promise - } - getSetCookieStringsSync( - url: string, - options: GetCookiesOptions = {}, - ): string[] { - return ( - this.callSync( - this.getSetCookieStrings.bind(this, url, options), - ) ?? [] - ) - } - - serialize(callback: Callback): void - serialize(): Promise - serialize(callback?: Callback): unknown - // eslint-disable-next-line @typescript-eslint/no-unused-vars - serialize(_callback?: Callback): unknown { - const promiseCallback = - createPromiseCallback(arguments) - const cb = promiseCallback.callback - - validators.validate(typeof cb === 'function', cb) - let type: string | null = this.store.constructor.name - if (validators.isObject(type)) { - type = null - } - - // update README.md "Serialization Format" if you change this, please! - const serialized: SerializedCookieJar = { - // The version of tough-cookie that serialized this jar. Generally a good - // practice since future versions can make data import decisions based on - // known past behavior. When/if this matters, use `semver`. - version: `tough-cookie@${version}`, - - // add the store type, to make humans happy: - storeType: type, - - // CookieJar configuration: - rejectPublicSuffixes: this.rejectPublicSuffixes, - enableLooseMode: this.enableLooseMode, - allowSpecialUseDomain: this.allowSpecialUseDomain, - prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), - - // this gets filled from getAllCookies: - cookies: [], - } - - if ( - !( - this.store.getAllCookies && - typeof this.store.getAllCookies === 'function' - ) - ) { - return promiseCallback.reject( - new Error( - 'store does not support getAllCookies and cannot be serialized', - ), - ) - } - - this.store.getAllCookies((err, cookies) => { - if (err) { - promiseCallback.callback(err) - return - } - - if (cookies == null) { - promiseCallback.callback(undefined, serialized) - return - } - - serialized.cookies = cookies.map((cookie) => { - // convert to serialized 'raw' cookies - const serializedCookie = cookie.toJSON() - - // Remove the index so new ones get assigned during deserialization - delete serializedCookie.creationIndex - - return serializedCookie - }) - - promiseCallback.callback(undefined, serialized) - }) - - return promiseCallback.promise - } - serializeSync(): SerializedCookieJar | undefined { - return this.callSync((callback) => { - this.serialize(callback) - }) - } - - toJSON() { - return this.serializeSync() - } - - // use the class method CookieJar.deserialize instead of calling this directly - _importCookies(serialized: unknown, callback: Callback) { - let cookies: unknown[] | undefined = undefined - - if ( - serialized && - typeof serialized === 'object' && - inOperator('cookies', serialized) && - Array.isArray(serialized.cookies) - ) { - cookies = serialized.cookies - } - - if (!cookies) { - return callback( - new Error('serialized jar has no cookies array'), - undefined, - ) - } - - cookies = cookies.slice() // do not modify the original - - const putNext = (err?: Error): void => { - if (err) { - return callback(err, undefined) - } - - if (Array.isArray(cookies)) { - if (!cookies.length) { - return callback(err, this) - } - - let cookie - try { - cookie = fromJSON(cookies.shift()) - } catch (e) { - return callback(e instanceof Error ? e : new Error(), undefined) - } - - if (cookie === null) { - return putNext(undefined) // skip this cookie - } - - this.store.putCookie(cookie, putNext) - } - } - - putNext() - } - - _importCookiesSync(serialized: unknown): void { - this.callSync(this._importCookies.bind(this, serialized)) - } - - clone(callback: Callback): void - clone(newStore: Store, callback: Callback): void - clone(): Promise - clone(newStore: Store): Promise - clone( - newStore?: Store | Callback, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _callback?: Callback, - ): unknown { - if (typeof newStore === 'function') { - newStore = undefined - } - - const promiseCallback = createPromiseCallback(arguments) - const cb = promiseCallback.callback - - this.serialize((err, serialized) => { - if (err) { - return promiseCallback.reject(err) - } - return CookieJar.deserialize(serialized ?? '', newStore, cb) - }) - - return promiseCallback.promise - } - - _cloneSync(newStore?: Store): CookieJar | undefined { - const cloneFn = - newStore && typeof newStore !== 'function' - ? this.clone.bind(this, newStore) - : this.clone.bind(this) - return this.callSync((callback) => cloneFn(callback)) - } - - cloneSync(newStore?: Store): CookieJar | undefined { - if (!newStore) { - return this._cloneSync() - } - if (!newStore.synchronous) { - throw new Error( - 'CookieJar clone destination store is not synchronous; use async API instead.', - ) - } - return this._cloneSync(newStore) - } - - removeAllCookies(callback: ErrorCallback): void - removeAllCookies(): Promise - removeAllCookies(callback?: ErrorCallback): unknown - // eslint-disable-next-line @typescript-eslint/no-unused-vars - removeAllCookies(_callback?: ErrorCallback): unknown { - const promiseCallback = createPromiseCallback(arguments) - const cb = promiseCallback.callback - - const store = this.store - - // Check that the store implements its own removeAllCookies(). The default - // implementation in Store will immediately call the callback with a "not - // implemented" Error. - if ( - typeof store.removeAllCookies === 'function' && - store.removeAllCookies !== Store.prototype.removeAllCookies - ) { - store.removeAllCookies(cb) - return promiseCallback.promise - } - - store.getAllCookies((err, cookies): void => { - if (err) { - cb(err) - return - } - - if (!cookies) { - cookies = [] - } - - if (cookies.length === 0) { - cb(null) - return - } - - let completedCount = 0 - const removeErrors: Error[] = [] - - function removeCookieCb(removeErr: Error | undefined) { - if (removeErr) { - removeErrors.push(removeErr) - } - - completedCount++ - - if (completedCount === cookies?.length) { - cb(removeErrors.length ? removeErrors[0] : null) - return - } - } - - cookies.forEach((cookie) => { - store.removeCookie( - cookie.domain, - cookie.path, - cookie.key, - removeCookieCb, - ) - }) - }) - - return promiseCallback.promise - } - removeAllCookiesSync(): void { - return this.callSync((callback) => this.removeAllCookies(callback)) - } - - static deserialize( - strOrObj: string | object, - callback: Callback, - ): void - static deserialize( - strOrObj: string | object, - store: Store, - callback: Callback, - ): void - static deserialize(strOrObj: string | object): Promise - static deserialize( - strOrObj: string | object, - store: Store, - ): Promise - static deserialize( - strOrObj: string | object, - store?: Store | Callback, - callback?: Callback, - ): unknown - static deserialize( - strOrObj: string | object, - store?: Store | Callback, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _callback?: Callback, - ): unknown { - if (typeof store === 'function') { - store = undefined - } - - const promiseCallback = createPromiseCallback(arguments) - - let serialized: unknown - if (typeof strOrObj === 'string') { - try { - serialized = JSON.parse(strOrObj) - } catch (e) { - return promiseCallback.reject(e instanceof Error ? e : new Error()) - } - } else { - serialized = strOrObj - } - - const readSerializedProperty = (property: string): unknown | undefined => { - return serialized && - typeof serialized === 'object' && - inOperator(property, serialized) - ? serialized[property] - : undefined - } - - const readSerializedBoolean = (property: string): boolean | undefined => { - const value = readSerializedProperty(property) - return typeof value === 'boolean' ? value : undefined - } - - const readSerializedString = (property: string): string | undefined => { - const value = readSerializedProperty(property) - return typeof value === 'string' ? value : undefined - } - - const jar = new CookieJar(store, { - rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), - looseMode: readSerializedBoolean('enableLooseMode'), - allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), - prefixSecurity: getNormalizedPrefixSecurity( - readSerializedString('prefixSecurity') ?? 'silent', - ), - }) - - jar._importCookies(serialized, (err) => { - if (err) { - promiseCallback.callback(err) - return - } - promiseCallback.callback(undefined, jar) - }) - - return promiseCallback.promise - } - - static deserializeSync( - strOrObj: string | SerializedCookieJar, - store?: Store, - ): CookieJar { - const serialized: unknown = - typeof strOrObj === 'string' ? JSON.parse(strOrObj) : strOrObj - - const readSerializedProperty = (property: string): unknown | undefined => { - return serialized && - typeof serialized === 'object' && - inOperator(property, serialized) - ? serialized[property] - : undefined - } - - const readSerializedBoolean = (property: string): boolean | undefined => { - const value = readSerializedProperty(property) - return typeof value === 'boolean' ? value : undefined - } - - const readSerializedString = (property: string): string | undefined => { - const value = readSerializedProperty(property) - return typeof value === 'string' ? value : undefined - } - - const jar = new CookieJar(store, { - rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), - looseMode: readSerializedBoolean('enableLooseMode'), - allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), - prefixSecurity: getNormalizedPrefixSecurity( - readSerializedString('prefixSecurity') ?? 'silent', - ), - }) - - // catch this mistake early: - if (!jar.store.synchronous) { - throw new Error( - 'CookieJar store is not synchronous; use async API instead.', - ) - } - - jar._importCookiesSync(serialized) - return jar - } - - static fromJSON(jsonString: SerializedCookieJar, store?: Store): CookieJar { - return CookieJar.deserializeSync(jsonString, store) - } -} - -const getPublicSuffix = pubsuffix.getPublicSuffix -const ParameterError = validators.ParameterError - -export { version as version } -export { Store as Store } -export { MemoryCookieStore as MemoryCookieStore } -export { parseDate as parseDate } -export { formatDate as formatDate } -export { parse as parse } -export { fromJSON as fromJSON } -export { domainMatch as domainMatch } -export { defaultPath as defaultPath } -export { pathMatch as pathMatch } -export { getPublicSuffix as getPublicSuffix } -export { cookieCompare as cookieCompare } -export { permuteDomain as permuteDomain } -export { permutePath as permutePath } -export { canonicalDomain as canonicalDomain } -export { PrefixSecurityEnum as PrefixSecurityEnum } -export { ParameterError as ParameterError } - -type SetCookieOptions = { - loose?: boolean | undefined - sameSiteContext?: 'strict' | 'lax' | 'none' | undefined - ignoreError?: boolean | undefined - http?: boolean | undefined - now?: Date | undefined -} - -type GetCookiesOptions = { - http?: boolean | undefined - expire?: boolean | undefined - allPaths?: boolean | undefined - sameSiteContext?: 'none' | 'lax' | 'strict' | undefined - sort?: boolean | undefined -} - -type ParseCookieOptions = { - loose?: boolean | undefined -} - -interface PromiseCallback { - promise: Promise - callback: (error: Error | undefined | null, result?: T) => void - resolve: (value: T | undefined) => Promise - reject: (error: Error | undefined | null) => Promise -} - -export interface SerializedCookieJar { - version: string - storeType: string | null - rejectPublicSuffixes: boolean - [key: string]: unknown - cookies: SerializedCookie[] -} - -export interface SerializedCookie { - key?: string - value?: string - [key: string]: unknown -} - -export type Callback = ( - error: Error | undefined, - result: T | undefined, -) => void - -function inOperator( - k: K, - o: T, -): o is T & Record { - return k in o -} diff --git a/lib/cookie/canonicalDomain.ts b/lib/cookie/canonicalDomain.ts new file mode 100644 index 00000000..1db890d5 --- /dev/null +++ b/lib/cookie/canonicalDomain.ts @@ -0,0 +1,22 @@ +import * as punycode from 'punycode/' +import { IP_V6_REGEX_OBJECT } from './constants' + +// S5.1.2 Canonicalized Host Names +export function canonicalDomain(str: string | null) { + if (str == null) { + return null + } + let _str = str.trim().replace(/^\./, '') // S4.1.2.3 & S5.2.3: ignore leading . + + if (IP_V6_REGEX_OBJECT.test(_str)) { + _str = _str.replace('[', '').replace(']', '') + } + + // convert to IDN if any non-ASCII characters + // eslint-disable-next-line no-control-regex + if (/[^\u0001-\u007f]/.test(_str)) { + _str = punycode.toASCII(_str) + } + + return _str.toLowerCase() +} diff --git a/lib/cookie/constants.ts b/lib/cookie/constants.ts new file mode 100644 index 00000000..278449b3 --- /dev/null +++ b/lib/cookie/constants.ts @@ -0,0 +1,36 @@ +export const PrefixSecurityEnum = Object.freeze({ + SILENT: 'silent', + STRICT: 'strict', + DISABLED: 'unsafe-disabled', +}) + +const IP_V6_REGEX = ` +\\[?(?: +(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)| +(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)| +(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)| +(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)| +(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)| +(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)) +)(?:%[0-9a-zA-Z]{1,})?\\]? +` + .replace(/\s*\/\/.*$/gm, '') + .replace(/\n/g, '') + .trim() +export const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`) + +export interface SerializedCookieJar { + version: string + storeType: string | null + rejectPublicSuffixes: boolean + [key: string]: unknown + cookies: SerializedCookie[] +} + +export interface SerializedCookie { + key?: string + value?: string + [key: string]: unknown +} diff --git a/lib/cookie/cookie.ts b/lib/cookie/cookie.ts new file mode 100644 index 00000000..15052309 --- /dev/null +++ b/lib/cookie/cookie.ts @@ -0,0 +1,733 @@ +/*! + * Copyright (c) 2015-2020, Salesforce.com, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of Salesforce.com nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import * as pubsuffix from '../pubsuffix-psl' +import * as validators from '../validators' +import { getCustomInspectSymbol } from '../utilHelper' +import { inOperator } from '../utils' + +import { formatDate } from './formatDate' +import { parseDate } from './parseDate' +import { canonicalDomain } from './canonicalDomain' +import type { SerializedCookie } from './constants' + +// From RFC6265 S4.1.1 +// note that it excludes \x3B ";" +const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/ + +// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"' +// Note ';' is \x3B +const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/ + +// eslint-disable-next-line no-control-regex +const CONTROL_CHARS = /[\x00-\x1F]/ + +// From Chromium // '\r', '\n' and '\0' should be treated as a terminator in +// the "relaxed" mode, see: +// https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60 +const TERMINATORS = ['\n', '\r', '\0'] + +function trimTerminator(str: string) { + if (validators.isEmptyString(str)) return str + for (let t = 0; t < TERMINATORS.length; t++) { + const terminator = TERMINATORS[t] + const terminatorIdx = terminator ? str.indexOf(terminator) : -1 + if (terminatorIdx !== -1) { + str = str.slice(0, terminatorIdx) + } + } + + return str +} + +function parseCookiePair(cookiePair: string, looseMode: boolean) { + cookiePair = trimTerminator(cookiePair) + validators.validate(validators.isString(cookiePair), cookiePair) + + let firstEq = cookiePair.indexOf('=') + if (looseMode) { + if (firstEq === 0) { + // '=' is immediately at start + cookiePair = cookiePair.substr(1) + firstEq = cookiePair.indexOf('=') // might still need to split on '=' + } + } else { + // non-loose mode + if (firstEq <= 0) { + // no '=' or is at start + return undefined // needs to have non-empty "cookie-name" + } + } + + let cookieName, cookieValue + if (firstEq <= 0) { + cookieName = '' + cookieValue = cookiePair.trim() + } else { + cookieName = cookiePair.slice(0, firstEq).trim() + cookieValue = cookiePair.slice(firstEq + 1).trim() + } + + if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) { + return undefined + } + + const c = new Cookie() + c.key = cookieName + c.value = cookieValue + return c +} + +type ParseCookieOptions = { + loose?: boolean | undefined +} + +function parse( + str: string, + options?: ParseCookieOptions, +): Cookie | undefined | null { + if (validators.isEmptyString(str) || !validators.isString(str)) { + return null + } + + str = str.trim() + + // We use a regex to parse the "name-value-pair" part of S5.2 + const firstSemi = str.indexOf(';') // S5.2 step 1 + const cookiePair = firstSemi === -1 ? str : str.slice(0, firstSemi) + const c = parseCookiePair(cookiePair, options?.loose ?? false) + if (!c) { + return undefined + } + + if (firstSemi === -1) { + return c + } + + // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string + // (including the %x3B (";") in question)." plus later on in the same section + // "discard the first ";" and trim". + const unparsed = str.slice(firstSemi + 1).trim() + + // "If the unparsed-attributes string is empty, skip the rest of these + // steps." + if (unparsed.length === 0) { + return c + } + + /* + * S5.2 says that when looping over the items "[p]rocess the attribute-name + * and attribute-value according to the requirements in the following + * subsections" for every item. Plus, for many of the individual attributes + * in S5.3 it says to use the "attribute-value of the last attribute in the + * cookie-attribute-list". Therefore, in this implementation, we overwrite + * the previous value. + */ + const cookie_avs = unparsed.split(';') + while (cookie_avs.length) { + const av = (cookie_avs.shift() ?? '').trim() + if (av.length === 0) { + // happens if ";;" appears + continue + } + const av_sep = av.indexOf('=') + let av_key, av_value + + if (av_sep === -1) { + av_key = av + av_value = null + } else { + av_key = av.slice(0, av_sep) + av_value = av.slice(av_sep + 1) + } + + av_key = av_key.trim().toLowerCase() + + if (av_value) { + av_value = av_value.trim() + } + + switch (av_key) { + case 'expires': // S5.2.1 + if (av_value) { + const exp = parseDate(av_value) + // "If the attribute-value failed to parse as a cookie date, ignore the + // cookie-av." + if (exp) { + // over and underflow not realistically a concern: V8's getTime() seems to + // store something larger than a 32-bit time_t (even with 32-bit node) + c.expires = exp + } + } + break + + case 'max-age': // S5.2.2 + if (av_value) { + // "If the first character of the attribute-value is not a DIGIT or a "-" + // character ...[or]... If the remainder of attribute-value contains a + // non-DIGIT character, ignore the cookie-av." + if (/^-?[0-9]+$/.test(av_value)) { + const delta = parseInt(av_value, 10) + // "If delta-seconds is less than or equal to zero (0), let expiry-time + // be the earliest representable date and time." + c.setMaxAge(delta) + } + } + break + + case 'domain': // S5.2.3 + // "If the attribute-value is empty, the behavior is undefined. However, + // the user agent SHOULD ignore the cookie-av entirely." + if (av_value) { + // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E + // (".") character." + const domain = av_value.trim().replace(/^\./, '') + if (domain) { + // "Convert the cookie-domain to lower case." + c.domain = domain.toLowerCase() + } + } + break + + case 'path': // S5.2.4 + /* + * "If the attribute-value is empty or if the first character of the + * attribute-value is not %x2F ("/"): + * Let cookie-path be the default-path. + * Otherwise: + * Let cookie-path be the attribute-value." + * + * We'll represent the default-path as null since it depends on the + * context of the parsing. + */ + c.path = av_value && av_value[0] === '/' ? av_value : null + break + + case 'secure': // S5.2.5 + /* + * "If the attribute-name case-insensitively matches the string "Secure", + * the user agent MUST append an attribute to the cookie-attribute-list + * with an attribute-name of Secure and an empty attribute-value." + */ + c.secure = true + break + + case 'httponly': // S5.2.6 -- effectively the same as 'secure' + c.httpOnly = true + break + + case 'samesite': // RFC6265bis-02 S5.3.7 + switch (av_value ? av_value.toLowerCase() : '') { + case 'strict': + c.sameSite = 'strict' + break + case 'lax': + c.sameSite = 'lax' + break + case 'none': + c.sameSite = 'none' + break + default: + c.sameSite = undefined + break + } + break + + default: + c.extensions = c.extensions || [] + c.extensions.push(av) + break + } + } + + return c +} + +function fromJSON(str: string | SerializedCookie | null | undefined | unknown) { + if (!str || validators.isEmptyString(str)) { + return null + } + + let obj: unknown + if (typeof str === 'string') { + try { + obj = JSON.parse(str) + } catch (e) { + return null + } + } else { + // assume it's an Object + obj = str + } + + const c = new Cookie() + Cookie.serializableProperties.forEach((prop) => { + if (obj && typeof obj === 'object' && inOperator(prop, obj)) { + const val = obj[prop] + if (val === undefined) { + return + } + + if (inOperator(prop, cookieDefaults) && val === cookieDefaults[prop]) { + return + } + + switch (prop) { + case 'key': + case 'value': + case 'sameSite': + if (typeof val === 'string') { + c[prop] = val + } + break + case 'expires': + case 'creation': + case 'lastAccessed': + if ( + typeof val === 'number' || + typeof val === 'string' || + val instanceof Date + ) { + c[prop] = obj[prop] == 'Infinity' ? 'Infinity' : new Date(val) + } else if (val === null) { + c[prop] = null + } + break + case 'maxAge': + if ( + typeof val === 'number' || + val === 'Infinity' || + val === '-Infinity' + ) { + c[prop] = val + } + break + case 'domain': + case 'path': + if (typeof val === 'string' || val === null) { + c[prop] = val + } + break + case 'secure': + case 'httpOnly': + if (typeof val === 'boolean') { + c[prop] = val + } + break + case 'extensions': + if ( + Array.isArray(val) && + val.every((item) => typeof item === 'string') + ) { + c[prop] = val + } + break + case 'hostOnly': + case 'pathIsDefault': + if (typeof val === 'boolean' || val === null) { + c[prop] = val + } + break + } + } + }) + + return c +} + +const cookieDefaults = { + // the order in which the RFC has them: + key: '', + value: '', + expires: 'Infinity', + maxAge: null, + domain: null, + path: null, + secure: false, + httpOnly: false, + extensions: null, + // set by the CookieJar: + hostOnly: null, + pathIsDefault: null, + creation: null, + lastAccessed: null, + sameSite: undefined, +} + +type CreateCookieOptions = { + key?: string + value?: string + expires?: Date | 'Infinity' | null + maxAge?: number | 'Infinity' | '-Infinity' + domain?: string | null + path?: string | null + secure?: boolean + httpOnly?: boolean + extensions?: string[] | null + creation?: Date | 'Infinity' | null + creationIndex?: number + hostOnly?: boolean | null + pathIsDefault?: boolean | null + lastAccessed?: Date | 'Infinity' | null + sameSite?: string | undefined +} + +export class Cookie { + key: string | undefined + value: string | undefined + expires: Date | 'Infinity' | null | undefined + maxAge: number | 'Infinity' | '-Infinity' | undefined + domain: string | null | undefined + path: string | null | undefined + secure: boolean | undefined + httpOnly: boolean | undefined + extensions: string[] | null | undefined + creation: Date | 'Infinity' | null + creationIndex: number | undefined + hostOnly: boolean | null | undefined + pathIsDefault: boolean | null | undefined + lastAccessed: Date | 'Infinity' | null | undefined + sameSite: string | undefined + + constructor(options: CreateCookieOptions = {}) { + // supports inspect if that feature is available in the environment + const customInspectSymbol = getCustomInspectSymbol() + if (customInspectSymbol) { + Object.defineProperty(this, customInspectSymbol, { + value: this.inspect.bind(this), + enumerable: false, + writable: false, + configurable: false, + }) + } + + Object.assign(this, cookieDefaults, options) + this.creation = options.creation ?? cookieDefaults.creation ?? new Date() + + // used to break creation ties in cookieCompare(): + Object.defineProperty(this, 'creationIndex', { + configurable: false, + enumerable: false, // important for assert.deepEqual checks + writable: true, + value: ++Cookie.cookiesCreated, + }) + } + + inspect() { + const now = Date.now() + const hostOnly = this.hostOnly != null ? this.hostOnly.toString() : '?' + const createAge = + this.creation && this.creation !== 'Infinity' + ? `${now - this.creation.getTime()}ms` + : '?' + const accessAge = + this.lastAccessed && this.lastAccessed !== 'Infinity' + ? `${now - this.lastAccessed.getTime()}ms` + : '?' + return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"` + } + + toJSON(): SerializedCookie { + const obj: SerializedCookie = {} + + for (const prop of Cookie.serializableProperties) { + const val = this[prop] + + if (val === cookieDefaults[prop]) { + continue // leave as prototype default + } + + switch (prop) { + case 'key': + case 'value': + case 'sameSite': + if (typeof val === 'string') { + obj[prop] = val + } + break + case 'expires': + case 'creation': + case 'lastAccessed': + if ( + typeof val === 'number' || + typeof val === 'string' || + val instanceof Date + ) { + obj[prop] = + val == 'Infinity' ? 'Infinity' : new Date(val).toISOString() + } else if (val === null) { + obj[prop] = null + } + break + case 'maxAge': + if ( + typeof val === 'number' || + val === 'Infinity' || + val === '-Infinity' + ) { + obj[prop] = val + } + break + case 'domain': + case 'path': + if (typeof val === 'string' || val === null) { + obj[prop] = val + } + break + case 'secure': + case 'httpOnly': + if (typeof val === 'boolean') { + obj[prop] = val + } + break + case 'extensions': + if (Array.isArray(val)) { + obj[prop] = val + } + break + case 'hostOnly': + case 'pathIsDefault': + if (typeof val === 'boolean' || val === null) { + obj[prop] = val + } + break + } + } + + return obj + } + + clone() { + return fromJSON(this.toJSON()) + } + + validate() { + if (this.value == null || !COOKIE_OCTETS.test(this.value)) { + return false + } + if ( + this.expires != 'Infinity' && + !(this.expires instanceof Date) && + !parseDate(this.expires) + ) { + return false + } + if (this.maxAge != null && this.maxAge <= 0) { + return false // "Max-Age=" non-zero-digit *DIGIT + } + if (this.path != null && !PATH_VALUE.test(this.path)) { + return false + } + + const cdomain = this.cdomain() + if (cdomain) { + if (cdomain.match(/\.$/)) { + return false // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this + } + const suffix = pubsuffix.getPublicSuffix(cdomain) + if (suffix == null) { + // it's a public suffix + return false + } + } + return true + } + + setExpires(exp: string | Date) { + if (exp instanceof Date) { + this.expires = exp + } else { + this.expires = parseDate(exp) || 'Infinity' + } + } + + setMaxAge(age: number) { + if (age === Infinity) { + this.maxAge = 'Infinity' + } else if (age === -Infinity) { + this.maxAge = '-Infinity' + } else { + this.maxAge = age + } + } + + cookieString() { + const val = this.value ?? '' + if (this.key) { + return `${this.key}=${val}` + } + return val + } + + // gives Set-Cookie header format + toString() { + let str = this.cookieString() + + if (this.expires != 'Infinity') { + if (this.expires instanceof Date) { + str += `; Expires=${formatDate(this.expires)}` + } + } + + if (this.maxAge != null && this.maxAge != Infinity) { + str += `; Max-Age=${this.maxAge}` + } + + if (this.domain && !this.hostOnly) { + str += `; Domain=${this.domain}` + } + if (this.path) { + str += `; Path=${this.path}` + } + + if (this.secure) { + str += '; Secure' + } + if (this.httpOnly) { + str += '; HttpOnly' + } + if (this.sameSite && this.sameSite !== 'none') { + if ( + this.sameSite.toLowerCase() === + Cookie.sameSiteCanonical.lax.toLowerCase() + ) { + str += `; SameSite=${Cookie.sameSiteCanonical.lax}` + } else if ( + this.sameSite.toLowerCase() === + Cookie.sameSiteCanonical.strict.toLowerCase() + ) { + str += `; SameSite=${Cookie.sameSiteCanonical.strict}` + } else { + str += `; SameSite=${this.sameSite}` + } + } + if (this.extensions) { + this.extensions.forEach((ext) => { + str += `; ${ext}` + }) + } + + return str + } + + // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie() + // elsewhere) + // S5.3 says to give the "latest representable date" for which we use Infinity + // For "expired" we use 0 + TTL(now: number = Date.now()): number { + /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires + * attribute, the Max-Age attribute has precedence and controls the + * expiration date of the cookie. + * (Concurs with S5.3 step 3) + */ + if (this.maxAge != null && typeof this.maxAge === 'number') { + return this.maxAge <= 0 ? 0 : this.maxAge * 1000 + } + + const expires = this.expires + if (expires === 'Infinity') { + return Infinity + } + + return (expires?.getTime() ?? now) - (now || Date.now()) + } + + // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() + // elsewhere) + expiryTime(now?: Date): number | undefined { + if (this.maxAge != null) { + const relativeTo = now || this.creation || new Date() + const maxAge = typeof this.maxAge === 'number' ? this.maxAge : -Infinity + const age = maxAge <= 0 ? -Infinity : maxAge * 1000 + if (relativeTo === 'Infinity') { + return Infinity + } + return relativeTo.getTime() + age + } + + if (this.expires == 'Infinity') { + return Infinity + } + + return this.expires ? this.expires.getTime() : undefined + } + + // This replaces the "persistent-flag" parts of S5.3 step 3 + isPersistent(): boolean { + return this.maxAge != null || this.expires != 'Infinity' + } + + // Mostly S5.1.2 and S5.2.3: + canonicalizedDomain() { + if (this.domain == null) { + return null + } + return canonicalDomain(this.domain) + } + + cdomain() { + return this.canonicalizedDomain() + } + + static parse = parse + + static fromJSON = fromJSON + + static cookiesCreated = 0 + + static sameSiteLevel = { + strict: 3, + lax: 2, + none: 1, + } as const + + static sameSiteCanonical = { + strict: 'Strict', + lax: 'Lax', + } as const + + static serializableProperties = [ + 'key', + 'value', + 'expires', + 'maxAge', + 'domain', + 'path', + 'secure', + 'httpOnly', + 'extensions', + 'hostOnly', + 'pathIsDefault', + 'creation', + 'lastAccessed', + 'sameSite', + ] as const +} diff --git a/lib/cookie/cookieCompare.ts b/lib/cookie/cookieCompare.ts new file mode 100644 index 00000000..dd793f4e --- /dev/null +++ b/lib/cookie/cookieCompare.ts @@ -0,0 +1,47 @@ +import { safeToString } from '../utils' +import * as validators from '../validators' +import type { Cookie } from './cookie' +/* Section 5.4 part 2: + * "* Cookies with longer paths are listed before cookies with + * shorter paths. + * + * * Among cookies that have equal-length path fields, cookies with + * earlier creation-times are listed before cookies with later + * creation-times." + */ + +/** + * The maximum timestamp a cookie, in milliseconds. The value is (2^31 - 1) seconds since the Unix + * epoch, corresponding to 2038-01-19. + */ +const MAX_TIME = 2147483647000 + +/** Compares two cookies for sorting. */ +export function cookieCompare(a: Cookie, b: Cookie) { + validators.validate(validators.isObject(a), safeToString(a)) + validators.validate(validators.isObject(b), safeToString(b)) + let cmp: number + + // descending for length: b CMP a + const aPathLen = a.path ? a.path.length : 0 + const bPathLen = b.path ? b.path.length : 0 + cmp = bPathLen - aPathLen + if (cmp !== 0) { + return cmp + } + + // ascending for time: a CMP b + const aTime = + a.creation && a.creation instanceof Date ? a.creation.getTime() : MAX_TIME + const bTime = + b.creation && b.creation instanceof Date ? b.creation.getTime() : MAX_TIME + cmp = aTime - bTime + if (cmp !== 0) { + return cmp + } + + // break ties for the same millisecond (precision of JavaScript's clock) + cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0) + + return cmp +} diff --git a/lib/cookie/cookieJar.ts b/lib/cookie/cookieJar.ts new file mode 100644 index 00000000..aff9c2be --- /dev/null +++ b/lib/cookie/cookieJar.ts @@ -0,0 +1,1137 @@ +import urlParse from 'url-parse' + +import * as pubsuffix from '../pubsuffix-psl' +import * as validators from '../validators' +import { Store } from '../store' +import { MemoryCookieStore } from '../memstore' +import { pathMatch } from '../pathMatch' +import { Cookie } from './cookie' +import { + Callback, + ErrorCallback, + createPromiseCallback, + inOperator, + safeToString, +} from '../utils' +import { canonicalDomain } from './canonicalDomain' +import { + IP_V6_REGEX_OBJECT, + PrefixSecurityEnum, + SerializedCookieJar, +} from './constants' +import { defaultPath } from './defaultPath' +import { domainMatch } from './domainMatch' +import { cookieCompare } from './cookieCompare' +import { version } from '../version' + +const defaultSetCookieOptions: SetCookieOptions = { + loose: false, + sameSiteContext: undefined, + ignoreError: false, + http: true, +} + +const defaultGetCookieOptions: GetCookiesOptions = { + http: true, + expire: true, + allPaths: false, + sameSiteContext: undefined, + sort: undefined, +} + +type SetCookieOptions = { + loose?: boolean | undefined + sameSiteContext?: 'strict' | 'lax' | 'none' | undefined + ignoreError?: boolean | undefined + http?: boolean | undefined + now?: Date | undefined +} + +type GetCookiesOptions = { + http?: boolean | undefined + expire?: boolean | undefined + allPaths?: boolean | undefined + sameSiteContext?: 'none' | 'lax' | 'strict' | undefined + sort?: boolean | undefined +} + +type CreateCookieJarOptions = { + rejectPublicSuffixes?: boolean | undefined + looseMode?: boolean | undefined + prefixSecurity?: 'strict' | 'silent' | 'unsafe-disabled' | undefined + allowSpecialUseDomain?: boolean | undefined +} + +const SAME_SITE_CONTEXT_VAL_ERR = + 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"' + +function getCookieContext(url: string | URL) { + if (url instanceof URL && 'query' in url) { + return url + } + + if (typeof url === 'string') { + try { + return urlParse(decodeURI(url)) + } catch { + return urlParse(url) + } + } + + throw new Error('`url` argument is invalid') +} + +function checkSameSiteContext(value: string) { + validators.validate(validators.isNonEmptyString(value), value) + const context = String(value).toLowerCase() + if (context === 'none' || context === 'lax' || context === 'strict') { + return context + } else { + return null + } +} + +/** + * If the cookie-name begins with a case-sensitive match for the + * string "__Secure-", abort these steps and ignore the cookie + * entirely unless the cookie's secure-only-flag is true. + * @param cookie + * @returns boolean + */ +function isSecurePrefixConditionMet(cookie: Cookie) { + validators.validate(validators.isObject(cookie), safeToString(cookie)) + const startsWithSecurePrefix = + typeof cookie.key === 'string' && cookie.key.startsWith('__Secure-') + return !startsWithSecurePrefix || cookie.secure +} + +/** + * If the cookie-name begins with a case-sensitive match for the + * string "__Host-", abort these steps and ignore the cookie + * entirely unless the cookie meets all the following criteria: + * 1. The cookie's secure-only-flag is true. + * 2. The cookie's host-only-flag is true. + * 3. The cookie-attribute-list contains an attribute with an + * attribute-name of "Path", and the cookie's path is "/". + * @param cookie + * @returns boolean + */ +function isHostPrefixConditionMet(cookie: Cookie) { + validators.validate(validators.isObject(cookie)) + const startsWithHostPrefix = + typeof cookie.key === 'string' && cookie.key.startsWith('__Host-') + return ( + !startsWithHostPrefix || + (cookie.secure && + cookie.hostOnly && + cookie.path != null && + cookie.path === '/') + ) +} + +function getNormalizedPrefixSecurity(prefixSecurity: string) { + if (prefixSecurity != null) { + const normalizedPrefixSecurity = prefixSecurity.toLowerCase() + /* The three supported options */ + switch (normalizedPrefixSecurity) { + case PrefixSecurityEnum.STRICT: + case PrefixSecurityEnum.SILENT: + case PrefixSecurityEnum.DISABLED: + return normalizedPrefixSecurity + } + } + /* Default is SILENT */ + return PrefixSecurityEnum.SILENT +} + +export class CookieJar { + readonly store: Store + private readonly rejectPublicSuffixes: boolean + private readonly enableLooseMode: boolean + private readonly allowSpecialUseDomain: boolean + readonly prefixSecurity: string + + constructor( + store?: Store | null | undefined, + options?: CreateCookieJarOptions | boolean, + ) { + if (typeof options === 'boolean') { + options = { rejectPublicSuffixes: options } + } + this.rejectPublicSuffixes = options?.rejectPublicSuffixes ?? true + this.enableLooseMode = options?.looseMode ?? false + this.allowSpecialUseDomain = options?.allowSpecialUseDomain ?? true + this.prefixSecurity = getNormalizedPrefixSecurity( + options?.prefixSecurity ?? 'silent', + ) + this.store = store ?? new MemoryCookieStore() + } + + private callSync(fn: (callback: Callback) => void): T | undefined { + if (!this.store.synchronous) { + throw new Error( + 'CookieJar store is not synchronous; use async API instead.', + ) + } + let syncErr: Error | undefined + let syncResult: T | undefined = undefined + fn.call(this, (error, result) => { + syncErr = error + syncResult = result + }) + if (syncErr) { + throw syncErr + } + + return syncResult + } + + setCookie( + cookie: string | Cookie, + url: string, + callback: Callback, + ): void + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions, + callback: Callback, + ): void + setCookie(cookie: string | Cookie, url: string): Promise + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions, + ): Promise + setCookie( + cookie: string | Cookie, + url: string, + options: SetCookieOptions | Callback, + callback?: Callback, + ): unknown + setCookie( + cookie: string | Cookie, + url: string, + options?: SetCookieOptions | Callback, + callback?: Callback, + ): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + validators.validate( + validators.isNonEmptyString(url), + callback, + safeToString(options), + ) + let err + + if (typeof url === 'function') { + return promiseCallback.reject(new Error('No URL was specified')) + } + + const context = getCookieContext(url) + if (typeof options === 'function') { + options = defaultSetCookieOptions + } + + validators.validate(typeof cb === 'function', cb) + + if ( + !validators.isNonEmptyString(cookie) && + !validators.isObject(cookie) && + cookie instanceof String && + cookie.length == 0 + ) { + return promiseCallback.reject(null) + } + + const host = canonicalDomain(context.hostname) + const loose = options?.loose || this.enableLooseMode + + let sameSiteContext = null + if (options?.sameSiteContext) { + sameSiteContext = checkSameSiteContext(options.sameSiteContext) + if (!sameSiteContext) { + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + } + } + + // S5.3 step 1 + if (typeof cookie === 'string' || cookie instanceof String) { + const parsedCookie = Cookie.parse(cookie.toString(), { loose: loose }) + if (!parsedCookie) { + err = new Error('Cookie failed to parse') + return promiseCallback.reject(options?.ignoreError ? null : err) + } + cookie = parsedCookie + } else if (!(cookie instanceof Cookie)) { + // If you're seeing this error, and are passing in a Cookie object, + // it *might* be a Cookie object from another loaded version of tough-cookie. + err = new Error( + 'First argument to setCookie must be a Cookie object or string', + ) + return promiseCallback.reject(options?.ignoreError ? null : err) + } + + // S5.3 step 2 + const now = options?.now || new Date() // will assign later to save effort in the face of errors + + // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie() + + // S5.3 step 4: NOOP; domain is null by default + + // S5.3 step 5: public suffixes + if (this.rejectPublicSuffixes && cookie.domain) { + try { + const cdomain = cookie.cdomain() + const suffix = + typeof cdomain === 'string' + ? pubsuffix.getPublicSuffix(cdomain, { + allowSpecialUseDomain: this.allowSpecialUseDomain, + ignoreError: options?.ignoreError, + }) + : null + if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) { + // e.g. "com" + err = new Error('Cookie has domain set to a public suffix') + return promiseCallback.reject(options?.ignoreError ? null : err) + } + } catch (err) { + if (options?.ignoreError) { + return promiseCallback.reject(null) + } else { + if (err instanceof Error) { + return promiseCallback.reject(err) + } else { + return promiseCallback.reject(null) + } + } + } + } + + // S5.3 step 6: + if (cookie.domain) { + if ( + !domainMatch(host ?? undefined, cookie.cdomain() ?? undefined, false) + ) { + err = new Error( + `Cookie not in this host's domain. Cookie:${ + cookie.cdomain() ?? 'null' + } Request:${host ?? 'null'}`, + ) + return promiseCallback.reject(options?.ignoreError ? null : err) + } + + if (cookie.hostOnly == null) { + // don't reset if already set + cookie.hostOnly = false + } + } else { + cookie.hostOnly = true + cookie.domain = host + } + + //S5.2.4 If the attribute-value is empty or if the first character of the + //attribute-value is not %x2F ("/"): + //Let cookie-path be the default-path. + if (!cookie.path || cookie.path[0] !== '/') { + cookie.path = defaultPath(context.pathname ?? undefined) + cookie.pathIsDefault = true + } + + // S5.3 step 8: NOOP; secure attribute + // S5.3 step 9: NOOP; httpOnly attribute + + // S5.3 step 10 + if (options?.http === false && cookie.httpOnly) { + err = new Error("Cookie is HttpOnly and this isn't an HTTP API") + return promiseCallback.reject(options?.ignoreError ? null : err) + } + + // 6252bis-02 S5.4 Step 13 & 14: + if ( + cookie.sameSite !== 'none' && + cookie.sameSite !== undefined && + sameSiteContext + ) { + // "If the cookie's "same-site-flag" is not "None", and the cookie + // is being set from a context whose "site for cookies" is not an + // exact match for request-uri's host's registered domain, then + // abort these steps and ignore the newly created cookie entirely." + if (sameSiteContext === 'none') { + err = new Error('Cookie is SameSite but this is a cross-origin request') + return promiseCallback.reject(options?.ignoreError ? null : err) + } + } + + /* 6265bis-02 S5.4 Steps 15 & 16 */ + const ignoreErrorForPrefixSecurity = + this.prefixSecurity === PrefixSecurityEnum.SILENT + const prefixSecurityDisabled = + this.prefixSecurity === PrefixSecurityEnum.DISABLED + /* If prefix checking is not disabled ...*/ + if (!prefixSecurityDisabled) { + let errorFound = false + let errorMsg + /* Check secure prefix condition */ + if (!isSecurePrefixConditionMet(cookie)) { + errorFound = true + errorMsg = 'Cookie has __Secure prefix but Secure attribute is not set' + } else if (!isHostPrefixConditionMet(cookie)) { + /* Check host prefix condition */ + errorFound = true + errorMsg = + "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'" + } + if (errorFound) { + return promiseCallback.reject( + options?.ignoreError || ignoreErrorForPrefixSecurity + ? null + : new Error(errorMsg), + ) + } + } + + const store = this.store + + if (!store.updateCookie) { + store.updateCookie = function ( + _oldCookie: Cookie, + newCookie: Cookie, + cb?: Callback, + ): Promise { + return this.putCookie(newCookie).then( + () => { + if (cb) { + cb(undefined, undefined) + } + }, + (error: Error) => { + if (cb) { + cb(error, undefined) + } + }, + ) + } + } + + function withCookie( + err: Error | undefined, + oldCookie: Cookie | undefined | null, + ): void { + if (err) { + cb(err) + return + } + + const next = function (err: Error | undefined): void { + if (err || typeof cookie === 'string') { + cb(err) + } else { + cb(null, cookie) + } + } + + if (oldCookie) { + // S5.3 step 11 - "If the cookie store contains a cookie with the same name, + // domain, and path as the newly created cookie:" + if ( + options && + 'http' in options && + options.http === false && + oldCookie.httpOnly + ) { + // step 11.2 + err = new Error("old Cookie is HttpOnly and this isn't an HTTP API") + cb(options.ignoreError ? null : err) + return + } + if (cookie instanceof Cookie) { + cookie.creation = oldCookie.creation + // step 11.3 + cookie.creationIndex = oldCookie.creationIndex + // preserve tie-breaker + cookie.lastAccessed = now + // Step 11.4 (delete cookie) is implied by just setting the new one: + store.updateCookie(oldCookie, cookie, next) // step 12 + } + } else { + if (cookie instanceof Cookie) { + cookie.creation = cookie.lastAccessed = now + store.putCookie(cookie, next) // step 12 + } + } + } + + store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie) + return promiseCallback.promise + } + setCookieSync( + cookie: string | Cookie, + url: string, + options?: SetCookieOptions, + ): Cookie | undefined { + const setCookieFn = this.setCookie.bind( + this, + cookie, + url, + options as SetCookieOptions, + ) + return this.callSync(setCookieFn) + } + + // RFC6365 S5.4 + getCookies(url: string, callback: Callback): void + getCookies( + url: string, + options: GetCookiesOptions | undefined, + callback: Callback, + ): void + getCookies(url: string): Promise + getCookies( + url: string, + options: GetCookiesOptions | undefined, + ): Promise + getCookies( + url: string, + options: GetCookiesOptions | undefined | Callback, + callback?: Callback, + ): unknown + getCookies( + url: string, + options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _callback?: Callback, + ): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + validators.validate(validators.isNonEmptyString(url), cb, url) + const context = getCookieContext(url) + if (typeof options === 'function' || options === undefined) { + options = defaultGetCookieOptions + } + validators.validate(validators.isObject(options), cb, safeToString(options)) + validators.validate(typeof cb === 'function', cb) + + const host = canonicalDomain(context.hostname) + const path = context.pathname || '/' + + const secure = + context.protocol && + (context.protocol == 'https:' || context.protocol == 'wss:') + + let sameSiteLevel = 0 + if (options?.sameSiteContext) { + const sameSiteContext = checkSameSiteContext(options.sameSiteContext) + if (sameSiteContext == null) { + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + } + sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext] + if (!sameSiteLevel) { + return promiseCallback.reject(new Error(SAME_SITE_CONTEXT_VAL_ERR)) + } + } + + const http = options?.http ?? true + + const now = Date.now() + const expireCheck = options?.expire ?? true + const allPaths = options?.allPaths ?? false + const store = this.store + + function matchingCookie(c: Cookie) { + // "Either: + // The cookie's host-only-flag is true and the canonicalized + // request-host is identical to the cookie's domain. + // Or: + // The cookie's host-only-flag is false and the canonicalized + // request-host domain-matches the cookie's domain." + if (c.hostOnly) { + if (c.domain != host) { + return false + } + } else { + if (!domainMatch(host ?? undefined, c.domain ?? undefined, false)) { + return false + } + } + + // "The request-uri's path path-matches the cookie's path." + if (!allPaths && typeof c.path === 'string' && !pathMatch(path, c.path)) { + return false + } + + // "If the cookie's secure-only-flag is true, then the request-uri's + // scheme must denote a "secure" protocol" + if (c.secure && !secure) { + return false + } + + // "If the cookie's http-only-flag is true, then exclude the cookie if the + // cookie-string is being generated for a "non-HTTP" API" + if (c.httpOnly && !http) { + return false + } + + // RFC6265bis-02 S5.3.7 + if (sameSiteLevel) { + let cookieLevel: number + if (c.sameSite === 'lax') { + cookieLevel = Cookie.sameSiteLevel.lax + } else if (c.sameSite === 'strict') { + cookieLevel = Cookie.sameSiteLevel.strict + } else { + cookieLevel = Cookie.sameSiteLevel.none + } + if (cookieLevel > sameSiteLevel) { + // only allow cookies at or below the request level + return false + } + } + + // deferred from S5.3 + // non-RFC: allow retention of expired cookies by choice + const expiryTime = c.expiryTime() + if (expireCheck && expiryTime && expiryTime <= now) { + // eslint-disable-next-line @typescript-eslint/no-empty-function + store.removeCookie(c.domain, c.path, c.key, () => {}) // result ignored + return false + } + + return true + } + + store.findCookies( + host, + allPaths ? null : path, + this.allowSpecialUseDomain, + (err, cookies): void => { + if (err) { + cb(err) + return + } + + if (cookies == null) { + cb(undefined, []) + return + } + + cookies = cookies.filter(matchingCookie) + + // sorting of S5.4 part 2 + if (options && 'sort' in options && options.sort !== false) { + cookies = cookies.sort(cookieCompare) + } + + // S5.4 part 3 + const now = new Date() + for (const cookie of cookies) { + cookie.lastAccessed = now + } + // TODO persist lastAccessed + + cb(null, cookies) + }, + ) + + return promiseCallback.promise + } + getCookiesSync(url: string, options?: GetCookiesOptions): Cookie[] { + return ( + this.callSync(this.getCookies.bind(this, url, options)) ?? [] + ) + } + + getCookieString( + url: string, + options: GetCookiesOptions, + callback: Callback, + ): void + getCookieString(url: string, callback: Callback): void + getCookieString(url: string): Promise + getCookieString(url: string, options: GetCookiesOptions): Promise + getCookieString( + url: string, + options: GetCookiesOptions | Callback, + callback?: Callback, + ): unknown + getCookieString( + url: string, + options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _callback?: Callback, + ): unknown { + const promiseCallback = createPromiseCallback(arguments) + + if (typeof options === 'function') { + options = undefined + } + + const next: Callback = function ( + err: Error | undefined, + cookies: Cookie[] | undefined, + ) { + if (err || cookies === undefined) { + promiseCallback.callback(err) + } else { + promiseCallback.callback( + undefined, + cookies + .sort(cookieCompare) + .map((c) => c.cookieString()) + .join('; '), + ) + } + } + + this.getCookies(url, options, next) + return promiseCallback.promise + } + getCookieStringSync(url: string, options?: GetCookiesOptions): string { + return ( + this.callSync( + this.getCookieString.bind(this, url, options as GetCookiesOptions), + ) ?? '' + ) + } + + getSetCookieStrings(url: string, callback: Callback): void + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + callback: Callback, + ): void + getSetCookieStrings(url: string): Promise + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + ): Promise + getSetCookieStrings( + url: string, + options: GetCookiesOptions, + callback?: Callback, + ): unknown + getSetCookieStrings( + url: string, + options?: GetCookiesOptions | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _callback?: Callback, + ): unknown { + const promiseCallback = createPromiseCallback(arguments) + + if (typeof options === 'function') { + options = undefined + } + + const next: Callback = function ( + err: Error | undefined, + cookies: Cookie[] | undefined, + ) { + if (err || cookies === undefined) { + promiseCallback.callback(err) + } else { + promiseCallback.callback( + null, + cookies.map((c) => { + return c.toString() + }), + ) + } + } + + this.getCookies(url, options, next) + return promiseCallback.promise + } + getSetCookieStringsSync( + url: string, + options: GetCookiesOptions = {}, + ): string[] { + return ( + this.callSync( + this.getSetCookieStrings.bind(this, url, options), + ) ?? [] + ) + } + + serialize(callback: Callback): void + serialize(): Promise + serialize(callback?: Callback): unknown + // eslint-disable-next-line @typescript-eslint/no-unused-vars + serialize(_callback?: Callback): unknown { + const promiseCallback = + createPromiseCallback(arguments) + const cb = promiseCallback.callback + + validators.validate(typeof cb === 'function', cb) + let type: string | null = this.store.constructor.name + if (validators.isObject(type)) { + type = null + } + + // update README.md "Serialization Format" if you change this, please! + const serialized: SerializedCookieJar = { + // The version of tough-cookie that serialized this jar. Generally a good + // practice since future versions can make data import decisions based on + // known past behavior. When/if this matters, use `semver`. + version: `tough-cookie@${version}`, + + // add the store type, to make humans happy: + storeType: type, + + // CookieJar configuration: + rejectPublicSuffixes: this.rejectPublicSuffixes, + enableLooseMode: this.enableLooseMode, + allowSpecialUseDomain: this.allowSpecialUseDomain, + prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity), + + // this gets filled from getAllCookies: + cookies: [], + } + + if ( + !( + this.store.getAllCookies && + typeof this.store.getAllCookies === 'function' + ) + ) { + return promiseCallback.reject( + new Error( + 'store does not support getAllCookies and cannot be serialized', + ), + ) + } + + this.store.getAllCookies((err, cookies) => { + if (err) { + promiseCallback.callback(err) + return + } + + if (cookies == null) { + promiseCallback.callback(undefined, serialized) + return + } + + serialized.cookies = cookies.map((cookie) => { + // convert to serialized 'raw' cookies + const serializedCookie = cookie.toJSON() + + // Remove the index so new ones get assigned during deserialization + delete serializedCookie.creationIndex + + return serializedCookie + }) + + promiseCallback.callback(undefined, serialized) + }) + + return promiseCallback.promise + } + serializeSync(): SerializedCookieJar | undefined { + return this.callSync((callback) => { + this.serialize(callback) + }) + } + + toJSON() { + return this.serializeSync() + } + + // use the class method CookieJar.deserialize instead of calling this directly + _importCookies(serialized: unknown, callback: Callback) { + let cookies: unknown[] | undefined = undefined + + if ( + serialized && + typeof serialized === 'object' && + inOperator('cookies', serialized) && + Array.isArray(serialized.cookies) + ) { + cookies = serialized.cookies + } + + if (!cookies) { + return callback( + new Error('serialized jar has no cookies array'), + undefined, + ) + } + + cookies = cookies.slice() // do not modify the original + + const putNext = (err?: Error): void => { + if (err) { + return callback(err, undefined) + } + + if (Array.isArray(cookies)) { + if (!cookies.length) { + return callback(err, this) + } + + let cookie + try { + cookie = Cookie.fromJSON(cookies.shift()) + } catch (e) { + return callback(e instanceof Error ? e : new Error(), undefined) + } + + if (cookie === null) { + return putNext(undefined) // skip this cookie + } + + this.store.putCookie(cookie, putNext) + } + } + + putNext() + } + + _importCookiesSync(serialized: unknown): void { + this.callSync(this._importCookies.bind(this, serialized)) + } + + clone(callback: Callback): void + clone(newStore: Store, callback: Callback): void + clone(): Promise + clone(newStore: Store): Promise + clone( + newStore?: Store | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _callback?: Callback, + ): unknown { + if (typeof newStore === 'function') { + newStore = undefined + } + + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + this.serialize((err, serialized) => { + if (err) { + return promiseCallback.reject(err) + } + return CookieJar.deserialize(serialized ?? '', newStore, cb) + }) + + return promiseCallback.promise + } + + _cloneSync(newStore?: Store): CookieJar | undefined { + const cloneFn = + newStore && typeof newStore !== 'function' + ? this.clone.bind(this, newStore) + : this.clone.bind(this) + return this.callSync((callback) => cloneFn(callback)) + } + + cloneSync(newStore?: Store): CookieJar | undefined { + if (!newStore) { + return this._cloneSync() + } + if (!newStore.synchronous) { + throw new Error( + 'CookieJar clone destination store is not synchronous; use async API instead.', + ) + } + return this._cloneSync(newStore) + } + + removeAllCookies(callback: ErrorCallback): void + removeAllCookies(): Promise + removeAllCookies(callback?: ErrorCallback): unknown + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeAllCookies(_callback?: ErrorCallback): unknown { + const promiseCallback = createPromiseCallback(arguments) + const cb = promiseCallback.callback + + const store = this.store + + // Check that the store implements its own removeAllCookies(). The default + // implementation in Store will immediately call the callback with a "not + // implemented" Error. + if ( + typeof store.removeAllCookies === 'function' && + store.removeAllCookies !== Store.prototype.removeAllCookies + ) { + store.removeAllCookies(cb) + return promiseCallback.promise + } + + store.getAllCookies((err, cookies): void => { + if (err) { + cb(err) + return + } + + if (!cookies) { + cookies = [] + } + + if (cookies.length === 0) { + cb(null) + return + } + + let completedCount = 0 + const removeErrors: Error[] = [] + + function removeCookieCb(removeErr: Error | undefined) { + if (removeErr) { + removeErrors.push(removeErr) + } + + completedCount++ + + if (completedCount === cookies?.length) { + cb(removeErrors.length ? removeErrors[0] : null) + return + } + } + + cookies.forEach((cookie) => { + store.removeCookie( + cookie.domain, + cookie.path, + cookie.key, + removeCookieCb, + ) + }) + }) + + return promiseCallback.promise + } + removeAllCookiesSync(): void { + return this.callSync((callback) => this.removeAllCookies(callback)) + } + + static deserialize( + strOrObj: string | object, + callback: Callback, + ): void + static deserialize( + strOrObj: string | object, + store: Store, + callback: Callback, + ): void + static deserialize(strOrObj: string | object): Promise + static deserialize( + strOrObj: string | object, + store: Store, + ): Promise + static deserialize( + strOrObj: string | object, + store?: Store | Callback, + callback?: Callback, + ): unknown + static deserialize( + strOrObj: string | object, + store?: Store | Callback, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _callback?: Callback, + ): unknown { + if (typeof store === 'function') { + store = undefined + } + + const promiseCallback = createPromiseCallback(arguments) + + let serialized: unknown + if (typeof strOrObj === 'string') { + try { + serialized = JSON.parse(strOrObj) + } catch (e) { + return promiseCallback.reject(e instanceof Error ? e : new Error()) + } + } else { + serialized = strOrObj + } + + const readSerializedProperty = (property: string): unknown | undefined => { + return serialized && + typeof serialized === 'object' && + inOperator(property, serialized) + ? serialized[property] + : undefined + } + + const readSerializedBoolean = (property: string): boolean | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'boolean' ? value : undefined + } + + const readSerializedString = (property: string): string | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'string' ? value : undefined + } + + const jar = new CookieJar(store, { + rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), + looseMode: readSerializedBoolean('enableLooseMode'), + allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), + prefixSecurity: getNormalizedPrefixSecurity( + readSerializedString('prefixSecurity') ?? 'silent', + ), + }) + + jar._importCookies(serialized, (err) => { + if (err) { + promiseCallback.callback(err) + return + } + promiseCallback.callback(undefined, jar) + }) + + return promiseCallback.promise + } + + static deserializeSync( + strOrObj: string | SerializedCookieJar, + store?: Store, + ): CookieJar { + const serialized: unknown = + typeof strOrObj === 'string' ? JSON.parse(strOrObj) : strOrObj + + const readSerializedProperty = (property: string): unknown | undefined => { + return serialized && + typeof serialized === 'object' && + inOperator(property, serialized) + ? serialized[property] + : undefined + } + + const readSerializedBoolean = (property: string): boolean | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'boolean' ? value : undefined + } + + const readSerializedString = (property: string): string | undefined => { + const value = readSerializedProperty(property) + return typeof value === 'string' ? value : undefined + } + + const jar = new CookieJar(store, { + rejectPublicSuffixes: readSerializedBoolean('rejectPublicSuffixes'), + looseMode: readSerializedBoolean('enableLooseMode'), + allowSpecialUseDomain: readSerializedBoolean('allowSpecialUseDomain'), + prefixSecurity: getNormalizedPrefixSecurity( + readSerializedString('prefixSecurity') ?? 'silent', + ), + }) + + // catch this mistake early: + if (!jar.store.synchronous) { + throw new Error( + 'CookieJar store is not synchronous; use async API instead.', + ) + } + + jar._importCookiesSync(serialized) + return jar + } + + static fromJSON(jsonString: SerializedCookieJar, store?: Store): CookieJar { + return CookieJar.deserializeSync(jsonString, store) + } +} diff --git a/lib/cookie/defaultPath.ts b/lib/cookie/defaultPath.ts new file mode 100644 index 00000000..1436d6e0 --- /dev/null +++ b/lib/cookie/defaultPath.ts @@ -0,0 +1,30 @@ +// RFC6265 S5.1.4 Paths and Path-Match + +/* + * "The user agent MUST use an algorithm equivalent to the following algorithm + * to compute the default-path of a cookie:" + * + * Assumption: the path (and not query part or absolute uri) is passed in. + */ +export function defaultPath(path?: string | null): string { + // "2. If the uri-path is empty or if the first character of the uri-path is not + // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. + if (!path || path.slice(0, 1) !== '/') { + return '/' + } + + // "3. If the uri-path contains no more than one %x2F ("/") character, output + // %x2F ("/") and skip the remaining step." + if (path === '/') { + return path + } + + const rightSlash = path.lastIndexOf('/') + if (rightSlash === 0) { + return '/' + } + + // "4. Output the characters of the uri-path from the first character up to, + // but not including, the right-most %x2F ("/")." + return path.slice(0, rightSlash) +} diff --git a/lib/cookie/domainMatch.ts b/lib/cookie/domainMatch.ts new file mode 100644 index 00000000..073f59b7 --- /dev/null +++ b/lib/cookie/domainMatch.ts @@ -0,0 +1,71 @@ +import { canonicalDomain } from './canonicalDomain' + +// Dumped from ip-regex@4.0.0, with the following changes: +// * all capturing groups converted to non-capturing -- "(?:)" +// * support for IPv6 Scoped Literal ("%eth1") removed +// * lowercase hexadecimal only +const IP_REGEX_LOWERCASE = + /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/ + +// S5.1.3 Domain Matching +export function domainMatch( + str?: string | null, + domStr?: string | null, + canonicalize?: boolean, +): boolean | null { + if (str == null || domStr == null) { + return null + } + + let _str: string | null + let _domStr: string | null + + if (canonicalize !== false) { + _str = canonicalDomain(str) + _domStr = canonicalDomain(domStr) + } else { + _str = str + _domStr = domStr + } + + if (_str == null || _domStr == null) { + return null + } + + /* + * S5.1.3: + * "A string domain-matches a given domain string if at least one of the + * following conditions hold:" + * + * " o The domain string and the string are identical. (Note that both the + * domain string and the string will have been canonicalized to lower case at + * this point)" + */ + if (_str == _domStr) { + return true + } + + /* " o All of the following [three] conditions hold:" */ + + /* "* The domain string is a suffix of the string" */ + const idx = _str.lastIndexOf(domStr) + if (idx <= 0) { + return false // it's a non-match (-1) or prefix (0) + } + + // next, check it's a proper suffix + // e.g., "a.b.c".indexOf("b.c") === 2 + // 5 === 3+2 + if (_str.length !== _domStr.length + idx) { + return false // it's not a suffix + } + + /* " * The last character of the string that is not included in the + * domain string is a %x2E (".") character." */ + if (_str.substr(idx - 1, 1) !== '.') { + return false // doesn't align on "." + } + + /* " * The string is a host name (i.e., not an IP address)." */ + return !IP_REGEX_LOWERCASE.test(_str) +} diff --git a/lib/cookie/formatDate.ts b/lib/cookie/formatDate.ts new file mode 100644 index 00000000..c039c1da --- /dev/null +++ b/lib/cookie/formatDate.ts @@ -0,0 +1,8 @@ +import * as validators from '../validators' +import { safeToString } from '../utils' + +/** Converts a Date to a UTC string representation. */ +export function formatDate(date: Date) { + validators.validate(validators.isDate(date), safeToString(date)) + return date.toUTCString() +} diff --git a/lib/cookie/index.ts b/lib/cookie/index.ts new file mode 100644 index 00000000..1b167c28 --- /dev/null +++ b/lib/cookie/index.ts @@ -0,0 +1,21 @@ +export { MemoryCookieStore } from '../memstore' +export { pathMatch } from '../pathMatch' +export { permuteDomain } from '../permuteDomain' +export { getPublicSuffix } from '../pubsuffix-psl' +export { Store } from '../store' +export { ParameterError } from '../validators' +export { version } from '../version' + +export { canonicalDomain } from './canonicalDomain' +export { PrefixSecurityEnum } from './constants' +export { Cookie } from './cookie' +export { cookieCompare } from './cookieCompare' +export { CookieJar } from './cookieJar' +export { defaultPath } from './defaultPath' +export { domainMatch } from './domainMatch' +export { formatDate } from './formatDate' +export { parseDate } from './parseDate' +export { permutePath } from './permutePath' + +import { Cookie } from './cookie' +export const fromJSON = Cookie.fromJSON diff --git a/lib/cookie/parseDate.ts b/lib/cookie/parseDate.ts new file mode 100644 index 00000000..57b193bc --- /dev/null +++ b/lib/cookie/parseDate.ts @@ -0,0 +1,254 @@ +// date-time parsing constants (RFC6265 S5.1.1) +// eslint-disable-next-line no-control-regex +const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/ + +const MONTH_TO_NUM = { + jan: 0, + feb: 1, + mar: 2, + apr: 3, + may: 4, + jun: 5, + jul: 6, + aug: 7, + sep: 8, + oct: 9, + nov: 10, + dec: 11, +} + +/* + * Parses a Natural number (i.e., non-negative integer) with either the + * *DIGIT ( non-digit *OCTET ) + * or + * *DIGIT + * grammar (RFC6265 S5.1.1). + * + * The "trailingOK" boolean controls if the grammar accepts a + * "( non-digit *OCTET )" trailer. + */ +function parseDigits( + token: string, + minDigits: number, + maxDigits: number, + trailingOK: boolean, +) { + let count = 0 + while (count < token.length) { + const c = token.charCodeAt(count) + // "non-digit = %x00-2F / %x3A-FF" + if (c <= 0x2f || c >= 0x3a) { + break + } + count++ + } + + // constrain to a minimum and maximum number of digits. + if (count < minDigits || count > maxDigits) { + return null + } + + if (!trailingOK && count != token.length) { + return null + } + + return parseInt(token.slice(0, count), 10) +} + +function parseTime(token: string) { + const parts = token.split(':') + const result = [0, 0, 0] + + /* RF6256 S5.1.1: + * time = hms-time ( non-digit *OCTET ) + * hms-time = time-field ":" time-field ":" time-field + * time-field = 1*2DIGIT + */ + + if (parts.length !== 3) { + return null + } + + for (let i = 0; i < 3; i++) { + // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be + // followed by "( non-digit *OCTET )" therefore the last time-field can + // have a trailer + const trailingOK = i == 2 + const numPart = parts[i] + if (numPart == null) { + return null + } + const num = parseDigits(numPart, 1, 2, trailingOK) + if (num === null) { + return null + } + result[i] = num + } + + return result +} + +function parseMonth(token: string) { + token = String(token).slice(0, 3).toLowerCase() + switch (token) { + case 'jan': + return MONTH_TO_NUM.jan + case 'feb': + return MONTH_TO_NUM.feb + case 'mar': + return MONTH_TO_NUM.mar + case 'apr': + return MONTH_TO_NUM.apr + case 'may': + return MONTH_TO_NUM.may + case 'jun': + return MONTH_TO_NUM.jun + case 'jul': + return MONTH_TO_NUM.jul + case 'aug': + return MONTH_TO_NUM.aug + case 'sep': + return MONTH_TO_NUM.sep + case 'oct': + return MONTH_TO_NUM.oct + case 'nov': + return MONTH_TO_NUM.nov + case 'dec': + return MONTH_TO_NUM.dec + default: + return null + } +} + +/* + * RFC6265 S5.1.1 date parser (see RFC for full grammar) + */ +export function parseDate(str: string | undefined | null): Date | undefined { + if (!str) { + return undefined + } + + /* RFC6265 S5.1.1: + * 2. Process each date-token sequentially in the order the date-tokens + * appear in the cookie-date + */ + const tokens = str.split(DATE_DELIM) + if (!tokens) { + return undefined + } + + let hour = null + let minute = null + let second = null + let dayOfMonth = null + let month = null + let year = null + + for (let i = 0; i < tokens.length; i++) { + const token = (tokens[i] ?? '').trim() + if (!token.length) { + continue + } + + let result + + /* 2.1. If the found-time flag is not set and the token matches the time + * production, set the found-time flag and set the hour- value, + * minute-value, and second-value to the numbers denoted by the digits in + * the date-token, respectively. Skip the remaining sub-steps and continue + * to the next date-token. + */ + if (second === null) { + result = parseTime(token) + if (result) { + hour = result[0] + minute = result[1] + second = result[2] + continue + } + } + + /* 2.2. If the found-day-of-month flag is not set and the date-token matches + * the day-of-month production, set the found-day-of- month flag and set + * the day-of-month-value to the number denoted by the date-token. Skip + * the remaining sub-steps and continue to the next date-token. + */ + if (dayOfMonth === null) { + // "day-of-month = 1*2DIGIT ( non-digit *OCTET )" + result = parseDigits(token, 1, 2, true) + if (result !== null) { + dayOfMonth = result + continue + } + } + + /* 2.3. If the found-month flag is not set and the date-token matches the + * month production, set the found-month flag and set the month-value to + * the month denoted by the date-token. Skip the remaining sub-steps and + * continue to the next date-token. + */ + if (month === null) { + result = parseMonth(token) + if (result !== null) { + month = result + continue + } + } + + /* 2.4. If the found-year flag is not set and the date-token matches the + * year production, set the found-year flag and set the year-value to the + * number denoted by the date-token. Skip the remaining sub-steps and + * continue to the next date-token. + */ + if (year === null) { + // "year = 2*4DIGIT ( non-digit *OCTET )" + result = parseDigits(token, 2, 4, true) + if (result !== null) { + year = result + /* From S5.1.1: + * 3. If the year-value is greater than or equal to 70 and less + * than or equal to 99, increment the year-value by 1900. + * 4. If the year-value is greater than or equal to 0 and less + * than or equal to 69, increment the year-value by 2000. + */ + if (year >= 70 && year <= 99) { + year += 1900 + } else if (year >= 0 && year <= 69) { + year += 2000 + } + } + } + } + + /* RFC 6265 S5.1.1 + * "5. Abort these steps and fail to parse the cookie-date if: + * * at least one of the found-day-of-month, found-month, found- + * year, or found-time flags is not set, + * * the day-of-month-value is less than 1 or greater than 31, + * * the year-value is less than 1601, + * * the hour-value is greater than 23, + * * the minute-value is greater than 59, or + * * the second-value is greater than 59. + * (Note that leap seconds cannot be represented in this syntax.)" + * + * So, in order as above: + */ + if ( + dayOfMonth === null || + month == null || + year == null || + hour == null || + minute == null || + second == null || + dayOfMonth < 1 || + dayOfMonth > 31 || + year < 1601 || + hour > 23 || + minute > 59 || + second > 59 + ) { + return undefined + } + + return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)) +} diff --git a/lib/cookie/permutePath.ts b/lib/cookie/permutePath.ts new file mode 100644 index 00000000..e8ccbf34 --- /dev/null +++ b/lib/cookie/permutePath.ts @@ -0,0 +1,23 @@ +import * as validators from '../validators' + +/** + * Gives the permutation of all possible `pathMatch`es of a given path. The + * array is in longest-to-shortest order. Handy for indexing. + */ +export function permutePath(path: string): string[] { + validators.validate(validators.isString(path)) + if (path === '/') { + return ['/'] + } + const permutations = [path] + while (path.length > 1) { + const lindex = path.lastIndexOf('/') + if (lindex === 0) { + break + } + path = path.slice(0, lindex) + permutations.push(path) + } + permutations.push('/') + return permutations +} diff --git a/lib/memstore.ts b/lib/memstore.ts index 05d959e0..2327c7ef 100644 --- a/lib/memstore.ts +++ b/lib/memstore.ts @@ -29,15 +29,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ 'use strict' -import { - Callback, - Cookie, - createPromiseCallback, - pathMatch, - permuteDomain, -} from './cookie' +import type { Cookie } from './cookie/cookie' +import { pathMatch } from './pathMatch' +import { permuteDomain } from './permuteDomain' import { Store } from './store' import { getCustomInspectSymbol, getUtilInspect } from './utilHelper' +import { type Callback, createPromiseCallback, inOperator } from './utils' export type MemoryCookieStoreIndex = { [domain: string]: { @@ -444,10 +441,3 @@ function formatPath(pathName: string, pathValue: unknown) { return String(pathValue) } - -function inOperator( - k: K, - o: T, -): o is T & Record { - return k in o -} diff --git a/lib/store.ts b/lib/store.ts index 1f082a24..ed5d6b73 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -35,7 +35,8 @@ 'use strict' -import type { Callback, Cookie } from './cookie' +import type { Cookie } from './cookie/cookie' +import type { Callback } from './utils' export class Store { synchronous: boolean diff --git a/lib/utils.ts b/lib/utils.ts index c0825923..8834b140 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,3 +1,9 @@ +/** A callback function that accepts an error or a result. */ +export type Callback = ( + error: Error | undefined, + result: T | undefined, +) => void + /** Signature for a callback function that expects an error to be passed. */ export type ErrorCallback = (error: Error, result?: never) => void @@ -15,3 +21,62 @@ export const safeToString = (val: unknown) => { return objectToString(val) } } + +/** Utility object for promise/callback interop. */ +export interface PromiseCallback { + promise: Promise + callback: (error: Error | undefined | null, result?: T) => void + resolve: (value: T | undefined) => Promise + reject: (error: Error | undefined | null) => Promise +} + +/** Converts a callback into a utility object where either a callback or a promise can be used. */ +export function createPromiseCallback(args: IArguments): PromiseCallback { + let callback: (error: Error | null | undefined, result: T | undefined) => void + let resolve: (result: T | undefined) => void + let reject: (error: Error | null) => void + + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + + const cb: unknown = args[args.length - 1] + if (typeof cb === 'function') { + callback = (err, result) => { + try { + cb(err, result) + } catch (e) { + reject(e instanceof Error ? e : new Error()) + } + } + } else { + callback = (err, result) => { + try { + err ? reject(err) : resolve(result) + } catch (e) { + reject(e instanceof Error ? e : new Error()) + } + } + } + + return { + promise, + callback, + resolve: (value: T | undefined) => { + callback(null, value) + return promise + }, + reject: (error: Error | null | undefined) => { + callback(error, undefined) + return promise + }, + } +} + +export function inOperator( + k: K, + o: T, +): o is T & Record { + return k in o +} diff --git a/package.json b/package.json index f8e2a921..8809e814 100644 --- a/package.json +++ b/package.json @@ -76,8 +76,8 @@ "bugs": { "url": "https://github.com/salesforce/tough-cookie/issues" }, - "main": "./dist/cookie.js", - "types": "./dist/cookie.d.ts", + "main": "./dist/cookie/index.js", + "types": "./dist/cookie/index.d.ts", "files": [ "dist/*.js", "dist/*.d.ts" diff --git a/tsconfig.json b/tsconfig.json index 6b328b27..d79eb3f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,9 @@ "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "allowUnusedLabels": false, - "allowUnreachableCode": false + "allowUnreachableCode": false, + /* Compatibility */ + "ignoreDeprecations": "5.0" }, "exclude": [ "jest.config.ts"