cookies are now parsed and populated on ctx.cookie by deault
authorJustin Wind <justin.wind+git@gmail.com>
Sat, 24 Feb 2024 23:38:33 +0000 (15:38 -0800)
committerJustin Wind <justin.wind+git@gmail.com>
Sat, 24 Feb 2024 23:38:33 +0000 (15:38 -0800)
CHANGELOG.md
lib/dingus.js
lib/enum.js
lib/patches/incoming-message.js
test/lib/dingus.js

index 2bfc96bce6298d9d20e6129fb373f9deae5d8937..e31efa43d3ce5e7a7d344a95f6b13ffeb2116e8a 100644 (file)
@@ -4,6 +4,9 @@ Releases and notable changes to this project are documented here.
 
 ## [Unreleased]
 
 
 ## [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
 
 - 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
 [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
index a5f77ab19fa02f1df8d8fec140ef5347c94eb5f2..f060c18f00d251f610a97929fdce663c9f01de89 100644 (file)
@@ -35,6 +35,8 @@ const defaultOptions = {
   querystring,
 };
 
   querystring,
 };
 
+const cookieSplitRE = /; */;
+
 class Dingus {
   /**
    * @param {Object} logger object which implements logging methods
 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 
    */
    * @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) {
   async preHandler(req, res, ctx) {
-    Dingus.tagContext(req, res, ctx);
+    this.constructor.tagContext(req, res, ctx);
     this.clientAddressContext(req, res, ctx);
     this.clientAddressContext(req, res, ctx);
+    this.constructor.ingestCookie(req, res, ctx);
   }
 
 
   }
 
 
index 8da3ad696ebc8fa0a8b0fa2c0be077caac88c28e..e426c01b27ffa72ee902759922af5a136619ac2e 100644 (file)
@@ -202,12 +202,14 @@ const Header = {
   ContentEncoding: 'Content-Encoding',
   ContentLength: 'Content-Length',
   ContentType: 'Content-Type',
   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',
   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',
   Vary: 'Vary',
   XCorrelationId: 'X-Correlation-ID',
   XForwardedFor: 'X-Forwarded-For',
index 0b2e259c499ec932708c783dfbd44391b9e93564..237d95e4ed8927bd3f3c46e98ed09ebb65e80f00 100644 (file)
@@ -12,6 +12,6 @@ if (typeof IncomingMessage.getHeader !== 'function') {
     if (typeof name !== 'string') {
       throw new TypeError('\'name\' must be a string');
     }
     if (typeof name !== 'string') {
       throw new TypeError('\'name\' must be a string');
     }
-    return this.headers && this.headers[name.toLowerCase()];
+    return this.headers?.[name.toLowerCase()];
   };
 }
   };
 }
index 8913436980c22cd5ad076af0dd39f2f722625381..01094a7c8e94bc9647411b422d76f8339783a0cd 100644 (file)
@@ -12,7 +12,7 @@ const Enum = require('../../lib/enum');
 
 const noExpectedException = 'did not get expected exception';
 
 
 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,
 const _logFn = (process.env['VERBOSE_TESTS'] && console.log) || _nop;
 const noLogger = {
   debug: _logFn,
@@ -180,6 +180,71 @@ describe('Dingus', function () {
     });
   }); // clientAddressContext
 
     });
   }); // 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 () {
   describe('getRequestContentType', function () {
     let req;
     beforeEach(function () {