From 7236510474b7c3f4821e9ebe74207b8566f75f60 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 19 Oct 2023 19:04:35 +0200 Subject: [PATCH] feat(node): Vendor `cookie` module (#9308) --- packages/node/package.json | 1 - packages/node/src/cookie.ts | 78 +++++++++++++++++++++++++++++++ packages/node/src/requestdata.ts | 6 +-- packages/node/test/cookie.test.ts | 67 ++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 packages/node/src/cookie.ts create mode 100644 packages/node/test/cookie.test.ts diff --git a/packages/node/package.json b/packages/node/package.json index 6f0da4a58815..ff847f28a518 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -27,7 +27,6 @@ "@sentry/core": "7.74.1", "@sentry/types": "7.74.1", "@sentry/utils": "7.74.1", - "cookie": "^0.5.0", "https-proxy-agent": "^5.0.0" }, "devDependencies": { diff --git a/packages/node/src/cookie.ts b/packages/node/src/cookie.ts new file mode 100644 index 000000000000..2a9d21654ba6 --- /dev/null +++ b/packages/node/src/cookie.ts @@ -0,0 +1,78 @@ +/** + * This code was originally copied from the 'cookie` module at v0.5.0 and was simplified for our use case. + * https://github.com/jshttp/cookie/blob/a0c84147aab6266bdb3996cf4062e93907c0b0fc/index.js + * It had the following license: + * + * (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. + */ + +/** + * Parses a cookie string + */ +export function parseCookie(str: string): Record { + const obj: Record = {}; + let index = 0; + + while (index < str.length) { + const eqIdx = str.indexOf('=', index); + + // no more cookie pairs + if (eqIdx === -1) { + break; + } + + let endIdx = str.indexOf(';', index); + + if (endIdx === -1) { + endIdx = str.length; + } else if (endIdx < eqIdx) { + // backtrack on prior semicolon + index = str.lastIndexOf(';', eqIdx - 1) + 1; + continue; + } + + const key = str.slice(index, eqIdx).trim(); + + // only assign once + if (undefined === obj[key]) { + let val = str.slice(eqIdx + 1, endIdx).trim(); + + // quoted values + if (val.charCodeAt(0) === 0x22) { + val = val.slice(1, -1); + } + + try { + obj[key] = val.indexOf('%') !== -1 ? decodeURIComponent(val) : val; + } catch (e) { + obj[key] = val; + } + } + + index = endIdx + 1; + } + + return obj; +} diff --git a/packages/node/src/requestdata.ts b/packages/node/src/requestdata.ts index e746db088a95..4d464ba13825 100644 --- a/packages/node/src/requestdata.ts +++ b/packages/node/src/requestdata.ts @@ -6,9 +6,10 @@ import type { TransactionSource, } from '@sentry/types'; import { isPlainObject, isString, normalize, stripUrlQueryAndFragment } from '@sentry/utils'; -import * as cookie from 'cookie'; import * as url from 'url'; +import { parseCookie } from './cookie'; + const DEFAULT_INCLUDES = { ip: false, request: true, @@ -202,11 +203,10 @@ export function extractRequestData( // cookies: // node, express, koa: req.headers.cookie // vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access requestData.cookies = // TODO (v8 / #5257): We're only sending the empty object for backwards compatibility, so the last bit can // come off in v8 - req.cookies || (headers.cookie && cookie.parse(headers.cookie)) || {}; + req.cookies || (headers.cookie && parseCookie(headers.cookie)) || {}; break; } case 'query_string': { diff --git a/packages/node/test/cookie.test.ts b/packages/node/test/cookie.test.ts new file mode 100644 index 000000000000..2110f384c9b6 --- /dev/null +++ b/packages/node/test/cookie.test.ts @@ -0,0 +1,67 @@ +/** + * This code was originally copied from the 'cookie` module at v0.5.0 and was simplified for our use case. + * https://github.com/jshttp/cookie/blob/a0c84147aab6266bdb3996cf4062e93907c0b0fc/test/parse.js + * It had the following license: + * + * (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. + */ + +import { parseCookie } from '../src/cookie'; + +describe('parseCookie(str)', function () { + it('should parse cookie string to object', function () { + expect(parseCookie('foo=bar')).toEqual({ foo: 'bar' }); + expect(parseCookie('foo=123')).toEqual({ foo: '123' }); + }); + + it('should ignore OWS', function () { + expect(parseCookie('FOO = bar; baz = raz')).toEqual({ FOO: 'bar', baz: 'raz' }); + }); + + it('should parse cookie with empty value', function () { + expect(parseCookie('foo= ; bar=')).toEqual({ foo: '', bar: '' }); + }); + + it('should URL-decode values', function () { + 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 return original value on escape error', function () { + expect(parseCookie('foo=%1;bar=bar')).toEqual({ foo: '%1', bar: 'bar' }); + }); + + it('should ignore cookies without value', function () { + expect(parseCookie('foo=bar;fizz ; buzz')).toEqual({ foo: 'bar' }); + expect(parseCookie(' fizz; foo= bar')).toEqual({ foo: 'bar' }); + }); + + it('should ignore duplicate cookies', function () { + expect(parseCookie('foo=%1;bar=bar;foo=boo')).toEqual({ foo: '%1', bar: 'bar' }); + expect(parseCookie('foo=false;bar=bar;foo=tre')).toEqual({ foo: 'false', bar: 'bar' }); + expect(parseCookie('foo=;bar=bar;foo=boo')).toEqual({ foo: '', bar: 'bar' }); + }); +});