Skip to content

Commit

Permalink
test(NODE-3447): prose tests for serialization of BSON with embedded …
Browse files Browse the repository at this point in the history
…null bytes in strings (#462)
  • Loading branch information
nbbeeken authored Sep 20, 2021
1 parent 52cfe9c commit cc5d04d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 147 deletions.
191 changes: 44 additions & 147 deletions test/node/bson_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1619,153 +1619,6 @@ describe('BSON', function () {
done();
});

// /**
// * A simple example showing the usage of BSON.deserialize function returning a deserialized Javascript function.
// *
// * @_class bson
// * @_function BSON.deserialize
// * @ignore
// */
// it('Should correctly deserialize a buffer using the BSON class level parser', function(done) {
// // Create a simple object
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
// // Create a BSON parser instance
// var bson = BSON;
// // Serialize the object to a buffer, checking keys and serializing functions
// var buffer = bson.serialize(doc, {
// checkKeys: true,
// serializeFunctions: true
// });
// // Validate the correctness
// expect(65).to.equal(buffer.length);
//
// // Deserialize the object with no eval for the functions
// var deserializedDoc = bson.deserialize(buffer);
// // Validate the correctness
// expect('object').to.equal(typeof deserializedDoc.func);
// expect(1).to.equal(deserializedDoc.a);
//
// // Deserialize the object with eval for the functions caching the functions
// deserializedDoc = bson.deserialize(buffer, {evalFunctions:true, cacheFunctions:true});
// // Validate the correctness
// expect('function').to.equal(typeof deserializedDoc.func);
// expect(1).to.equal(deserializedDoc.a);
// done();
// }

// /**
// * A simple example showing the usage of BSON instance deserialize function returning a deserialized Javascript function.
// *
// * @_class bson
// * @_function deserialize
// * @ignore
// */
// it('Should correctly deserialize a buffer using the BSON instance parser', function(done) {
// // Create a simple object
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
// // Create a BSON parser instance
// var bson = BSON;
// // Serialize the object to a buffer, checking keys and serializing functions
// var buffer = bson.serialize(doc, true, true, true);
// // Validate the correctness
// expect(65).to.equal(buffer.length);
//
// // Deserialize the object with no eval for the functions
// var deserializedDoc = bson.deserialize(buffer);
// // Validate the correctness
// expect('object').to.equal(typeof deserializedDoc.func);
// expect(1).to.equal(deserializedDoc.a);
//
// // Deserialize the object with eval for the functions caching the functions
// deserializedDoc = bson.deserialize(buffer, {evalFunctions:true, cacheFunctions:true});
// // Validate the correctness
// expect('function').to.equal(typeof deserializedDoc.func);
// expect(1).to.equal(deserializedDoc.a);
// done();
// }

// /**
// * A simple example showing the usage of BSON.deserializeStream function returning deserialized Javascript objects.
// *
// * @_class bson
// * @_function BSON.deserializeStream
// * @ignore
// */
// it('Should correctly deserializeStream a buffer object', function(done) {
// // Create a simple object
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
// var bson = BSON;
// // Serialize the object to a buffer, checking keys and serializing functions
// var buffer = bson.serialize(doc, {
// checkKeys: true,
// serializeFunctions: true
// });
// // Validate the correctness
// expect(65).to.equal(buffer.length);
//
// // The array holding the number of retuned documents
// var documents = new Array(1);
// // Deserialize the object with no eval for the functions
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0);
// // Validate the correctness
// expect(65).to.equal(index);
// expect(1).to.equal(documents.length);
// expect(1).to.equal(documents[0].a);
// expect('object').to.equal(typeof documents[0].func);
//
// // Deserialize the object with eval for the functions caching the functions
// // The array holding the number of retuned documents
// var documents = new Array(1);
// // Deserialize the object with no eval for the functions
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true});
// // Validate the correctness
// expect(65).to.equal(index);
// expect(1).to.equal(documents.length);
// expect(1).to.equal(documents[0].a);
// expect('function').to.equal(typeof documents[0].func);
// done();
// }

