Skip to content

Commit cc5d04d

Browse files
authored
test(NODE-3447): prose tests for serialization of BSON with embedded null bytes in strings (#462)
1 parent 52cfe9c commit cc5d04d

File tree

2 files changed

+78
-147
lines changed

2 files changed

+78
-147
lines changed

test/node/bson_test.js

+44-147
Original file line numberDiff line numberDiff line change
@@ -1619,153 +1619,6 @@ describe('BSON', function () {
16191619
done();
16201620
});
16211621

1622-
// /**
1623-
// * A simple example showing the usage of BSON.deserialize function returning a deserialized Javascript function.
1624-
// *
1625-
// * @_class bson
1626-
// * @_function BSON.deserialize
1627-
// * @ignore
1628-
// */
1629-
// it('Should correctly deserialize a buffer using the BSON class level parser', function(done) {
1630-
// // Create a simple object
1631-
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
1632-
// // Create a BSON parser instance
1633-
// var bson = BSON;
1634-
// // Serialize the object to a buffer, checking keys and serializing functions
1635-
// var buffer = bson.serialize(doc, {
1636-
// checkKeys: true,
1637-
// serializeFunctions: true
1638-
// });
1639-
// // Validate the correctness
1640-
// expect(65).to.equal(buffer.length);
1641-
//
1642-
// // Deserialize the object with no eval for the functions
1643-
// var deserializedDoc = bson.deserialize(buffer);
1644-
// // Validate the correctness
1645-
// expect('object').to.equal(typeof deserializedDoc.func);
1646-
// expect(1).to.equal(deserializedDoc.a);
1647-
//
1648-
// // Deserialize the object with eval for the functions caching the functions
1649-
// deserializedDoc = bson.deserialize(buffer, {evalFunctions:true, cacheFunctions:true});
1650-
// // Validate the correctness
1651-
// expect('function').to.equal(typeof deserializedDoc.func);
1652-
// expect(1).to.equal(deserializedDoc.a);
1653-
// done();
1654-
// }
1655-
1656-
// /**
1657-
// * A simple example showing the usage of BSON instance deserialize function returning a deserialized Javascript function.
1658-
// *
1659-
// * @_class bson
1660-
// * @_function deserialize
1661-
// * @ignore
1662-
// */
1663-
// it('Should correctly deserialize a buffer using the BSON instance parser', function(done) {
1664-
// // Create a simple object
1665-
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
1666-
// // Create a BSON parser instance
1667-
// var bson = BSON;
1668-
// // Serialize the object to a buffer, checking keys and serializing functions
1669-
// var buffer = bson.serialize(doc, true, true, true);
1670-
// // Validate the correctness
1671-
// expect(65).to.equal(buffer.length);
1672-
//
1673-
// // Deserialize the object with no eval for the functions
1674-
// var deserializedDoc = bson.deserialize(buffer);
1675-
// // Validate the correctness
1676-
// expect('object').to.equal(typeof deserializedDoc.func);
1677-
// expect(1).to.equal(deserializedDoc.a);
1678-
//
1679-
// // Deserialize the object with eval for the functions caching the functions
1680-
// deserializedDoc = bson.deserialize(buffer, {evalFunctions:true, cacheFunctions:true});
1681-
// // Validate the correctness
1682-
// expect('function').to.equal(typeof deserializedDoc.func);
1683-
// expect(1).to.equal(deserializedDoc.a);
1684-
// done();
1685-
// }
1686-
1687-
// /**
1688-
// * A simple example showing the usage of BSON.deserializeStream function returning deserialized Javascript objects.
1689-
// *
1690-
// * @_class bson
1691-
// * @_function BSON.deserializeStream
1692-
// * @ignore
1693-
// */
1694-
// it('Should correctly deserializeStream a buffer object', function(done) {
1695-
// // Create a simple object
1696-
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
1697-
// var bson = BSON;
1698-
// // Serialize the object to a buffer, checking keys and serializing functions
1699-
// var buffer = bson.serialize(doc, {
1700-
// checkKeys: true,
1701-
// serializeFunctions: true
1702-
// });
1703-
// // Validate the correctness
1704-
// expect(65).to.equal(buffer.length);
1705-
//
1706-
// // The array holding the number of retuned documents
1707-
// var documents = new Array(1);
1708-
// // Deserialize the object with no eval for the functions
1709-
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0);
1710-
// // Validate the correctness
1711-
// expect(65).to.equal(index);
1712-
// expect(1).to.equal(documents.length);
1713-
// expect(1).to.equal(documents[0].a);
1714-
// expect('object').to.equal(typeof documents[0].func);
1715-
//
1716-
// // Deserialize the object with eval for the functions caching the functions
1717-
// // The array holding the number of retuned documents
1718-
// var documents = new Array(1);
1719-
// // Deserialize the object with no eval for the functions
1720-
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true});
1721-
// // Validate the correctness
1722-
// expect(65).to.equal(index);
1723-
// expect(1).to.equal(documents.length);
1724-
// expect(1).to.equal(documents[0].a);
1725-
// expect('function').to.equal(typeof documents[0].func);
1726-
// done();
1727-
// }
1728-
1729-
// /**
1730-
// * A simple example showing the usage of BSON instance deserializeStream function returning deserialized Javascript objects.
1731-
// *
1732-
// * @_class bson
1733-
// * @_function deserializeStream
1734-
// * @ignore
1735-
// */
1736-
// it('Should correctly deserializeStream a buffer object', function(done) {
1737-
// // Create a simple object
1738-
// var doc = {a: 1, func:function(){ console.log('hello world'); }}
1739-
// // Create a BSON parser instance
1740-
// var bson = BSON;
1741-
// // Serialize the object to a buffer, checking keys and serializing functions
1742-
// var buffer = bson.serialize(doc, true, true, true);
1743-
// // Validate the correctness
1744-
// expect(65).to.equal(buffer.length);
1745-
//
1746-
// // The array holding the number of retuned documents
1747-
// var documents = new Array(1);
1748-
// // Deserialize the object with no eval for the functions
1749-
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0);
1750-
// // Validate the correctness
1751-
// expect(65).to.equal(index);
1752-
// expect(1).to.equal(documents.length);
1753-
// expect(1).to.equal(documents[0].a);
1754-
// expect('object').to.equal(typeof documents[0].func);
1755-
//
1756-
// // Deserialize the object with eval for the functions caching the functions
1757-
// // The array holding the number of retuned documents
1758-
// var documents = new Array(1);
1759-
// // Deserialize the object with no eval for the functions
1760-
// var index = bson.deserializeStream(buffer, 0, 1, documents, 0, {evalFunctions:true, cacheFunctions:true});
1761-
// // Validate the correctness
1762-
// expect(65).to.equal(index);
1763-
// expect(1).to.equal(documents.length);
1764-
// expect(1).to.equal(documents[0].a);
1765-
// expect('function').to.equal(typeof documents[0].func);
1766-
// done();
1767-
// }
1768-
17691622
it('should properly deserialize multiple documents using deserializeStream', function () {
17701623
const bson = BSON;
17711624
const docs = [{ foo: 'bar' }, { foo: 'baz' }, { foo: 'quux' }];
@@ -1900,6 +1753,14 @@ describe('BSON', function () {
19001753
const b = new BSONRegExp('cba', 'mix');
19011754
expect(b.options).to.equal('imx');
19021755
});
1756+
1757+
it('should correctly serialize JavaScript Regex with control character', () => {
1758+
const regex = /a\x34b/m;
1759+
const aNewLineB = BSON.serialize({ regex });
1760+
const { regex: roundTripRegex } = BSON.deserialize(aNewLineB);
1761+
expect(regex.source).to.equal(roundTripRegex.source);
1762+
expect(regex.flags).to.equal(roundTripRegex.flags);
1763+
});
19031764
});
19041765

