From 6b9df1506943566549dc3810655b4105e49a9182 Mon Sep 17 00:00:00 2001 From: cplepage Date: Thu, 16 Dec 2021 16:58:38 -0500 Subject: [PATCH 1/8] changed value check --- src/parser/serializer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index db9e015a..7088b838 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -767,7 +767,7 @@ export function serializeInto( let value = object[i]; // Is there an override value - if (value && value.toBSON) { + if (value !== null && value !== undefined && value.toBSON) { if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); value = value.toBSON(); } @@ -959,7 +959,7 @@ export function serializeInto( for (const key in object) { let value = object[key]; // Is there an override value - if (value && value.toBSON) { + if (value !== null && value !== undefined && value.toBSON) { if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); value = value.toBSON(); } From b52b1902b58e9be71db44e8e9fe35fb3d34b2d8e Mon Sep 17 00:00:00 2001 From: CP Lepage <32472542+CPLepage@users.noreply.github.com> Date: Sun, 19 Dec 2021 23:15:58 -0500 Subject: [PATCH 2/8] Update src/parser/serializer.ts Co-authored-by: Neal Beeken --- src/parser/serializer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 7088b838..081c0b26 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -767,8 +767,7 @@ export function serializeInto( let value = object[i]; // Is there an override value - if (value !== null && value !== undefined && value.toBSON) { - if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); + if (typeof value?.toBSON === 'function') { value = value.toBSON(); } From 22c06fb49f5da86913b017e42e975be49a69c557 Mon Sep 17 00:00:00 2001 From: CP Lepage <32472542+CPLepage@users.noreply.github.com> Date: Sun, 19 Dec 2021 23:16:04 -0500 Subject: [PATCH 3/8] Update src/parser/serializer.ts Co-authored-by: Neal Beeken --- src/parser/serializer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 081c0b26..ce790cd0 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -958,8 +958,7 @@ export function serializeInto( for (const key in object) { let value = object[key]; // Is there an override value - if (value !== null && value !== undefined && value.toBSON) { - if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); + if (typeof value?.toBSON === 'function') { value = value.toBSON(); } From 22ddacf77c6df8b6ca7e4244e6a6c7cf06ef1047 Mon Sep 17 00:00:00 2001 From: cplepage Date: Sun, 19 Dec 2021 23:33:20 -0500 Subject: [PATCH 4/8] tests --- test/node/to_bson_test.js | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/node/to_bson_test.js b/test/node/to_bson_test.js index 2903b509..e7fb09b1 100644 --- a/test/node/to_bson_test.js +++ b/test/node/to_bson_test.js @@ -129,4 +129,44 @@ describe('toBSON', function () { expect(true).to.equal(test2); done(); }); + + it('Should not fail with properly extended BigInt prototype', function (done) { + // extend BigInt prototype + BigInt.prototype.toBSON = function(){ + return 'hello'; + } + + // test with 0n + var doc = { + a: BigInt(0) + } + + // serialize / deserialize + var serialized_data = BSON.serialize(doc, false, true); + var deserialized_doc = BSON.deserialize(serialized_data); + expect('hello').to.deep.equal(deserialized_doc.a); + + // remove prototype extension intended for test + delete BigInt.prototype.toBSON; + + done(); + }); + + // by default, bigint is not supported + it('Should fail with unsupported primitive bigint', function (done) { + var doc = { + a: BigInt(0) + }; + + var test = false; + + try{ + BSON.serialize(doc, false, true); + } catch (err) { + test = true; + } + + expect(true).to.equal(test); + done(); + }); }); From c493035baee16687c025417a61a16fce9855a8a5 Mon Sep 17 00:00:00 2001 From: cplepage Date: Mon, 20 Dec 2021 11:15:00 -0500 Subject: [PATCH 5/8] lint --- test/node/to_bson_test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/node/to_bson_test.js b/test/node/to_bson_test.js index e7fb09b1..10fb3424 100644 --- a/test/node/to_bson_test.js +++ b/test/node/to_bson_test.js @@ -1,3 +1,4 @@ +/* globals BigInt */ 'use strict'; const BSON = require('../register-bson'); @@ -132,14 +133,14 @@ describe('toBSON', function () { it('Should not fail with properly extended BigInt prototype', function (done) { // extend BigInt prototype - BigInt.prototype.toBSON = function(){ + BigInt.prototype.toBSON = function () { return 'hello'; - } + }; // test with 0n var doc = { a: BigInt(0) - } + }; // serialize / deserialize var serialized_data = BSON.serialize(doc, false, true); @@ -160,7 +161,7 @@ describe('toBSON', function () { var test = false; - try{ + try { BSON.serialize(doc, false, true); } catch (err) { test = true; From 0cc9183063f8bee6e32ac549a225fca783db3618 Mon Sep 17 00:00:00 2001 From: cplepage Date: Mon, 20 Dec 2021 13:55:46 -0500 Subject: [PATCH 6/8] test --- test/node/to_bson_test.js | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/test/node/to_bson_test.js b/test/node/to_bson_test.js index 10fb3424..35c9959c 100644 --- a/test/node/to_bson_test.js +++ b/test/node/to_bson_test.js @@ -1,4 +1,3 @@ -/* globals BigInt */ 'use strict'; const BSON = require('../register-bson'); @@ -131,15 +130,15 @@ describe('toBSON', function () { done(); }); - it('Should not fail with properly extended BigInt prototype', function (done) { + it('Should not fail with properly extended primitive wrapper object', function (done) { // extend BigInt prototype - BigInt.prototype.toBSON = function () { + Number.prototype.toBSON = function () { return 'hello'; }; - // test with 0n + // test with 0 var doc = { - a: BigInt(0) + a: 0 }; // serialize / deserialize @@ -147,27 +146,6 @@ describe('toBSON', function () { var deserialized_doc = BSON.deserialize(serialized_data); expect('hello').to.deep.equal(deserialized_doc.a); - // remove prototype extension intended for test - delete BigInt.prototype.toBSON; - - done(); - }); - - // by default, bigint is not supported - it('Should fail with unsupported primitive bigint', function (done) { - var doc = { - a: BigInt(0) - }; - - var test = false; - - try { - BSON.serialize(doc, false, true); - } catch (err) { - test = true; - } - - expect(true).to.equal(test); done(); }); }); From 47c7d748e552fbb24bec7c1ca21ba6703aef4d51 Mon Sep 17 00:00:00 2001 From: cplepage Date: Mon, 20 Dec 2021 13:56:04 -0500 Subject: [PATCH 7/8] test --- test/node/to_bson_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node/to_bson_test.js b/test/node/to_bson_test.js index 35c9959c..2ee399d0 100644 --- a/test/node/to_bson_test.js +++ b/test/node/to_bson_test.js @@ -131,7 +131,7 @@ describe('toBSON', function () { }); it('Should not fail with properly extended primitive wrapper object', function (done) { - // extend BigInt prototype + // extend Number prototype Number.prototype.toBSON = function () { return 'hello'; }; From 177ee144bdecde17995aa03712043762d3d982a6 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 20 Dec 2021 14:17:50 -0500 Subject: [PATCH 8/8] fix: gate all toBSON usages and test more falsey --- src/parser/calculate_size.ts | 4 +- src/parser/serializer.ts | 8 ++-- test/node/to_bson_test.js | 88 ++++++++++++++++++++++++++++++------ 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/parser/calculate_size.ts b/src/parser/calculate_size.ts index 6a67d440..14529b98 100644 --- a/src/parser/calculate_size.ts +++ b/src/parser/calculate_size.ts @@ -24,7 +24,7 @@ export function calculateObjectSize( } else { // If we have toBSON defined, override the current object - if (object.toBSON) { + if (typeof object?.toBSON === 'function') { object = object.toBSON(); } @@ -47,7 +47,7 @@ function calculateElement( ignoreUndefined = false ) { // If we have toBSON defined, override the current object - if (value && value.toBSON) { + if (typeof value?.toBSON === 'function') { value = value.toBSON(); } diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index ce790cd0..da94e1c4 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -946,12 +946,12 @@ export function serializeInto( } } } else { - // Did we provide a custom serialization method - if (object.toBSON) { - if (typeof object.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); + if (typeof object?.toBSON === 'function') { + // Provided a custom serialization method object = object.toBSON(); - if (object != null && typeof object !== 'object') + if (object != null && typeof object !== 'object') { throw new BSONTypeError('toBSON function did not return an object'); + } } // Iterate over all the keys diff --git a/test/node/to_bson_test.js b/test/node/to_bson_test.js index 2ee399d0..2c5142a1 100644 --- a/test/node/to_bson_test.js +++ b/test/node/to_bson_test.js @@ -3,6 +3,8 @@ const BSON = require('../register-bson'); const ObjectId = BSON.ObjectId; +const BigInt = global.BigInt; + describe('toBSON', function () { /** * @ignore @@ -130,22 +132,82 @@ describe('toBSON', function () { done(); }); - it('Should not fail with properly extended primitive wrapper object', function (done) { - // extend Number prototype - Number.prototype.toBSON = function () { - return 'hello'; + describe('when used on global existing types', () => { + beforeEach(() => { + Number.prototype.toBSON = () => 'hello'; + String.prototype.toBSON = () => 'hello'; + Boolean.prototype.toBSON = () => 'hello'; + if (BigInt) BigInt.prototype.toBSON = () => 'hello'; + }); + + afterEach(() => { + // remove prototype extension intended for test + delete Number.prototype.toBSON; + delete String.prototype.toBSON; + delete Boolean.prototype.toBSON; + if (BigInt) delete BigInt.prototype.toBSON; + }); + + const testToBSONFor = value => { + it(`should use toBSON on false-y ${typeof value} ${value === '' ? "''" : value}`, () => { + const serialized_data = BSON.serialize({ a: value }); + expect(serialized_data.indexOf(Buffer.from('hello\0', 'utf8'))).to.be.greaterThan(0); + + const deserialized_doc = BSON.deserialize(serialized_data); + expect(deserialized_doc).to.have.property('a', 'hello'); + }); }; - // test with 0 - var doc = { - a: 0 - }; + testToBSONFor(0); + testToBSONFor(NaN); + testToBSONFor(''); + testToBSONFor(false); + if (BigInt) { + testToBSONFor(BigInt(0)); + } - // serialize / deserialize - var serialized_data = BSON.serialize(doc, false, true); - var deserialized_doc = BSON.deserialize(serialized_data); - expect('hello').to.deep.equal(deserialized_doc.a); + it('should use toBSON on false-y number in calculateObjectSize', () => { + // Normally is 20 bytes + // int32 0x04 'a\x00' + // int32 0x10 '0\x00' int32 \0 + // \0 + // --------- + // with toBSON is 26 bytes (hello + null) + // int32 0x04 'a\x00' + // int32 0x02 '0\x00' int32 'hello\0' \0 + // \0 + const sizeNestedToBSON = BSON.calculateObjectSize({ a: [0] }); + expect(sizeNestedToBSON).to.equal(26); + }); + }); - done(); + it('should use toBSON in calculateObjectSize', () => { + const sizeTopLvlToBSON = BSON.calculateObjectSize({ toBSON: () => ({ a: 1 }) }); + const sizeOfWhatToBSONReturns = BSON.calculateObjectSize({ a: 1 }); + expect(sizeOfWhatToBSONReturns).to.equal(12); + expect(sizeTopLvlToBSON).to.equal(12); + + const toBSONAsAKeySize = BSON.calculateObjectSize({ toBSON: { a: 1 } }); + expect(toBSONAsAKeySize).to.equal(25); + }); + + it('should serialize to a key for non-function values', () => { + // int32 0x10 'toBSON\x00' int32 \0 + const size = BSON.calculateObjectSize({ toBSON: 1 }); + expect(size).to.equal(17); + + const bytes = BSON.serialize({ toBSON: 1 }); + expect(bytes.indexOf(Buffer.from('toBSON\0', 'utf8'))).to.be.greaterThan(0); + }); + + it('should still be omitted if serializeFunctions is true', () => { + const bytes = BSON.serialize( + { toBSON: () => ({ a: 1, fn: () => ({ a: 1 }) }) }, + { serializeFunctions: true } + ); + expect(bytes.indexOf(Buffer.from('a\0', 'utf8'))).to.be.greaterThan(0); + const doc = BSON.deserialize(bytes); + expect(doc).to.have.property('a', 1); + expect(doc).to.have.property('fn').that.is.instanceOf(BSON.Code); }); });