From 91ab5a608e9609fe4c593972030fa521d2c8a603 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Fri, 16 Feb 2018 00:47:03 -0800 Subject: [PATCH] Match Node.js Buffer.from() argument handling (test-buffer-from.js) - Handle new String() and new Boolean() using valueOf() - Use Symbol.toPrimitive() if it exists - Be slightly stricter with accepting .length props - Match error messages --- index.js | 59 +++++++++++++++++++++--------- test/node/test-buffer-from.js | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 test/node/test-buffer-from.js diff --git a/index.js b/index.js index 121a438..19f3067 100644 --- a/index.js +++ b/index.js @@ -114,9 +114,18 @@ if (typeof Symbol !== 'undefined' && Symbol.species != null && Buffer.poolSize = 8192 // not used by this implementation function from (value, encodingOrOffset, length) { - if (typeof value === 'number') { - throw new TypeError( - 'The "value" argument must not be of type number. Received type number' + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + if (ArrayBuffer.isView(value)) { + return fromArrayLike(value) + } + + if (value == null) { + throw TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array ' + + 'or Array-like Object. Received type ' + (typeof value) ) } @@ -125,11 +134,31 @@ function from (value, encodingOrOffset, length) { return fromArrayBuffer(value, encodingOrOffset, length) } - if (typeof value === 'string') { - return fromString(value, encodingOrOffset) + if (typeof value === 'number') { + throw new TypeError( + 'The "value" argument must not be of type number. Received type number' + ) + } + + const valueOf = value.valueOf && value.valueOf() + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length) } - return fromObject(value) + var b = fromObject(value) + if (b) return b + + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && + typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from( + value[Symbol.toPrimitive]('string'), encodingOrOffset, length + ) + } + + throw new TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) } /** @@ -268,20 +297,16 @@ function fromObject (obj) { return buf } - if (obj) { - if (ArrayBuffer.isView(obj) || 'length' in obj) { - if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { - return createBuffer(0) - } - return fromArrayLike(obj) - } - - if (obj.type === 'Buffer' && Array.isArray(obj.data)) { - return fromArrayLike(obj.data) + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) } + return fromArrayLike(obj) } - throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.') + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } } function checked (length) { diff --git a/test/node/test-buffer-from.js b/test/node/test-buffer-from.js new file mode 100644 index 0000000..6857ba3 --- /dev/null +++ b/test/node/test-buffer-from.js @@ -0,0 +1,68 @@ +'use strict'; +var Buffer = require('../../').Buffer; + +const common = require('./common'); +const { deepStrictEqual, throws } = require('assert'); +const { runInNewContext } = require('vm'); + +const checkString = 'test'; + +const check = Buffer.from(checkString); + +class MyString extends String { + constructor() { + super(checkString); + } +} + +class MyPrimitive { + [Symbol.toPrimitive]() { + return checkString; + } +} + +class MyBadPrimitive { + [Symbol.toPrimitive]() { + return 1; + } +} + +deepStrictEqual(Buffer.from(new String(checkString)), check); +deepStrictEqual(Buffer.from(new MyString()), check); +deepStrictEqual(Buffer.from(new MyPrimitive()), check); +deepStrictEqual( + Buffer.from(runInNewContext('new String(checkString)', { checkString })), + check +); + +[ + [{}, 'object'], + [new Boolean(true), 'boolean'], + [{ valueOf() { return null; } }, 'object'], + [{ valueOf() { return undefined; } }, 'object'], + [{ valueOf: null }, 'object'], + [Object.create(null), 'object'] +].forEach(([input, actualType]) => { + const err = common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The first argument must be one of type string, Buffer, ' + + 'ArrayBuffer, Array, or Array-like Object. Received ' + + `type ${actualType}` + }); + throws(() => Buffer.from(input), err); +}); + +[ + new Number(true), + new MyBadPrimitive() +].forEach((input) => { + const errMsg = common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "value" argument must not be of type number. ' + + 'Received type number' + }); + throws(() => Buffer.from(input), errMsg); +}); + -- 2.34.1