diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 4488ae2ad9bf8d..3a04ad514b4d4e 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -908,6 +908,44 @@ console.log(buf2.toString()); A `TypeError` will be thrown if `str` is not a string. +### Class Method: Buffer.from(object[, offsetOrEncoding[, length]]) +<!-- YAML +added: REPLACEME +--> + +* `object` {Object} An object supporting `Symbol.toPrimitive` or `valueOf()` +* `offsetOrEncoding` {number|string} A byte-offset or encoding, depending on + the value returned either by `object.valueOf()` or + `object[Symbol.toPrimitive]()`. +* `length` {number} A length, depending on the value returned either by + `object.valueOf()` or `object[Symbol.toPrimitive]()`. + +For objects whose `valueOf()` function returns a value not strictly equal to +`object`, returns `Buffer.from(object.valueOf(), offsetOrEncoding, length)`. + +For example: + +```js +const buf = Buffer.from(new String('this is a test')); +// <Buffer 74 68 69 73 20 69 73 20 61 20 74 65 73 74> +``` + +For objects that support `Symbol.toPrimitive`, returns +`Buffer.from(object[Symbol.toPrimitive](), offsetOrEncoding, length)`. + +For example: + +```js +class Foo { + [Symbol.toPrimitive]() { + return 'this is a test'; + } +} + +const buf = Buffer.from(new Foo(), 'utf8'); +// <Buffer 74 68 69 73 20 69 73 20 61 20 74 65 73 74> +``` + ### Class Method: Buffer.isBuffer(obj) <!-- YAML added: v0.1.101 diff --git a/lib/buffer.js b/lib/buffer.js index 984b558be69160..bfa8656483f602 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -176,12 +176,26 @@ Buffer.from = function(value, encodingOrOffset, length) { if (isAnyArrayBuffer(value)) return fromArrayBuffer(value, encodingOrOffset, length); + if (value == null) + throw new TypeError(kFromErrorMsg); + + if (typeof value === 'number') + throw new TypeError('"value" argument must not be a number'); + + const valueOf = value.valueOf && value.valueOf(); + if (valueOf != null && valueOf !== value) + return Buffer.from(valueOf, encodingOrOffset, length); + var b = fromObject(value); if (b) return b; - if (typeof value === 'number') - throw new TypeError('"value" argument must not be a number'); + if (typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from(value[Symbol.toPrimitive]('string'), + encodingOrOffset, + length); + } + throw new TypeError(kFromErrorMsg); }; diff --git a/test/parallel/test-buffer-from.js b/test/parallel/test-buffer-from.js new file mode 100644 index 00000000000000..1d8ecb5533680a --- /dev/null +++ b/test/parallel/test-buffer-from.js @@ -0,0 +1,57 @@ +'use strict'; + +require('../common'); +const { deepStrictEqual, throws } = require('assert'); +const { Buffer } = require('buffer'); +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); + +const err = new RegExp('^TypeError: First argument must be a string, Buffer, ' + + 'ArrayBuffer, Array, or array-like object\\.$'); + +[ + {}, + new Boolean(true), + { valueOf() { return null; } }, + { valueOf() { return undefined; } }, + { valueOf: null }, + Object.create(null) +].forEach((input) => { + throws(() => Buffer.from(input), err); +}); + +[ + new Number(true), + new MyBadPrimitive() +].forEach((input) => { + throws(() => Buffer.from(input), + /^TypeError: "value" argument must not be a number$/); +});