diff --git a/lib/environment.js b/lib/environment.js index add774ff..7890befb 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -11,7 +11,7 @@ module.exports = { util: { preferBuiltin: true, glob: true }, stream: { preferBuiltin: true, glob: true }, string_decoder: { preferBuiltin: true, glob: true }, - buffer: { resolve: 'buffer/index.js', expose: 'buffer', glob: true }, + buffer: { resolve: '../vendor/buffer.js', expose: 'buffer', glob: true }, url: { preferBuiltin: true, glob: true }, punycode: { preferBuiltin: true, glob: true }, querystring: { preferBuiltin: true, glob: true }, diff --git a/lib/sandbox/index.js b/lib/sandbox/index.js index cde7c047..fa152b21 100644 --- a/lib/sandbox/index.js +++ b/lib/sandbox/index.js @@ -22,6 +22,13 @@ // Setup Timerz before we delete the global timers require('./timers'); +// Require buffer to make sure it's available in the sandbox +// Browserify statically analyses the usage of buffers and only +// injects Buffer into the scope if it's used. `Buffer` injected +// by browserify is part of the functional scope and does not get +// deleted when we mutate the global scope below. +require('buffer'); + // Although we execute the user code in a well-defined scope using the uniscope // module but still to cutoff the reference to the globally available properties // we sanitize the global scope by deleting the forbidden properties in this UVM diff --git a/lib/vendor/buffer.js b/lib/vendor/buffer.js new file mode 100644 index 00000000..13d0aa03 --- /dev/null +++ b/lib/vendor/buffer.js @@ -0,0 +1,37 @@ +const buffer = require('buffer/'); + +// Use the native Buffer when available. +// This is done to get better performance when operating on large buffers +// For example, when converting a large buffer to a string, the native Buffer +// is much faster than the isomorphic buffer and supports toString() operation on +// buffers of size up to 512MB , while the isomorphic buffer errors out after 100MB. +if (typeof globalThis.Buffer === 'function') { + const isomorphicBuffer = buffer.Buffer; + const nativeBuffer = globalThis.Buffer; + + // For node v20 and above `new Buffer()` or `Buffer()` syntax + // does not work. Patching it here for backward compatibility + buffer.Buffer = function () { + return new isomorphicBuffer(...arguments); + }; + + // Ensure the exposed Buffer has same interface as the isomorphicBuffer Buffer + for (const key in isomorphicBuffer) { + buffer.Buffer[key] = nativeBuffer[key]; + } + + // Ensure `instanceof` works for buffers created using the `new Buffer()` or `Buffer()` syntax + Object.defineProperty(buffer.Buffer, Symbol.hasInstance, { + value: function (instance) { + return instance instanceof nativeBuffer || instance instanceof isomorphicBuffer; + } + }); + + // Extend `isBuffer` for buffers created using the `new Buffer()` or `Buffer()` syntax + buffer.Buffer.isBuffer = function (obj) { + return nativeBuffer.isBuffer(obj) || isomorphicBuffer.isBuffer(obj); + }; +} + +module.exports = buffer; + diff --git a/test/unit/sandbox-libraries/buffer.test.js b/test/unit/sandbox-libraries/buffer.test.js index 6ee0baa7..e5b286dc 100644 --- a/test/unit/sandbox-libraries/buffer.test.js +++ b/test/unit/sandbox-libraries/buffer.test.js @@ -114,12 +114,61 @@ describe('sandbox library - buffer', function () { context.execute(` var assert = require('assert'), buf1 = new Buffer('buffer'), - buf2 = new Buffer(buf1); + buf2 = new Buffer(buf1), + buf3 = Buffer(1); buf1[0] = 0x61; + buf3[0] = 0x61; assert.strictEqual(buf1.toString(), 'auffer'); assert.strictEqual(buf2.toString(), 'buffer'); + assert.strictEqual(buf3.toString(), 'a'); `, done); }); + + it('should be able to detect Buffer instances using isBuffer', function (done) { + context.execute(` + var assert = require('assert'), + buffer = require('buffer'), + + bufUsingFrom = Buffer.from('test'), + bufUsingNew = new Buffer('test'), + buf = Buffer(1); + + assert.strictEqual(Buffer.isBuffer(bufUsingFrom), true); + assert.strictEqual(Buffer.isBuffer(bufUsingNew), true); + assert.strictEqual(Buffer.isBuffer(buf), true); + `, done); + }); + + it('should be able to detect Buffer instances using Symbol.hasInstance', function (done) { + context.execute(` + var assert = require('assert'), + buffer = require('buffer'), + + bufUsingFrom = Buffer.from('test'), + bufUsingNew = new Buffer('test'); + buf = Buffer(1); + + assert.strictEqual(bufUsingFrom instanceof Buffer, true); + assert.strictEqual(bufUsingNew instanceof Buffer, true); + assert.strictEqual(buf instanceof Buffer, true); + `, done); + }); + + it('should be able to convert large buffer to string', function (done) { + // For native buffer, the max string length is ~512MB + // For browser buffer, the max string length is ~100MB + const SIZE = (window ? 100 : 512) * 1024 * 1024; + + context.execute(` + var assert = require('assert'), + buffer = require('buffer'), + + // Max string length is ~512MB + buf = Buffer.alloc(${SIZE}, 'a'); + + assert.strictEqual(buf.toString().length, ${SIZE}); + `, done); + }); });