From 8dd1a8dd42b0a2654e61b898f6dafaf8702c62d5 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 15 Feb 2018 23:50:27 -0800 Subject: [PATCH] Match node.js Buffer.byteLength() behavior --- index.js | 13 ++- test/node/test-buffer-bytelength.js | 167 +++++++++++++++++----------- 2 files changed, 110 insertions(+), 70 deletions(-) diff --git a/index.js b/index.js index 8e44f6a..8ab3fff 100644 --- a/index.js +++ b/index.js @@ -391,11 +391,15 @@ function byteLength (string, encoding) { return string.byteLength } if (typeof string !== 'string') { - string = '' + string + throw new TypeError( + 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + + 'Received type ' + typeof string + ) } var len = string.length - if (len === 0) return 0 + var mustMatch = (arguments.length > 2 && arguments[2] === true) + if (!mustMatch && len === 0) return 0 // Use a for loop to avoid recursion var loweredCase = false @@ -407,7 +411,6 @@ function byteLength (string, encoding) { return len case 'utf8': case 'utf-8': - case undefined: return utf8ToBytes(string).length case 'ucs2': case 'ucs-2': @@ -419,7 +422,9 @@ function byteLength (string, encoding) { case 'base64': return base64ToBytes(string).length default: - if (loweredCase) return utf8ToBytes(string).length // assume utf8 + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 + } encoding = ('' + encoding).toLowerCase() loweredCase = true } diff --git a/test/node/test-buffer-bytelength.js b/test/node/test-buffer-bytelength.js index 8d7dc35..9028a34 100644 --- a/test/node/test-buffer-bytelength.js +++ b/test/node/test-buffer-bytelength.js @@ -1,90 +1,125 @@ 'use strict'; var Buffer = require('../../').Buffer; +const common = require('./common'); +const assert = require('assert'); +const SlowBuffer = require('../../').SlowBuffer; +const vm = require('vm'); +[ + [32, 'latin1'], + [NaN, 'utf8'], + [{}, 'latin1'], + [] +].forEach((args) => { + common.expectsError( + () => Buffer.byteLength(...args), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "string" argument must be one of type string, ' + + `Buffer, or ArrayBuffer. Received type ${typeof args[0]}` + } + ); +}); -var assert = require('assert'); -var Buffer = require('../../').Buffer; -var SlowBuffer = require('../../').SlowBuffer; - -// coerce values to string -assert.equal(Buffer.byteLength(32, 'latin1'), 2); -assert.equal(Buffer.byteLength(NaN, 'utf8'), 3); -assert.equal(Buffer.byteLength({}, 'latin1'), 15); -assert.equal(Buffer.byteLength(), 9); +assert.strictEqual(Buffer.byteLength('', undefined, true), -1); -var buff = new Buffer(10); -assert(ArrayBuffer.isView(buff)); -var slowbuff = new SlowBuffer(10); -assert(ArrayBuffer.isView(slowbuff)); +assert(ArrayBuffer.isView(new Buffer(10))); +assert(ArrayBuffer.isView(new SlowBuffer(10))); +assert(ArrayBuffer.isView(Buffer.alloc(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); +assert(ArrayBuffer.isView(Buffer.from(''))); // buffer -var incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); -assert.equal(Buffer.byteLength(incomplete), 5); -var ascii = Buffer.from('abc'); -assert.equal(Buffer.byteLength(ascii), 3); +const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); +assert.strictEqual(Buffer.byteLength(incomplete), 5); +const ascii = Buffer.from('abc'); +assert.strictEqual(Buffer.byteLength(ascii), 3); // ArrayBuffer -var buffer = new ArrayBuffer(8); -assert.equal(Buffer.byteLength(buffer), 8); +const buffer = new ArrayBuffer(8); +assert.strictEqual(Buffer.byteLength(buffer), 8); // TypedArray -var int8 = new Int8Array(8); -assert.equal(Buffer.byteLength(int8), 8); -var uint8 = new Uint8Array(8); -assert.equal(Buffer.byteLength(uint8), 8); -var uintc8 = new Uint8ClampedArray(2); -assert.equal(Buffer.byteLength(uintc8), 2); -var int16 = new Int16Array(8); -assert.equal(Buffer.byteLength(int16), 16); -var uint16 = new Uint16Array(8); -assert.equal(Buffer.byteLength(uint16), 16); -var int32 = new Int32Array(8); -assert.equal(Buffer.byteLength(int32), 32); -var uint32 = new Uint32Array(8); -assert.equal(Buffer.byteLength(uint32), 32); -var float32 = new Float32Array(8); -assert.equal(Buffer.byteLength(float32), 32); -var float64 = new Float64Array(8); -assert.equal(Buffer.byteLength(float64), 64); +const int8 = new Int8Array(8); +assert.strictEqual(Buffer.byteLength(int8), 8); +const uint8 = new Uint8Array(8); +assert.strictEqual(Buffer.byteLength(uint8), 8); +const uintc8 = new Uint8ClampedArray(2); +assert.strictEqual(Buffer.byteLength(uintc8), 2); +const int16 = new Int16Array(8); +assert.strictEqual(Buffer.byteLength(int16), 16); +const uint16 = new Uint16Array(8); +assert.strictEqual(Buffer.byteLength(uint16), 16); +const int32 = new Int32Array(8); +assert.strictEqual(Buffer.byteLength(int32), 32); +const uint32 = new Uint32Array(8); +assert.strictEqual(Buffer.byteLength(uint32), 32); +const float32 = new Float32Array(8); +assert.strictEqual(Buffer.byteLength(float32), 32); +const float64 = new Float64Array(8); +assert.strictEqual(Buffer.byteLength(float64), 64); // DataView -var dv = new DataView(new ArrayBuffer(2)); -assert.equal(Buffer.byteLength(dv), 2); +const dv = new DataView(new ArrayBuffer(2)); +assert.strictEqual(Buffer.byteLength(dv), 2); // special case: zero length string -assert.equal(Buffer.byteLength('', 'ascii'), 0); -assert.equal(Buffer.byteLength('', 'HeX'), 0); +assert.strictEqual(Buffer.byteLength('', 'ascii'), 0); +assert.strictEqual(Buffer.byteLength('', 'HeX'), 0); // utf8 -assert.equal(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19); -assert.equal(Buffer.byteLength('κλμνξο', 'utf8'), 12); -assert.equal(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15); -assert.equal(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12); +assert.strictEqual(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19); +assert.strictEqual(Buffer.byteLength('κλμνξο', 'utf8'), 12); +assert.strictEqual(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15); +assert.strictEqual(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12); // without an encoding, utf8 should be assumed -assert.equal(Buffer.byteLength('hey there'), 9); -assert.equal(Buffer.byteLength('𠱸挶νξ#xx :)'), 17); -assert.equal(Buffer.byteLength('hello world', ''), 11); +assert.strictEqual(Buffer.byteLength('hey there'), 9); +assert.strictEqual(Buffer.byteLength('𠱸挶νξ#xx :)'), 17); +assert.strictEqual(Buffer.byteLength('hello world', ''), 11); // it should also be assumed with unrecognized encoding -assert.equal(Buffer.byteLength('hello world', 'abc'), 11); -assert.equal(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10); +assert.strictEqual(Buffer.byteLength('hello world', 'abc'), 11); +assert.strictEqual(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10); // base64 -assert.equal(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11); -assert.equal(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14); -assert.equal(Buffer.byteLength('aGkk', 'base64'), 3); -assert.equal(Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', - 'base64'), 25); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'BASE64'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', 'base64'), 25 +); // special padding -assert.equal(Buffer.byteLength('aaa=', 'base64'), 2); -assert.equal(Buffer.byteLength('aaaa==', 'base64'), 3); - -assert.equal(Buffer.byteLength('Il était tué'), 14); -assert.equal(Buffer.byteLength('Il était tué', 'utf8'), 14); -assert.equal(Buffer.byteLength('Il était tué', 'ascii'), 12); -assert.equal(Buffer.byteLength('Il était tué', 'latin1'), 12); -assert.equal(Buffer.byteLength('Il était tué', 'binary'), 12); -['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach(function(encoding) { - assert.equal(24, Buffer.byteLength('Il était tué', encoding)); -}); +assert.strictEqual(Buffer.byteLength('aaa=', 'base64'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64'), 3); + +assert.strictEqual(Buffer.byteLength('Il était tué'), 14); +assert.strictEqual(Buffer.byteLength('Il était tué', 'utf8'), 14); + +['ascii', 'latin1', 'binary'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 12); + }); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 24); + }); + +// Test that ArrayBuffer from a different context is detected correctly +const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + +// Verify that invalid encodings are treated as utf8 +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.strictEqual(Buffer.byteLength('foo', encoding), + Buffer.byteLength('foo', 'utf8')); +} -- 2.34.1