Skip to content

Commit

Permalink
Use native Buffer whenever available
Browse files Browse the repository at this point in the history
  • Loading branch information
appurva21 committed Aug 29, 2024
1 parent 446c257 commit 1b10a07
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
unreleased:
chores:
- Add support for configuring module resolver based on environment
new features:
- Enhanced performance when operating on buffers in Node environment

5.1.1:
date: 2024-08-01
Expand Down
4 changes: 3 additions & 1 deletion lib/bundle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ class Bundle {
// Expose only the required node built-ins to be used in vendor modules
// These will be cleared out by sandbox when the bundle is loaded
const safeExposeNodeBuiltIns = `
global.node = {};
global.node = {
buffer: require('buffer')
};
// prevent access to node's require function
require = null;
Expand Down
7 changes: 6 additions & 1 deletion lib/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ 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/browser.js',
resolveNode: '../vendor/buffer/index.js',
expose: 'buffer',
glob: true
},
url: { preferBuiltin: true, glob: true },
punycode: { preferBuiltin: true, glob: true },
querystring: { preferBuiltin: true, glob: true },
Expand Down
7 changes: 7 additions & 0 deletions lib/sandbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions lib/vendor/buffer/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const SpecificBuffer = require('./buffer');
const buffer = require('buffer/');

// Using 32-bit implementation value from Node
// https://github.com/nodejs/node/blob/main/deps/v8/include/v8-primitive.h#L126
const K_STRING_MAX_LENGTH = (1 << 28) - 16;

module.exports = {
Buffer: SpecificBuffer(buffer.Buffer),
SlowBuffer: buffer.SlowBuffer,
INSPECT_MAX_BYTES: buffer.INSPECT_MAX_BYTES,
kMaxLength: buffer.kMaxLength,
kStringMaxLength: K_STRING_MAX_LENGTH,
constants: {
MAX_LENGTH: buffer.kMaxLength,
MAX_STRING_LENGTH: K_STRING_MAX_LENGTH
},
File: File,
Blob: Blob
}
61 changes: 61 additions & 0 deletions lib/vendor/buffer/buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
function SpecificBuffer (_Buffer) {
const Buffer = function () {
if (typeof arguments[0] === 'number') {
return _Buffer.alloc(...arguments);
}

return _Buffer.from(...arguments);
}

Buffer.poolSize = _Buffer.poolSize;

Object.defineProperty(Buffer, Symbol.hasInstance, {
value: function (instance) {
return instance instanceof _Buffer;
}
});

Buffer.isBuffer = function () {
return _Buffer.isBuffer(...arguments);
}

Buffer.alloc = function () {
return _Buffer.alloc(...arguments);
}

Buffer.allocUnsafe = function () {
return _Buffer.allocUnsafe(...arguments);
}

Buffer.allocUnsafeSlow = function () {
return _Buffer.allocUnsafeSlow(...arguments);
}

Buffer.from = function () {
return _Buffer.from(...arguments);
}

Buffer.compare = function () {
return _Buffer.compare(...arguments);
}

Buffer.isEncoding = function () {
return _Buffer.isEncoding(...arguments);
}

Buffer.concat = function () {
return _Buffer.concat(...arguments);
}

Buffer.byteLength = function () {
return _Buffer.byteLength(...arguments);
}

Buffer.copyBytesFrom = function (offset, length) {

}

return Buffer;
}

module.exports = SpecificBuffer;
13 changes: 13 additions & 0 deletions lib/vendor/buffer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const SpecificBuffer = require('./buffer');
const buffer = global.node.buffer;

module.exports = {
Buffer: SpecificBuffer(globalThis.Buffer),
SlowBuffer: buffer.SlowBuffer,
INSPECT_MAX_BYTES: buffer.INSPECT_MAX_BYTES,
kMaxLength: buffer.kMaxLength,
kStringMaxLength: buffer.kStringMaxLength,
constants: buffer.constants,
File: buffer.File,
Blob: buffer.Blob,
}
154 changes: 153 additions & 1 deletion test/unit/sandbox-libraries/buffer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,164 @@ 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(`
const assert = require('assert'),
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(`
const assert = require('assert'),
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 = (typeof window === 'undefined' ? 511 : 100) * 1024 * 1024;

context.execute(`
const assert = require('assert'),
buf = Buffer.alloc(${SIZE}, 'a');
assert.strictEqual(buf.toString().length, ${SIZE});
`, done);
});

it('should implement Buffer.compare', function (done) {
context.execute(`
const assert = require('assert'),
buf1 = Buffer.from('abc'),
buf2 = Buffer.from('abc'),
buf3 = Buffer.from('abd');
assert.strictEqual(Buffer.compare(buf1, buf2), 0);
assert.strictEqual(Buffer.compare(buf1, buf3), -1);
assert.strictEqual(Buffer.compare(buf3, buf1), 1);
`, done);
});

it('should implement Buffer.byteLength', function (done) {
context.execute(`
const assert = require('assert'),
buf = Buffer.from('abc');
assert.strictEqual(Buffer.byteLength(buf), 3);
`, done);
});

it('should implement Buffer.concat', function (done) {
context.execute(`
const assert = require('assert'),
buf1 = Buffer.from('abc'),
buf2 = Buffer.from('def');
assert.strictEqual(Buffer.concat([buf1, buf2]).toString(), 'abcdef');
`, done);
});

it('should implement Buffer.isEncoding', function (done) {
context.execute(`
const assert = require('assert');
assert.strictEqual(Buffer.isEncoding('utf8'), true);
assert.strictEqual(Buffer.isEncoding('hex'), true);
assert.strictEqual(Buffer.isEncoding('ascii'), true);
assert.strictEqual(Buffer.isEncoding('utf16le'), true);
assert.strictEqual(Buffer.isEncoding('ucs2'), true);
assert.strictEqual(Buffer.isEncoding('base64'), true);
assert.strictEqual(Buffer.isEncoding('binary'), true);
assert.strictEqual(Buffer.isEncoding('utf-8'), true);
assert.strictEqual(Buffer.isEncoding('utf/8'), false);
assert.strictEqual(Buffer.isEncoding(''), false);
`, done);
});

it('should expose Buffer.poolSize', function (done) {
context.execute(`
const assert = require('assert');
assert.strictEqual(typeof Buffer.poolSize, 'number');
`, done);
});

it('should expose SlowBuffer', function (done) {
context.execute(`
const assert = require('assert'),
buffer = require('buffer');
const buf = new buffer.SlowBuffer(10);
assert.strictEqual(buf.length, 10);
`, done);
});

it('should expose constants', function (done) {
context.execute(`
const assert = require('assert'),
buffer = require('buffer');
assert.strictEqual(typeof buffer.kMaxLength, 'number');
assert.strictEqual(typeof buffer.kStringMaxLength, 'number');
assert.strictEqual(typeof buffer.constants.MAX_LENGTH, 'number');
assert.strictEqual(typeof buffer.constants.MAX_STRING_LENGTH, 'number');
assert.strictEqual(typeof buffer.INSPECT_MAX_BYTES, 'number');
`, done);
});

it('should expose File class', function (done) {
context.execute(`
const assert = require('assert'),
buffer = require('buffer');
const lastModified = Date.now();
const file = new buffer.File([], 'filename.txt', { type: 'text/plain', lastModified });
assert.strictEqual(file.name, 'filename.txt');
assert.strictEqual(file.lastModified, lastModified);
`, done);
});

it('should expose Blob class', function (done) {
context.execute(`
const assert = require('assert'),
buffer = require('buffer');
const blob = new buffer.Blob(['hello world'], { type: 'text/plain' });
assert.strictEqual(blob.size, 11);
`, done);
});
});

0 comments on commit 1b10a07

Please sign in to comment.