Skip to content

Commit 22cf25c

Browse files
committed
buffer: support boxed strings and toPrimitive
Add support for `Buffer.from(new String('...'))` and `Buffer.from({[Symbol.toPrimitive]() { return '...'; }})` PR-URL: #13725 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent d3c668c commit 22cf25c

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

doc/api/buffer.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,44 @@ console.log(buf2.toString());
908908

909909
A `TypeError` will be thrown if `str` is not a string.
910910

911+
### Class Method: Buffer.from(object[, offsetOrEncoding[, length]])
912+
<!-- YAML
913+
added: REPLACEME
914+
-->
915+
916+
* `object` {Object} An object supporting `Symbol.toPrimitive` or `valueOf()`
917+
* `offsetOrEncoding` {number|string} A byte-offset or encoding, depending on
918+
the value returned either by `object.valueOf()` or
919+
`object[Symbol.toPrimitive]()`.
920+
* `length` {number} A length, depending on the value returned either by
921+
`object.valueOf()` or `object[Symbol.toPrimitive]()`.
922+
923+
For objects whose `valueOf()` function returns a value not strictly equal to
924+
`object`, returns `Buffer.from(object.valueOf(), offsetOrEncoding, length)`.
925+
926+
For example:
927+
928+
```js
929+
const buf = Buffer.from(new String('this is a test'));
930+
// <Buffer 74 68 69 73 20 69 73 20 61 20 74 65 73 74>
931+
```
932+
933+
For objects that support `Symbol.toPrimitive`, returns
934+
`Buffer.from(object[Symbol.toPrimitive](), offsetOrEncoding, length)`.
935+
936+
For example:
937+
938+
```js
939+
class Foo {
940+
[Symbol.toPrimitive]() {
941+
return 'this is a test';
942+
}
943+
}
944+
945+
const buf = Buffer.from(new Foo(), 'utf8');
946+
// <Buffer 74 68 69 73 20 69 73 20 61 20 74 65 73 74>
947+
```
948+
911949
### Class Method: Buffer.isBuffer(obj)
912950
<!-- YAML
913951
added: v0.1.101

lib/buffer.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,26 @@ Buffer.from = function(value, encodingOrOffset, length) {
176176
if (isAnyArrayBuffer(value))
177177
return fromArrayBuffer(value, encodingOrOffset, length);
178178

179+
if (value == null)
180+
throw new TypeError(kFromErrorMsg);
181+
182+
if (typeof value === 'number')
183+
throw new TypeError('"value" argument must not be a number');
184+
185+
const valueOf = value.valueOf && value.valueOf();
186+
if (valueOf != null && valueOf !== value)
187+
return Buffer.from(valueOf, encodingOrOffset, length);
188+
179189
var b = fromObject(value);
180190
if (b)
181191
return b;
182192

183-
if (typeof value === 'number')
184-
throw new TypeError('"value" argument must not be a number');
193+
if (typeof value[Symbol.toPrimitive] === 'function') {
194+
return Buffer.from(value[Symbol.toPrimitive]('string'),
195+
encodingOrOffset,
196+
length);
197+
}
198+
185199
throw new TypeError(kFromErrorMsg);
186200
};
187201

test/parallel/test-buffer-from.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
require('../common');
4+
const { deepStrictEqual, throws } = require('assert');
5+
const { Buffer } = require('buffer');
6+
const { runInNewContext } = require('vm');
7+
8+
const checkString = 'test';
9+
10+
const check = Buffer.from(checkString);
11+
12+
class MyString extends String {
13+
constructor() {
14+
super(checkString);
15+
}
16+
}
17+
18+
class MyPrimitive {
19+
[Symbol.toPrimitive]() {
20+
return checkString;
21+
}
22+
}
23+
24+
class MyBadPrimitive {
25+
[Symbol.toPrimitive]() {
26+
return 1;
27+
}
28+
}
29+
30+
deepStrictEqual(Buffer.from(new String(checkString)), check);
31+
deepStrictEqual(Buffer.from(new MyString()), check);
32+
deepStrictEqual(Buffer.from(new MyPrimitive()), check);
33+
deepStrictEqual(Buffer.from(
34+
runInNewContext('new String(checkString)', {checkString})),
35+
check);
36+
37+
const err = new RegExp('^TypeError: First argument must be a string, Buffer, ' +
38+
'ArrayBuffer, Array, or array-like object\\.$');
39+
40+
[
41+
{},
42+
new Boolean(true),
43+
{ valueOf() { return null; } },
44+
{ valueOf() { return undefined; } },
45+
{ valueOf: null },
46+
Object.create(null)
47+
].forEach((input) => {
48+
throws(() => Buffer.from(input), err);
49+
});
50+
51+
[
52+
new Number(true),
53+
new MyBadPrimitive()
54+
].forEach((input) => {
55+
throws(() => Buffer.from(input),
56+
/^TypeError: "value" argument must not be a number$/);
57+
});

0 commit comments

Comments
 (0)