From 5b18a1fa46ef9c41f6089e5db259af80f3e98b0a Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Sat, 24 Feb 2024 15:38:33 -0800 Subject: [PATCH 1/1] cookies are now parsed and populated on ctx.cookie by deault --- CHANGELOG.md | 6 ++- lib/dingus.js | 33 ++++++++++++++-- lib/enum.js | 2 + lib/patches/incoming-message.js | 2 +- test/lib/dingus.js | 67 ++++++++++++++++++++++++++++++++- 5 files changed, 104 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfc96b..e31efa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Releases and notable changes to this project are documented here. ## [Unreleased] +## [v2.1.0] - TBD + +- cookies are now parsed and populated into ctx.cookie as a default behavior - added HTTP status and message enums - updated devDependencies @@ -107,7 +110,8 @@ Releases and notable changes to this project are documented here. --- -[Unreleased]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=HEAD;hp=v2.0.1 +[Unreleased]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=HEAD;hp=v2.1.0 +[v2.1.0]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=v2.1.0;hp=v2.0.1 [v2.0.1]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=v2.0.1;hp=v2.0.0 [v2.0.0]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=v2.0.0;hp=v1.2.10 [v1.2.10]: https://git.squeep.com/?p=squeep-api-dingus;a=commitdiff;h=v1.2.10;hp=v1.2.9 diff --git a/lib/dingus.js b/lib/dingus.js index a5f77ab..f060c18 100644 --- a/lib/dingus.js +++ b/lib/dingus.js @@ -35,6 +35,8 @@ const defaultOptions = { querystring, }; +const cookieSplitRE = /; */; + class Dingus { /** * @param {Object} logger object which implements logging methods @@ -179,15 +181,40 @@ class Dingus { /** - * Called before every request handler. - * Sets tracking identifiers and client information on ctx. + * Sets ctx.cookie from Cookie header. * @param {http.ClientRequest} req * @param {http.ServerResponse} res * @param {object} ctx */ + static ingestCookie(req, res, ctx) { + ctx.cookie = {}; + req.getHeader(Enum.Header.Cookie)?.split(cookieSplitRE).forEach((cookie) => { + const [ name, value ] = common.splitFirst(cookie, '=', null).map((x) => { + try { + return decodeURIComponent(x.trim()); + } catch (e) { + return x; + } + }); + if (name && !(name in ctx.cookie)) { + const isQuoted = value?.startsWith('"') && value.endsWith('"'); + ctx.cookie[name] = isQuoted ? value.slice(1, -1) : value; // eslint-disable-line security/detect-object-injection + } + }); + } + + + /** + * Called before every request handler. + * Sets tracking identifiers and client information on ctx. + * @param {http.ClientRequest} req + * @param {http.ServerResponse} res + * @param {object} ctx + */ async preHandler(req, res, ctx) { - Dingus.tagContext(req, res, ctx); + this.constructor.tagContext(req, res, ctx); this.clientAddressContext(req, res, ctx); + this.constructor.ingestCookie(req, res, ctx); } diff --git a/lib/enum.js b/lib/enum.js index 8da3ad6..e426c01 100644 --- a/lib/enum.js +++ b/lib/enum.js @@ -202,12 +202,14 @@ const Header = { ContentEncoding: 'Content-Encoding', ContentLength: 'Content-Length', ContentType: 'Content-Type', + Cookie: 'Cookie', ETag: 'ETag', IfModifiedSince: 'If-Modified-Since', IfNoneMatch: 'If-None-Match', LastModified: 'Last-Modified', Location: 'Location', RequestId: 'Request-ID', + SetCookie: 'Set-Cookie', Vary: 'Vary', XCorrelationId: 'X-Correlation-ID', XForwardedFor: 'X-Forwarded-For', diff --git a/lib/patches/incoming-message.js b/lib/patches/incoming-message.js index 0b2e259..237d95e 100644 --- a/lib/patches/incoming-message.js +++ b/lib/patches/incoming-message.js @@ -12,6 +12,6 @@ if (typeof IncomingMessage.getHeader !== 'function') { if (typeof name !== 'string') { throw new TypeError('\'name\' must be a string'); } - return this.headers && this.headers[name.toLowerCase()]; + return this.headers?.[name.toLowerCase()]; }; } diff --git a/test/lib/dingus.js b/test/lib/dingus.js index 8913436..01094a7 100644 --- a/test/lib/dingus.js +++ b/test/lib/dingus.js @@ -12,7 +12,7 @@ const Enum = require('../../lib/enum'); const noExpectedException = 'did not get expected exception'; -const _nop = () => {}; +const _nop = () => undefined; const _logFn = (process.env['VERBOSE_TESTS'] && console.log) || _nop; const noLogger = { debug: _logFn, @@ -180,6 +180,71 @@ describe('Dingus', function () { }); }); // clientAddressContext + describe('ingestCookie', function () { + let req, res, ctx; + beforeEach(function () { + req = { + getHeader: sinon.stub(), + }; + ctx = {}; + }); + it('covers no header', function () { + const expected = {}; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('covers non variable', function () { + req.getHeader.returns('foo'); + const expected = { + foo: null, + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses cookies', function () { + req.getHeader.returns('foo=bar; baz="quux"'); + const expected = { + foo: 'bar', + baz: 'quux', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses nulls', function () { + req.getHeader.returns('foo=; bar='); + const expected = { + foo: '', + bar: '', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('parses non-uri-encoded', function () { + req.getHeader.returns('foo%=%qux'); + const expected = { + 'foo%': '%qux', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + it('covers nameless cookie', function () { + req.getHeader.returns('=bar'); + const expected = { + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + + }); + it('covers duplicate cookie', function () { + req.getHeader.returns('foo=bar; foo="quux"'); + const expected = { + foo: 'bar', + }; + Dingus.ingestCookie(req, res, ctx); + assert.deepStrictEqual(ctx.cookie, expected); + }); + }); // ingestCookie + describe('getRequestContentType', function () { let req; beforeEach(function () { -- 2.45.2