Skip to content

Commit e9a3426

Browse files
nbbeekenaduh95
authored andcommitted
buffer: make methods work on Uint8Array instances
Removes the reliance on prototype bound methods internally so that Uint8Arrays can be set as the bound `this` value when calling the various Buffer methods. Introduces some additional tamper protection by removing internal reliance on writable properties. Fixes: #56577 PR-URL: #56578 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 8dd379d commit e9a3426

File tree

8 files changed

+1337
-156
lines changed

8 files changed

+1337
-156
lines changed

doc/api/buffer.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,21 @@ function:
415415
* [`Buffer.from(arrayBuffer[, byteOffset[, length]])`][`Buffer.from(arrayBuf)`]
416416
* [`Buffer.from(string[, encoding])`][`Buffer.from(string)`]
417417

418+
### Buffer methods are callable with `Uint8Array` instances
419+
420+
All methods on the Buffer prototype are callable with a `Uint8Array` instance.
421+
422+
```js
423+
const { toString, write } = Buffer.prototype;
424+
425+
const uint8array = new Uint8Array(5);
426+
427+
write.call(uint8array, 'hello', 0, 5, 'utf8'); // 5
428+
// <Uint8Array 68 65 6c 6c 6f>
429+
430+
toString.call(uint8array, 'utf8'); // 'hello'
431+
```
432+
418433
## Buffers and iteration
419434

420435
`Buffer` instances can be iterated over using `for..of` syntax:
@@ -2058,6 +2073,10 @@ console.log(buf.fill('zz', 'hex'));
20582073

20592074
<!-- YAML
20602075
added: v5.3.0
2076+
changes:
2077+
- version: REPLACEME
2078+
pr-url: https://github.com/nodejs/node/pull/56578
2079+
description: supports Uint8Array as `this` value.
20612080
-->
20622081

20632082
* `value` {string|Buffer|Uint8Array|integer} What to search for.
@@ -2945,10 +2964,14 @@ console.log(buf.readInt32LE(1));
29452964
<!-- YAML
29462965
added: v0.11.15
29472966
changes:
2967+
- version: REPLACEME
2968+
pr-url: https://github.com/nodejs/node/pull/56578
2969+
description: supports Uint8Array as `this` value.
29482970
- version: v10.0.0
29492971
pr-url: https://github.com/nodejs/node/pull/18395
29502972
description: Removed `noAssert` and no implicit coercion of the offset
29512973
and `byteLength` to `uint32` anymore.
2974+
29522975
-->
29532976

29542977
* `offset` {integer} Number of bytes to skip before starting to read. Must
@@ -2992,10 +3015,14 @@ console.log(buf.readIntBE(1, 0).toString(16));
29923015
<!-- YAML
29933016
added: v0.11.15
29943017
changes:
3018+
- version: REPLACEME
3019+
pr-url: https://github.com/nodejs/node/pull/56578
3020+
description: supports Uint8Array as `this` value.
29953021
- version: v10.0.0
29963022
pr-url: https://github.com/nodejs/node/pull/18395
29973023
description: Removed `noAssert` and no implicit coercion of the offset
29983024
and `byteLength` to `uint32` anymore.
3025+
29993026
-->
30003027

30013028
* `offset` {integer} Number of bytes to skip before starting to read. Must
@@ -3269,6 +3296,9 @@ console.log(buf.readUInt32LE(1).toString(16));
32693296
<!-- YAML
32703297
added: v0.11.15
32713298
changes:
3299+
- version: REPLACEME
3300+
pr-url: https://github.com/nodejs/node/pull/56578
3301+
description: supports Uint8Array as `this` value.
32723302
- version:
32733303
- v14.9.0
32743304
- v12.19.0
@@ -3319,6 +3349,9 @@ console.log(buf.readUIntBE(1, 6).toString(16));
33193349
<!-- YAML
33203350
added: v0.11.15
33213351
changes:
3352+
- version: REPLACEME
3353+
pr-url: https://github.com/nodejs/node/pull/56578
3354+
description: supports Uint8Array as `this` value.
33223355
- version:
33233356
- v14.9.0
33243357
- v12.19.0
@@ -3771,6 +3804,10 @@ console.log(copy);
37713804

