Skip to content

Commit c35a917

Browse files
committed
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
1 parent f4fcf0e commit c35a917

File tree

4 files changed

+1187
-35
lines changed

4 files changed

+1187
-35
lines changed

doc/api/buffer.md

+15
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:

lib/buffer.js

+41-23
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
ArrayBufferIsView,
2727
ArrayIsArray,
2828
ArrayPrototypeForEach,
29+
FunctionPrototypeCall,
2930
MathFloor,
3031
MathMin,
3132
MathTrunc,
@@ -135,6 +136,23 @@ FastBuffer.prototype.constructor = Buffer;
135136
Buffer.prototype = FastBuffer.prototype;
136137
addBufferPrototypeMethods(Buffer.prototype);
137138

139+
const {
140+
asciiWrite,
141+
latin1Write,
142+
utf8Write,
143+
asciiSlice,
144+
base64Slice,
145+
base64urlSlice,
146+
latin1Slice,
147+
hexSlice,
148+
ucs2Slice,
149+
utf8Slice,
150+
base64Write,
151+
base64urlWrite,
152+
hexWrite,
153+
ucs2Write,
154+
} = Buffer.prototype;
155+
138156
const constants = ObjectDefineProperties({}, {
139157
MAX_LENGTH: {
140158
__proto__: null,
@@ -633,44 +651,44 @@ const encodingOps = {
633651
encoding: 'utf8',
634652
encodingVal: encodingsMap.utf8,
635653
byteLength: byteLengthUtf8,
636-
write: (buf, string, offset, len) => buf.utf8Write(string, offset, len),
637-
slice: (buf, start, end) => buf.utf8Slice(start, end),
654+
write: (buf, string, offset, len) => FunctionPrototypeCall(utf8Write, buf, string, offset, len),
655+
slice: (buf, start, end) => FunctionPrototypeCall(utf8Slice, buf, start, end),
638656
indexOf: (buf, val, byteOffset, dir) =>
639657
indexOfString(buf, val, byteOffset, encodingsMap.utf8, dir),
640658
},
641659
ucs2: {
642660
encoding: 'ucs2',
643661
encodingVal: encodingsMap.utf16le,
644662
byteLength: (string) => string.length * 2,
645-
write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len),
646-
slice: (buf, start, end) => buf.ucs2Slice(start, end),
663+
write: (buf, string, offset, len) => FunctionPrototypeCall(ucs2Write, buf, string, offset, len),
664+
slice: (buf, start, end) => FunctionPrototypeCall(ucs2Slice, buf, start, end),
647665
indexOf: (buf, val, byteOffset, dir) =>
648666
indexOfString(buf, val, byteOffset, encodingsMap.utf16le, dir),
649667
},
650668
utf16le: {
651669
encoding: 'utf16le',
652670
encodingVal: encodingsMap.utf16le,
653671
byteLength: (string) => string.length * 2,
654-
write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len),
655-
slice: (buf, start, end) => buf.ucs2Slice(start, end),
672+
write: (buf, string, offset, len) => FunctionPrototypeCall(ucs2Write, buf, string, offset, len),
673+
slice: (buf, start, end) => FunctionPrototypeCall(ucs2Slice, buf, start, end),
656674
indexOf: (buf, val, byteOffset, dir) =>
657675
indexOfString(buf, val, byteOffset, encodingsMap.utf16le, dir),
658676
},
659677
latin1: {
660678
encoding: 'latin1',
661679
encodingVal: encodingsMap.latin1,
662680
byteLength: (string) => string.length,
663-
write: (buf, string, offset, len) => buf.latin1Write(string, offset, len),
664-
slice: (buf, start, end) => buf.latin1Slice(start, end),
681+
write: (buf, string, offset, len) => FunctionPrototypeCall(latin1Write, buf, string, offset, len),
682+
slice: (buf, start, end) => FunctionPrototypeCall(latin1Slice, buf, start, end),
665683
indexOf: (buf, val, byteOffset, dir) =>
666684
indexOfString(buf, val, byteOffset, encodingsMap.latin1, dir),
667685
},
668686
ascii: {
669687
encoding: 'ascii',
670688
encodingVal: encodingsMap.ascii,
671689
byteLength: (string) => string.length,
672-
write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len),
673-
slice: (buf, start, end) => buf.asciiSlice(start, end),
690+
write: (buf, string, offset, len) => FunctionPrototypeCall(asciiWrite, buf, string, offset, len),
691+
slice: (buf, start, end) => FunctionPrototypeCall(asciiSlice, buf, start, end),
674692
indexOf: (buf, val, byteOffset, dir) =>
675693
indexOfBuffer(buf,
676694
fromStringFast(val, encodingOps.ascii),
@@ -682,8 +700,8 @@ const encodingOps = {
682700
encoding: 'base64',
683701
encodingVal: encodingsMap.base64,
684702
byteLength: (string) => base64ByteLength(string, string.length),
685-
write: (buf, string, offset, len) => buf.base64Write(string, offset, len),
686-
slice: (buf, start, end) => buf.base64Slice(start, end),
703+
write: (buf, string, offset, len) => FunctionPrototypeCall(base64Write, buf, string, offset, len),
704+
slice: (buf, start, end) => FunctionPrototypeCall(base64Slice, buf, start, end),
687705
indexOf: (buf, val, byteOffset, dir) =>
688706
indexOfBuffer(buf,
689707
fromStringFast(val, encodingOps.base64),
@@ -696,8 +714,8 @@ const encodingOps = {
696714
encodingVal: encodingsMap.base64url,
697715
byteLength: (string) => base64ByteLength(string, string.length),
698716
write: (buf, string, offset, len) =>
699-
buf.base64urlWrite(string, offset, len),
700-
slice: (buf, start, end) => buf.base64urlSlice(start, end),
717+
FunctionPrototypeCall(base64urlWrite, buf, string, offset, len),
718+
slice: (buf, start, end) => FunctionPrototypeCall(base64urlSlice, buf, start, end),
701719
indexOf: (buf, val, byteOffset, dir) =>
702720
indexOfBuffer(buf,
703721
fromStringFast(val, encodingOps.base64url),
@@ -709,8 +727,8 @@ const encodingOps = {
709727
encoding: 'hex',
710728
encodingVal: encodingsMap.hex,
711729
byteLength: (string) => string.length >>> 1,
712-
write: (buf, string, offset, len) => buf.hexWrite(string, offset, len),
713-
slice: (buf, start, end) => buf.hexSlice(start, end),
730+
write: (buf, string, offset, len) => FunctionPrototypeCall(hexWrite, buf, string, offset, len),
731+
slice: (buf, start, end) => FunctionPrototypeCall(hexSlice, buf, start, end),
714732
indexOf: (buf, val, byteOffset, dir) =>
715733
indexOfBuffer(buf,
716734
fromStringFast(val, encodingOps.hex),
@@ -835,7 +853,7 @@ Buffer.prototype.copy =
835853
// to their upper/lower bounds if the value passed is out of range.
836854
Buffer.prototype.toString = function toString(encoding, start, end) {
837855
if (arguments.length === 0) {
838-
return this.utf8Slice(0, this.length);
856+
return FunctionPrototypeCall(utf8Slice, this, 0, this.length);
839857
}
840858

841859
const len = this.length;
@@ -856,7 +874,7 @@ Buffer.prototype.toString = function toString(encoding, start, end) {
856874
return '';
857875

858876
if (encoding === undefined)
859-
return this.utf8Slice(start, end);
877+
return FunctionPrototypeCall(utf8Slice, this, start, end);
860878

861879
const ops = getEncodingOps(encoding);
862880
if (ops === undefined)
@@ -887,7 +905,7 @@ Buffer.prototype[customInspectSymbol] = function inspect(recurseTimes, ctx) {
887905
const actualMax = MathMin(max, this.length);
888906
const remaining = this.length - max;
889907
let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace(
890-
/(.{2})/g, this.hexSlice(0, actualMax), '$1 '));
908+
/(.{2})/g, FunctionPrototypeCall(hexSlice, this, 0, actualMax), '$1 '));
891909
if (remaining > 0)
892910
str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
893911
// Inspect special properties as well, if possible.
@@ -1026,7 +1044,7 @@ Buffer.prototype.lastIndexOf = function lastIndexOf(val, byteOffset, encoding) {
10261044
};
10271045

10281046
Buffer.prototype.includes = function includes(val, byteOffset, encoding) {
1029-
return this.indexOf(val, byteOffset, encoding) !== -1;
1047+
return bidirectionalIndexOf(this, val, byteOffset, encoding) !== -1;
10301048
};
10311049

10321050
// Usage:
@@ -1111,7 +1129,7 @@ function _fill(buf, value, offset, end, encoding) {
11111129
Buffer.prototype.write = function write(string, offset, length, encoding) {
11121130
// Buffer#write(string);
11131131
if (offset === undefined) {
1114-
return this.utf8Write(string, 0, this.length);
1132+
return FunctionPrototypeCall(utf8Write, this, string, 0, this.length);
11151133
}
11161134
// Buffer#write(string, encoding)
11171135
if (length === undefined && typeof offset === 'string') {
@@ -1138,9 +1156,9 @@ Buffer.prototype.write = function write(string, offset, length, encoding) {
11381156
}
11391157

11401158
if (!encoding || encoding === 'utf8')
1141-
return this.utf8Write(string, offset, length);
1159+
return FunctionPrototypeCall(utf8Write, this, string, offset, length);
11421160
if (encoding === 'ascii')
1143-
return this.asciiWrite(string, offset, length);
1161+
return FunctionPrototypeCall(asciiWrite, this, string, offset, length);
11441162

11451163
const ops = getEncodingOps(encoding);
11461164
if (ops === undefined)

lib/internal/buffer.js

+13-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
BigInt,
55
Float32Array,
66
Float64Array,
7+
FunctionPrototypeCall,
78
MathFloor,
89
Number,
910
Uint8Array,
@@ -177,11 +178,11 @@ function readUIntLE(offset, byteLength) {
177178
if (byteLength === 3)
178179
return readUInt24LE(this, offset);
179180
if (byteLength === 4)
180-
return this.readUInt32LE(offset);
181+
return FunctionPrototypeCall(readUInt32LE, this, offset);
181182
if (byteLength === 2)
182-
return this.readUInt16LE(offset);
183+
return FunctionPrototypeCall(readUInt16LE, this, offset);
183184
if (byteLength === 1)
184-
return this.readUInt8(offset);
185+
return FunctionPrototypeCall(readUInt8, this, offset);
185186

186187
boundsError(byteLength, 6, 'byteLength');
187188
}
@@ -266,11 +267,11 @@ function readUIntBE(offset, byteLength) {
266267
if (byteLength === 3)
267268
return readUInt24BE(this, offset);
268269
if (byteLength === 4)
269-
return this.readUInt32BE(offset);
270+
return FunctionPrototypeCall(readUInt32BE, this, offset);
270271
if (byteLength === 2)
271-
return this.readUInt16BE(offset);
272+
return FunctionPrototypeCall(readUInt16BE, this, offset);
272273
if (byteLength === 1)
273-
return this.readUInt8(offset);
274+
return FunctionPrototypeCall(readUInt8, this, offset);
274275

275276
boundsError(byteLength, 6, 'byteLength');
276277
}
@@ -346,11 +347,11 @@ function readIntLE(offset, byteLength) {
346347
if (byteLength === 3)
347348
return readInt24LE(this, offset);
348349
if (byteLength === 4)
349-
return this.readInt32LE(offset);
350+
return FunctionPrototypeCall(readInt32LE, this, offset);
350351
if (byteLength === 2)
351-
return this.readInt16LE(offset);
352+
return FunctionPrototypeCall(readInt16LE, this, offset);
352353
if (byteLength === 1)
353-
return this.readInt8(offset);
354+
return FunctionPrototypeCall(readInt8, this, offset);
354355

355356
boundsError(byteLength, 6, 'byteLength');
356357
}
@@ -438,11 +439,11 @@ function readIntBE(offset, byteLength) {
438439
if (byteLength === 3)
439440
return readInt24BE(this, offset);
440441
if (byteLength === 4)
441-
return this.readInt32BE(offset);
442+
return FunctionPrototypeCall(readInt32BE, this, offset);
442443
if (byteLength === 2)
443-
return this.readInt16BE(offset);
444+
return FunctionPrototypeCall(readInt16BE, this, offset);
444445
if (byteLength === 1)
445-
return this.readInt8(offset);
446+
return FunctionPrototypeCall(readInt8, this, offset);
446447

447448
boundsError(byteLength, 6, 'byteLength');
448449
}

0 commit comments

Comments
 (0)