Skip to content

fix(NODE-3662): error checking to make sure that ObjectId results in object with correct properties #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 2, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 29 additions & 34 deletions src/objectid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,54 +43,49 @@ export class ObjectId {
/**
* Create an ObjectId type
*
* @param id - Can be a 24 character hex string, 12 byte binary Buffer, or a number.
* @param inputId - Can be a 24 character hex string, 12 byte binary Buffer, or a number.
*/
constructor(id?: string | Buffer | number | ObjectIdLike | ObjectId) {
if (!(this instanceof ObjectId)) return new ObjectId(id);
constructor(inputId?: string | Buffer | number | ObjectIdLike | ObjectId) {
if (!(this instanceof ObjectId)) return new ObjectId(inputId);

// Duck-typing to support ObjectId from different npm packages
if (id instanceof ObjectId) {
this[kId] = id.id;
this.__id = id.__id;
}

if (typeof id === 'object' && id && 'id' in id) {
if ('toHexString' in id && typeof id.toHexString === 'function') {
this[kId] = Buffer.from(id.toHexString(), 'hex');
let workingId;
if (typeof inputId === 'object' && inputId && 'id' in inputId) {
if (typeof inputId.id !== 'string' && !ArrayBuffer.isView(inputId.id)) {
throw new BSONTypeError(
'Argument passed in must have an id that is of type string or Buffer'
);
}
if ('toHexString' in inputId && typeof inputId.toHexString === 'function') {
workingId = Buffer.from(inputId.toHexString(), 'hex');
} else {
this[kId] = typeof id.id === 'string' ? Buffer.from(id.id) : id.id;
workingId = inputId.id;
}
} else {
workingId = inputId;
}

// The most common use case (blank id, new objectId instance)
if (id == null || typeof id === 'number') {
if (workingId == null || typeof workingId === 'number') {
// The most common use case (blank id, new objectId instance)
// Generate a new id
this[kId] = ObjectId.generate(typeof id === 'number' ? id : undefined);
// If we are caching the hex string
if (ObjectId.cacheHexString) {
this.__id = this.id.toString('hex');
}
}

if (ArrayBuffer.isView(id) && id.byteLength === 12) {
this[kId] = ensureBuffer(id);
}

if (typeof id === 'string') {
if (id.length === 12) {
const bytes = Buffer.from(id);
this[kId] = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined);
} else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) {
this[kId] = ensureBuffer(workingId);
} else if (typeof workingId === 'string') {
if (workingId.length === 12) {
const bytes = Buffer.from(workingId);
if (bytes.byteLength === 12) {
this[kId] = bytes;
}
} else if (id.length === 24 && checkForHexRegExp.test(id)) {
this[kId] = Buffer.from(id, 'hex');
} else if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
this[kId] = Buffer.from(workingId, 'hex');
} else {
throw new BSONTypeError(
'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters'
'Argument passed in must be a string of 12 bytes or a string of 24 hex characters'
);
}
} else {
throw new BSONTypeError('Argument passed in does not match the accepted types');
}

// If we are caching the hex string
if (ObjectId.cacheHexString) {
this.__id = this.id.toString('hex');
}
Expand Down
200 changes: 198 additions & 2 deletions test/node/object_id_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ const BSON = require('../register-bson');
const util = require('util');
const ObjectId = BSON.ObjectId;

describe('ObjectId', function () {
describe.only('ObjectId', function () {
/**
* @ignore
*/

it('should correctly handle objectId timestamps', function (done) {
// var test_number = {id: ObjectI()};
var a = ObjectId.createFromTime(1);
Expand All @@ -24,6 +25,195 @@ describe('ObjectId', function () {
done();
});

it('should correctly create ObjectId from ObjectId', function () {
var tmp = new ObjectId();
expect(() => new ObjectId(tmp)).to.not.throw();
});

it('should throw error if empty array is passed in', function () {
expect(() => new ObjectId([])).to.throw(TypeError);
});

it('should throw error if nonempty array is passed in', function () {
expect(() => new ObjectId(['abcdefŽhijkl'])).to.throw(TypeError);
});

it('should throw error if empty object is passed in', function () {
expect(() => new ObjectId({})).to.throw(TypeError);
});

it('should throw error if object without an id property is passed in', function () {
var tmp = new ObjectId();
var objectIdLike = {
toHexString: function () {
return tmp.toHexString();
}
};

expect(() => new ObjectId(objectIdLike)).to.throw(TypeError);
});

it('should correctly create ObjectId from object with valid string id', function () {
var objectValidStringId1 = {
id: 'aaaaaaaaaaaaaaaaaaaaaaaa'
};
var objectValidStringId2 = {
id: 'abcdefghijkl'
};
var buf1 = Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaa', 'hex');
var buf2 = Buffer.from('abcdefghijkl', 'hex');
expect(() => new ObjectId(objectValidStringId1).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectValidStringId1).id).equals(buf1));
expect(() => new ObjectId(objectValidStringId2).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectValidStringId2).id).equals(buf2));
});

it('should correctly create ObjectId from object with valid string id and toHexString method', function () {
function newToHexString() {
return 'BBBBBBBBBBBBBBBBBBBBBBBB';
}
var buf = Buffer.from('BBBBBBBBBBBBBBBBBBBBBBBB', 'hex');
var objectValidStringId1 = {
id: 'aaaaaaaaaaaaaaaaaaaaaaaa',
toHexString: newToHexString
};
var objectValidStringId2 = {
id: 'abcdefghijkl',
toHexString: newToHexString
};
expect(() => new ObjectId(objectValidStringId1).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectValidStringId1).id).equals(buf));
expect(() => new ObjectId(objectValidStringId2).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectValidStringId2).id).equals(buf));
});

it('should correctly create ObjectId from object with valid Buffer id', function () {
var validBuffer1 = Buffer.from('AAAAAAAAAAAAAAAAAAAAAAAA', 'hex');
var validBuffer2 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
var objectBufferId = {
id: validBuffer1
};
var objectBufferFromArray = {
id: validBuffer2
};
expect(() => new ObjectId(objectBufferId).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectBufferId).id).equals(validBuffer1));
expect(() => new ObjectId(objectBufferFromArray).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectBufferId).id).equals(validBuffer2));
});