// /**
// * A simple example showing the usage of BSON instance deserializeStream function returning deserialized Javascript objects.
// *
// * @_class bson
// * @_function deserializeStream
// * @ignore
// */
// it('Should correctly deserializeStream a buffer object', function(done) {
// // Create a simple object
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
// // Create a BSON parser instance
// var bson = BSON;
// // Serialize the object to a buffer, checking keys and serializing functions
// var buffer = bson.serialize(doc, true, true, true);
// // Validate the correctness
// expect(65).to.equal(buffer.length);
//
// // The array holding the number of retuned documents
// var documents = new Array(1);
// // Deserialize the object with no eval for the functions
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0);
// // Validate the correctness
// expect(65).to.equal(index);
// expect(1).to.equal(documents.length);
// expect(1).to.equal(documents[0].a);
// expect('object').to.equal(typeof documents[0].func);
//
// // Deserialize the object with eval for the functions caching the functions
// // The array holding the number of retuned documents
// var documents = new Array(1);
// // Deserialize the object with no eval for the functions
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true});
// // Validate the correctness
// expect(65).to.equal(index);
// expect(1).to.equal(documents.length);
// expect(1).to.equal(documents[0].a);
// expect('function').to.equal(typeof documents[0].func);
// done();
// }

it('should properly deserialize multiple documents using deserializeStream', function () {
const bson = BSON;
const docs = [{ foo: 'bar' }, { foo: 'baz' }, { foo: 'quux' }];
Expand Down Expand Up @@ -1900,6 +1753,14 @@ describe('BSON', function () {
const b = new BSONRegExp('cba', 'mix');
expect(b.options).to.equal('imx');
});

it('should correctly serialize JavaScript Regex with control character', () => {
const regex = /a\x34b/m;
const aNewLineB = BSON.serialize({ regex });
const { regex: roundTripRegex } = BSON.deserialize(aNewLineB);
expect(regex.source).to.equal(roundTripRegex.source);
expect(regex.flags).to.equal(roundTripRegex.flags);
});
});

/**
Expand Down Expand Up @@ -2124,4 +1985,40 @@ describe('BSON', function () {
expect(inspect(timestamp)).to.equal('new Timestamp({ t: 100, i: 1 })');
});
});

/**
* The BSON spec uses null-terminated strings to represent document field names and
* regex components (i.e. pattern and flags/options). Drivers MUST assert that null
* bytes are prohibited in the following contexts when encoding BSON (i.e. creating
* raw BSON bytes or constructing BSON-specific type classes):
* - Field name within a root document
* - Field name within a sub-document
* - Pattern for a regular expression
* - Flags/options for a regular expression
* Depending on how drivers implement BSON encoding, they MAY expect an error when
* constructing a type class (e.g. BSON Document or Regex class) or when encoding a
* language representation to BSON (e.g. converting a dictionary, which might allow
* null bytes in its keys, to raw BSON bytes).
*/
describe('null byte handling during serializing', () => {
it('should throw when null byte in BSON Field name within a root document', () => {
expect(() => BSON.serialize({ 'a\x00b': 1 })).to.throw(/null bytes/);
});

it('should throw when null byte in BSON Field name within a sub-document', () => {
expect(() => BSON.serialize({ a: { 'a\x00b': 1 } })).to.throw(/null bytes/);
});

it('should throw when null byte in Pattern for a regular expression', () => {
// eslint-disable-next-line no-control-regex
expect(() => BSON.serialize({ a: new RegExp('a\x00b') })).to.throw(/null bytes/);
expect(() => BSON.serialize({ a: new BSONRegExp('a\x00b') })).to.throw(/null bytes/);
});

it('should throw when null byte in Flags/options for a regular expression', () => {
expect(() => BSON.serialize({ a: new BSONRegExp('a', 'i\x00m') })).to.throw(
/regular expression option/
);
});
});
});
34 changes: 34 additions & 0 deletions test/node/tools/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,37 @@ exports.assertBuffersEqual = function (done, buffer1, buffer2) {
expect(buffer1[i]).to.equal(buffer2[i]);
}
};

/**
* A helper to turn hex string sequences into BSON.
* Omit the first 8 hex digits for the document it will be calculated
* As well as the BSON document's null terminator '00'
*
* @example
* ```js
* const bytes = bufferFromHexArray([
* '10', // int32 type
* '6100', // 'a' key with key null terminator
* '01000000' // little endian int32
* ])
* BSON.serialize(bytes) // { a: 1 }
* ```
*
* @param {string[]} array - sequences of hex digits broken up to be human readable
* @returns
*/
const bufferFromHexArray = array => {
const string = array.concat(['00']).join('');
const size = string.length / 2 + 4;

const byteLength = [size & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, (size >> 24) & 0xff]
.map(n => {
const hexCode = n.toString(16);
return hexCode.length === 2 ? hexCode : '0' + hexCode;
})
.join('');

return Buffer.from(byteLength + string, 'hex');
};

exports.bufferFromHexArray = bufferFromHexArray;

0 comments on commit cc5d04d

Please sign in to comment.