From 957bbe31c622ebbcd18bf03ab87c4aceab89dcd2 Mon Sep 17 00:00:00 2001 From: JAYA DILEEP Date: Sun, 6 Sep 2026 04:24:43 +0000 Subject: [PATCH] Reject reserved names in morgan.token() Registering a token named token/format/compile overwrote morgan's own API methods because tokens are stored on the export object. Throw a TypeError instead, document the reserved names, and add regression tests. Fixes #265 Signed-off-by: JAYA DILEEP --- README.md | 4 ++++ index.js | 15 +++++++++++++++ test/morgan.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/README.md b/README.md index 2e53be2..db98876 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,10 @@ morgan.token('type', function (req, res) { return req.headers['content-type'] }) Calling `morgan.token()` using the same name as an existing token will overwrite that token definition. +The names `token`, `format`, and `compile` are reserved by morgan itself and cannot be +used as custom token names. Attempting to register a token with one of these names will +throw a `TypeError`. + The token function is expected to be called with the arguments `req` and `res`, representing the HTTP request and HTTP response. Additionally, the token can accept further arguments of its choosing to customize behavior. diff --git a/index.js b/index.js index 7f4bfac..d4fca6a 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,17 @@ module.exports.compile = compile module.exports.format = format module.exports.token = token +/** + * Names that must not be registered as custom tokens, because token() + * stores callbacks on the morgan export object itself. + * @private + */ +var RESERVED_TOKEN_NAMES = { + compile: true, + format: true, + token: true +} + /** * Module dependencies. * @private @@ -585,6 +596,10 @@ function recordStartTime () { */ function token (name, fn) { + if (Object.prototype.hasOwnProperty.call(RESERVED_TOKEN_NAMES, name)) { + throw new TypeError('morgan.token cannot use reserved name "' + name + '"') + } + // wrap the token so its string output is always escaped for line-oriented // logs, regardless of whether the format is a string or a function morgan[name] = function tokenValue () { diff --git a/test/morgan.js b/test/morgan.js index 2f6dfd4..d9f6795 100644 --- a/test/morgan.js +++ b/test/morgan.js @@ -1830,6 +1830,36 @@ describe('morgan.compile(format)', function () { }) }) +describe('morgan.token(name, fn)', function () { + describe('arguments', function () { + describe('name', function () { + it('should reject reserved names', function () { + assert.throws(morgan.token.bind(morgan, 'token', function () {}), /reserved name/) + assert.throws(morgan.token.bind(morgan, 'format', function () {}), /reserved name/) + assert.throws(morgan.token.bind(morgan, 'compile', function () {}), /reserved name/) + }) + + it('should not corrupt morgan.token after rejecting a reserved name', function () { + assert.throws(morgan.token.bind(morgan, 'token', function () {}), /reserved name/) + + // morgan.token itself must still be a working function + assert.strictEqual(typeof morgan.token, 'function') + + // and registering a normal token afterwards must still work + morgan.token('my-custom-token', function () { return 'custom-value' }) + assert.strictEqual(typeof morgan['my-custom-token'], 'function') + assert.strictEqual(morgan['my-custom-token']({}, {}), 'custom-value') + }) + + it('should allow non-reserved names', function () { + morgan.token('some-other-token', function () { return 'value' }) + assert.strictEqual(typeof morgan['some-other-token'], 'function') + assert.strictEqual(morgan['some-other-token']({}, {}), 'value') + }) + }) + }) +}) + function after (count, callback) { var args = new Array(3) var i = 0