Skip to content

Commit 5167be2

Browse files
authored
fix: backwards compatibility with older BSON package versions (#411)
The isBuffer check in the buffer module was too strict and led to a failure when dealing with older BSON module Binary types. There is now an improved more relaxed check. NODE-2848
1 parent 50e4529 commit 5167be2

File tree

7 files changed

+174
-64
lines changed

7 files changed

+174
-64
lines changed

package-lock.json

Lines changed: 38 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@babel/plugin-external-helpers": "^7.10.4",
2929
"@babel/preset-env": "^7.11.0",
3030
"@istanbuljs/nyc-config-typescript": "^1.0.1",
31-
"@microsoft/api-extractor": "^7.9.10",
31+
"@microsoft/api-extractor": "^7.11.2",
3232
"@rollup/plugin-babel": "^5.2.0",
3333
"@rollup/plugin-commonjs": "^15.0.0",
3434
"@rollup/plugin-json": "^4.1.0",
@@ -50,6 +50,7 @@
5050
"karma-mocha-reporter": "^2.2.5",
5151
"karma-rollup-preprocessor": "^7.0.5",
5252
"mocha": "5.2.0",
53+
"node-fetch": "^2.6.1",
5354
"nyc": "^15.1.0",
5455
"prettier": "^2.1.1",
5556
"rimraf": "^3.0.2",
@@ -79,7 +80,7 @@
7980
"scripts": {
8081
"docs": "typedoc",
8182
"test": "npm run build && npm run test-node && npm run test-browser",
82-
"test-node": "mocha test/node",
83+
"test-node": "mocha test/node test/*_tests.js",
8384
"test-browser": "karma start karma.conf.js",
8485
"build:ts": "tsc",
8586
"build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && rimraf 'lib/**/*.d.ts*' && downlevel-dts bson.d.ts bson.d.ts",

src/bson.ts

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DBRef } from './db_ref';
55
import { Decimal128 } from './decimal128';
66
import { Double } from './double';
77
import { ensureBuffer } from './ensure_buffer';
8+
import { EJSON } from './extended_json';
89
import { Int32 } from './int_32';
910
import { Long } from './long';
1011
import { Map } from './map';
@@ -20,42 +21,7 @@ import { BSONSymbol } from './symbol';
2021
import { Timestamp } from './timestamp';
2122
export { BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary';
2223
export { CodeExtended } from './code';
23-
export {
24-
BSON_BINARY_SUBTYPE_BYTE_ARRAY,
25-
BSON_BINARY_SUBTYPE_DEFAULT,
26-
BSON_BINARY_SUBTYPE_FUNCTION,
27-
BSON_BINARY_SUBTYPE_MD5,
28-
BSON_BINARY_SUBTYPE_USER_DEFINED,
29-
BSON_BINARY_SUBTYPE_UUID,
30-
BSON_BINARY_SUBTYPE_UUID_NEW,
31-
BSON_DATA_ARRAY,
32-
BSON_DATA_BINARY,
33-
BSON_DATA_BOOLEAN,
34-
BSON_DATA_CODE,
35-
BSON_DATA_CODE_W_SCOPE,
36-
BSON_DATA_DATE,
37-
BSON_DATA_DBPOINTER,
38-
BSON_DATA_DECIMAL128,
39-
BSON_DATA_INT,
40-
BSON_DATA_LONG,
41-
BSON_DATA_MAX_KEY,
42-
BSON_DATA_MIN_KEY,
43-
BSON_DATA_NULL,
44-
BSON_DATA_NUMBER,
45-
BSON_DATA_OBJECT,
46-
BSON_DATA_OID,
47-
BSON_DATA_REGEXP,
48-
BSON_DATA_STRING,
49-
BSON_DATA_SYMBOL,
50-
BSON_DATA_TIMESTAMP,
51-
BSON_DATA_UNDEFINED,
52-
BSON_INT32_MAX,
53-
BSON_INT32_MIN,
54-
BSON_INT64_MAX,
55-
BSON_INT64_MIN,
56-
JS_INT_MAX,
57-
JS_INT_MIN
58-
} from './constants';
24+
export * from './constants';
5925
export { DBRefLike } from './db_ref';
6026
export { Decimal128Extended } from './decimal128';
6127
export { DoubleExtended } from './double';
@@ -291,3 +257,37 @@ export function deserializeStream(
291257
// Return object containing end index of parsing and list of documents
292258
return index;
293259
}
260+
261+
/**
262+
* BSON default export
263+
* @deprecated Please use named exports
264+
* @privateRemarks
265+
* We want to someday deprecate the default export,
266+
* so none of the new TS types are being exported on the default
267+
* @public
268+
*/
269+
const BSON = {
270+
Binary,
271+
Code,
272+
DBRef,
273+
Decimal128,
274+
Double,
275+
Int32,
276+
Long,
277+
Map,
278+
MaxKey,
279+
MinKey,
280+
ObjectId,
281+
ObjectID: ObjectId,
282+
BSONRegExp,
283+
BSONSymbol,
284+
Timestamp,
285+
EJSON,
286+
setInternalBufferSize,
287+
serialize,
288+
serializeWithBufferAndIndex,
289+
deserialize,
290+
calculateObjectSize,
291+
deserializeStream
292+
};
293+
export default BSON;

src/ensure_buffer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Buffer } from 'buffer';
2+
import { isBuffer } from './parser/utils';
23

34
/**
45
* Makes sure that, if a Uint8Array is passed in, it is wrapped in a Buffer.
@@ -9,7 +10,7 @@ import { Buffer } from 'buffer';
910
* @throws TypeError If anything other than a Buffer or Uint8Array is passed in
1011
*/
1112
export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBuffer): Buffer {
12-
if (Buffer.isBuffer(potentialBuffer)) {
13+
if (isBuffer(potentialBuffer)) {
1314
return potentialBuffer;
1415
}
1516

src/parser/serializer.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Buffer } from 'buffer';
1+
import type { Buffer } from 'buffer';
22
import { Binary } from '../binary';
33
import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson';
44
import type { Code } from '../code';
@@ -18,6 +18,7 @@ import type { BSONRegExp } from '../regexp';
1818
import {
1919
isBigInt64Array,
2020
isBigUInt64Array,
21+
isBuffer,
2122
isDate,
2223
isUint8Array,
2324
normalizedFunctionString
@@ -785,7 +786,7 @@ export function serializeInto(
785786
index = serializeNull(buffer, key, value, index, true);
786787
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
787788
index = serializeObjectId(buffer, key, value, index, true);
788-
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
789+
} else if (isBuffer(value) || isUint8Array(value)) {
789790
index = serializeBuffer(buffer, key, value, index, true);
790791
} else if (value instanceof RegExp || isRegExp(value)) {
791792
index = serializeRegExp(buffer, key, value, index, true);
@@ -891,7 +892,7 @@ export function serializeInto(
891892
index = serializeNull(buffer, key, value, index);
892893
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
893894
index = serializeObjectId(buffer, key, value, index);
894-
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
895+
} else if (isBuffer(value) || isUint8Array(value)) {
895896
index = serializeBuffer(buffer, key, value, index);
896897
} else if (value instanceof RegExp || isRegExp(value)) {
897898
index = serializeRegExp(buffer, key, value, index);
@@ -997,7 +998,7 @@ export function serializeInto(
997998
index = serializeNull(buffer, key, value, index);
998999
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
9991000
index = serializeObjectId(buffer, key, value, index);
1000-
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
1001+
} else if (isBuffer(value) || isUint8Array(value)) {
10011002
index = serializeBuffer(buffer, key, value, index);
10021003
} else if (value instanceof RegExp || isRegExp(value)) {
10031004
index = serializeRegExp(buffer, key, value, index);

src/parser/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function haveBuffer(): boolean {
5858

5959
/** Callable in any environment to check if value is a Buffer */
6060
export function isBuffer(value: unknown): value is Buffer {
61-
return haveBuffer() && Buffer.isBuffer(value);
61+
return typeof value === 'object' && value?.constructor?.name === 'Buffer';
6262
}
6363

6464
// To ensure that 0.4 of node works correctly

test/bson_older_versions_tests.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
3+
const newBSON = require('./register-bson');
4+
const fs = require('fs');
5+
const fetch = require('node-fetch').default;
6+
const rimraf = require('rimraf');
7+
const cp = require('child_process');
8+
9+
/*
10+
* This file tests that previous versions of BSON
11+
* serialize and deserialize correctly in the most recent version of BSON
12+
*
13+
* This is an unusual situation to run into as users should be using one BSON lib version
14+
* but it does arise with sub deps etc. and we wish to protect against unexpected behavior
15+
*
16+
* If backwards compatibility breaks there should be clear warnings/failures
17+
* rather than empty or zero-ed values.
18+
*/
19+
20+
const OLD_VERSIONS = ['v1.1.5', 'v1.1.4'];
21+
const getZipUrl = ver => `https://github.com/mongodb/js-bson/archive/${ver}.zip`;
22+
const getImportPath = ver => `../bson-${ver}/js-bson-${ver.substring(1)}`;
23+
24+
function downloadZip(version, done) {
25+
// downloads a zip of previous BSON version
26+
fetch(getZipUrl(version))
27+
.then(r => {
28+
return r.arrayBuffer();
29+
})
30+
.then(r => {
31+
fs.writeFileSync(`bson-${version}.zip`, new Uint8Array(r));
32+
try {
33+
// unzips the code, right now these test won't handle versions written in TS
34+
cp.execSync(`unzip bson-${version}.zip -d bson-${version}`);
35+
} catch (err) {
36+
return done(err);
37+
}
38+
done();
39+
});
40+
}
41+
42+
describe('Current version', function () {
43+
OLD_VERSIONS.forEach(version => {
44+
before(function (done) {
45+
if (Number(process.version.split('.')[0].substring(1)) < 8) {
46+
// WHATWG fetch doesn't download correctly prior to node 8
47+
// but we should be safe by testing on node 8 +
48+
this.skip();
49+
}
50+
if (fs.existsSync(`bson-${version}.zip`)) {
51+
fs.unlinkSync(`bson-${version}.zip`);
52+
rimraf(`./bson-${version}`, err => {
53+
if (err) done(err);
54+
55+
// download old versions
56+
downloadZip(version, done);
57+
});
58+
} else {
59+
// download old versions
60+
downloadZip(version, done);
61+
}
62+
});
63+
64+
after(function (done) {
65+
try {
66+
fs.unlinkSync(`bson-${version}.zip`);
67+
} catch (e) {
68+
// ignore
69+
}
70+
rimraf(`./bson-${version}`, err => {
71+
if (err) done(err);
72+
done();
73+
});
74+
});
75+
76+
it(`serializes correctly against ${version} Binary class`, function () {
77+
const oldBSON = require(getImportPath(version));
78+
const binFromNew = {
79+
binary: new newBSON.Binary('aaaa')
80+
};
81+
const binFromOld = {
82+
binary: new oldBSON.Binary('aaaa')
83+
};
84+
expect(oldBSON.prototype.serialize(binFromNew).toString('hex')).to.equal(
85+
newBSON.serialize(binFromOld).toString('hex')
86+
);
87+
});
88+
});
89+
});

0 commit comments

Comments
 (0)