Skip to content

Commit 1d898b6

Browse files
authored
fix(NODE-3821): nullish check before using toBSON override function (#477)
1 parent 95e8293 commit 1d898b6

File tree

3 files changed

+89
-10
lines changed

3 files changed

+89
-10
lines changed

src/parser/calculate_size.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function calculateObjectSize(
2424
} else {
2525
// If we have toBSON defined, override the current object
2626

27-
if (object.toBSON) {
27+
if (typeof object?.toBSON === 'function') {
2828
object = object.toBSON();
2929
}
3030

@@ -47,7 +47,7 @@ function calculateElement(
4747
ignoreUndefined = false
4848
) {
4949
// If we have toBSON defined, override the current object
50-
if (value && value.toBSON) {
50+
if (typeof value?.toBSON === 'function') {
5151
value = value.toBSON();
5252
}
5353

src/parser/serializer.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,7 @@ export function serializeInto(
767767
let value = object[i];
768768

769769
// Is there an override value
770-
if (value && value.toBSON) {
771-
if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function');
770+
if (typeof value?.toBSON === 'function') {
772771
value = value.toBSON();
773772
}
774773

@@ -947,20 +946,19 @@ export function serializeInto(
947946
}
948947
}
949948
} else {
950-
// Did we provide a custom serialization method
951-
if (object.toBSON) {
952-
if (typeof object.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function');
949+
if (typeof object?.toBSON === 'function') {
950+
// Provided a custom serialization method
953951
object = object.toBSON();
954-
if (object != null && typeof object !== 'object')
952+
if (object != null && typeof object !== 'object') {
955953
throw new BSONTypeError('toBSON function did not return an object');
954+
}
956955
}
957956

958957
// Iterate over all the keys
959958
for (const key in object) {
960959
let value = object[key];
961960
// Is there an override value
962-
if (value && value.toBSON) {
963-
if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function');
961+
if (typeof value?.toBSON === 'function') {
964962
value = value.toBSON();
965963
}
966964

test/node/to_bson_test.js

+81
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const BSON = require('../register-bson');
44
const ObjectId = BSON.ObjectId;
55

6+
const BigInt = global.BigInt;
7+
68
describe('toBSON', function () {
79
/**
810
* @ignore
@@ -129,4 +131,83 @@ describe('toBSON', function () {
129131
expect(true).to.equal(test2);
130132
done();
131133
});
134+
135+
describe('when used on global existing types', () => {
136+
beforeEach(() => {
137+
Number.prototype.toBSON = () => 'hello';
138+
String.prototype.toBSON = () => 'hello';
139+
Boolean.prototype.toBSON = () => 'hello';
140+
if (BigInt) BigInt.prototype.toBSON = () => 'hello';
141+
});
142+
143+
afterEach(() => {
144+
// remove prototype extension intended for test
145+
delete Number.prototype.toBSON;
146+
delete String.prototype.toBSON;
147+
delete Boolean.prototype.toBSON;
148+
if (BigInt) delete BigInt.prototype.toBSON;
149+
});
150+
151+
const testToBSONFor = value => {
152+
it(`should use toBSON on false-y ${typeof value} ${value === '' ? "''" : value}`, () => {
153+
const serialized_data = BSON.serialize({ a: value });
154+
expect(serialized_data.indexOf(Buffer.from('hello\0', 'utf8'))).to.be.greaterThan(0);
155+
156+
const deserialized_doc = BSON.deserialize(serialized_data);
157+
expect(deserialized_doc).to.have.property('a', 'hello');
158+
});
159+
};
160+
161+
testToBSONFor(0);
162+
testToBSONFor(NaN);
163+
testToBSONFor('');
164+
testToBSONFor(false);
165+
if (BigInt) {
166+
testToBSONFor(BigInt(0));
167+
}
168+
169+
it('should use toBSON on false-y number in calculateObjectSize', () => {
170+
// Normally is 20 bytes
171+
// int32 0x04 'a\x00'
172+
// int32 0x10 '0\x00' int32 \0
173+
// \0
174+
// ---------
175+
// with toBSON is 26 bytes (hello + null)
176+
// int32 0x04 'a\x00'
177+
// int32 0x02 '0\x00' int32 'hello\0' \0
178+
// \0
179+
const sizeNestedToBSON = BSON.calculateObjectSize({ a: [0] });
180+
expect(sizeNestedToBSON).to.equal(26);
181+
});
182+
});
183+
184+
it('should use toBSON in calculateObjectSize', () => {
185+
const sizeTopLvlToBSON = BSON.calculateObjectSize({ toBSON: () => ({ a: 1 }) });
186+
const sizeOfWhatToBSONReturns = BSON.calculateObjectSize({ a: 1 });
187+
expect(sizeOfWhatToBSONReturns).to.equal(12);
188+
expect(sizeTopLvlToBSON).to.equal(12);
189+
190+
const toBSONAsAKeySize = BSON.calculateObjectSize({ toBSON: { a: 1 } });
191+
expect(toBSONAsAKeySize).to.equal(25);
192+
});
193+
194+
it('should serialize to a key for non-function values', () => {
195+
// int32 0x10 'toBSON\x00' int32 \0
196+
const size = BSON.calculateObjectSize({ toBSON: 1 });
197+
expect(size).to.equal(17);
198+
199+
const bytes = BSON.serialize({ toBSON: 1 });
200+
expect(bytes.indexOf(Buffer.from('toBSON\0', 'utf8'))).to.be.greaterThan(0);
201+
});
202+
203+
it('should still be omitted if serializeFunctions is true', () => {
204+
const bytes = BSON.serialize(
205+
{ toBSON: () => ({ a: 1, fn: () => ({ a: 1 }) }) },
206+
{ serializeFunctions: true }
207+
);
208+
expect(bytes.indexOf(Buffer.from('a\0', 'utf8'))).to.be.greaterThan(0);
209+
const doc = BSON.deserialize(bytes);
210+
expect(doc).to.have.property('a', 1);
211+
expect(doc).to.have.property('fn').that.is.instanceOf(BSON.Code);
212+
});
132213
});

0 commit comments

Comments
 (0)