it('should correctly create ObjectId from object with valid Buffer id and toHexString method', function () {
var validBuffer1 = Buffer.from('AAAAAAAAAAAAAAAAAAAAAAAA', 'hex');
var validBuffer2 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
var bufferToHex = Buffer.from('BBBBBBBBBBBBBBBBBBBBBBBB', 'hex');
function newToHexString() {
return 'BBBBBBBBBBBBBBBBBBBBBBBB';
}
var objectBufferId = {
id: validBuffer1,
toHexString: newToHexString
};
var objectBufferFromArray = {
id: validBuffer2,
toHexString: newToHexString
};
expect(() => new ObjectId(objectBufferId).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectBufferId).id).equals(bufferToHex));

expect(() => new ObjectId(objectBufferFromArray).toHexString()).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectBufferFromArray).id).equals(bufferToHex));
});

it('should throw error if object with non-Buffer non-string id is passed in', function () {
var objectNumId = {
id: 5
};
var objectNullId = {
id: null
};
expect(() => new ObjectId(objectNumId)).to.throw(TypeError);
expect(() => new ObjectId(objectNullId)).to.throw(TypeError);
});

it('should throw an error if object with invalid string id is passed in', function () {
var objectInvalidString = {
id: 'FFFFFFFFFFFFFFFFFFFFFFFG'
};
expect(() => new ObjectId(objectInvalidString)).to.throw(TypeError);
});

it('should correctly create ObjectId from object with invalid string id and toHexString method', function () {
function newToHexString() {
return 'BBBBBBBBBBBBBBBBBBBBBBBB';
}
var objectInvalidString = {
id: 'FFFFFFFFFFFFFFFFFFFFFFFG',
toHexString: newToHexString
};
var buf = Buffer.from('BBBBBBBBBBBBBBBBBBBBBBBB', 'hex');
expect(() => new ObjectId(objectInvalidString)).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectInvalidString).id).equals(buf));
});

it('should throw an error if object with invalid Buffer id is passed in', function () {
var objectInvalidBuffer = {
id: Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
};
expect(() => new ObjectId(objectInvalidBuffer)).to.throw(TypeError);
});

it('should correctly create ObjectId from object with invalid Buffer id and toHexString method', function () {
function newToHexString() {
return 'BBBBBBBBBBBBBBBBBBBBBBBB';
}
var objectInvalidBuffer = {
id: Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]),
toHexString: newToHexString
};
var buf = Buffer.from('BBBBBBBBBBBBBBBBBBBBBBBB', 'hex');
expect(() => new ObjectId(objectInvalidBuffer)).to.not.throw(TypeError);
expect(Buffer.from(new ObjectId(objectInvalidBuffer).id).equals(buf));
});

it('should correctly create ObjectId from object with objectIdLike properties', function () {
var tmp = new ObjectId();
var objectIdLike = {
id: tmp.id,
toHexString: function () {
return tmp.toHexString();
}
};

expect(() => new ObjectId(objectIdLike).toHexString()).to.not.throw(TypeError);
});

it('should correctly create ObjectId from number or null', function () {
expect(() => new ObjectId(42).toHexString()).to.not.throw();
expect(() => new ObjectId(0x2a).toHexString()).to.not.throw();
expect(() => new ObjectId(NaN).toHexString()).to.not.throw();
expect(() => new ObjectId(null).toHexString()).to.not.throw();
});

it('should throw error if non-12 byte non-24 hex string passed in', function () {
expect(() => new ObjectId('FFFFFFFFFFFFFFFFFFFFFFFG')).to.throw();
expect(() => new ObjectId('thisstringisdefinitelytoolong')).to.throw();
expect(() => new ObjectId('tooshort')).to.throw();
expect(() => new ObjectId('101010')).to.throw();
expect(() => new ObjectId('')).to.throw();
});

it('should correctly create ObjectId from 12 byte or 24 hex string', function () {
expect(() => new ObjectId('AAAAAAAAAAAAAAAAAAAAAAAA').toHexString()).to.not.throw();
expect(() => new ObjectId('FFFFFFFFFFFFFFFFFFFFFFFF').toHexString()).to.not.throw();
expect(() => new ObjectId('abcdefghijkl').toHexString()).to.not.throw();
});

it('should correctly create ObjectId from 12 byte sequence', function () {
var a = '111111111111';
expect(() => new ObjectId(a).toHexString()).to.not.throw();
expect(Buffer.from(new ObjectId(a).id).equals(Buffer.from('111111111111', 'latin1')));
});

/**
* @ignore
*/
Expand All @@ -45,7 +235,7 @@ describe('ObjectId', function () {
/**
* @ignore
*/
it('should correctly create ObjectId from Buffer', function (done) {
it('should correctly create ObjectId from valid Buffer', function (done) {
if (!Buffer.from) return done();
var a = 'AAAAAAAAAAAAAAAAAAAAAAAA';
var b = new ObjectId(Buffer.from(a, 'hex'));
Expand All @@ -60,6 +250,12 @@ describe('ObjectId', function () {
done();
});

it('should throw an error if invalid Buffer passed in', function (done) {
var a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
expect(() => new ObjectId(a)).to.throw(TypeError);
done();
});

/**
* @ignore
*/
Expand Down