From 51c52f90d7b69a913dedc233357a91e54c4ca890 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Mon, 18 Apr 2016 23:50:48 -0700 Subject: [PATCH] Add Buffer.from, alloc, allocUnsafe, allocUnsafeSlow, swap16, swap32 Brings buffer up-to-date with node.js commit 6275249 on Apr 15, 2016. https://github.com/nodejs/node/blob/e38bade82801645d8cc1010e3f4e98260522 44cb/lib/buffer.js --- index.js | 506 ++++++++++++++++++++++++++------------- test/node/test-buffer.js | 22 +- 2 files changed, 336 insertions(+), 192 deletions(-) diff --git a/index.js b/index.js index 5dae0da..cb2b3e0 100644 --- a/index.js +++ b/index.js @@ -65,6 +65,25 @@ function kMaxLength () { : 0x3fffffff } +function createBuffer (that, length) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = new Uint8Array(length) + that.__proto__ = Buffer.prototype + } else { + // Fallback: Return an object instance of the Buffer class + if (that === null) { + that = new Buffer(length) + } + that.length = length + } + + var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 + if (fromPool) that.parent = rootParent + + return that +} + /** * The Buffer constructor returns instances of `Uint8Array` that have their * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of @@ -74,30 +93,28 @@ function kMaxLength () { * * The `Uint8Array` prototype remains unmodified. */ -function Buffer (arg) { - if (!(this instanceof Buffer)) { - // Avoid going through an ArgumentsAdaptorTrampoline in the common case. - if (arguments.length > 1) return new Buffer(arg, arguments[1]) - return new Buffer(arg) - } - if (!Buffer.TYPED_ARRAY_SUPPORT) { - this.length = 0 - this.parent = undefined +function Buffer (arg, encodingOrOffset, length) { + var that + if (Buffer.TYPED_ARRAY_SUPPORT) { + that = null + } else { + if (!(this instanceof Buffer)) { + return new Buffer(arg, encodingOrOffset, length) + } + that = this } // Common case. if (typeof arg === 'number') { - return fromNumber(this, arg) - } - - // Slightly less common case. - if (typeof arg === 'string') { - return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8') + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(that, arg) } - - // Unusual. - return fromObject(this, arg) + return from(that, arg, encodingOrOffset, length) } // TODO: Legacy, not needed anymore. Remove in next major version. @@ -106,73 +123,123 @@ Buffer._augment = function (arr) { return arr } -function fromNumber (that, length) { - that = allocate(that, length < 0 ? 0 : checked(length) | 0) - if (!Buffer.TYPED_ARRAY_SUPPORT) { - for (var i = 0; i < length; i++) { - that[i] = 0 - } +function from (that, value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') } - return that -} -function fromString (that, string, encoding) { - if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8' + if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { + return fromArrayBuffer(that, value, encodingOrOffset, length) + } - // Assumption: byteLength() return value is always < kMaxLength. - var length = byteLength(string, encoding) | 0 - that = allocate(that, length) + if (typeof value === 'string') { + return fromString(that, value, encodingOrOffset) + } - that.write(string, encoding) - return that + return fromObject(that, value) } -function fromObject (that, object) { - if (Buffer.isBuffer(object)) return fromBuffer(that, object) - - if (isArray(object)) return fromArray(that, object) +/** + * 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) { + return from(null, value, encodingOrOffset, length) +} - if (object == null) { - throw new TypeError('must start with number, buffer, array or string') +if (Buffer.TYPED_ARRAY_SUPPORT) { + Buffer.prototype.__proto__ = Uint8Array.prototype + Buffer.__proto__ = Uint8Array + if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true + }) } +} else { + Buffer.prototype.length = undefined + Buffer.prototype.parent = undefined +} - if (typeof ArrayBuffer !== 'undefined') { - if (object.buffer instanceof ArrayBuffer) { - return fromTypedArray(that, object) - } - if (object instanceof ArrayBuffer) { - return fromArrayBuffer(that, object) - } +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be a number') } +} - if (object.length) return fromArrayLike(that, object) +function alloc (that, size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(that, size) + } + if (fill !== undefined) { + // 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(that, size).fill(fill, encoding) + : createBuffer(that, size).fill(fill) + } + return createBuffer(that, size) +} - return fromJsonObject(that, object) +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(null, size, fill, encoding) } -function fromBuffer (that, buffer) { - var length = checked(buffer.length) | 0 - that = allocate(that, length) - buffer.copy(that, 0, 0, length) +function allocUnsafe (that, size) { + assertSize(size) + that = createBuffer(that, size < 0 ? 0 : checked(size) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < size; i++) { + that[i] = 0 + } + } return that } -function fromArray (that, array) { - var length = checked(array.length) | 0 - that = allocate(that, length) - for (var i = 0; i < length; i += 1) { - that[i] = array[i] & 255 +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(null, size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(null, size) +} + +function fromString (that, 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) | 0 + that = createBuffer(that, length) + + that.write(string, encoding) return that } -// Duplicate of fromArray() to keep fromArray() monomorphic. -function fromTypedArray (that, array) { +function fromArrayLike (that, array) { var length = checked(array.length) | 0 - that = allocate(that, length) - // Truncating the elements is probably not what people expect from typed - // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior - // of the old Buffer constructor. + that = createBuffer(that, length) for (var i = 0; i < length; i += 1) { that[i] = array[i] & 255 } @@ -188,69 +255,39 @@ function fromArrayBuffer (that, array) { that.__proto__ = Buffer.prototype } else { // Fallback: Return an object instance of the Buffer class - that = fromTypedArray(that, new Uint8Array(array)) - } - return that -} - -function fromArrayLike (that, array) { - var length = checked(array.length) | 0 - that = allocate(that, length) - for (var i = 0; i < length; i += 1) { - that[i] = array[i] & 255 + that = fromArrayLike(that, new Uint8Array(array)) } return that } -// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. -// Returns a zero-length buffer for inputs that don't conform to the spec. -function fromJsonObject (that, object) { - var array - var length = 0 +function fromObject (that, obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + that = createBuffer(that, len) - if (object.type === 'Buffer' && isArray(object.data)) { - array = object.data - length = checked(array.length) | 0 - } - that = allocate(that, length) + if (that.length === 0) { + return that + } - for (var i = 0; i < length; i += 1) { - that[i] = array[i] & 255 + obj.copy(that, 0, 0, len) + return that } - return that -} -if (Buffer.TYPED_ARRAY_SUPPORT) { - Buffer.prototype.__proto__ = Uint8Array.prototype - Buffer.__proto__ = Uint8Array - if (typeof Symbol !== 'undefined' && Symbol.species && - Buffer[Symbol.species] === Buffer) { - // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 - Object.defineProperty(Buffer, Symbol.species, { - value: null, - configurable: true - }) - } -} else { - // pre-set for values that may exist in the future - Buffer.prototype.length = undefined - Buffer.prototype.parent = undefined -} + if (obj) { + if ((typeof ArrayBuffer !== 'undefined' && + obj.buffer instanceof ArrayBuffer) || 'length' in obj) { + if (typeof obj.length !== 'number' || isnan(obj.length)) { + return createBuffer(that, 0) + } + return fromArrayLike(that, obj) + } -function allocate (that, length) { - if (Buffer.TYPED_ARRAY_SUPPORT) { - // Return an augmented `Uint8Array` instance, for best performance - that = new Uint8Array(length) - that.__proto__ = Buffer.prototype - } else { - // Fallback: Return an object instance of the Buffer class - that.length = length + if (obj.type === 'Buffer' && isArray(obj.data)) { + return fromArrayLike(that, obj.data) + } } - var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 - if (fromPool) that.parent = rootParent - - return that + throw new TypeError('Must start with number, buffer, array or string') } function checked (length) { @@ -318,10 +355,12 @@ Buffer.isEncoding = function isEncoding (encoding) { } Buffer.concat = function concat (list, length) { - if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.') + if (!isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } if (list.length === 0) { - return new Buffer(0) + return Buffer.alloc(0) } var i @@ -332,18 +371,26 @@ Buffer.concat = function concat (list, length) { } } - var buf = new Buffer(length) + var buffer = Buffer.allocUnsafe(length) var pos = 0 for (i = 0; i < list.length; i++) { - var item = list[i] - item.copy(buf, pos) - pos += item.length + var buf = list[i] + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length } - return buf + return buffer } function byteLength (string, encoding) { - if (typeof string !== 'string') string = '' + string + if (Buffer.isBuffer(string)) { + return string.length + } + if (typeof string !== 'string') { + string = '' + string + } var len = string.length if (len === 0) return 0 @@ -360,6 +407,7 @@ function byteLength (string, encoding) { return len case 'utf8': case 'utf-8': + case undefined: return utf8ToBytes(string).length case 'ucs2': case 'ucs-2': @@ -382,13 +430,39 @@ Buffer.byteLength = byteLength function slowToString (encoding, start, end) { var loweredCase = false - start = start | 0 - end = end === undefined || end === Infinity ? this.length : end | 0 + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } if (!encoding) encoding = 'utf8' - if (start < 0) start = 0 - if (end > this.length) end = this.length - if (end <= start) return '' while (true) { switch (encoding) { @@ -426,6 +500,35 @@ function slowToString (encoding, start, end) { // Buffer instances. Buffer.prototype._isBuffer = true +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + Buffer.prototype.toString = function toString () { var length = this.length | 0 if (length === 0) return '' @@ -454,9 +557,28 @@ Buffer.prototype.compare = function compare (b) { return Buffer.compare(this, b) } -Buffer.prototype.indexOf = function indexOf (val, byteOffset) { - if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff - else if (byteOffset < -0x80000000) byteOffset = -0x80000000 +function arrayIndexOf (arr, val, byteOffset) { + var foundIndex = -1 + for (var i = 0; byteOffset + i < arr.length; i++) { + if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex + } else { + foundIndex = -1 + } + } + return -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } byteOffset >>= 0 if (this.length === 0) return -1 @@ -467,6 +589,9 @@ Buffer.prototype.indexOf = function indexOf (val, byteOffset) { if (typeof val === 'string') { if (val.length === 0) return -1 // special case: looking for empty string always fails + if (encoding !== undefined) { + val = new Buffer(val, encoding).toString() + } return String.prototype.indexOf.call(this, val, byteOffset) } if (Buffer.isBuffer(val)) { @@ -479,22 +604,13 @@ Buffer.prototype.indexOf = function indexOf (val, byteOffset) { return arrayIndexOf(this, [ val ], byteOffset) } - function arrayIndexOf (arr, val, byteOffset) { - var foundIndex = -1 - for (var i = 0; byteOffset + i < arr.length; i++) { - if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { - if (foundIndex === -1) foundIndex = i - if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex - } else { - foundIndex = -1 - } - } - return -1 - } - throw new TypeError('val must be string, number or Buffer') } +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + function hexWrite (buf, string, offset, length) { offset = Number(offset) || 0 var remaining = buf.length - offset @@ -565,17 +681,16 @@ Buffer.prototype.write = function write (string, offset, length, encoding) { } // legacy write(string, encoding, offset, length) - remove in v0.13 } else { - var swap = encoding - encoding = offset - offset = length | 0 - length = swap + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) } var remaining = this.length - offset if (length === undefined || length > remaining) length = remaining if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { - throw new RangeError('attempt to write outside buffer bounds') + throw new RangeError('Attempt to write outside buffer bounds') } if (!encoding) encoding = 'utf8' @@ -970,16 +1085,19 @@ Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { } function checkInt (buf, value, offset, ext, max, min) { - if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance') - if (value > max || value < min) throw new RangeError('value is out of bounds') - if (offset + ext > buf.length) throw new RangeError('index out of range') + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') } Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { value = +value offset = offset | 0 byteLength = byteLength | 0 - if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } var mul = 1 var i = 0 @@ -995,7 +1113,10 @@ Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, value = +value offset = offset | 0 byteLength = byteLength | 0 - if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } var i = byteLength - 1 var mul = 1 @@ -1098,9 +1219,12 @@ Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, no var i = 0 var mul = 1 - var sub = value < 0 ? 1 : 0 + var sub = 0 this[offset] = value & 0xFF while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } @@ -1118,9 +1242,12 @@ Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, no var i = byteLength - 1 var mul = 1 - var sub = value < 0 ? 1 : 0 + var sub = 0 this[offset + i] = value & 0xFF while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } @@ -1195,8 +1322,8 @@ Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) } function checkIEEE754 (buf, value, offset, ext, max, min) { - if (offset + ext > buf.length) throw new RangeError('index out of range') - if (offset < 0) throw new RangeError('index out of range') + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') } function writeFloat (buf, value, offset, littleEndian, noAssert) { @@ -1280,28 +1407,59 @@ Buffer.prototype.copy = function copy (target, targetStart, start, end) { return len } -// fill(value, start=0, end=buffer.length) -Buffer.prototype.fill = function fill (value, start, end) { - if (!value) value = 0 - if (!start) start = 0 - if (!end) end = this.length +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if (code < 256) { + val = code + } + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + } else if (typeof val === 'number') { + val = val & 255 + } - if (end < start) throw new RangeError('end < start') + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || start >= this.length || end < 0 || end > this.length) { + throw new RangeError('Out of range index') + } - // Fill 0 bytes; we're done - if (end === start) return - if (this.length === 0) return + if (end <= start) { + return this + } - if (start < 0 || start >= this.length) throw new RangeError('start out of bounds') - if (end < 0 || end > this.length) throw new RangeError('end out of bounds') + if (!val) val = 0 + if (!start) start = 0 + if (!end) end = this.length var i - if (typeof value === 'number') { + if (typeof val === 'number') { for (i = start; i < end; i++) { - this[i] = value + this[i] = val } } else { - var bytes = utf8ToBytes(value.toString()) + var bytes = Buffer.isBuffer(val) + ? val + : utf8ToBytes(new Buffer(val, encoding).toString()) var len = bytes.length for (i = start; i < end; i++) { this[i] = bytes[i % len] @@ -1454,3 +1612,7 @@ function blitBuffer (src, dst, offset, length) { } return i } + +function isnan (val) { + return val !== val // eslint-disable-line no-self-compare +} diff --git a/test/node/test-buffer.js b/test/node/test-buffer.js index d6468f6..06dfe5c 100644 --- a/test/node/test-buffer.js +++ b/test/node/test-buffer.js @@ -261,15 +261,6 @@ assert.equal(new Buffer('abc').toString({toString: function() { return 'ascii'; }}), 'abc'); -// testing for smart defaults and ability to pass string values as offset -var writeTest = new Buffer('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 var asciiString = 'hello world'; @@ -743,15 +734,6 @@ 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); @@ -1186,9 +1168,9 @@ assert.throws(function() { assert.throws(function() { new Buffer(); -}, /must start with number, buffer, array or string/); +}, /Must start with number, buffer, array or string/); assert.throws(function() { new Buffer(null); -}, /must start with number, buffer, array or string/); +}, /Must start with number, buffer, array or string/); -- 2.34.1