From ad5e1452904c791fea873fac002e6256230177a0 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 17 Mar 2016 10:35:11 -0700 Subject: [PATCH] buffer: backport new buffer constructor APIs This backports the new `Buffer.alloc()`, `Buffer.allocUnsafe()`, and `Buffer.from()` APIs for v5. Also included in this backport is the change that allows fill('') to zero-fill (as opposed to doing nothing) and the additional `byteOffset` and `length` arguments for `Buffer(arrayBuffer)` and `Buffer.from(arrayBuffer)`. This backport includes the new test cases. This backport *does not* update all of the internal uses of the existing `Buffer()` constructor. This backport also *does not* include the soft deprecation of the existing `Buffer()` constructor. --- doc/api/buffer.markdown | 484 ++++++-- lib/buffer.js | 127 +- src/node_buffer.cc | 15 +- test/parallel/test-buffer-alloc.js | 1431 ++++++++++++++++++++++ test/parallel/test-buffer-safe-unsafe.js | 14 + 5 files changed, 1951 insertions(+), 120 deletions(-) create mode 100644 test/parallel/test-buffer-alloc.js create mode 100644 test/parallel/test-buffer-safe-unsafe.js diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 4c8a48de906508..cad33d897b9c21 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -20,20 +20,97 @@ resized. The `Buffer` class is a global within Node.js, making it unlikely that one would need to ever use `require('buffer')`. + ```js -const buf1 = new Buffer(10); - // creates a buffer of length 10 +const buf1 = Buffer.alloc(10); + // Creates a zero-filled Buffer of length 10. + +const buf2 = Buffer.alloc(10, 1); + // Creates a Buffer of length 10, filled with 0x01. + +const buf3 = Buffer.allocUnsafe(10); + // Creates an uninitialized buffer of length 10. + // This is faster than calling Buffer.alloc() but the returned + // Buffer instance might contain old data that needs to be + // overwritten using either fill() or write(). -const buf2 = new Buffer([1,2,3]); - // creates a buffer containing [01, 02, 03] +const buf4 = Buffer.from([1,2,3]); + // Creates a Buffer containing [01, 02, 03]. -const buf3 = new Buffer('test'); - // creates a buffer containing ASCII bytes [74, 65, 73, 74] +const buf5 = Buffer.from('test'); + // Creates a Buffer containing ASCII bytes [74, 65, 73, 74]. -const buf4 = new Buffer('tést', 'utf8'); - // creates a buffer containing UTF8 bytes [74, c3, a9, 73, 74] +const buf6 = Buffer.from('tést', 'utf8'); + // Creates a Buffer containing UTF8 bytes [74, c3, a9, 73, 74]. ``` +## `Buffer.from()`, `Buffer.alloc()`, and `Buffer.allocUnsafe()` + +Historically, `Buffer` instances have been created using the `Buffer` +constructor function, which allocates the returned `Buffer` +differently based on what arguments are provided: + +* Passing a number as the first argument to `Buffer()` (e.g. `new Buffer(10)`), + allocates a new `Buffer` object of the specified size. The memory allocated + for such `Buffer` instances is *not* initialized and *can contain sensitive + data*. Such `Buffer` objects *must* be initialized *manually* by using either + [`buf.fill(0)`][] or by writing to the `Buffer` completely. While this + behavior is *intentional* to improve performance, development experience has + demonstrated that a more explicit distinction is required between creating a + fast-but-uninitialized `Buffer` versus creating a slower-but-safer `Buffer`. +* Passing a string, array, or `Buffer` as the first argument copies the + passed object's data into the `Buffer`. +* Passing an `ArrayBuffer` returns a `Buffer` that shares allocated memory with + the given `ArrayBuffer`. + +Because the behavior of `new Buffer()` changes significantly based on the type +of value passed as the first argument, applications that do not properly +validate the input arguments passed to `new Buffer()`, or that fail to +appropriately initialize newly allocated `Buffer` content, can inadvertently +introduce security and reliability issues into their code. + +To make the creation of `Buffer` objects more reliable and less error prone, +new `Buffer.from()`, `Buffer.alloc()`, and `Buffer.allocUnsafe()` methods have +been introduced as an alternative means of creating `Buffer` instances. + +*Developers should migrate all existing uses of the `new Buffer()` constructors +to one of these new APIs.* + +* [`Buffer.from(array)`][buffer_from_array] returns a new `Buffer` containing + a *copy* of the provided octets. +* [`Buffer.from(arrayBuffer[, byteOffset [, length]])`][buffer_from_arraybuf] + returns a new `Buffer` that *shares* the same allocated memory as the given + `ArrayBuffer`. +* [`Buffer.from(buffer)`][buffer_from_buffer] returns a new `Buffer` + containing a *copy* of the contents of the given `Buffer`. +* [`Buffer.from(str[, encoding])`][buffer_from_string] returns a new `Buffer` + containing a *copy* of the provided string. +* [`Buffer.alloc(size[, fill[, encoding]])`][buffer_alloc] returns a "filled" + `Buffer` instance of the specified size. This method can be significantly + slower than [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] but ensures that + newly created `Buffer` instances never contain old and potentially sensitive + data. +* [`Buffer.allocUnsafe(size)`][buffer_allocunsafe] returns a new `Buffer` of + the specified `size` whose content *must* be initialized using either + [`buf.fill(0)`][] or written to completely. + +`Buffer` instances returned by `Buffer.allocUnsafe(size)` *may* be allocated +off a shared internal memory pool if the `size` is less than or equal to half +`Buffer.poolSize`. + +### What makes `Buffer.allocUnsafe(size)` "unsafe"? + +When calling `Buffer.allocUnsafe()`, the segment of allocated memory is +*uninitialized* (it is not zeroed-out). While this design makes the allocation +of memory quite fast, the allocated segment of memory might contain old data +that is potentially sensitive. Using a `Buffer` created by +`Buffer.allocUnsafe(size)` without *completely* overwriting the memory can +allow this old data to be leaked when the `Buffer` memory is read. + +While there are clear performance advantages to using `Buffer.allocUnsafe()`, +extra care *must* be taken in order to avoid introducing security +vulnerabilities into an application. + ## Buffers and Character Encodings Buffers are commonly used to represent sequences of encoded characters @@ -42,7 +119,7 @@ convert back and forth between Buffers and ordinary JavaScript string objects by using an explicit encoding method. ```js -const buf = new Buffer('hello world', 'ascii'); +const buf = Buffer.from('hello world', 'ascii'); console.log(buf.toString('hex')); // prints: 68656c6c6f20776f726c64 console.log(buf.toString('base64')); @@ -83,24 +160,24 @@ existing Buffer without copying, making `Buffer#slice()` far more efficient. It is also possible to create new TypedArray instances from a `Buffer` with the following caveats: -1. The Buffer instances's memory is copied to the TypedArray, not shared. +1. The `Buffer` object's memory is copied to the TypedArray, not shared. -2. The Buffer's memory is interpreted as an array of distinct elements, and not -as a byte array of the target type. That is, -`new Uint32Array(new Buffer([1,2,3,4]))` creates a 4-element `Uint32Array` +2. The `Buffer` object's memory is interpreted as an array of distinct + elements, and not as a byte array of the target type. That is, + `new Uint32Array(Buffer.from([1,2,3,4]))` creates a 4-element `Uint32Array` with elements `[1,2,3,4]`, not a `Uint32Array` with a single element `[0x1020304]` or `[0x4030201]`. -It is possible to create a new Buffer that shares the same allocated memory as -a TypedArray instance by using the TypeArray objects `.buffer` property: +It is possible to create a new `Buffer` that shares the same allocated memory as +a TypedArray instance by using the TypeArray object's `.buffer` property: ```js const arr = new Uint16Array(2); arr[0] = 5000; arr[1] = 4000; -const buf1 = new Buffer(arr); // copies the buffer -const buf2 = new Buffer(arr.buffer); // shares the memory with arr; +const buf1 = Buffer.from(arr); // copies the buffer +const buf2 = Buffer.from(arr.buffer); // shares the memory with arr; console.log(buf1); // Prints: , copied buffer has only two elements @@ -114,24 +191,38 @@ console.log(buf2); // Prints: ``` -Note that when creating a Buffer using the TypeArray's `.buffer`, it is not -currently possible to use only a portion of the underlying `ArrayBuffer`. To -create a Buffer that uses only a part of the `ArrayBuffer`, use the -[`buf.slice()`][] function after the Buffer is created: +Note that when creating a `Buffer` using the TypedArray's `.buffer`, it is +possible to use only a portion of the underlying `ArrayBuffer` by passing in +`byteOffset` and `length` parameters: ```js const arr = new Uint16Array(20); -const buf = new Buffer(arr.buffer).slice(0, 16); +const buf = Buffer.from(arr.buffer, 0, 16); console.log(buf.length); // Prints: 16 ``` +The `Buffer.from()` and [`TypedArray.from()`][] (e.g.`Uint8Array.from()`) have +different signatures and implementations. Specifically, the TypedArray variants +accept a second argument that is a mapping function that is invoked on every +element of the typed array: + +* `TypedArray.from(source[, mapFn[, thisArg]])` + +The `Buffer.from()` method, however, does not support the use of a mapping +function: + +* [`Buffer.from(array)`][buffer_from_array] +* [`Buffer.from(buffer)`][buffer_from_buffer] +* [`Buffer.from(arrayBuffer[, byteOffset [, length]])`][buffer_from_arraybuf] +* [`Buffer.from(str[, encoding])`][buffer_from_string] + ## Buffers and ES6 iteration Buffers can be iterated over using the ECMAScript 2015 (ES6) `for..of` syntax: ```js -const buf = new Buffer([1, 2, 3]); +const buf = Buffer(.from[1, 2, 3]); for (var b of buf) console.log(b) @@ -179,15 +270,20 @@ console.log(buf2.toString()); // 'buffer' (copy is not changed) ``` -### new Buffer(arrayBuffer) +### new Buffer(arrayBuffer[, byteOffset[, length]]) * `arrayBuffer` - The `.buffer` property of a `TypedArray` or a `new ArrayBuffer()` +* `byteOffset` {Number} Default: `0` +* `length` {Number} Default: `arrayBuffer.length - byteOffset` When passed a reference to the `.buffer` property of a `TypedArray` instance, the newly created Buffer will share the same allocated memory as the TypedArray. +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + ```js const arr = new Uint16Array(2); arr[0] = 5000; @@ -209,16 +305,16 @@ console.log(buf); * `size` {Number} -Allocates a new Buffer of `size` bytes. The `size` must be less than +Allocates a new `Buffer` of `size` bytes. The `size` must be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. If a `size` less than 0 is specified, a zero-length Buffer will be created. -Unlike `ArrayBuffers`, the underlying memory for Buffer instances created in -this way is not initialized. The contents of a newly created `Buffer` are -unknown and could contain sensitive data. Use [`buf.fill(0)`][] to initialize a -Buffer to zeroes. +Unlike `ArrayBuffers`, the underlying memory for `Buffer` instances created in +this way is *not initialized*. The contents of a newly created `Buffer` are +unknown and *could contain sensitive data*. Use [`buf.fill(0)`][] to initialize +a Buffer to zeroes. ```js const buf = new Buffer(5); @@ -250,6 +346,92 @@ console.log(buf2.toString()); // prints: this is a tést ``` +### Class Method: Buffer.alloc(size[, fill[, encoding]]) + +* `size` {Number} +* `fill` {Value} Default: `undefined` +* `encoding` {String} Default: `utf8` + +Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the +`Buffer` will be *zero-filled*. + +```js +const buf = Buffer.alloc(5); +console.log(buf); + // +``` + +The `size` must be less than or equal to the value of +`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is +`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. If a `size` less than 0 +is specified, a zero-length `Buffer` will be created. + +If `fill` is specified, the allocated `Buffer` will be initialized by calling +`buf.fill(fill)`. See [`buf.fill()`][] for more information. + +```js +const buf = Buffer.alloc(5, 'a'); +console.log(buf); + // +``` + +If both `fill` and `encoding` are specified, the allocated `Buffer` will be +initialized by calling `buf.fill(fill, encoding)`. For example: + +```js +const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); +console.log(buf); + // +``` + +Calling `Buffer.alloc(size)` can be significantly slower than the alternative +`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance +contents will *never contain sensitive data*. + +A `TypeError` will be thrown if `size` is not a number. + +### Class Method: Buffer.allocUnsafe(size) + +* `size` {Number} + +Allocates a new *non-zero-filled* `Buffer` of `size` bytes. The `size` must +be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit +architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is +thrown. If a `size` less than 0 is specified, a zero-length `Buffer` will be +created. + +The underlying memory for `Buffer` instances created in this way is *not +initialized*. The contents of the newly created `Buffer` are unknown and +*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such +`Buffer` instances to zeroes. + +```js +const buf = Buffer.allocUnsafe(5); +console.log(buf); + // + // (octets will be different, every time) +buf.fill(0); +console.log(buf); + // +``` + +A `TypeError` will be thrown if `size` is not a number. + +Note that the `Buffer` module pre-allocates an internal `Buffer` instance of +size `Buffer.poolSize` that is used as a pool for the fast allocation of new +`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the +`new Buffer(size)` constructor) only when `size` is less than or equal to +`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default +value of `Buffer.poolSize` is `8192` but can be modified. + +Use of this pre-allocated internal memory pool is a key difference between +calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. +Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer +pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal +Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The +difference is subtle but can be important when an application requires the +additional performance that `Buffer.allocUnsafe(size)` provides. + ### Class Method: Buffer.byteLength(string[, encoding]) * `string` {String} @@ -281,7 +463,7 @@ Compares `buf1` to `buf2` typically for the purpose of sorting arrays of Buffers. This is equivalent is calling [`buf1.compare(buf2)`][]. ```js -const arr = [Buffer('1234'), Buffer('0123')]; +const arr = [Buffer.from('1234'), Buffer.from('0123')]; arr.sort(Buffer.compare); ``` @@ -304,9 +486,9 @@ to provide the length explicitly. Example: build a single Buffer from a list of three Buffers: ```js -const buf1 = new Buffer(10).fill(0); -const buf2 = new Buffer(14).fill(0); -const buf3 = new Buffer(18).fill(0); +const buf1 = Buffer.alloc(10, 0); +const buf2 = Buffer.alloc(14, 0); +const buf3 = Buffer.alloc(18, 0); const totalLength = buf1.length + buf2.length + buf3.length; console.log(totalLength); @@ -319,6 +501,102 @@ console.log(bufA.length); // 42 ``` +### Class Method: Buffer.from(array) + +* `array` {Array} + +Allocates a new `Buffer` using an `array` of octets. + +```js +const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]); + // creates a new Buffer containing ASCII bytes + // ['b','u','f','f','e','r'] +``` + +A `TypeError` will be thrown if `array` is not an `Array`. + +### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]]) + +* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or + a `new ArrayBuffer()` +* `byteOffset` {Number} Default: `0` +* `length` {Number} Default: `arrayBuffer.length - byteOffset` + +When passed a reference to the `.buffer` property of a `TypedArray` instance, +the newly created `Buffer` will share the same allocated memory as the +TypedArray. + +```js +const arr = new Uint16Array(2); +arr[0] = 5000; +arr[1] = 4000; + +const buf = Buffer.from(arr.buffer); // shares the memory with arr; + +console.log(buf); + // Prints: + +// changing the TypedArray changes the Buffer also +arr[1] = 6000; + +console.log(buf); + // Prints: +``` + +The optional `byteOffset` and `length` arguments specify a memory range within +the `arrayBuffer` that will be shared by the `Buffer`. + +```js +const ab = new ArrayBuffer(10); +const buf = Buffer.from(ab, 0, 2); +console.log(buf.length); + // Prints: 2 +``` + +A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`. + +### Class Method: Buffer.from(buffer) + +* `buffer` {Buffer} + +Copies the passed `buffer` data onto a new `Buffer` instance. + +```js +const buf1 = Buffer.from('buffer'); +const buf2 = Buffer.from(buf1); + +buf1[0] = 0x61; +console.log(buf1.toString()); + // 'auffer' +console.log(buf2.toString()); + // 'buffer' (copy is not changed) +``` + +A `TypeError` will be thrown if `buffer` is not a `Buffer`. + +### Class Method: Buffer.from(str[, encoding]) + +* `str` {String} String to encode. +* `encoding` {String} Encoding to use, Default: `'utf8'` + +Creates a new `Buffer` containing the given JavaScript string `str`. If +provided, the `encoding` parameter identifies the character encoding. +If not provided, `encoding` defaults to `'utf8'`. + +```js +const buf1 = Buffer.from('this is a tést'); +console.log(buf1.toString()); + // prints: this is a tést +console.log(buf1.toString('ascii')); + // prints: this is a tC)st + +const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex'); +console.log(buf2.toString()); + // prints: this is a tést +``` + +A `TypeError` will be thrown if `str` is not a string. + ### Class Method: Buffer.isBuffer(obj) * `obj` {Object} @@ -347,7 +625,7 @@ Example: copy an ASCII string into a Buffer, one byte at a time: ```js const str = "Node.js"; -const buf = new Buffer(str.length); +const buf = Buffer.allocUnsafe(str.length); for (var i = 0; i < str.length ; i++) { buf[i] = str.charCodeAt(i); @@ -371,9 +649,9 @@ Comparison is based on the actual sequence of bytes in each Buffer. * `-1` is returned if `otherBuffer` should come *after* `buf` when sorted. ```js -const buf1 = new Buffer('ABC'); -const buf2 = new Buffer('BCD'); -const buf3 = new Buffer('ABCD'); +const buf1 = Buffer.from('ABC'); +const buf2 = Buffer.from('BCD'); +const buf3 = Buffer.from('ABCD'); console.log(buf1.compare(buf1)); // Prints: 0 @@ -405,8 +683,8 @@ Example: build two Buffers, then copy `buf1` from byte 16 through byte 19 into `buf2`, starting at the 8th byte in `buf2`. ```js -const buf1 = new Buffer(26); -const buf2 = new Buffer(26).fill('!'); +const buf1 = Buffer.allocUnsafe(26); +const buf2 = Buffer.allocUnsafe(26).fill('!'); for (var i = 0 ; i < 26 ; i++) { buf1[i] = i + 97; // 97 is ASCII a @@ -421,7 +699,7 @@ Example: Build a single Buffer, then copy data from one region to an overlapping region in the same Buffer ```js -const buf = new Buffer(26); +const buf = Buffer.allocUnsafe(26); for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97; // 97 is ASCII a @@ -441,7 +719,7 @@ Creates and returns an [iterator][] of `[index, byte]` pairs from the Buffer contents. ```js -const buf = new Buffer('buffer'); +const buf = Buffer.from('buffer'); for (var pair of buf.entries()) { console.log(pair); } @@ -463,9 +741,9 @@ Returns a boolean indicating whether `this` and `otherBuffer` have exactly the same bytes. ```js -const buf1 = new Buffer('ABC'); -const buf2 = new Buffer('414243', 'hex'); -const buf3 = new Buffer('ABCD'); +const buf1 = Buffer.from('ABC'); +const buf2 = Buffer.from('414243', 'hex'); +const buf3 = Buffer.from('ABCD'); console.log(buf1.equals(buf2)); // Prints: true @@ -488,7 +766,7 @@ This is meant as a small simplification to creating a Buffer. Allowing the creation and fill of the Buffer to be done on a single line: ```js -const b = new Buffer(50).fill('h'); +const b = Buffer.alloc(50, 'h'); console.log(b.toString()); // Prints: hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh ``` @@ -501,7 +779,7 @@ falls in between a multi-byte character then whatever bytes fit into the buffer are written. ```js -Buffer(3).fill('\u0222'); +Buffer.alloc(3, '\u0222'); // Prints: ``` @@ -519,22 +797,22 @@ default interpreted as UTF8. Buffers will use the entire Buffer (to compare a partial Buffer use [`buf.slice()`][]). Numbers can range from 0 to 255. ```js -const buf = new Buffer('this is a buffer'); +const buf = Buffer.from('this is a buffer'); buf.indexOf('this'); // returns 0 buf.indexOf('is'); // returns 2 -buf.indexOf(new Buffer('a buffer')); +buf.indexOf(Buffer.from('a buffer')); // returns 8 buf.indexOf(97); // ascii for 'a' // returns 8 -buf.indexOf(new Buffer('a buffer example')); +buf.indexOf(Buffer.from('a buffer example')); // returns -1 -buf.indexOf(new Buffer('a buffer example').slice(0,8)); +buf.indexOf(Buffer.from('a buffer example').slice(0,8)); // returns 8 -const utf16Buffer = new Buffer('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); +const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); utf16Buffer.indexOf('\u03a3', 0, 'ucs2'); // returns 4 @@ -557,19 +835,19 @@ Buffer use [`buf.slice()`][]). Numbers can range from 0 to 255. The `byteOffset` indicates the index in `buf` where searching begins. ```js -const buf = new Buffer('this is a buffer'); +const buf = Buffer.from('this is a buffer'); buf.includes('this'); // returns true buf.includes('is'); // returns true -buf.includes(new Buffer('a buffer')); +buf.includes(Buffer.from('a buffer')); // returns true buf.includes(97); // ascii for 'a' // returns true -buf.includes(new Buffer('a buffer example')); +buf.includes(Buffer.from('a buffer example')); // returns false -buf.includes(new Buffer('a buffer example').slice(0,8)); +buf.includes(Buffer.from('a buffer example').slice(0,8)); // returns true buf.includes('this', 4); // returns false @@ -582,7 +860,7 @@ buf.includes('this', 4); Creates and returns an [iterator][] of Buffer keys (indices). ```js -const buf = new Buffer('buffer'); +const buf = Buffer.from('buffer'); for (var key of buf.keys()) { console.log(key); } @@ -605,7 +883,7 @@ Buffer. For instance, in the example below, a Buffer with 1234 bytes is allocated, but only 11 ASCII bytes are written. ```js -const buf = new Buffer(1234); +const buf = Buffer.allocUnsafe(1234); console.log(buf.length); // Prints: 1234 @@ -621,7 +899,7 @@ modify the length of a Buffer should therefore treat `length` as read-only and use [`buf.slice()`][] to create a new Buffer. ```js -var buf = new Buffer(10); +var buf = Buffer.allocUnsafe(10); buf.write('abcdefghj', 0, 'ascii'); console.log(buf.length); // Prints: 10 @@ -645,7 +923,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the `offset` to be beyond the end of the Buffer. ```js -const buf = new Buffer([1,2,3,4,5,6,7,8]); +const buf = Buffer.from([1,2,3,4,5,6,7,8]); buf.readDoubleBE(); // Returns: 8.20788039913184e-304 @@ -673,7 +951,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the `offset` to be beyond the end of the Buffer. ```js -const buf = new Buffer([1,2,3,4]); +const buf = Buffer.from([1,2,3,4]); buf.readFloatBE(); // Returns: 2.387939260590663e-38 @@ -700,7 +978,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the Integers read from the Buffer are interpreted as two's complement signed values. ```js -const buf = new Buffer([1,-2,3,4]); +const buf = Buffer.from([1,-2,3,4]); buf.readInt8(0); // returns 1 @@ -725,7 +1003,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the Integers read from the Buffer are interpreted as two's complement signed values. ```js -const buf = new Buffer([1,-2,3,4]); +const buf = Buffer.from([1,-2,3,4]); buf.readInt16BE(); // returns 510 @@ -750,7 +1028,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the Integers read from the Buffer are interpreted as two's complement signed values. ```js -const buf = new Buffer([1,-2,3,4]); +const buf = Buffer.from([1,-2,3,4]); buf.readInt32BE(); // returns 33424132 @@ -771,7 +1049,7 @@ and interprets the result as a two's complement signed value. Supports up to 48 bits of accuracy. For example: ```js -const buf = new Buffer(6); +const buf = Buffer.allocUnsafe(6); buf.writeUInt16LE(0x90ab, 0); buf.writeUInt32LE(0x12345678, 2); buf.readIntLE(0, 6).toString(16); // Specify 6 bytes (48 bits) @@ -796,7 +1074,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the `offset` to be beyond the end of the Buffer. ```js -const buf = new Buffer([1,-2,3,4]); +const buf = Buffer.from([1,-2,3,4]); buf.readUInt8(0); // returns 1 @@ -821,7 +1099,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the Example: ```js -const buf = new Buffer([0x3, 0x4, 0x23, 0x42]); +const buf = Buffer.from([0x3, 0x4, 0x23, 0x42]); buf.readUInt16BE(0); // Returns: 0x0304 @@ -854,7 +1132,7 @@ Setting `noAssert` to `true` skips validation of the `offset`. This allows the Example: ```js -const buf = new Buffer([0x3, 0x4, 0x23, 0x42]); +const buf = Buffer.from([0x3, 0x4, 0x23, 0x42]); buf.readUInt32BE(0); // Returns: 0x03042342 @@ -875,7 +1153,7 @@ and interprets the result as an unsigned integer. Supports up to 48 bits of accuracy. For example: ```js -const buf = new Buffer(6); +const buf = Buffer.allocUnsafe(6); buf.writeUInt16LE(0x90ab, 0); buf.writeUInt32LE(0x12345678, 2); buf.readUIntLE(0, 6).toString(16); // Specify 6 bytes (48 bits) @@ -904,7 +1182,7 @@ Example: build a Buffer with the ASCII alphabet, take a slice, then modify one byte from the original Buffer. ```js -const buf1 = new Buffer(26); +const buf1 = Buffer.allocUnsafe(26); for (var i = 0 ; i < 26 ; i++) { buf1[i] = i + 97; // 97 is ASCII a @@ -922,7 +1200,7 @@ Specifying negative indexes causes the slice to be generated relative to the end of the Buffer rather than the beginning. ```js -const buf = new Buffer('buffer'); +const buf = Buffer.from('buffer'); buf.slice(-6, -1).toString(); // Returns 'buffe', equivalent to buf.slice(0, 5) @@ -943,7 +1221,7 @@ Decodes and returns a string from the Buffer data using the specified character set `encoding`. ```js -const buf = new Buffer(26); +const buf = Buffer.allocUnsafe(26); for (var i = 0 ; i < 26 ; i++) { buf[i] = i + 97; // 97 is ASCII a } @@ -967,7 +1245,7 @@ implicitly calls this function when stringifying a Buffer instance. Example: ```js -const buf = new Buffer('test'); +const buf = Buffer.from('test'); const json = JSON.stringify(buf); console.log(json); @@ -975,7 +1253,7 @@ console.log(json); const copy = JSON.parse(json, (key, value) => { return value && value.type === 'Buffer' - ? new Buffer(value.data) + ? Buffer.from(value.data) : value; }); @@ -991,7 +1269,7 @@ Creates and returns an [iterator][] for Buffer values (bytes). This function is called automatically when the Buffer is used in a `for..of` statement. ```js -const buf = new Buffer('buffer'); +const buf = Buffer.from('buffer'); for (var value of buf.values()) { console.log(value); } @@ -1030,7 +1308,7 @@ string will be written however, it will not write only partially encoded characters. ```js -const buf = new Buffer(256); +const buf = Buffer.allocUnsafe(256); const len = buf.write('\u00bd + \u00bc = \u00be', 0); console.log(`${len} bytes: ${buf.toString('utf8', 0, len)}`); // Prints: 12 bytes: ½ + ¼ = ¾ @@ -1056,7 +1334,7 @@ should not be used unless you are certain of correctness. Example: ```js -const buf = new Buffer(8); +const buf = Buffer.allocUnsafe(8); buf.writeDoubleBE(0xdeadbeefcafebabe, 0); console.log(buf); @@ -1089,7 +1367,7 @@ should not be used unless you are certain of correctness. Example: ```js -const buf = new Buffer(4); +const buf = Buffer.allocUnsafe(4); buf.writeFloatBE(0xcafebabe, 0); console.log(buf); @@ -1119,7 +1397,7 @@ should not be used unless you are certain of correctness. The `value` is interpreted and written as a two's complement signed integer. ```js -const buf = new Buffer(2); +const buf = Buffer.allocUnsafe(2); buf.writeInt8(2, 0); buf.writeInt8(-2, 1); console.log(buf); @@ -1146,7 +1424,7 @@ should not be used unless you are certain of correctness. The `value` is interpreted and written as a two's complement signed integer. ```js -const buf = new Buffer(4); +const buf = Buffer.allocUnsafe(4); buf.writeInt16BE(0x0102,0); buf.writeInt16LE(0x0304,2); console.log(buf); @@ -1173,7 +1451,7 @@ should not be used unless you are certain of correctness. The `value` is interpreted and written as a two's complement signed integer. ```js -const buf = new Buffer(8); +const buf = Buffer.allocUnsafe(8); buf.writeInt32BE(0x01020304,0); buf.writeInt32LE(0x05060708,4); console.log(buf); @@ -1193,12 +1471,12 @@ Writes `value` to the Buffer at the specified `offset` and `byteLength`. Supports up to 48 bits of accuracy. For example: ```js -const buf1 = new Buffer(6); +const buf1 = Buffer.allocUnsafe(6); buf1.writeUIntBE(0x1234567890ab, 0, 6); console.log(buf1); // Prints: -const buf2 = new Buffer(6); +const buf2 = Buffer.allocUnsafe(6); buf2.writeUIntLE(0x1234567890ab, 0, 6); console.log(buf2); // Prints: @@ -1227,7 +1505,7 @@ should not be used unless you are certain of correctness. Example: ```js -const buf = new Buffer(4); +const buf = Buffer.allocUnsafe(4); buf.writeUInt8(0x3, 0); buf.writeUInt8(0x4, 1); buf.writeUInt8(0x23, 2); @@ -1257,7 +1535,7 @@ should not be used unless you are certain of correctness. Example: ```js -const buf = new Buffer(4); +const buf = Buffer.allocUnsafe(4); buf.writeUInt16BE(0xdead, 0); buf.writeUInt16BE(0xbeef, 2); @@ -1291,7 +1569,7 @@ should not be used unless you are certain of correctness. Example: ```js -const buf = new Buffer(4); +const buf = Buffer.allocUnsafe(4); buf.writeUInt32BE(0xfeedface, 0); console.log(buf); @@ -1316,7 +1594,7 @@ Writes `value` to the Buffer at the specified `offset` and `byteLength`. Supports up to 48 bits of accuracy. For example: ```js -const buf = new Buffer(6); +const buf = Buffer.allocUnsafe(6); buf.writeUIntBE(0x1234567890ab, 0, 6); console.log(buf); // Prints: @@ -1358,7 +1636,7 @@ const store = []; socket.on('readable', () => { var data = socket.read(); // allocate for retained data - var sb = new SlowBuffer(10); + var sb = SlowBuffer(10); // copy the data into the new allocation data.copy(sb, 0, 0, 10); store.push(sb); @@ -1368,6 +1646,31 @@ socket.on('readable', () => { Use of `SlowBuffer` should be used only as a last resort *after* a developer has observed undue memory retention in their applications. +### new SlowBuffer(size) + +* `size` Number + +Allocates a new `SlowBuffer` of `size` bytes. The `size` must be less than +or equal to the value of `require('buffer').kMaxLength` (on 64-bit +architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is +thrown. If a `size` less than 0 is specified, a zero-length `SlowBuffer` will be +created. + +The underlying memory for `SlowBuffer` instances is *not initialized*. The +contents of a newly created `SlowBuffer` are unknown and could contain +sensitive data. Use [`buf.fill(0)`][] to initialize a `SlowBuffer` to zeroes. + +```js +const SlowBuffer = require('buffer').SlowBuffer; +const buf = new SlowBuffer(5); +console.log(buf); + // + // (octets will be different, every time) +buf.fill(0); +console.log(buf); + // +``` + [`Array#includes()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes [`Array#indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf [`buf.entries()`]: #buffer_buf_entries @@ -1382,3 +1685,10 @@ has observed undue memory retention in their applications. [`util.inspect()`]: util.html#util_util_inspect_object_options [iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols [RFC 4648, Section 5]: https://tools.ietf.org/html/rfc4648#section-5 +[buffer_from_array]: #buffer_class_method_buffer_from_array +[buffer_from_buffer]: #buffer_class_method_buffer_from_buffer +[buffer_from_arraybuf]: #buffer_class_method_buffer_from_arraybuffer +[buffer_from_string]: #buffer_class_method_buffer_from_str_encoding +[buffer_allocunsafe]: #buffer_class_method_buffer_allocraw_size +[buffer_alloc]: #buffer_class_method_buffer_alloc_size_fill_encoding +[`TypedArray.from()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from \ No newline at end of file diff --git a/lib/buffer.js b/lib/buffer.js index f126aee5a3e8cf..58e5e47d9d0c18 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -10,6 +10,7 @@ exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; exports.kMaxLength = binding.kMaxLength; +const kFromErrorMsg = 'must start with number, buffer, array or string'; Buffer.poolSize = 8 * 1024; var poolSize, poolOffset, allocPool; @@ -44,27 +45,77 @@ function alignPool() { } -function Buffer(arg, encoding) { +function Buffer(arg, encodingOrOffset, length) { // Common case. if (typeof arg === 'number') { - // If less than zero, or NaN. - if (arg < 0 || arg !== arg) - arg = 0; - return allocate(arg); - } - - // Slightly less common case. - if (typeof arg === 'string') { - return fromString(arg, encoding); + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ); + } + return Buffer.allocUnsafe(arg); } - - // Unusual. - return fromObject(arg); + return Buffer.from(arg, encodingOrOffset, length); } +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function(value, encodingOrOffset, length) { + if (typeof value === 'number') + throw new TypeError('"value" argument must not be a number'); + + if (value instanceof ArrayBuffer) + return fromArrayBuffer(value, encodingOrOffset, length); + + if (typeof value === 'string') + return fromString(value, encodingOrOffset); + + return fromObject(value); +}; + Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); Object.setPrototypeOf(Buffer, Uint8Array); +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function(size, fill, encoding) { + if (typeof size !== 'number') + throw new TypeError('"size" argument must be a number'); + if (size <= 0) + return createBuffer(size); + if (fill !== undefined) { + // Since we are filling anyway, don't zero fill initially. + flags[kNoZeroFill] = 1; + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' ? + createBuffer(size).fill(fill, encoding) : + createBuffer(size).fill(fill); + } + flags[kNoZeroFill] = 0; + return createBuffer(size); +}; + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer + * instance. If `--zero-fill-buffers` is set, will zero-fill the buffer. + **/ +Buffer.allocUnsafe = function(size) { + if (typeof size !== 'number') + throw new TypeError('"size" argument must be a number'); + if (size > 0) + flags[kNoZeroFill] = 1; + return allocate(size); +}; function SlowBuffer(length) { if (+length != length) @@ -104,6 +155,9 @@ function fromString(string, encoding) { if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'; + if (!Buffer.isEncoding(encoding)) + throw new TypeError('"encoding" must be a valid string encoding'); + var length = byteLength(string, encoding); if (length >= (Buffer.poolSize >>> 1)) return binding.createFromString(string, encoding); @@ -125,6 +179,16 @@ function fromArrayLike(obj) { return b; } +function fromArrayBuffer(obj, byteOffset, length) { + byteOffset >>>= 0; + + if (typeof length === 'undefined') + return binding.createFromArrayBuffer(obj, byteOffset); + + length >>>= 0; + return binding.createFromArrayBuffer(obj, byteOffset, length); +} + function fromObject(obj) { if (obj instanceof Buffer) { const b = allocate(obj.length); @@ -136,26 +200,20 @@ function fromObject(obj) { return b; } - if (obj == null) { - throw new TypeError('must start with number, buffer, array or string'); - } - - if (obj instanceof ArrayBuffer) { - return binding.createFromArrayBuffer(obj); - } - - if (obj.buffer instanceof ArrayBuffer || 'length' in obj) { - if (typeof obj.length !== 'number' || obj.length !== obj.length) { - return allocate(0); + if (obj) { + if (obj.buffer instanceof ArrayBuffer || 'length' in obj) { + if (typeof obj.length !== 'number' || obj.length !== obj.length) { + return allocate(0); + } + return fromArrayLike(obj); } - return fromArrayLike(obj); - } - if (obj.type === 'Buffer' && Array.isArray(obj.data)) { - return fromArrayLike(obj.data); + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data); + } } - throw new TypeError('must start with number, buffer, array or string'); + throw new TypeError(kFromErrorMsg); } @@ -211,7 +269,7 @@ Buffer.concat = function(list, length) { throw new TypeError('list argument must be an Array of Buffers'); if (list.length === 0) - return new Buffer(0); + return Buffer.alloc(0); if (length === undefined) { length = 0; @@ -221,7 +279,7 @@ Buffer.concat = function(list, length) { length = length >>> 0; } - var buffer = new Buffer(length); + var buffer = Buffer.allocUnsafe(length); var pos = 0; for (let i = 0; i < list.length; i++) { var buf = list[i]; @@ -450,7 +508,7 @@ function slowIndexOf(buffer, val, byteOffset, encoding) { case 'ascii': case 'hex': return binding.indexOfBuffer( - buffer, Buffer(val, encoding), byteOffset, encoding); + buffer, Buffer.from(val, encoding), byteOffset, encoding); default: if (loweredCase) { @@ -514,6 +572,11 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { if (code < 256) val = code; } + if (val.length === 0) { + // Previously, if val === '', the Buffer would not fill, + // which is rather surprising. + val = 0; + } if (encoding !== undefined && typeof encoding !== 'string') { throw new TypeError('encoding must be a string'); } diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 1c60a311a531e8..99ae5abee216d8 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -429,7 +429,20 @@ void CreateFromArrayBuffer(const FunctionCallbackInfo& args) { if (!args[0]->IsArrayBuffer()) return env->ThrowTypeError("argument is not an ArrayBuffer"); Local ab = args[0].As(); - Local ui = Uint8Array::New(ab, 0, ab->ByteLength()); + + size_t ab_length = ab->ByteLength(); + size_t offset; + size_t max_length; + + CHECK_NOT_OOB(ParseArrayIndex(args[1], 0, &offset)); + CHECK_NOT_OOB(ParseArrayIndex(args[2], ab_length - offset, &max_length)); + + if (offset >= ab_length) + return env->ThrowRangeError("'offset' is out of bounds"); + if (max_length > ab_length - offset) + return env->ThrowRangeError("'length' is out of bounds"); + + Local ui = Uint8Array::New(ab, offset, max_length); Maybe mb = ui->SetPrototype(env->context(), env->buffer_prototype_object()); if (!mb.FromMaybe(false)) diff --git a/test/parallel/test-buffer-alloc.js b/test/parallel/test-buffer-alloc.js new file mode 100644 index 00000000000000..8b9dae91647231 --- /dev/null +++ b/test/parallel/test-buffer-alloc.js @@ -0,0 +1,1431 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); + +var Buffer = require('buffer').Buffer; +var SlowBuffer = require('buffer').SlowBuffer; + +// counter to ensure unique value is always copied +var cntr = 0; + +var b = Buffer.alloc(1024); + +console.log('b.length == %d', b.length); +assert.strictEqual(1024, b.length); + +b[0] = -1; +assert.strictEqual(b[0], 255); + +for (let i = 0; i < 1024; i++) { + b[i] = i % 256; +} + +for (let i = 0; i < 1024; i++) { + assert.strictEqual(i % 256, b[i]); +} + +var c = Buffer.alloc(512); +console.log('c.length == %d', c.length); +assert.strictEqual(512, c.length); + +var d = Buffer.from([]); +assert.strictEqual(0, d.length); + +var ui32 = new Uint32Array(4).fill(42); +var e = Buffer.from(ui32); +assert.deepEqual(ui32, e); + +// First check Buffer#fill() works as expected. + +assert.throws(function() { + Buffer.allocUnsafe(8).fill('a', -1); +}); + +assert.throws(function() { + Buffer.allocUnsafe(8).fill('a', 0, 9); +}); + +// Make sure this doesn't hang indefinitely. +Buffer.allocUnsafe(8).fill(''); + +{ + const buf = Buffer.allocUnsafe(64); + buf.fill(10); + for (let i = 0; i < buf.length; i++) + assert.equal(buf[i], 10); + + buf.fill(11, 0, buf.length >> 1); + for (let i = 0; i < buf.length >> 1; i++) + assert.equal(buf[i], 11); + for (let i = (buf.length >> 1) + 1; i < buf.length; i++) + assert.equal(buf[i], 10); + + buf.fill('h'); + for (let i = 0; i < buf.length; i++) + assert.equal('h'.charCodeAt(0), buf[i]); + + buf.fill(0); + for (let i = 0; i < buf.length; i++) + assert.equal(0, buf[i]); + + buf.fill(null); + for (let i = 0; i < buf.length; i++) + assert.equal(0, buf[i]); + + buf.fill(1, 16, 32); + for (let i = 0; i < 16; i++) + assert.equal(0, buf[i]); + for (let i = 16; i < 32; i++) + assert.equal(1, buf[i]); + for (let i = 32; i < buf.length; i++) + assert.equal(0, buf[i]); +} + +{ + const buf = Buffer.allocUnsafe(10); + buf.fill('abc'); + assert.equal(buf.toString(), 'abcabcabca'); + buf.fill('է'); + assert.equal(buf.toString(), 'էէէէէ'); +} + +{ + // copy 512 bytes, from 0 to 512. + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 512); + console.log('copied %d bytes from b into c', copied); + assert.strictEqual(512, copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // copy c into b, without specifying sourceEnd + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0, 0); + console.log('copied %d bytes from c into b w/o sourceEnd', copied); + assert.strictEqual(c.length, copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // copy c into b, without specifying sourceStart + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0); + console.log('copied %d bytes from c into b w/o sourceStart', copied); + assert.strictEqual(c.length, copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // copy longer buffer b to shorter c without targetStart + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c); + console.log('copied %d bytes from b into c w/o targetStart', copied); + assert.strictEqual(c.length, copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // copy starting near end of b to c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, b.length - Math.floor(c.length / 2)); + console.log('copied %d bytes from end of b into beginning of c', copied); + assert.strictEqual(Math.floor(c.length / 2), copied); + for (let i = 0; i < Math.floor(c.length / 2); i++) { + assert.strictEqual(b[b.length - Math.floor(c.length / 2) + i], c[i]); + } + for (let i = Math.floor(c.length / 2) + 1; i < c.length; i++) { + assert.strictEqual(c[c.length - 1], c[i]); + } +} + +{ + // try to copy 513 bytes, and check we don't overrun c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 513); + console.log('copied %d bytes from b trying to overrun c', copied); + assert.strictEqual(c.length, copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // copy 768 bytes from b into b + b.fill(++cntr); + b.fill(++cntr, 256); + const copied = b.copy(b, 0, 256, 1024); + console.log('copied %d bytes from b into b', copied); + assert.strictEqual(768, copied); + for (let i = 0; i < b.length; i++) { + assert.strictEqual(cntr, b[i]); + } +} + +// copy string longer than buffer length (failure will segfault) +var bb = Buffer.allocUnsafe(10); +bb.fill('hello crazy world'); + + +// try to copy from before the beginning of b +assert.doesNotThrow(() => { b.copy(c, 0, 100, 10); }); + +// copy throws at negative sourceStart +assert.throws(function() { + Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), 0, -1); +}, RangeError); + +{ + // check sourceEnd resets to targetEnd if former is greater than the latter + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 1025); + console.log('copied %d bytes from b into c', copied); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +// throw with negative sourceEnd +console.log('test copy at negative sourceEnd'); +assert.throws(function() { + b.copy(c, 0, 0, -1); +}, RangeError); + +// when sourceStart is greater than sourceEnd, zero copied +assert.equal(b.copy(c, 0, 100, 10), 0); + +// when targetStart > targetLength, zero copied +assert.equal(b.copy(c, 512, 0, 10), 0); + +var caught_error; + +// invalid encoding for Buffer.toString +caught_error = null; +try { + b.toString('invalid'); +} catch (err) { + caught_error = err; +} +assert.strictEqual('Unknown encoding: invalid', caught_error.message); + +// invalid encoding for Buffer.write +caught_error = null; +try { + b.write('test string', 0, 5, 'invalid'); +} catch (err) { + caught_error = err; +} +assert.strictEqual('Unknown encoding: invalid', caught_error.message); + +// try to create 0-length buffers +Buffer.from(''); +Buffer.from('', 'ascii'); +Buffer.from('', 'binary'); +Buffer.alloc(0); + +// try to write a 0-length string beyond the end of b +assert.throws(function() { + b.write('', 2048); +}, RangeError); + +// throw when writing to negative offset +assert.throws(function() { + b.write('a', -1); +}, RangeError); + +// throw when writing past bounds from the pool +assert.throws(function() { + b.write('a', 2048); +}, RangeError); + +// throw when writing to negative offset +assert.throws(function() { + b.write('a', -1); +}, RangeError); + +// try to copy 0 bytes worth of data into an empty buffer +b.copy(Buffer.alloc(0), 0, 0, 0); + +// try to copy 0 bytes past the end of the target buffer +b.copy(Buffer.alloc(0), 1, 1, 1); +b.copy(Buffer.allocUnsafe(1), 1, 1, 1); + +// try to copy 0 bytes from past the end of the source buffer +b.copy(Buffer.allocUnsafe(1), 0, 2048, 2048); + +const rangeBuffer = Buffer.from('abc'); + +// if start >= buffer's length, empty string will be returned +assert.equal(rangeBuffer.toString('ascii', 3), ''); +assert.equal(rangeBuffer.toString('ascii', +Infinity), ''); +assert.equal(rangeBuffer.toString('ascii', 3.14, 3), ''); +assert.equal(rangeBuffer.toString('ascii', 'Infinity', 3), ''); + +// if end <= 0, empty string will be returned +assert.equal(rangeBuffer.toString('ascii', 1, 0), ''); +assert.equal(rangeBuffer.toString('ascii', 1, -1.2), ''); +assert.equal(rangeBuffer.toString('ascii', 1, -100), ''); +assert.equal(rangeBuffer.toString('ascii', 1, -Infinity), ''); + +// if start < 0, start will be taken as zero +assert.equal(rangeBuffer.toString('ascii', -1, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', -1.99, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', -Infinity, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '-1', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '-1.99', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc'); + +// if start is an invalid integer, start will be taken as zero +assert.equal(rangeBuffer.toString('ascii', 'node.js', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', {}, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', [], 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', NaN, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', null, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', undefined, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', false, 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '', 3), 'abc'); + +// but, if start is an integer when coerced, then it will be coerced and used. +assert.equal(rangeBuffer.toString('ascii', '-1', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '1', 3), 'bc'); +assert.equal(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', '3', 3), ''); +assert.equal(rangeBuffer.toString('ascii', Number(3), 3), ''); +assert.equal(rangeBuffer.toString('ascii', '3.14', 3), ''); +assert.equal(rangeBuffer.toString('ascii', '1.99', 3), 'bc'); +assert.equal(rangeBuffer.toString('ascii', '-1.99', 3), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 1.99, 3), 'bc'); +assert.equal(rangeBuffer.toString('ascii', true, 3), 'bc'); + +// if end > buffer's length, end will be taken as buffer's length +assert.equal(rangeBuffer.toString('ascii', 0, 5), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, 6.99), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, Infinity), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, '5'), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, '6.99'), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, 'Infinity'), 'abc'); + +// if end is an invalid integer, end will be taken as buffer's length +assert.equal(rangeBuffer.toString('ascii', 0, 'node.js'), ''); +assert.equal(rangeBuffer.toString('ascii', 0, {}), ''); +assert.equal(rangeBuffer.toString('ascii', 0, NaN), ''); +assert.equal(rangeBuffer.toString('ascii', 0, undefined), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, null), ''); +assert.equal(rangeBuffer.toString('ascii', 0, []), ''); +assert.equal(rangeBuffer.toString('ascii', 0, false), ''); +assert.equal(rangeBuffer.toString('ascii', 0, ''), ''); + +// but, if end is an integer when coerced, then it will be coerced and used. +assert.equal(rangeBuffer.toString('ascii', 0, '-1'), ''); +assert.equal(rangeBuffer.toString('ascii', 0, '1'), 'a'); +assert.equal(rangeBuffer.toString('ascii', 0, '-Infinity'), ''); +assert.equal(rangeBuffer.toString('ascii', 0, '3'), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, Number(3)), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, '3.14'), 'abc'); +assert.equal(rangeBuffer.toString('ascii', 0, '1.99'), 'a'); +assert.equal(rangeBuffer.toString('ascii', 0, '-1.99'), ''); +assert.equal(rangeBuffer.toString('ascii', 0, 1.99), 'a'); +assert.equal(rangeBuffer.toString('ascii', 0, true), 'a'); + +// try toString() with a object as a encoding +assert.equal(rangeBuffer.toString({toString: function() { + return 'ascii'; +}}), 'abc'); + +// testing for smart defaults and ability to pass string values as offset +var writeTest = Buffer.from('abcdes'); +writeTest.write('n', 'ascii'); +writeTest.write('o', 'ascii', '1'); +writeTest.write('d', '2', 'ascii'); +writeTest.write('e', 3, 'ascii'); +writeTest.write('j', 'ascii', 4); +assert.equal(writeTest.toString(), 'nodejs'); + +// ASCII slice test +{ + const asciiString = 'hello world'; + + for (let i = 0; i < asciiString.length; i++) { + b[i] = asciiString.charCodeAt(i); + } + const asciiSlice = b.toString('ascii', 0, asciiString.length); + assert.equal(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + const written = b.write(asciiString, offset, 'ascii'); + assert.equal(asciiString.length, written); + const asciiSlice = b.toString('ascii', offset, offset + asciiString.length); + assert.equal(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + const sliceA = b.slice(offset, offset + asciiString.length); + const sliceB = b.slice(offset, offset + asciiString.length); + for (let i = 0; i < asciiString.length; i++) { + assert.equal(sliceA[i], sliceB[i]); + } +} + +// UTF-8 slice test + +var utf8String = '¡hέlló wôrld!'; +var offset = 100; + +b.write(utf8String, 0, Buffer.byteLength(utf8String), 'utf8'); +var utf8Slice = b.toString('utf8', 0, Buffer.byteLength(utf8String)); +assert.equal(utf8String, utf8Slice); + +var written = b.write(utf8String, offset, 'utf8'); +assert.equal(Buffer.byteLength(utf8String), written); +utf8Slice = b.toString('utf8', offset, offset + Buffer.byteLength(utf8String)); +assert.equal(utf8String, utf8Slice); + +var sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); +var sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); +for (let i = 0; i < Buffer.byteLength(utf8String); i++) { + assert.equal(sliceA[i], sliceB[i]); +} + +{ + const slice = b.slice(100, 150); + assert.equal(50, slice.length); + for (let i = 0; i < 50; i++) { + assert.equal(b[100 + i], slice[i]); + } +} + +{ + // make sure only top level parent propagates from allocPool + const b = Buffer.allocUnsafe(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.equal(b.parent, c.parent); + assert.equal(b.parent, d.parent); +} + +{ + // also from a non-pooled instance + const b = new SlowBuffer(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.equal(c.parent, d.parent); +} + +{ + // Bug regression test + const testValue = '\u00F6\u65E5\u672C\u8A9E'; // ö日本語 + const buffer = Buffer.allocUnsafe(32); + const size = buffer.write(testValue, 0, 'utf8'); + console.log('bytes written to buffer: ' + size); + const slice = buffer.toString('utf8', 0, size); + assert.equal(slice, testValue); +} + +{ + // Test triple slice + const a = Buffer.allocUnsafe(8); + for (let i = 0; i < 8; i++) a[i] = i; + const b = a.slice(4, 8); + assert.equal(4, b[0]); + assert.equal(5, b[1]); + assert.equal(6, b[2]); + assert.equal(7, b[3]); + const c = b.slice(2, 4); + assert.equal(6, c[0]); + assert.equal(7, c[1]); +} + +{ + const d = Buffer.from([23, 42, 255]); + assert.equal(d.length, 3); + assert.equal(d[0], 23); + assert.equal(d[1], 42); + assert.equal(d[2], 255); + assert.deepEqual(d, Buffer.from(d)); +} + +{ + const e = Buffer.from('über'); + console.error('uber: \'%s\'', e.toString()); + assert.deepEqual(e, Buffer.from([195, 188, 98, 101, 114])); +} + +{ + const f = Buffer.from('über', 'ascii'); + console.error('f.length: %d (should be 4)', f.length); + assert.deepEqual(f, Buffer.from([252, 98, 101, 114])); +} + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { + { + const f = Buffer.from('über', encoding); + console.error('f.length: %d (should be 8)', f.length); + assert.deepEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); + } + + { + const f = Buffer.from('привет', encoding); + console.error('f.length: %d (should be 12)', f.length); + assert.deepEqual(f, + Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])); + assert.equal(f.toString(encoding), 'привет'); + } + + { + const f = Buffer.from([0, 0, 0, 0, 0]); + assert.equal(f.length, 5); + const size = f.write('あいうえお', encoding); + console.error('bytes written to buffer: %d (should be 4)', size); + assert.equal(size, 4); + assert.deepEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); + } +}); + +{ + const f = Buffer.from('\uD83D\uDC4D', 'utf-16le'); // THUMBS UP SIGN (U+1F44D) + assert.equal(f.length, 4); + assert.deepEqual(f, Buffer.from('3DD84DDC', 'hex')); +} + + +var arrayIsh = {0: 0, 1: 1, 2: 2, 3: 3, length: 4}; +var g = Buffer.from(arrayIsh); +assert.deepEqual(g, Buffer.from([0, 1, 2, 3])); +var strArrayIsh = {0: '0', 1: '1', 2: '2', 3: '3', length: 4}; +g = Buffer.from(strArrayIsh); +assert.deepEqual(g, Buffer.from([0, 1, 2, 3])); + + +// +// Test toString('base64') +// +assert.equal('TWFu', (Buffer.from('Man')).toString('base64')); + +{ + // test that regular and URL-safe base64 both work + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; + assert.deepEqual(Buffer.from('//++/++/++//', 'base64'), + Buffer.from(expected)); + assert.deepEqual(Buffer.from('__--_--_--__', 'base64'), + Buffer.from(expected)); +} + +{ + // big example + const quote = 'Man is distinguished, not only by his reason, but by this ' + + 'singular passion from other animals, which is a lust ' + + 'of the mind, that by a perseverance of delight in the ' + + 'continued and indefatigable generation of knowledge, exceeds ' + + 'the short vehemence of any carnal pleasure.'; + const expected = 'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb' + + '24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBh' + + 'bmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk' + + 'gYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIG' + + 'FuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBle' + + 'GNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVh' + + 'c3VyZS4='; + assert.equal(expected, (Buffer.from(quote)).toString('base64')); + + let b = Buffer.allocUnsafe(1024); + let bytesWritten = b.write(expected, 0, 'base64'); + assert.equal(quote.length, bytesWritten); + assert.equal(quote, b.toString('ascii', 0, quote.length)); + + // check that the base64 decoder ignores whitespace + const expectedWhite = expected.slice(0, 60) + ' \n' + + expected.slice(60, 120) + ' \n' + + expected.slice(120, 180) + ' \n' + + expected.slice(180, 240) + ' \n' + + expected.slice(240, 300) + '\n' + + expected.slice(300, 360) + '\n'; + b = Buffer.allocUnsafe(1024); + bytesWritten = b.write(expectedWhite, 0, 'base64'); + assert.equal(quote.length, bytesWritten); + assert.equal(quote, b.toString('ascii', 0, quote.length)); + + // check that the base64 decoder on the constructor works + // even in the presence of whitespace. + b = Buffer.from(expectedWhite, 'base64'); + assert.equal(quote.length, b.length); + assert.equal(quote, b.toString('ascii', 0, quote.length)); + + // check that the base64 decoder ignores illegal chars + const expectedIllegal = expected.slice(0, 60) + ' \x80' + + expected.slice(60, 120) + ' \xff' + + expected.slice(120, 180) + ' \x00' + + expected.slice(180, 240) + ' \x98' + + expected.slice(240, 300) + '\x03' + + expected.slice(300, 360); + b = Buffer.from(expectedIllegal, 'base64'); + assert.equal(quote.length, b.length); + assert.equal(quote, b.toString('ascii', 0, quote.length)); +} + +assert.equal(Buffer.from('', 'base64').toString(), ''); +assert.equal(Buffer.from('K', 'base64').toString(), ''); + +// multiple-of-4 with padding +assert.equal(Buffer.from('Kg==', 'base64').toString(), '*'); +assert.equal(Buffer.from('Kio=', 'base64').toString(), '**'); +assert.equal(Buffer.from('Kioq', 'base64').toString(), '***'); +assert.equal(Buffer.from('KioqKg==', 'base64').toString(), '****'); +assert.equal(Buffer.from('KioqKio=', 'base64').toString(), '*****'); +assert.equal(Buffer.from('KioqKioq', 'base64').toString(), '******'); +assert.equal(Buffer.from('KioqKioqKg==', 'base64').toString(), '*******'); +assert.equal(Buffer.from('KioqKioqKio=', 'base64').toString(), '********'); +assert.equal(Buffer.from('KioqKioqKioq', 'base64').toString(), '*********'); +assert.equal(Buffer.from('KioqKioqKioqKg==', 'base64').toString(), + '**********'); +assert.equal(Buffer.from('KioqKioqKioqKio=', 'base64').toString(), + '***********'); +assert.equal(Buffer.from('KioqKioqKioqKioq', 'base64').toString(), + '************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKg==', 'base64').toString(), + '*************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKio=', 'base64').toString(), + '**************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioq', 'base64').toString(), + '***************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKg==', 'base64').toString(), + '****************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKio=', 'base64').toString(), + '*****************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKioq', 'base64').toString(), + '******************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKioqKg==', 'base64').toString(), + '*******************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKioqKio=', 'base64').toString(), + '********************'); + +// no padding, not a multiple of 4 +assert.equal(Buffer.from('Kg', 'base64').toString(), '*'); +assert.equal(Buffer.from('Kio', 'base64').toString(), '**'); +assert.equal(Buffer.from('KioqKg', 'base64').toString(), '****'); +assert.equal(Buffer.from('KioqKio', 'base64').toString(), '*****'); +assert.equal(Buffer.from('KioqKioqKg', 'base64').toString(), '*******'); +assert.equal(Buffer.from('KioqKioqKio', 'base64').toString(), '********'); +assert.equal(Buffer.from('KioqKioqKioqKg', 'base64').toString(), '**********'); +assert.equal(Buffer.from('KioqKioqKioqKio', 'base64').toString(), + '***********'); +assert.equal(Buffer.from('KioqKioqKioqKioqKg', 'base64').toString(), + '*************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKio', 'base64').toString(), + '**************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKg', 'base64').toString(), + '****************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKio', 'base64').toString(), + '*****************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKioqKg', 'base64').toString(), + '*******************'); +assert.equal(Buffer.from('KioqKioqKioqKioqKioqKioqKio', 'base64').toString(), + '********************'); + +// handle padding graciously, multiple-of-4 or not +assert.equal(Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==', + 'base64').length, 32); +assert.equal(Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=', + 'base64').length, 32); +assert.equal(Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw', + 'base64').length, 32); +assert.equal(Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', + 'base64').length, 31); +assert.equal(Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', + 'base64').length, 31); +assert.equal(Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', + 'base64').length, 31); + +// This string encodes single '.' character in UTF-16 +var dot = Buffer.from('//4uAA==', 'base64'); +assert.equal(dot[0], 0xff); +assert.equal(dot[1], 0xfe); +assert.equal(dot[2], 0x2e); +assert.equal(dot[3], 0x00); +assert.equal(dot.toString('base64'), '//4uAA=='); + +{ + // Writing base64 at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ['TWFkbmVzcz8h', 'IFRoaXM=', 'IGlz', 'IG5vZGUuanMh']; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, 'base64'); + } + assert.equal(b.toString('binary', 0, pos), 'Madness?! This is node.js!'); +} + +// Regression test for https://github.com/nodejs/node/issues/3496. +assert.equal(Buffer.from('=bad'.repeat(1e4), 'base64').length, 0); + +{ + // Creating buffers larger than pool size. + const l = Buffer.poolSize + 5; + let s = ''; + for (let i = 0; i < l; i++) { + s += 'h'; + } + + const b = Buffer.from(s); + + for (let i = 0; i < l; i++) { + assert.equal('h'.charCodeAt(0), b[i]); + } + + const sb = b.toString(); + assert.equal(sb.length, s.length); + assert.equal(sb, s); +} + +{ + // Single argument slice + const b = Buffer.from('abcde'); + assert.equal('bcde', b.slice(1).toString()); +} + +// slice(0,0).length === 0 +assert.equal(0, Buffer.from('hello').slice(0, 0).length); + +// test hex toString +console.log('Create hex string from buffer'); +var hexb = Buffer.allocUnsafe(256); +for (let i = 0; i < 256; i++) { + hexb[i] = i; +} +var hexStr = hexb.toString('hex'); +assert.equal(hexStr, + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); + +console.log('Create buffer from hex string'); +var hexb2 = Buffer.from(hexStr, 'hex'); +for (let i = 0; i < 256; i++) { + assert.equal(hexb2[i], hexb[i]); +} + +{ + // test an invalid slice end. + console.log('Try to slice off the end of the buffer'); + const b = Buffer.from([1, 2, 3, 4, 5]); + const b2 = b.toString('hex', 1, 10000); + const b3 = b.toString('hex', 1, 5); + const b4 = b.toString('hex', 1); + assert.equal(b2, b3); + assert.equal(b2, b4); +} + +function buildBuffer(data) { + if (Array.isArray(data)) { + var buffer = Buffer.allocUnsafe(data.length); + data.forEach(function(v, k) { + buffer[k] = v; + }); + return buffer; + } + return null; +} + +var x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); + +console.log(x.inspect()); +assert.equal('', x.inspect()); + +{ + const z = x.slice(4); + console.log(z.inspect()); + console.log(z.length); + assert.equal(5, z.length); + assert.equal(0x6f, z[0]); + assert.equal(0xa3, z[1]); + assert.equal(0x62, z[2]); + assert.equal(0x61, z[3]); + assert.equal(0x72, z[4]); +} + +{ + const z = x.slice(0); + console.log(z.inspect()); + console.log(z.length); + assert.equal(z.length, x.length); +} + +{ + const z = x.slice(0, 4); + console.log(z.inspect()); + console.log(z.length); + assert.equal(4, z.length); + assert.equal(0x81, z[0]); + assert.equal(0xa3, z[1]); +} + +{ + const z = x.slice(0, 9); + console.log(z.inspect()); + console.log(z.length); + assert.equal(9, z.length); +} + +{ + const z = x.slice(1, 4); + console.log(z.inspect()); + console.log(z.length); + assert.equal(3, z.length); + assert.equal(0xa3, z[0]); +} + +{ + const z = x.slice(2, 4); + console.log(z.inspect()); + console.log(z.length); + assert.equal(2, z.length); + assert.equal(0x66, z[0]); + assert.equal(0x6f, z[1]); +} + +assert.equal(0, Buffer.from('hello').slice(0, 0).length); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { + const b = Buffer.allocUnsafe(10); + b.write('あいうえお', encoding); + assert.equal(b.toString(encoding), 'あいうえお'); +}); + +{ + // Binary encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, 'binary'); + assert.equal(0xff, b[0]); + assert.equal(0xad, b[1]); + assert.equal(0xbe, b[2]); + assert.equal(0xef, b[3]); + s = String.fromCharCode(0xaaee); + b.write(s, 0, 'binary'); + assert.equal(0xee, b[0]); + assert.equal(0xad, b[1]); + assert.equal(0xbe, b[2]); + assert.equal(0xef, b[3]); +} + +{ + // #1210 Test UTF-8 string includes null character + let buf = Buffer.from('\0'); + assert.equal(buf.length, 1); + buf = Buffer.from('\0\0'); + assert.equal(buf.length, 2); +} + +{ + const buf = Buffer.allocUnsafe(2); + let written = buf.write(''); // 0byte + assert.equal(written, 0); + written = buf.write('\0'); // 1byte (v8 adds null terminator) + assert.equal(written, 1); + written = buf.write('a\0'); // 1byte * 2 + assert.equal(written, 2); + written = buf.write('あ'); // 3bytes + assert.equal(written, 0); + written = buf.write('\0あ'); // 1byte + 3bytes + assert.equal(written, 1); + written = buf.write('\0\0あ'); // 1byte * 2 + 3bytes + assert.equal(written, 2); +} + +{ + const buf = Buffer.allocUnsafe(10); + written = buf.write('あいう'); // 3bytes * 3 (v8 adds null terminator) + assert.equal(written, 9); + written = buf.write('あいう\0'); // 3bytes * 3 + 1byte + assert.equal(written, 10); +} + +{ + // #243 Test write() with maxLength + const buf = Buffer.allocUnsafe(4); + buf.fill(0xFF); + let written = buf.write('abcd', 1, 2, 'utf8'); + console.log(buf); + assert.equal(written, 2); + assert.equal(buf[0], 0xFF); + assert.equal(buf[1], 0x61); + assert.equal(buf[2], 0x62); + assert.equal(buf[3], 0xFF); + + buf.fill(0xFF); + written = buf.write('abcd', 1, 4); + console.log(buf); + assert.equal(written, 3); + assert.equal(buf[0], 0xFF); + assert.equal(buf[1], 0x61); + assert.equal(buf[2], 0x62); + assert.equal(buf[3], 0x63); + + buf.fill(0xFF); + written = buf.write('abcd', 'utf8', 1, 2); // legacy style + console.log(buf); + assert.equal(written, 2); + assert.equal(buf[0], 0xFF); + assert.equal(buf[1], 0x61); + assert.equal(buf[2], 0x62); + assert.equal(buf[3], 0xFF); + + buf.fill(0xFF); + written = buf.write('abcdef', 1, 2, 'hex'); + console.log(buf); + assert.equal(written, 2); + assert.equal(buf[0], 0xFF); + assert.equal(buf[1], 0xAB); + assert.equal(buf[2], 0xCD); + assert.equal(buf[3], 0xFF); + + ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { + buf.fill(0xFF); + written = buf.write('abcd', 0, 2, encoding); + console.log(buf); + assert.equal(written, 2); + assert.equal(buf[0], 0x61); + assert.equal(buf[1], 0x00); + assert.equal(buf[2], 0xFF); + assert.equal(buf[3], 0xFF); + }); +} + +{ + // test offset returns are correct + const b = Buffer.allocUnsafe(16); + assert.equal(4, b.writeUInt32LE(0, 0)); + assert.equal(6, b.writeUInt16LE(0, 4)); + assert.equal(7, b.writeUInt8(0, 6)); + assert.equal(8, b.writeInt8(0, 7)); + assert.equal(16, b.writeDoubleLE(0, 8)); +} + +{ + // test unmatched surrogates not producing invalid utf8 output + // ef bf bd = utf-8 representation of unicode replacement character + // see https://codereview.chromium.org/121173009/ + const buf = Buffer.from('ab\ud800cd', 'utf8'); + assert.equal(buf[0], 0x61); + assert.equal(buf[1], 0x62); + assert.equal(buf[2], 0xef); + assert.equal(buf[3], 0xbf); + assert.equal(buf[4], 0xbd); + assert.equal(buf[5], 0x63); + assert.equal(buf[6], 0x64); +} + +{ + // test for buffer overrun + const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 + var sub = buf.slice(0, 4); // length: 4 + written = sub.write('12345', 'binary'); + assert.equal(written, 4); + assert.equal(buf[4], 0); +} + +// Check for fractional length args, junk length args, etc. +// https://github.com/joyent/node/issues/1758 + +// Call .fill() first, stops valgrind warning about uninitialized memory reads. +Buffer.allocUnsafe(3.3).fill().toString(); +// throws bad argument error in commit 43cb4ec +assert.equal(Buffer.allocUnsafe(-1).length, 0); +assert.equal(Buffer.allocUnsafe(NaN).length, 0); +assert.equal(Buffer.allocUnsafe(3.3).length, 3); +assert.equal(Buffer.from({length: 3.3}).length, 3); +assert.equal(Buffer.from({length: 'BAM'}).length, 0); + +// Make sure that strings are not coerced to numbers. +assert.equal(Buffer.from('99').length, 2); +assert.equal(Buffer.from('13.37').length, 5); + +// Ensure that the length argument is respected. +'ascii utf8 hex base64 binary'.split(' ').forEach(function(enc) { + assert.equal(Buffer.allocUnsafe(1).write('aaaaaa', 0, 1, enc), 1); +}); + +{ + // Regression test, guard against buffer overrun in the base64 decoder. + const a = Buffer.allocUnsafe(3); + const b = Buffer.from('xxx'); + a.write('aaaaaaaa', 'base64'); + assert.equal(b.toString(), 'xxx'); +} + +// issue GH-3416 +Buffer.from(Buffer.alloc(0), 0, 0); + +[ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'binary', + 'base64', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le' ].forEach(function(enc) { + assert.equal(Buffer.isEncoding(enc), true); + }); + +[ 'utf9', + 'utf-7', + 'Unicode-FTW', + 'new gnu gun' ].forEach(function(enc) { + assert.equal(Buffer.isEncoding(enc), false); + }); + + +// GH-5110 +(function() { + const buffer = Buffer.from('test'); + const string = JSON.stringify(buffer); + + assert.equal(string, '{"type":"Buffer","data":[116,101,115,116]}'); + + assert.deepEqual(buffer, JSON.parse(string, function(key, value) { + return value && value.type === 'Buffer' + ? Buffer.from(value.data) + : value; + })); +})(); + +// issue GH-7849 +(function() { + var buf = Buffer.from('test'); + var json = JSON.stringify(buf); + var obj = JSON.parse(json); + var copy = Buffer.from(obj); + + assert(buf.equals(copy)); +})(); + +// issue GH-4331 +assert.throws(function() { + Buffer.allocUnsafe(0xFFFFFFFF); +}, RangeError); +assert.throws(function() { + Buffer.allocUnsafe(0xFFFFFFFFF); +}, RangeError); + + +// attempt to overflow buffers, similar to previous bug in array buffers +assert.throws(function() { + var buf = Buffer(8); + buf.readFloatLE(0xffffffff); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.writeFloatLE(0.0, 0xffffffff); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.readFloatLE(0xffffffff); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.writeFloatLE(0.0, 0xffffffff); +}, RangeError); + + +// ensure negative values can't get past offset +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.readFloatLE(-1); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.writeFloatLE(0.0, -1); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.readFloatLE(-1); +}, RangeError); + +assert.throws(function() { + var buf = Buffer.allocUnsafe(8); + buf.writeFloatLE(0.0, -1); +}, RangeError); + +// offset checks +{ + const buf = Buffer.alloc(0); + + assert.throws(function() { buf.readUInt8(0); }, RangeError); + assert.throws(function() { buf.readInt8(0); }, RangeError); +} + +{ + const buf = Buffer.from([0xFF]); + + assert.equal(buf.readUInt8(0), 255); + assert.equal(buf.readInt8(0), -1); +} + +[16, 32].forEach(function(bits) { + var buf = Buffer.allocUnsafe(bits / 8 - 1); + + assert.throws(function() { buf['readUInt' + bits + 'BE'](0); }, + RangeError, + 'readUInt' + bits + 'BE'); + + assert.throws(function() { buf['readUInt' + bits + 'LE'](0); }, + RangeError, + 'readUInt' + bits + 'LE'); + + assert.throws(function() { buf['readInt' + bits + 'BE'](0); }, + RangeError, + 'readInt' + bits + 'BE()'); + + assert.throws(function() { buf['readInt' + bits + 'LE'](0); }, + RangeError, + 'readInt' + bits + 'LE()'); +}); + +[16, 32].forEach(function(bits) { + var buf = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + + assert.equal(buf['readUInt' + bits + 'BE'](0), + (0xFFFFFFFF >>> (32 - bits))); + + assert.equal(buf['readUInt' + bits + 'LE'](0), + (0xFFFFFFFF >>> (32 - bits))); + + assert.equal(buf['readInt' + bits + 'BE'](0), + (0xFFFFFFFF >> (32 - bits))); + + assert.equal(buf['readInt' + bits + 'LE'](0), + (0xFFFFFFFF >> (32 - bits))); +}); + +// test for common read(U)IntLE/BE +(function() { + var buf = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); + + assert.equal(buf.readUIntLE(0, 1), 0x01); + assert.equal(buf.readUIntBE(0, 1), 0x01); + assert.equal(buf.readUIntLE(0, 3), 0x030201); + assert.equal(buf.readUIntBE(0, 3), 0x010203); + assert.equal(buf.readUIntLE(0, 5), 0x0504030201); + assert.equal(buf.readUIntBE(0, 5), 0x0102030405); + assert.equal(buf.readUIntLE(0, 6), 0x060504030201); + assert.equal(buf.readUIntBE(0, 6), 0x010203040506); + assert.equal(buf.readIntLE(0, 1), 0x01); + assert.equal(buf.readIntBE(0, 1), 0x01); + assert.equal(buf.readIntLE(0, 3), 0x030201); + assert.equal(buf.readIntBE(0, 3), 0x010203); + assert.equal(buf.readIntLE(0, 5), 0x0504030201); + assert.equal(buf.readIntBE(0, 5), 0x0102030405); + assert.equal(buf.readIntLE(0, 6), 0x060504030201); + assert.equal(buf.readIntBE(0, 6), 0x010203040506); +})(); + +// test for common write(U)IntLE/BE +(function() { + var buf = Buffer.allocUnsafe(3); + buf.writeUIntLE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.equal(buf.readUIntLE(0, 3), 0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeUIntBE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.equal(buf.readUIntBE(0, 3), 0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeIntLE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.equal(buf.readIntLE(0, 3), 0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeIntBE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.equal(buf.readIntBE(0, 3), 0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeIntLE(-0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); + assert.equal(buf.readIntLE(0, 3), -0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeIntBE(-0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); + assert.equal(buf.readIntBE(0, 3), -0x123456); + + buf = Buffer.allocUnsafe(3); + buf.writeIntLE(-0x123400, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); + assert.equal(buf.readIntLE(0, 3), -0x123400); + + buf = Buffer.allocUnsafe(3); + buf.writeIntBE(-0x123400, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); + assert.equal(buf.readIntBE(0, 3), -0x123400); + + buf = Buffer.allocUnsafe(3); + buf.writeIntLE(-0x120000, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); + assert.equal(buf.readIntLE(0, 3), -0x120000); + + buf = Buffer.allocUnsafe(3); + buf.writeIntBE(-0x120000, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); + assert.equal(buf.readIntBE(0, 3), -0x120000); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntLE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.equal(buf.readUIntLE(0, 5), 0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntBE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.equal(buf.readUIntBE(0, 5), 0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeIntLE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.equal(buf.readIntLE(0, 5), 0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeIntBE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.equal(buf.readIntBE(0, 5), 0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeIntLE(-0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); + assert.equal(buf.readIntLE(0, 5), -0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeIntBE(-0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); + assert.equal(buf.readIntBE(0, 5), -0x1234567890); + + buf = Buffer.allocUnsafe(5); + buf.writeIntLE(-0x0012000000, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); + assert.equal(buf.readIntLE(0, 5), -0x0012000000); + + buf = Buffer.allocUnsafe(5); + buf.writeIntBE(-0x0012000000, 0, 5); + assert.deepEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); + assert.equal(buf.readIntBE(0, 5), -0x0012000000); +})(); + +// test Buffer slice +(function() { + var buf = Buffer.from('0123456789'); + assert.equal(buf.slice(-10, 10), '0123456789'); + assert.equal(buf.slice(-20, 10), '0123456789'); + assert.equal(buf.slice(-20, -10), ''); + assert.equal(buf.slice(), '0123456789'); + assert.equal(buf.slice(0), '0123456789'); + assert.equal(buf.slice(0, 0), ''); + assert.equal(buf.slice(undefined), '0123456789'); + assert.equal(buf.slice('foobar'), '0123456789'); + assert.equal(buf.slice(undefined, undefined), '0123456789'); + + assert.equal(buf.slice(2), '23456789'); + assert.equal(buf.slice(5), '56789'); + assert.equal(buf.slice(10), ''); + assert.equal(buf.slice(5, 8), '567'); + assert.equal(buf.slice(8, -1), '8'); + assert.equal(buf.slice(-10), '0123456789'); + assert.equal(buf.slice(0, -9), '0'); + assert.equal(buf.slice(0, -10), ''); + assert.equal(buf.slice(0, -1), '012345678'); + assert.equal(buf.slice(2, -2), '234567'); + assert.equal(buf.slice(0, 65536), '0123456789'); + assert.equal(buf.slice(65536, 0), ''); + assert.equal(buf.slice(-5, -8), ''); + assert.equal(buf.slice(-5, -3), '56'); + assert.equal(buf.slice(-10, 10), '0123456789'); + for (let i = 0, s = buf.toString(); i < buf.length; ++i) { + assert.equal(buf.slice(i), s.slice(i)); + assert.equal(buf.slice(0, i), s.slice(0, i)); + assert.equal(buf.slice(-i), s.slice(-i)); + assert.equal(buf.slice(0, -i), s.slice(0, -i)); + } + + var utf16Buf = Buffer.from('0123456789', 'utf16le'); + assert.deepEqual(utf16Buf.slice(0, 6), Buffer.from('012', 'utf16le')); + + assert.equal(buf.slice('0', '1'), '0'); + assert.equal(buf.slice('-5', '10'), '56789'); + assert.equal(buf.slice('-10', '10'), '0123456789'); + assert.equal(buf.slice('-10', '-5'), '01234'); + assert.equal(buf.slice('-10', '-0'), ''); + assert.equal(buf.slice('111'), ''); + assert.equal(buf.slice('0', '-111'), ''); + + // try to slice a zero length Buffer + // see https://github.com/joyent/node/issues/5881 + SlowBuffer(0).slice(0, 1); +})(); + +// Regression test for #5482: should throw but not assert in C++ land. +assert.throws(function() { + Buffer.from('', 'buffer'); +}, TypeError); + +// Regression test for #6111. Constructing a buffer from another buffer +// should a) work, and b) not corrupt the source buffer. +(function() { + var a = [0]; + for (let i = 0; i < 7; ++i) a = a.concat(a); + a = a.map(function(_, i) { return i; }); + const b = Buffer.from(a); + const c = Buffer.from(b); + assert.equal(b.length, a.length); + assert.equal(c.length, a.length); + for (let i = 0, k = a.length; i < k; ++i) { + assert.equal(a[i], i); + assert.equal(b[i], i); + assert.equal(c[i], i); + } +})(); + + +assert.throws(function() { + Buffer.allocUnsafe((-1 >>> 0) + 1); +}, RangeError); + +assert.throws(function() { + new SlowBuffer((-1 >>> 0) + 1); +}, RangeError); + +if (common.hasCrypto) { + // Test truncation after decode + var crypto = require('crypto'); + + var b1 = Buffer.from('YW55=======', 'base64'); + var b2 = Buffer.from('YW55', 'base64'); + + assert.equal( + crypto.createHash('sha1').update(b1).digest('hex'), + crypto.createHash('sha1').update(b2).digest('hex') + ); +} else { + console.log('1..0 # Skipped: missing crypto'); +} + +// Test Compare +{ + const b = Buffer.alloc(1, 'a'); + const c = Buffer.alloc(1, 'c'); + const d = Buffer.alloc(2, 'aa'); + + assert.equal(b.compare(c), -1); + assert.equal(c.compare(d), 1); + assert.equal(d.compare(b), 1); + assert.equal(b.compare(d), -1); + assert.equal(b.compare(b), 0); + + assert.equal(Buffer.compare(b, c), -1); + assert.equal(Buffer.compare(c, d), 1); + assert.equal(Buffer.compare(d, b), 1); + assert.equal(Buffer.compare(b, d), -1); + assert.equal(Buffer.compare(c, c), 0); + + assert.equal(Buffer.compare(Buffer.alloc(0), Buffer.alloc(0)), 0); + assert.equal(Buffer.compare(Buffer.alloc(0), Buffer.alloc(1)), -1); + assert.equal(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0)), 1); +} + +assert.throws(function() { + var b = Buffer.allocUnsafe(1); + Buffer.compare(b, 'abc'); +}); + +assert.throws(function() { + var b = Buffer.allocUnsafe(1); + Buffer.compare('abc', b); +}); + +assert.throws(function() { + var b = Buffer.allocUnsafe(1); + b.compare('abc'); +}); + +// Test Equals +{ + const b = Buffer.alloc(5, 'abcdf'); + const c = Buffer.alloc(5, 'abcdf'); + const d = Buffer.alloc(5, 'abcde'); + const e = Buffer.alloc(6, 'abcdef'); + + assert.ok(b.equals(c)); + assert.ok(!c.equals(d)); + assert.ok(!d.equals(e)); + assert.ok(d.equals(d)); +} + +assert.throws(function() { + var b = Buffer.allocUnsafe(1); + b.equals('abc'); +}); + +// Regression test for https://github.com/nodejs/node/issues/649. +assert.throws(function() { + Buffer.allocUnsafe(1422561062959).toString('utf8'); +}); + +var ps = Buffer.poolSize; +Buffer.poolSize = 0; +assert.equal(Buffer.allocUnsafe(1).parent, undefined); +Buffer.poolSize = ps; + +// Test Buffer.copy() segfault +assert.throws(function() { + Buffer.allocUnsafe(10).copy(); +}); + +// Test prototype getters don't throw +assert.equal(Buffer.prototype.parent, undefined); +assert.equal(Buffer.prototype.offset, undefined); +assert.equal(SlowBuffer.prototype.parent, undefined); +assert.equal(SlowBuffer.prototype.offset, undefined); diff --git a/test/parallel/test-buffer-safe-unsafe.js b/test/parallel/test-buffer-safe-unsafe.js new file mode 100644 index 00000000000000..6646f760564432 --- /dev/null +++ b/test/parallel/test-buffer-safe-unsafe.js @@ -0,0 +1,14 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const safe = Buffer.alloc(10); + +function isZeroFilled(buf) { + for (let n = 0; n < buf.length; n++) + if (buf[n] > 0) return false; + return true; +} + +assert(isZeroFilled(safe));