19051766
/**
@@ -2124,4 +1985,40 @@ describe('BSON', function () {
21241985
expect(inspect(timestamp)).to.equal('new Timestamp({ t: 100, i: 1 })');
21251986
});
21261987
});
1988+
1989+
/**
1990+
* The BSON spec uses null-terminated strings to represent document field names and
1991+
* regex components (i.e. pattern and flags/options). Drivers MUST assert that null
1992+
* bytes are prohibited in the following contexts when encoding BSON (i.e. creating
1993+
* raw BSON bytes or constructing BSON-specific type classes):
1994+
* - Field name within a root document
1995+
* - Field name within a sub-document
1996+
* - Pattern for a regular expression
1997+
* - Flags/options for a regular expression
1998+
* Depending on how drivers implement BSON encoding, they MAY expect an error when
1999+
* constructing a type class (e.g. BSON Document or Regex class) or when encoding a
2000+
* language representation to BSON (e.g. converting a dictionary, which might allow
2001+
* null bytes in its keys, to raw BSON bytes).
2002+
*/
2003+
describe('null byte handling during serializing', () => {
2004+
it('should throw when null byte in BSON Field name within a root document', () => {
2005+
expect(() => BSON.serialize({ 'a\x00b': 1 })).to.throw(/null bytes/);
2006+
});
2007+
2008+
it('should throw when null byte in BSON Field name within a sub-document', () => {
2009+
expect(() => BSON.serialize({ a: { 'a\x00b': 1 } })).to.throw(/null bytes/);
2010+
});
2011+
2012+
it('should throw when null byte in Pattern for a regular expression', () => {
2013+
// eslint-disable-next-line no-control-regex
2014+
expect(() => BSON.serialize({ a: new RegExp('a\x00b') })).to.throw(/null bytes/);
2015+
expect(() => BSON.serialize({ a: new BSONRegExp('a\x00b') })).to.throw(/null bytes/);
2016+
});
2017+
2018+
it('should throw when null byte in Flags/options for a regular expression', () => {
2019+
expect(() => BSON.serialize({ a: new BSONRegExp('a', 'i\x00m') })).to.throw(
2020+
/regular expression option/
2021+
);
2022+
});
2023+
});
21272024
});

test/node/tools/utils.js

+34
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,37 @@ exports.assertBuffersEqual = function (done, buffer1, buffer2) {
9191
expect(buffer1[i]).to.equal(buffer2[i]);
9292
}
9393
};
94+
95+
/**
96+
* A helper to turn hex string sequences into BSON.
97+
* Omit the first 8 hex digits for the document it will be calculated
98+
* As well as the BSON document's null terminator '00'
99+
*
100+
* @example
101+
* ```js
102+
* const bytes = bufferFromHexArray([
103+
* '10', // int32 type
104+
* '6100', // 'a' key with key null terminator
105+
* '01000000' // little endian int32
106+
* ])
107+
* BSON.serialize(bytes) // { a: 1 }
108+
* ```
109+
*
110+
* @param {string[]} array - sequences of hex digits broken up to be human readable
111+
* @returns
112+
*/
113+
const bufferFromHexArray = array => {
114+
const string = array.concat(['00']).join('');
115+
const size = string.length / 2 + 4;
116+
117+
const byteLength = [size & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, (size >> 24) & 0xff]
118+
.map(n => {
119+
const hexCode = n.toString(16);
120+
return hexCode.length === 2 ? hexCode : '0' + hexCode;
121+
})
122+
.join('');
123+
124+
return Buffer.from(byteLength + string, 'hex');
125+
};
126+
127+
exports.bufferFromHexArray = bufferFromHexArray;

0 commit comments

Comments
 (0)