37723805
<!-- YAML
37733806
added: v0.1.90
3807+
changes:
3808+
- version: REPLACEME
3809+
pr-url: https://github.com/nodejs/node/pull/56578
3810+
description: supports Uint8Array as `this` value.
37743811
-->
37753812

37763813
* `encoding` {string} The character encoding to use. **Default:** `'utf8'`.
@@ -3909,6 +3946,10 @@ for (const value of buf) {
39093946

39103947
<!-- YAML
39113948
added: v0.1.90
3949+
changes:
3950+
- version: REPLACEME
3951+
pr-url: https://github.com/nodejs/node/pull/56578
3952+
description: supports Uint8Array as `this` value.
39123953
-->
39133954

39143955
* `string` {string} String to write to `buf`.

lib/buffer.js

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ const {
7272
kStringMaxLength,
7373
atob: _atob,
7474
btoa: _btoa,
75+
asciiSlice,
76+
base64Slice,
77+
base64urlSlice,
78+
latin1Slice,
79+
hexSlice,
80+
ucs2Slice,
81+
utf8Slice,
82+
base64Write,
83+
base64urlWrite,
84+
hexWrite,
85+
ucs2Write,
7586
} = internalBinding('buffer');
7687
const {
7788
constants: {
@@ -127,6 +138,9 @@ const {
127138
markAsUntransferable,
128139
addBufferPrototypeMethods,
129140
createUnsafeBuffer,
141+
asciiWrite,
142+
latin1Write,
143+
utf8Write,
130144
} = require('internal/buffer');
131145

132146
FastBuffer.prototype.constructor = Buffer;
@@ -634,44 +648,44 @@ const encodingOps = {
634648
encoding: 'utf8',
635649
encodingVal: encodingsMap.utf8,
636650
byteLength: byteLengthUtf8,
637-
write: (buf, string, offset, len) => buf.utf8Write(string, offset, len),
638-
slice: (buf, start, end) => buf.utf8Slice(start, end),
651+
write: utf8Write,
652+
slice: utf8Slice,
639653
indexOf: (buf, val, byteOffset, dir) =>
640654
indexOfString(buf, val, byteOffset, encodingsMap.utf8, dir),
641655
},
642656
ucs2: {
643657
encoding: 'ucs2',
644658
encodingVal: encodingsMap.utf16le,
645659
byteLength: (string) => string.length * 2,
646-
write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len),
647-
slice: (buf, start, end) => buf.ucs2Slice(start, end),
660+
write: ucs2Write,
661+
slice: ucs2Slice,
648662
indexOf: (buf, val, byteOffset, dir) =>
649663
indexOfString(buf, val, byteOffset, encodingsMap.utf16le, dir),
650664
},
651665
utf16le: {
652666
encoding: 'utf16le',
653667
encodingVal: encodingsMap.utf16le,
654668
byteLength: (string) => string.length * 2,
655-
write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len),
656-
slice: (buf, start, end) => buf.ucs2Slice(start, end),
669+
write: ucs2Write,
670+
slice: ucs2Slice,
657671
indexOf: (buf, val, byteOffset, dir) =>
658672
indexOfString(buf, val, byteOffset, encodingsMap.utf16le, dir),
659673
},
660674
latin1: {
661675
encoding: 'latin1',
662676
encodingVal: encodingsMap.latin1,
663677
byteLength: (string) => string.length,
664-
write: (buf, string, offset, len) => buf.latin1Write(string, offset, len),
665-
slice: (buf, start, end) => buf.latin1Slice(start, end),
678+
write: latin1Write,
679+
slice: latin1Slice,
666680
indexOf: (buf, val, byteOffset, dir) =>
667681
indexOfString(buf, val, byteOffset, encodingsMap.latin1, dir),
668682
},
669683
ascii: {
670684
encoding: 'ascii',
671685
encodingVal: encodingsMap.ascii,
672686
byteLength: (string) => string.length,
673-
write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len),
674-
slice: (buf, start, end) => buf.asciiSlice(start, end),
687+
write: asciiWrite,
688+
slice: asciiSlice,
675689
indexOf: (buf, val, byteOffset, dir) =>
676690
indexOfBuffer(buf,
677691
fromStringFast(val, encodingOps.ascii),
@@ -683,8 +697,8 @@ const encodingOps = {
683697
encoding: 'base64',
684698
encodingVal: encodingsMap.base64,
685699
byteLength: (string) => base64ByteLength(string, string.length),
686-
write: (buf, string, offset, len) => buf.base64Write(string, offset, len),
687-
slice: (buf, start, end) => buf.base64Slice(start, end),
700+
write: base64Write,
701+
slice: base64Slice,
688702
indexOf: (buf, val, byteOffset, dir) =>
689703
indexOfBuffer(buf,
690704
fromStringFast(val, encodingOps.base64),
@@ -696,9 +710,8 @@ const encodingOps = {
696710
encoding: 'base64url',
697711
encodingVal: encodingsMap.base64url,
698712
byteLength: (string) => base64ByteLength(string, string.length),
699-
write: (buf, string, offset, len) =>
700-
buf.base64urlWrite(string, offset, len),
701-
slice: (buf, start, end) => buf.base64urlSlice(start, end),
713+
write: base64urlWrite,
714+
slice: base64urlSlice,
702715
indexOf: (buf, val, byteOffset, dir) =>
703716
indexOfBuffer(buf,
704717
fromStringFast(val, encodingOps.base64url),
@@ -710,8 +723,8 @@ const encodingOps = {
710723
encoding: 'hex',
711724
encodingVal: encodingsMap.hex,
712725
byteLength: (string) => string.length >>> 1,
713-
write: (buf, string, offset, len) => buf.hexWrite(string, offset, len),
714-
slice: (buf, start, end) => buf.hexSlice(start, end),
726+
write: hexWrite,
727+
slice: hexSlice,
715728
indexOf: (buf, val, byteOffset, dir) =>
716729
indexOfBuffer(buf,
717730
fromStringFast(val, encodingOps.hex),
@@ -836,7 +849,7 @@ Buffer.prototype.copy =
836849
// to their upper/lower bounds if the value passed is out of range.
837850
Buffer.prototype.toString = function toString(encoding, start, end) {
838851
if (arguments.length === 0) {
839-
return this.utf8Slice(0, this.length);
852+
return utf8Slice(this, 0, this.length);
840853
}
841854

842855
const len = this.length;
@@ -857,7 +870,7 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
857870
return '';
858871

859872
if (encoding === undefined)
860-
return this.utf8Slice(start, end);
873+
return utf8Slice(this, start, end);
861874

862875
const ops = getEncodingOps(encoding);
863876
if (ops === undefined)
@@ -888,7 +901,7 @@ Buffer.prototype[customInspectSymbol] = function inspect(recurseTimes, ctx) {
888901
const actualMax = MathMin(max, this.length);
889902
const remaining = this.length - max;
890903
let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace(
891-
/(.{2})/g, this.hexSlice(0, actualMax), '$1 '));
904+
/(.{2})/g, hexSlice(this, 0, actualMax), '$1 '));
892905
if (remaining > 0)
893906
str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
894907
// Inspect special properties as well, if possible.
@@ -1027,7 +1040,7 @@ Buffer.prototype.lastIndexOf = function lastIndexOf(val, byteOffset, encoding) {
10271040
};
10281041

10291042
Buffer.prototype.includes = function includes(val, byteOffset, encoding) {
1030-
return this.indexOf(val, byteOffset, encoding) !== -1;
1043+
return bidirectionalIndexOf(this, val, byteOffset, encoding, true) !== -1;
10311044
};
10321045

10331046
// Usage:
@@ -1112,7 +1125,7 @@ function _fill(buf, value, offset, end, encoding) {
11121125
Buffer.prototype.write = function write(string, offset, length, encoding) {
11131126
// Buffer#write(string);
11141127
if (offset === undefined) {
1115-
return this.utf8Write(string, 0, this.length);
1128+
return utf8Write(this, string, 0, this.length);
11161129
}
11171130
// Buffer#write(string, encoding)
11181131
if (length === undefined && typeof offset === 'string') {
@@ -1139,9 +1152,9 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
11391152
}
11401153

11411154
if (!encoding || encoding === 'utf8')
1142-
return this.utf8Write(string, offset, length);
1155+
return utf8Write(this, string, offset, length);
11431156
if (encoding === 'ascii')
1144-
return this.asciiWrite(string, offset, length);
1157+
return asciiWrite(this, string, offset, length);
11451158

11461159
const ops = getEncodingOps(encoding);
11471160
if (ops === undefined)

0 commit comments

Comments
 (0)