Instantly share code, notes, and snippets.
Created
January 23, 2025 10:42
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save tkrotoff/a1f83070067300174fbe9f35d0075dd3 to your computer and use it in GitHub Desktop.
Parses a `Cookie` HTTP header value or `document.cookie` into an object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function decode(str: string) { | |
try { | |
return decodeURIComponent(str); | |
} catch { | |
return str; | |
} | |
} | |
/** | |
* Parses a `Cookie` HTTP header value or `document.cookie` into an object. | |
* | |
* Implementation adapted from https://gist.github.com/rendro/525bbbf85e84fa9042c2?permalink_comment_id=3084097#gistcomment-3084097 | |
*/ | |
export function parseCookie(cookies: string) { | |
const obj = Object.create(null) as Record<string, string | undefined>; | |
const list = cookies.split(';'); | |
for (const cookie of list) { | |
if (cookie === '') continue; | |
const eq = cookie.indexOf('='); | |
const key = (eq > 0 ? cookie.slice(0, eq) : cookie).trim(); | |
const value = eq > 0 ? decode(cookie.slice(eq + 1).trim()) : undefined; | |
if (!(key in obj)) { | |
obj[key] = value; | |
} | |
} | |
return obj; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { bench, describe } from 'vitest'; | |
import { parseCookie } from './parseCookie'; | |
// Benchs taken from https://github.com/jshttp/cookie/blob/v1.0.2/src/parse.bench.ts | |
const topSites = { | |
'accounts.google.com': '__Aaaa-AAAA=0:0AaaAaaaAAA0aA0aAA0aaAaAAaaaAA:AAAaAA0AAAaAAa0a', | |
'amazon.com': | |
'aaaaaaa-aa=000-0000000-0000000; bbbbbbb-bb-bbbb=1111111111b; c22c-ccccc=CCC; dddd=dddddd', | |
'apple.com': 'aaa=AA', | |
'cloudflare.com': | |
'__aa_aa=aaA0aaAAaAaaaa0aaaaAAAA_0aa0a.0AAA000aAA0aa-0000000000-0.0.0.0-Aaa00aA0aaAaAA0AaaAAAA0AaAaaaAAA.AaAaAaAA_0aAaAaaAA0AAAAaaAaaaaA0aaAAAaAAAaaaaa0AAa0AAAAAaaA0aaA0a0aaaaaAAA', | |
'docs.google.com': '__Aaaa-AAAA=0:aAAaA00AA0A0AAaAAAaAAAAAaA_AAA:aAAA00AAaaAAaaAA', | |
'drive.google.com': '__Aaaa-AAAA=0:AA0a0AAaaAaaA0A-AAAA0aaA_0A-aa:0aaAa0aA0aaAAAAa', | |
'en.wikipedia.org': | |
'AAA-Aaaa-Aaaaaa=00-Aaa-0000; BBB-Bbbb-Bbbbbb-Bbbbbb=11-Bbb-1111; CCC-CC=222; DddDD=DD:DD:Ddd_Ddddddddd:33.33:-333.33:d3; EeeeeeeEeeeeEeeee=4.444', | |
'istockphoto.com': | |
'aaaaaaa=aAAAAAAaAAAaaa0aa0aAAAa0aAaaaAAA0A%22aaA0aAaaAaa0AAaaaaAAaAaaa0AaaaA%2200a0a00AA00A0aAaAaA0aa%22AAaaAA0000aAa00%220AaAaAAA0AaaAaAAAaaaAaAAa00AaAa0AaaaAaAaAaaaaaaa%22aAaaaAaaAaaaAAAAaAAaaAAaAaaAaaaAAa0A%22--aaA0aaAAAAAaaAAa--a0a%22A%22aAAaAAAa%22AaaAaAA%22%22; bbb=bb=1&bb=1111-11-11B11%2211%2211B; ccc=ccc=22222ccc-2c22-2c22-2222-22c2222c2222; ddd=d=DD%22dDddddDdddD3ddDddd33dddD3dDD33dDd3DDdDdddDdD33D3Dd33DDdd3dDd3dDDdDDDdD3dD3d3D3dDDdd3dddD%223DDDDdd3dD3ddd3dddDdDdD33D3DdDDd3dD3dDddDDdDdDDDdDDDDDDdddDdDdDdD3dddDDDd3DDD3D%22%2233d%22D3DDDDdDDDDDddddDDDdd3DdDDdDDDDdDdddd3ddD3D3DDddd3ddD3D3DDddDddDDDd3DDDDDdD3DDddDdDdDDD3DDDdDDddDDDdDDD3DD33DDddDDD3DDDdDDDdDDD3DDdD%223%223%223&d; eeee=e=eEEEeEee44eE4eeEEeeEEEeEE4444eeE4eE4eEE4%224e%22', | |
'maps.google.com': | |
'AAA=000=aaaaAa0Aaa0aaaaaaaaaaAAAAAAaaaaAAA0AaAAA0aaaA00AAA0aAAAA0aaA0aAAAAaAAA0aAAAAaaAAaa_AAAAAaA0a0aaaAaaaaa--aa0aAAA0A0AaAA0AAaaAAa0AaAAa0A_aAAAAaAA_aAaAa0AaA0aAa0Aa-AAa0aaaAAaAA-AaaaA0aaAaAA0aaaa', | |
'play.google.com': | |
'AAA=000=A0AaA-aaaA00AaAAa00A0aaAAaA0AaAaaaa0A_Aa0_AaaAAAaAaa0aa0a-0aAaA0__AaAAaaaaAAaaaaaAAaaa0aAAAa0a_a0aaAAAAAaAaa0aaaaaAaaaaAAA_aAa0aaa0aaa00aaAAAaA0AAAAa00A0A0AaAaA0AAA0A0AAaAA0aaAAa', | |
'policies.google.com': | |
'AAA=000=AaAAAA_aAAa0aAaaaaAAa000A0AAaAaAAaAaaAa00-0aAaaAAaaAAAA0a0aaA0AaAaaaaaaaaAAAA0a0AAa_AaaAaaaAa_aaAAAaa00aAAAAAA_AAAAa0aA0a0AaA_00A0-0AaA-A_AAaA00aaAAAAAAaAaAaaaa0aaAAa0AaaA0aaAaaa', | |
'pt.wikipedia.org': | |
'AAA-Aaaa-Aaaaaa=00-Aaa-0000; BBB-Bbbb-Bbbbbb-Bbbbbb=11-Bbb-1111; CccCC=CC:CC:Ccc_Ccccccccc:22.22:-222.22:c2; DddddddDddddDdddd=3.333', | |
'sites.google.com': '__Aaaa-AAAA=0:aaaAaAAA0aAAaAAAaAaaAaa0A00AAA:aAAaaaAA_aa0A0aA', | |
'support.google.com': | |
'AAA=000=aaAAAaaAaaaAaA0aAA-00-AAaA0Aa0A0aaaAaaAa00_aAAAAAaaA0AAa0AAA0aAaA0aA0A0AAAAA0A0aaAAAA0aa-AAaAa00a0AaaaAaaAaa0Aaa0AA0aAAaaaaaAAAaAAAA0AA0AAaaaAa0aAAAaaaaaaAAA_0aaAaaAA0AaA0a0a0aAaA; BBB=111=bbBBBbbBbbbBbB1bBB-11-BBbB1Bb1B1bbbBbbBb11_bBBBBBbbB1BBb1BBB1bBbB1bB1B1BBBBB1B1bbBBBB1bb-BBbBb11b1BbbbBbbBbb1Bbb1BB1bBBbbbbbBBBbBBBB1BB1BBbbbBb1bBBBbbbbbbBBB_1bbBbbBB1BbB1b1b1bBbB', | |
't.me': 'aaaa_aaaa=aa0a00a00a000000a0_0000000000000000000', | |
'vk.com': | |
'aaaaaaa=AAAAAAA; bbbbbbbbb=1; cccccccccc=2222222222222222222_CcCCccCCccc2CcCcCCCccCCCCccc2CccCC2cc2CcCCc; ddddddd=DDDDDDD; eeeeeEEEE=EEEEEEE; fffffffff=5555555555_fFfFffFfffFF55FF5ffffF5ffFFfff5f5F55FfF5fFF; ggggggg=-6%22-6%22-6%226666666666; hhhhhhh=7; iiiiiii=-8%22-8%22-8%228888888888', | |
'www.google.com': | |
'AAA=AAAA0aaA00aaAaA-aaA0AaAaaAaa0aaAaAaaAAaA0AA00A0aAaAAAA0aaA; BBB=111=bbb111bBBb11-bbbBbbBBb11BbBB1bbb-bbBbbBBBbb-bbbb1BBbBBbbbb_bBb1BbbBBB1B1bBbb1b11bbBB11B11BBbBbbBB11BBBbb1b-BBbBBbBbBBB11bBbBB1BbbBBBBbbBbbBBbbB1b1bBb1B_BBb1BBB1bB1BbBBBbb1bB11b1bbbBBbbBbBBBbbbBBBb', | |
'youtu.be': | |
'AAA=0; BBB=bbbBbBbbbbb; CCCCCCC_CCCC2_CCCC=Cccc-cCCcCC; DDDDDDD_DDDDDDD_DDDDDDDD=DdDDDdDDDdDdDD%22%22', | |
'youtube.com': | |
'AAA=0; BBB=BbBbbbb1bBb; CCCCCCC_CCCC2_CCCC=CCc2ccCccCc; DDDDDDD_DDDDDDD_DDDDDDDD=DdDDDdDDDdDdDd%22%22', | |
'example.com': '' | |
}; | |
function genCookies(count: number) { | |
let str = ''; | |
for (let i = 0; i < count; i++) { | |
str += `; foo${i}=bar`; | |
} | |
return str.slice(2); | |
} | |
bench('simple', () => { | |
parseCookie('foo=bar'); | |
}); | |
bench('decode', () => { | |
parseCookie('foo=hello%20there!'); | |
}); | |
bench('unquote', () => { | |
parseCookie('foo="foo bar"'); | |
}); | |
const duplicates = `${genCookies(2)}; ${genCookies(2)}`; | |
bench('duplicates', () => { | |
parseCookie(duplicates); | |
}); | |
const cookies10 = genCookies(10); | |
bench('10 cookies', () => { | |
parseCookie(cookies10); | |
}); | |
const cookies100 = genCookies(100); | |
bench('100 cookies', () => { | |
parseCookie(cookies100); | |
}); | |
describe('parse top-sites', () => { | |
Object.entries(topSites).forEach(([domain, value]) => { | |
bench(`parse ${domain}`, () => { | |
parseCookie(value); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { expect, it } from 'vitest'; | |
import { parseCookie } from './parseCookie'; | |
// Tests taken from https://github.com/jshttp/cookie/blob/v1.0.2/src/parse.spec.ts | |
it('should parse cookie string to object', () => { | |
expect(parseCookie('foo=bar')).toEqual({ foo: 'bar' }); | |
expect(parseCookie('foo=123')).toEqual({ foo: '123' }); | |
}); | |
it('should ignore optional whitespaces (OWS)', () => { | |
expect(parseCookie('FOO = bar; baz = raz')).toEqual({ | |
FOO: 'bar', | |
baz: 'raz' | |
}); | |
}); | |
it('should parse cookie with empty value', () => { | |
expect(parseCookie('foo=; bar=')).toEqual({ foo: '', bar: '' }); | |
}); | |
it('should parse cookie with minimum length', () => { | |
expect(parseCookie('f=')).toEqual({ f: '' }); | |
expect(parseCookie('f=;b=')).toEqual({ f: '', b: '' }); | |
}); | |
it('should URL-decode values', () => { | |
expect(parseCookie('foo="bar=123456789&name=Magic+Mouse"')).toEqual({ | |
foo: '"bar=123456789&name=Magic+Mouse"' | |
}); | |
expect(parseCookie('email=%20%22%2c%3b%2f')).toEqual({ email: ' ",;/' }); | |
}); | |
it('should trim whitespace around key and value', () => { | |
expect(parseCookie(' foo = "bar" ')).toEqual({ foo: '"bar"' }); | |
expect(parseCookie(' foo = bar ; fizz = buzz ')).toEqual({ | |
foo: 'bar', | |
fizz: 'buzz' | |
}); | |
expect(parseCookie(' foo = " a b c " ')).toEqual({ foo: '" a b c "' }); | |
expect(parseCookie(' = bar ')).toEqual({ '': 'bar' }); | |
expect(parseCookie(' foo = ')).toEqual({ foo: '' }); | |
expect(parseCookie(' = ')).toEqual({ '': '' }); | |
expect(parseCookie('\tfoo\t=\tbar\t')).toEqual({ foo: 'bar' }); | |
}); | |
it('should return original value on escape error', () => { | |
expect(parseCookie('foo=%1;bar=bar')).toEqual({ foo: '%1', bar: 'bar' }); | |
}); | |
it('should ignore cookies without value', () => { | |
expect(parseCookie('foo=bar;fizz ; buzz')).toEqual({ foo: 'bar' }); | |
expect(parseCookie(' fizz; foo= bar')).toEqual({ foo: 'bar' }); | |
}); | |
it('should ignore duplicate cookies', () => { | |
expect(parseCookie('foo=%1;bar=bar;foo=boo')).toEqual({ | |
foo: '%1', | |
bar: 'bar' | |
}); | |
expect(parseCookie('foo=false;bar=bar;foo=true')).toEqual({ | |
foo: 'false', | |
bar: 'bar' | |
}); | |
expect(parseCookie('foo=;bar=bar;foo=boo')).toEqual({ | |
foo: '', | |
bar: 'bar' | |
}); | |
}); | |
it('should parse native properties', () => { | |
expect(parseCookie('toString=foo;valueOf=bar')).toEqual({ | |
toString: 'foo', | |
valueOf: 'bar' | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment