From dac8e73dd766ba6262546b57bbda7955ba71b1b6 Mon Sep 17 00:00:00 2001 From: Khafra Date: Wed, 30 Oct 2024 18:32:18 -0400 Subject: [PATCH] add tests from cookie package (#3789) --- index.js | 3 +- lib/web/cookies/index.js | 13 ++++- lib/web/cookies/parse.js | 7 ++- test/cookie/npm-cookie.js | 113 ++++++++++++++++++++++++++++++++++++++ types/cookies.d.ts | 2 + 5 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 test/cookie/npm-cookie.js diff --git a/index.js b/index.js index 8f28f6ccc7e..d53868f35cd 100644 --- a/index.js +++ b/index.js @@ -136,12 +136,13 @@ const { kConstruct } = require('./lib/core/symbols') // in an older version of Node, it doesn't have any use without fetch. module.exports.caches = new CacheStorage(kConstruct) -const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/web/cookies') +const { deleteCookie, getCookies, getSetCookies, setCookie, parseCookie } = require('./lib/web/cookies') module.exports.deleteCookie = deleteCookie module.exports.getCookies = getCookies module.exports.getSetCookies = getSetCookies module.exports.setCookie = setCookie +module.exports.parseCookie = parseCookie const { parseMIMEType, serializeAMimeType } = require('./lib/web/fetch/data-url') diff --git a/lib/web/cookies/index.js b/lib/web/cookies/index.js index 372112307bc..8bcb4e24a72 100644 --- a/lib/web/cookies/index.js +++ b/lib/web/cookies/index.js @@ -91,6 +91,16 @@ function getSetCookies (headers) { return cookies.map((pair) => parseSetCookie(pair)) } +/** + * Parses a cookie string + * @param {string} cookie + */ +function parseCookie (cookie) { + cookie = webidl.converters.DOMString(cookie) + + return parseSetCookie(cookie) +} + /** * @param {Headers} headers * @param {Cookie} cookie @@ -184,5 +194,6 @@ module.exports = { getCookies, deleteCookie, getSetCookies, - setCookie + setCookie, + parseCookie } diff --git a/lib/web/cookies/parse.js b/lib/web/cookies/parse.js index 26dff025184..4ac66dc0974 100644 --- a/lib/web/cookies/parse.js +++ b/lib/web/cookies/parse.js @@ -4,6 +4,7 @@ const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants') const { isCTLExcludingHtab } = require('./util') const { collectASequenceOfCodePointsFast } = require('../fetch/data-url') const assert = require('node:assert') +const { unescape } = require('node:querystring') /** * @description Parses the field-value attributes of a set-cookie header string. @@ -76,8 +77,12 @@ function parseSetCookie (header) { // 6. The cookie-name is the name string, and the cookie-value is the // value string. + // https://datatracker.ietf.org/doc/html/rfc6265 + // To maximize compatibility with user agents, servers that wish to + // store arbitrary data in a cookie-value SHOULD encode that data, for + // example, using Base64 [RFC4648]. return { - name, value, ...parseUnparsedAttributes(unparsedAttributes) + name, value: unescape(value), ...parseUnparsedAttributes(unparsedAttributes) } } diff --git a/test/cookie/npm-cookie.js b/test/cookie/npm-cookie.js new file mode 100644 index 00000000000..28e86c1d279 --- /dev/null +++ b/test/cookie/npm-cookie.js @@ -0,0 +1,113 @@ +'use strict' + +// (The MIT License) +// +// Copyright (c) 2012-2014 Roman Shtylman +// Copyright (c) 2015 Douglas Christopher Wilson +// +// 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 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. + +const { describe, it } = require('node:test') +const assert = require('node:assert') +const { parseCookie } = require('../..') + +describe('parseCookie(str)', function () { + it('should parse cookie string to object', function () { + assert.deepStrictEqual(parseCookie('foo=bar'), { name: 'foo', value: 'bar' }) + assert.deepStrictEqual(parseCookie('foo=123'), { name: 'foo', value: '123' }) + }) + + it('should ignore OWS', function () { + assert.deepStrictEqual(parseCookie('FOO = bar; baz = raz'), { + name: 'FOO', + value: 'bar', + unparsed: ['baz=raz'] + }) + }) + + it('should parse cookie with empty value', function () { + assert.deepStrictEqual(parseCookie('foo=; bar='), { name: 'foo', value: '', unparsed: ['bar='] }) + }) + + it('should parse cookie with minimum length', function () { + assert.deepStrictEqual(parseCookie('f='), { name: 'f', value: '' }) + assert.deepStrictEqual(parseCookie('f=;b='), { name: 'f', value: '', unparsed: ['b='] }) + }) + + it('should URL-decode values', function () { + assert.deepStrictEqual(parseCookie('foo="bar=123456789&name=Magic+Mouse"'), { + name: 'foo', + value: '"bar=123456789&name=Magic+Mouse"' + }) + + assert.deepStrictEqual(parseCookie('email=%20%22%2c%3b%2f'), { name: 'email', value: ' ",;/' }) + }) + + it('should trim whitespace around key and value', function () { + assert.deepStrictEqual(parseCookie(' foo = "bar" '), { name: 'foo', value: '"bar"' }) + assert.deepStrictEqual(parseCookie(' foo = bar ; fizz = buzz '), { + name: 'foo', + value: 'bar', + unparsed: ['fizz=buzz'] + }) + assert.deepStrictEqual(parseCookie(' foo = " a b c " '), { name: 'foo', value: '" a b c "' }) + assert.deepStrictEqual(parseCookie(' = bar '), { name: '', value: 'bar' }) + assert.deepStrictEqual(parseCookie(' foo = '), { name: 'foo', value: '' }) + assert.deepStrictEqual(parseCookie(' = '), { name: '', value: '' }) + assert.deepStrictEqual(parseCookie('\tfoo\t=\tbar\t'), { name: 'foo', value: 'bar' }) + }) + + it('should return original value on escape error', function () { + assert.deepStrictEqual(parseCookie('foo=%1;bar=bar'), { name: 'foo', value: '%1', unparsed: ['bar=bar'] }) + }) + + it('should ignore cookies without value', function () { + assert.deepStrictEqual(parseCookie('foo=bar;fizz ; buzz'), { name: 'foo', value: 'bar', unparsed: ['fizz=', 'buzz='] }) + assert.deepStrictEqual(parseCookie(' fizz; foo= bar'), { name: '', value: 'fizz', unparsed: ['foo=bar'] }) + }) + + it('should ignore duplicate cookies', function () { + assert.deepStrictEqual(parseCookie('foo=%1;bar=bar;foo=boo'), { + name: 'foo', + value: '%1', + unparsed: ['bar=bar', 'foo=boo'] + }) + assert.deepStrictEqual(parseCookie('foo=false;bar=bar;foo=true'), { + name: 'foo', + value: 'false', + unparsed: ['bar=bar', 'foo=true'] + }) + assert.deepStrictEqual(parseCookie('foo=;bar=bar;foo=boo'), { + name: 'foo', + value: '', + unparsed: ['bar=bar', 'foo=boo'] + }) + }) + + it('should parse native properties', function () { + assert.deepStrictEqual(parseCookie('toString=foo;valueOf=bar'), { + name: 'toString', + unparsed: [ + 'valueOf=bar' + ], + value: 'foo' + }) + }) +}) diff --git a/types/cookies.d.ts b/types/cookies.d.ts index aa38cae49d7..f746d35853f 100644 --- a/types/cookies.d.ts +++ b/types/cookies.d.ts @@ -26,3 +26,5 @@ export function getCookies (headers: Headers): Record export function getSetCookies (headers: Headers): Cookie[] export function setCookie (headers: Headers, cookie: Cookie): void + +export function parseCookie (cookie: string): Cookie | null