Comparing version 2.0.2 to 3.0.0
@@ -1,1 +0,3 @@ | ||
module.exports = require("./lib/main") | ||
if (parseInt(process.versions.node.split('.')[0]) < 6) throw new Error('vm2 requires Node.js version 6 or newer.'); | ||
module.exports = require("./lib/main"); |
@@ -1,34 +0,34 @@ | ||
// Generated by CoffeeScript 1.10.0 | ||
var NodeVM, VMError, error, ex, fs, pa, path, ref, stack, started; | ||
const fs = require('fs'); | ||
const pa = require('path'); | ||
fs = require('fs'); | ||
const {NodeVM, VMError} = require('../'); | ||
pa = require('path'); | ||
ref = require('../'), NodeVM = ref.NodeVM, VMError = ref.VMError; | ||
if (process.argv[2]) { | ||
path = require('path').resolve(process.argv[2]); | ||
console.log("\x1B[90m[vm] creating VM for " + path + "\x1B[39m"); | ||
started = Date.now(); | ||
try { | ||
NodeVM.file(path, { | ||
require: true, | ||
requireExternal: true, | ||
verbose: true | ||
}); | ||
console.log("\x1B[90m[vm] VM created in " + (Date.now() - started) + "ms\x1B[39m"); | ||
} catch (error) { | ||
ex = error; | ||
if (ex instanceof VMError) { | ||
console.error("\x1B[31m[vm:error] " + ex.message + "\x1B[39m"); | ||
} else { | ||
stack = ex.stack; | ||
if (stack) { | ||
console.error("\x1B[31m[vm:error] " + stack + "\x1B[39m"); | ||
} else { | ||
console.error("\x1B[31m[vm:error] " + ex + "\x1B[39m"); | ||
} | ||
} | ||
} | ||
let path = pa.resolve(process.argv[2]); | ||
console.log(`\x1B[90m[vm] creating VM for ${path}\x1B[39m`); | ||
let started = Date.now(); | ||
try { | ||
NodeVM.file(path, { | ||
verbose: true, | ||
require: { | ||
external: true | ||
} | ||
}); | ||
console.log(`\x1B[90m[vm] VM completed in ${Date.now() - started}ms\x1B[39m`); | ||
} catch (ex) { | ||
if (ex instanceof VMError) { | ||
console.error(`\x1B[31m[vm:error] ${ex.message}\x1B[39m`); | ||
} else { | ||
let {stack} = ex; | ||
if (stack) { | ||
console.error(`\x1B[31m[vm:error] ${stack}\x1B[39m`); | ||
} else { | ||
console.error(`\x1B[31m[vm:error] ${ex}\x1B[39m`); | ||
} | ||
} | ||
} | ||
} |
@@ -1,84 +0,461 @@ | ||
// Generated by CoffeeScript 1.10.0 | ||
var contextify, global; | ||
'use strict' | ||
global = this; | ||
const global = this; | ||
const console = host.console; | ||
global.global = global.GLOBAL = global.root = global; | ||
// global is originally prototype of host.Object so it can be used to climu up from the sandbox. | ||
Object.setPrototypeOf(global, Object.prototype); | ||
global.SANDBOX = true; | ||
Object.defineProperties(global, { | ||
global: {value: global}, | ||
GLOBAL: {value: global}, | ||
root: {value: global}, | ||
isVM: {value: true} | ||
}); | ||
const DEBUG = false; | ||
const OPNA = 'Operation not allowed on contextified object.'; | ||
const ERROR_CST = Error.captureStackTrace; | ||
/* | ||
Contextify is similar to deep clone, but changes context of all objects to vm's context. | ||
// Map of contextified objects to original objects | ||
const Contextified = new host.WeakMap(); | ||
const Decontextified = new host.WeakMap(); | ||
/** | ||
* VMError definition. | ||
*/ | ||
contextify = (function(_this) { | ||
return function(value, addtoglobal) { | ||
'use strict'; | ||
var desc, i, j, key, len, o, ref, ut; | ||
ut = require('util'); | ||
switch (typeof value) { | ||
case 'object': | ||
if (value === null) { | ||
o = null; | ||
} else if (ut.isDate(value)) { | ||
o = new Date(value.getTime()); | ||
} else if (ut.isError(value)) { | ||
o = new Error(value.message); | ||
} else if (ut.isArray(value)) { | ||
o = (function() { | ||
var j, len, results; | ||
results = []; | ||
for (j = 0, len = value.length; j < len; j++) { | ||
i = value[j]; | ||
results.push(contextify(i)); | ||
} | ||
return results; | ||
})(); | ||
} else if (ut.isRegExp(value)) { | ||
o = new RegExp(value.source, "" + (value.global ? "g" : "") + (value.ignoreCase ? "i" : "") + (value.multiline ? "i" : "")); | ||
} else if (ut.isBuffer(value)) { | ||
if (_this.Buffer) { | ||
o = new _this.Buffer(value.length); | ||
value.copy(o); | ||
} else { | ||
o = null; | ||
} | ||
} else { | ||
o = {}; | ||
ref = Object.getOwnPropertyNames(value); | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
key = ref[j]; | ||
desc = Object.getOwnPropertyDescriptor(value, key); | ||
if (desc.value != null) { | ||
desc.value = contextify(desc.value); | ||
} | ||
if (desc.get != null) { | ||
desc.get = contextify(desc.get); | ||
} | ||
if (desc.set != null) { | ||
desc.set = contextify(desc.set); | ||
} | ||
Object.defineProperty(o, key, desc); | ||
} | ||
} | ||
break; | ||
case 'function': | ||
o = function() { | ||
return value.apply(null, arguments); | ||
}; | ||
break; | ||
case 'undefined': | ||
o = void 0; | ||
break; | ||
default: | ||
o = value; | ||
} | ||
if (addtoglobal) { | ||
_this[addtoglobal] = o; | ||
} | ||
return o; | ||
}; | ||
})(this); | ||
global.VMError = class VMError extends Error { | ||
constructor(message, code) { | ||
super(message); | ||
this.name = 'VMError'; | ||
this.code = code; | ||
ERROR_CST(this, this.constructor); | ||
} | ||
} | ||
return contextify; | ||
/** | ||
* Decontextify. | ||
*/ | ||
const Decontextify = { | ||
proxies: new host.WeakMap(), | ||
arguments: function(args) { | ||
if (!host.Array.isArray(args)) return new host.Array(); | ||
let arr = new host.Array(); | ||
for (let i = 0, l = args.length; i < l; i++) arr[i] = Decontextify.value(args[i]); | ||
return arr; | ||
}, | ||
class: function(instance, klass) { | ||
return Decontextify.object(instance, { | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return instance; | ||
if (key === 'isVMProxy') return true; | ||
if (key === 'constructor') return klass; | ||
if (key === '__proto__') return klass.prototype; | ||
try { | ||
return Decontextify.value(instance[key]); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return klass.prototype; | ||
} | ||
}); | ||
}, | ||
function: function(fnc, traps, deepTraps, mock) { | ||
let self = Decontextify.object(fnc, host.Object.assign({ | ||
apply: (target, context, args) => { | ||
try { | ||
context = Contextify.value(context); | ||
// Set context of all arguments to vm's context. | ||
return Decontextify.value(fnc.apply(context, Contextify.arguments(args))); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
construct: (target, args, newTarget) => { | ||
try { | ||
return Decontextify.class(new fnc(...Contextify.arguments(args)), self); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return fnc; | ||
if (key === 'isVMProxy') return true; | ||
if (mock && key in mock) return mock[key]; | ||
if (key === 'constructor') return host.Function; | ||
if (key === '__proto__') return host.Function.prototype; | ||
try { | ||
return Decontextify.value(fnc[key], deepTraps); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return host.Function.prototype; | ||
} | ||
}, traps), deepTraps); | ||
return self; | ||
}, | ||
object: function(object, traps, deepTraps, mock) { | ||
let proxy = new host.Proxy(object, host.Object.assign({ | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return object; | ||
if (key === 'isVMProxy') return true; | ||
if (mock && key in mock) return mock[key]; | ||
if (key === 'constructor') return host.Object; | ||
if (key === '__proto__') return host.Object.prototype; | ||
try { | ||
return Decontextify.value(object[key], deepTraps); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
set: (target, key, value, receiver) => { | ||
try { | ||
object[key] = Contextify.value(value); | ||
return true; | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
getOwnPropertyDescriptor: (target, prop) => { | ||
try { | ||
var def = host.Object.getOwnPropertyDescriptor(object, prop); | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
// Following code prevents V8 to throw | ||
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '<prop>' which is either non-existant or configurable in the proxy target | ||
if (!def) { | ||
return undefined; | ||
} else if (def.get || def.set) { | ||
return { | ||
get: Decontextify.value(def.get) || undefined, | ||
set: Decontextify.value(def.set) || undefined, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
} | ||
} else { | ||
return { | ||
value: Decontextify.value(def.value), | ||
writable: def.writable === true, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
} | ||
} | ||
}, | ||
defineProperty: (target, key, descriptor) => { | ||
try { | ||
if (descriptor.get || descriptor.set) { | ||
return host.Object.defineProperty(target, key, { | ||
get: Contextify.value(def.get, deepTraps) || undefined, | ||
set: Contextify.value(def.set, deepTraps) || undefined, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
}) | ||
} else { | ||
return host.Object.defineProperty(target, key, { | ||
value: Contextify.value(def.value, deepTraps), | ||
writable: def.writable === true, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
}) | ||
} | ||
} catch (e) { | ||
throw Decontextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return host.Object.prototype; | ||
}, | ||
setPrototypeOf: (target) => { | ||
throw new host.Error(OPNA); | ||
} | ||
}, traps)); | ||
Decontextify.proxies.set(object, proxy); | ||
Decontextified.set(proxy, object); | ||
return proxy; | ||
}, | ||
value: function(value, traps, deepTraps, mock) { | ||
if (Contextified.has(value)) { | ||
// Contextified object has returned back from vm | ||
return Contextified.get(value); | ||
} else if (Decontextify.proxies.has(value)) { | ||
// Decontextified proxy already exists, reuse | ||
return Decontextify.proxies.get(value); | ||
} | ||
switch (typeof value) { | ||
case 'object': | ||
if (value === null) { | ||
return null; | ||
} else if (value instanceof Number) { return host.Number(value); | ||
} else if (value instanceof String) { return host.String(value); | ||
} else if (value instanceof Boolean) { return host.Boolean(value); | ||
} else if (value instanceof Date) { return Decontextify.class(value, host.Date); | ||
} else if (value instanceof RangeError) { return Decontextify.class(value, host.RangeError); | ||
} else if (value instanceof ReferenceError) { return Decontextify.class(value, host.ReferenceError); | ||
} else if (value instanceof SyntaxError) { return Decontextify.class(value, host.SyntaxError); | ||
} else if (value instanceof TypeError) { return Decontextify.class(value, host.TypeError); | ||
} else if (value instanceof VMError) { return Decontextify.class(value, host.VMError); | ||
} else if (value instanceof Error) { return Decontextify.class(value, host.Error); | ||
} else if (value instanceof Array) { return Decontextify.class(value, host.Array); | ||
} else if (value instanceof RegExp) { return Decontextify.class(value, host.RegExp); | ||
} else if (value instanceof Map) { return Decontextify.class(value, host.Map); | ||
} else if (value instanceof WeakMap) { return Decontextify.class(value, host.WeakMap); | ||
} else if (value instanceof Set) { return Decontextify.class(value, host.Set); | ||
} else if (value instanceof WeakSet) { return Decontextify.class(value, host.WeakSet); | ||
} else if (value instanceof Promise) { return Decontextify.class(value, host.Promise); | ||
} else { | ||
return Decontextify.object(value, traps, deepTraps, mock); | ||
} | ||
case 'function': | ||
return Decontextify.function(value, traps, deepTraps, mock); | ||
case 'undefined': | ||
return undefined; | ||
default: // string, number, boolean, symbol | ||
return value; | ||
} | ||
} | ||
} | ||
/** | ||
* Contextify. | ||
*/ | ||
const Contextify = { | ||
proxies: new host.WeakMap(), | ||
arguments: function(args) { | ||
if (!host.Array.isArray(args)) return new Array(); | ||
let arr = new Array(); | ||
for (let i = 0, l = args.length; i < l; i++) arr[i] = Contextify.value(args[i]); | ||
return arr; | ||
}, | ||
class: function(instance, klass) { | ||
return Contextify.object(instance, { | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return instance; | ||
if (key === 'isVMProxy') return true; | ||
if (key === 'constructor') return klass; | ||
if (key === '__proto__') return klass.prototype; | ||
try { | ||
return Contextify.value(instance[key]); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return klass.prototype; | ||
} | ||
}); | ||
}, | ||
function: function(fnc, traps, deepTraps, mock) { | ||
let self = Contextify.object(fnc, host.Object.assign({ | ||
apply: (target, context, args) => { | ||
try { | ||
context = Decontextify.value(context); | ||
// Set context of all arguments to host's context. | ||
return Contextify.value(fnc.apply(context, Decontextify.arguments(args))); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
construct: (target, args, newTarget) => { | ||
try { | ||
return Contextify.class(new fnc(...Decontextify.arguments(args)), self); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return fnc; | ||
if (key === 'isVMProxy') return true; | ||
if (mock && key in mock) return mock[key]; | ||
if (key === 'constructor') return Function; | ||
if (key === '__proto__') return Function.prototype; | ||
try { | ||
return Contextify.value(fnc[key], deepTraps); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return Function.prototype; | ||
} | ||
}, traps), deepTraps); | ||
return self; | ||
}, | ||
object: function(object, traps, deepTraps, mock) { | ||
let proxy = new host.Proxy(object, host.Object.assign({ | ||
get: (target, key, receiver) => { | ||
if (key === 'vmProxyTarget' && DEBUG) return object; | ||
if (key === 'isVMProxy') return true; | ||
if (mock && key in mock) return mock[key]; | ||
if (key === 'constructor') return Object; | ||
if (key === '__proto__') return Object.prototype; | ||
try { | ||
return Contextify.value(object[key], deepTraps); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
set: (target, key, value, receiver) => { | ||
try { | ||
object[key] = Decontextify.value(value); | ||
return true; | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
getOwnPropertyDescriptor: (target, prop) => { | ||
try { | ||
var def = host.Object.getOwnPropertyDescriptor(object, prop); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
// Following code prevents V8 to throw | ||
// TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '<prop>' which is either non-existant or configurable in the proxy target | ||
if (!def) { | ||
return undefined; | ||
} else if (def.get || def.set) { | ||
return { | ||
get: Contextify.value(def.get, deepTraps) || undefined, | ||
set: Contextify.value(def.set, deepTraps) || undefined, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
} | ||
} else { | ||
return { | ||
value: Contextify.value(def.value, deepTraps), | ||
writable: def.writable === true, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
} | ||
} | ||
}, | ||
defineProperty: (target, key, descriptor) => { | ||
try { | ||
if (descriptor.get || descriptor.set) { | ||
return host.Object.defineProperty(target, key, { | ||
get: Decontextify.value(def.get, deepTraps) || undefined, | ||
set: Decontextify.value(def.set, deepTraps) || undefined, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
}) | ||
} else { | ||
return host.Object.defineProperty(target, key, { | ||
value: Decontextify.value(def.value, deepTraps), | ||
writable: def.writable === true, | ||
enumerable: def.enumerable === true, | ||
configurable: def.configurable === true | ||
}) | ||
} | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
getPrototypeOf: (target) => { | ||
return Object.prototype; | ||
}, | ||
setPrototypeOf: (target) => { | ||
throw new VMError(OPNA); | ||
} | ||
}, traps, deepTraps)); | ||
Contextify.proxies.set(object, proxy); | ||
Contextified.set(proxy, object); | ||
return proxy; | ||
}, | ||
value: function(value, traps, deepTraps, mock) { | ||
if (Decontextified.has(value)) { | ||
// Decontextified object has returned back to vm | ||
return Decontextified.get(value); | ||
} else if (Contextify.proxies.has(value)) { | ||
// Contextified proxy already exists, reuse | ||
return Contextify.proxies.get(value); | ||
} | ||
switch (typeof value) { | ||
case 'object': | ||
if (value === null) { | ||
return null; | ||
} else if (value instanceof host.Number) { return host.Number(value); | ||
} else if (value instanceof host.String) { return host.String(value); | ||
} else if (value instanceof host.Boolean) { return host.Boolean(value); | ||
} else if (value instanceof host.Date) { return Contextify.class(value, Date); | ||
} else if (value instanceof host.RangeError) { return Contextify.class(value, RangeError); | ||
} else if (value instanceof host.ReferenceError) { return Contextify.class(value, ReferenceError); | ||
} else if (value instanceof host.SyntaxError) { return Contextify.class(value, SyntaxError); | ||
} else if (value instanceof host.TypeError) { return Contextify.class(value, TypeError); | ||
} else if (value instanceof host.VMError) { return Contextify.class(value, VMError); | ||
} else if (value instanceof host.Error) { return Contextify.class(value, Error); | ||
} else if (value instanceof host.Array) { return Contextify.class(value, Array); | ||
} else if (value instanceof host.RegExp) { return Contextify.class(value, RegExp); | ||
} else if (value instanceof host.Map) { return Contextify.class(value, Map); | ||
} else if (value instanceof host.WeakMap) { return Contextify.class(value, WeakMap); | ||
} else if (value instanceof host.Set) { return Contextify.class(value, Set); | ||
} else if (value instanceof host.WeakSet) { return Contextify.class(value, WeakSet); | ||
} else if (value instanceof host.Promise) { return Contextify.class(value, Promise); | ||
} else if (value instanceof host.Buffer) { return Contextify.class(value, LocalBuffer); | ||
} else { | ||
return Contextify.object(value, traps, deepTraps, mock); | ||
} | ||
case 'function': | ||
return Contextify.function(value, traps, deepTraps, mock); | ||
case 'undefined': | ||
return undefined; | ||
default: // string, number, boolean, symbol | ||
return value; | ||
} | ||
}, | ||
globalValue: function(value, name) { | ||
return global[name] = Contextify.value(value); | ||
}, | ||
readonly: function(value, mock) { | ||
return Contextify.value(value, null, { | ||
set: (target, key) => false, | ||
setPrototypeOf: (target, key) => false, | ||
defineProperty: (target, key) => false, | ||
deleteProperty: (target, key) => false, | ||
isExtensible: (target, key) => false, | ||
preventExtensions: (target) => false | ||
}, mock); | ||
} | ||
} | ||
const LocalBuffer = global.Buffer = Contextify.readonly(host.Buffer); | ||
return { | ||
Contextify, | ||
Decontextify, | ||
Buffer: LocalBuffer | ||
} |
805
lib/main.js
@@ -1,485 +0,382 @@ | ||
// Generated by CoffeeScript 1.10.0 | ||
var AVAILABLE_NATIVE_MODULES, EventEmitter, NodeVM, VM, VMError, _compileToJS, _prepareContextify, cf, fs, pa, sb, ut, version, vm, | ||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, | ||
hasProp = {}.hasOwnProperty, | ||
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | ||
const fs = require('fs'); | ||
const vm = require('vm'); | ||
const pa = require('path'); | ||
const {EventEmitter} = require('events'); | ||
version = process.versions.node.split('.'); | ||
const sb = fs.readFileSync(`${__dirname}/sandbox.js`, 'utf8'); | ||
const cf = fs.readFileSync(`${__dirname}/contextify.js`, 'utf8'); | ||
if (parseInt(version[0]) === 0 && parseInt(version[1]) < 11) { | ||
throw new Error("vm2 requires Node.js version 0.11+ or io.js 1.0+ (current version: " + process.versions.node + ")"); | ||
} | ||
const _compileToJS = function(code, compiler) { | ||
if ('function' === typeof compiler) return compiler(code); | ||
fs = require('fs'); | ||
vm = require('vm'); | ||
pa = require('path'); | ||
ut = require('util'); | ||
EventEmitter = require('events').EventEmitter; | ||
sb = fs.readFileSync(__dirname + "/sandbox.js", "utf8"); | ||
cf = fs.readFileSync(__dirname + "/contextify.js", "utf8"); | ||
AVAILABLE_NATIVE_MODULES = ['assert', 'buffer', 'child_process', 'constants', 'crypto', 'tls', 'dgram', 'dns', 'http', 'https', 'net', 'querystring', 'url', 'domain', 'events', 'fs', 'path', 'module', 'os', 'punycode', 'stream', 'string_decoder', 'timers', 'tty', 'util', 'sys', 'vm', 'zlib']; | ||
/* | ||
Prepare value for contextification. | ||
@property {Object} value Value to prepare. | ||
@return {Object} Prepared value. | ||
@private | ||
*/ | ||
_prepareContextify = function(value) { | ||
var desc, i, j, key, len, o, ref; | ||
if (typeof value === 'object') { | ||
if (value === null) { | ||
return value; | ||
} | ||
if (value instanceof String) { | ||
return String(value); | ||
} | ||
if (value instanceof Number) { | ||
return Number(value); | ||
} | ||
if (value instanceof Boolean) { | ||
return Boolean(value); | ||
} | ||
if (value instanceof Array) { | ||
return (function() { | ||
var j, len, results; | ||
results = []; | ||
for (j = 0, len = value.length; j < len; j++) { | ||
i = value[j]; | ||
results.push(_prepareContextify(i)); | ||
} | ||
return results; | ||
})(); | ||
} | ||
if (value instanceof Error) { | ||
return value; | ||
} | ||
if (value instanceof Date) { | ||
return value; | ||
} | ||
if (value instanceof RegExp) { | ||
return value; | ||
} | ||
if (value instanceof Buffer) { | ||
return value; | ||
} | ||
o = {}; | ||
ref = Object.getOwnPropertyNames(value); | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
key = ref[j]; | ||
desc = Object.getOwnPropertyDescriptor(value, key); | ||
if (desc.value != null) { | ||
desc.value = _prepareContextify(desc.value); | ||
} | ||
Object.defineProperty(o, key, desc); | ||
} | ||
return o; | ||
} else { | ||
return value; | ||
} | ||
switch (compiler) { | ||
case 'coffeescript': | ||
case 'coffee-script': | ||
case 'cs': | ||
case 'text/coffeescript': | ||
return require('coffee-script').compile(code, {header: false, bare: true}); | ||
case 'javascript': | ||
case 'java-script': | ||
case 'js': | ||
case 'text/javascript': | ||
return code; | ||
default: | ||
throw new VMError(`Unsupported compiler '${compiler}'.`); | ||
} | ||
}; | ||
_compileToJS = function(code, language) { | ||
switch (language) { | ||
case 'coffeescript': | ||
case 'coffee-script': | ||
case 'cs': | ||
case 'text/coffeescript': | ||
return require('coffee-script').compile(code, { | ||
header: false, | ||
bare: true | ||
}); | ||
case 'javascript': | ||
case 'java-script': | ||
case 'js': | ||
case 'text/javascript': | ||
return code; | ||
default: | ||
throw new VMError("Unsupported language '" + language + "'."); | ||
} | ||
}; | ||
/* | ||
Class VM. | ||
@property {Boolean} running True if VM was initialized. | ||
@property {Object} options VM options. | ||
@property {Object} context VM's context. | ||
/** | ||
* Class VM. | ||
* | ||
* @property {Object} options VM options. | ||
*/ | ||
VM = (function(superClass) { | ||
extend(VM, superClass); | ||
class VM extends EventEmitter { | ||
/** | ||
* Create VM instance. | ||
* | ||
* @param {Object} [options] VM options. | ||
* @return {VM} | ||
*/ | ||
constructor(options = {}) { | ||
super(); | ||
// defaults | ||
this.options = { | ||
timeout: options.timeout != null ? options.timeout : undefined, | ||
sandbox: options.sandbox != null ? options.sandbox : null, | ||
compiler: options.compiler != null ? options.compiler : 'javascript' | ||
}; | ||
VM.prototype.running = false; | ||
let host = { | ||
console, | ||
String, | ||
Number, | ||
Buffer, | ||
Boolean, | ||
Array, | ||
Date, | ||
Error, | ||
RangeError, | ||
ReferenceError, | ||
SyntaxError, | ||
TypeError, | ||
RegExp, | ||
Function, | ||
Object, | ||
VMError, | ||
Proxy, | ||
Reflect, | ||
Map, | ||
WeakMap, | ||
Set, | ||
WeakSet, | ||
Promise | ||
}; | ||
VM.prototype.options = null; | ||
this._context = vm.createContext(); | ||
VM.prototype.context = null; | ||
Reflect.defineProperty(this, '_internal', { | ||
value: vm.runInContext(`(function(require, host) { ${cf} \n})`, this._context, { | ||
filename: `${__dirname}/contextify.js`, | ||
displayErrors: false | ||
}).call(this._context, require, host) | ||
}); | ||
// prepare global sandbox | ||
if (this.options.sandbox) { | ||
if ('object' !== typeof this.options.sandbox) { | ||
throw new VMError("Sandbox must be object."); | ||
} | ||
for (let name in this.options.sandbox) { | ||
this._internal.Contextify.globalValue(this.options.sandbox[name], name); | ||
} | ||
} | ||
} | ||
/** | ||
* Run the code in VM. | ||
* | ||
* @param {String} code Code to run. | ||
* @return {*} Result of executed code. | ||
*/ | ||
run(code) { | ||
if (this.options.compiler !== 'javascript') { | ||
code = _compileToJS(code, this.options.compiler); | ||
} | ||
/* | ||
Create VM instance. | ||
@param {Object} [options] VM options. | ||
@return {VM} | ||
*/ | ||
function VM(options) { | ||
var ref, ref1, ref2; | ||
if (options == null) { | ||
options = {}; | ||
} | ||
this.options = { | ||
timeout: (ref = options.timeout) != null ? ref : void 0, | ||
sandbox: (ref1 = options.sandbox) != null ? ref1 : null, | ||
language: (ref2 = options.language) != null ? ref2 : 'javascript' | ||
}; | ||
} | ||
/* | ||
Run the code in VM. | ||
@param {String} code Code to run. | ||
@return {*} Result of executed code. | ||
*/ | ||
VM.prototype.run = function(code) { | ||
'use strict'; | ||
var contextify, name, ref, script, value; | ||
if (this.options.language !== 'javascript') { | ||
code = _compileToJS(code, this.options.language); | ||
} | ||
if (this.running) { | ||
script = new vm.Script(code, { | ||
filename: "vm", | ||
displayErrors: false | ||
}); | ||
return script.runInContext(this.context, { | ||
filename: "vm", | ||
displayErrors: false, | ||
timeout: this.options.timeout | ||
}); | ||
} | ||
this.context = vm.createContext(); | ||
contextify = vm.runInContext("(function(require) { " + cf + " \n})", this.context, { | ||
filename: "contextify.js", | ||
displayErrors: false | ||
}).call(this.context, require); | ||
if (this.options.sandbox) { | ||
if (typeof this.options.sandbox !== 'object') { | ||
throw new VMError("Sandbox must be object"); | ||
} | ||
ref = this.options.sandbox; | ||
for (name in ref) { | ||
value = ref[name]; | ||
contextify(_prepareContextify(value), name); | ||
} | ||
} | ||
script = new vm.Script(code, { | ||
filename: "vm", | ||
displayErrors: false | ||
}); | ||
this.running = true; | ||
return script.runInContext(this.context, { | ||
filename: "vm", | ||
displayErrors: false, | ||
timeout: this.options.timeout | ||
}); | ||
}; | ||
return VM; | ||
})(EventEmitter); | ||
/* | ||
Class NodeVM. | ||
@property {Object} cache Cache of loaded modules. | ||
@property {Object} natives Cache of native modules. | ||
@property {Object} module Pointer to main module. | ||
@property {Function} proxy Proxy used by `call` method to securely call methods in VM. | ||
let script = new vm.Script(code, { | ||
filename: "vm.js", | ||
displayErrors: false | ||
}); | ||
try { | ||
return this._internal.Decontextify.value(script.runInContext(this._context, { | ||
filename: "vm.js", | ||
displayErrors: false, | ||
timeout: this.options.timeout | ||
})); | ||
} catch (e) { | ||
throw this._internal.Decontextify.value(e); | ||
} | ||
} | ||
} | ||
/** | ||
* Class NodeVM. | ||
* | ||
* @property {Object} module Pointer to main module. | ||
*/ | ||
NodeVM = (function(superClass) { | ||
extend(NodeVM, superClass); | ||
class NodeVM extends EventEmitter { | ||
/** | ||
* Create NodeVM instance. | ||
* | ||
* Unlike VM, NodeVM lets you use require same way like in regular node. | ||
* | ||
* @param {Object} [options] VM options. | ||
* @return {NodeVM} | ||
*/ | ||
constructor(options = {}) { | ||
super(); | ||
// defaults | ||
this.options = { | ||
sandbox: options.sandbox != null ? options.sandbox : null, | ||
console: options.console != null ? options.console : 'inherit', | ||
require: options.require != null ? options.require : false, | ||
compiler: options.compiler != null ? options.compiler : 'javascript', | ||
require: options.require != null ? options.require : false, | ||
nesting: options.nesting != null ? options.nesting : false | ||
}; | ||
NodeVM.prototype.cache = null; | ||
let host = { | ||
require, | ||
process, | ||
console, | ||
setTimeout, | ||
setInterval, | ||
setImmediate, | ||
clearTimeout, | ||
clearInterval, | ||
clearImmediate, | ||
String, | ||
Number, | ||
Buffer, | ||
Boolean, | ||
Array, | ||
Date, | ||
Error, | ||
RangeError, | ||
ReferenceError, | ||
SyntaxError, | ||
TypeError, | ||
RegExp, | ||
Function, | ||
Object, | ||
VMError, | ||
Proxy, | ||
Reflect, | ||
Map, | ||
WeakMap, | ||
Set, | ||
WeakSet, | ||
Promise | ||
} | ||
NodeVM.prototype.natives = null; | ||
if (this.options.nesting) { | ||
host.VM = VM; | ||
host.NodeVM = NodeVM; | ||
} | ||
NodeVM.prototype.module = null; | ||
this._context = vm.createContext(); | ||
NodeVM.prototype.proxy = null; | ||
Object.defineProperty(this, '_internal', { | ||
value: vm.runInContext(`(function(require, host) { ${cf} \n})`, this._context, { | ||
filename: `${__dirname}/contextify.js`, | ||
displayErrors: false | ||
}).call(this._context, require, host) | ||
}) | ||
let closure = vm.runInContext(`(function (vm, host, Contextify, Decontextify, Buffer) { ${sb} \n})`, this._context, { | ||
filename: `${__dirname}/sandbox.js`, | ||
displayErrors: false | ||
}) | ||
Object.defineProperty(this, '_prepareRequire', { | ||
value: closure.call(this._context, this, host, this._internal.Contextify, this._internal.Decontextify, this._internal.Buffer) | ||
}) | ||
/* | ||
Create NodeVM instance. | ||
Unlike VM, NodeVM lets you use require same way like in regular node. | ||
@param {Object} [options] VM options. | ||
@return {NodeVM} | ||
*/ | ||
// prepare global sandbox | ||
if (this.options.sandbox) { | ||
if ('object' !== typeof this.options.sandbox) { | ||
throw new VMError("Sandbox must be object."); | ||
} | ||
for (let name in this.options.sandbox) { | ||
this._internal.Contextify.globalValue(this.options.sandbox[name], name); | ||
} | ||
} | ||
if (this.options.require && this.options.require.import) { | ||
if (!Array.isArray(this.options.require.import)) { | ||
this.options.require.import = [this.options.require.import]; | ||
} | ||
for (let i = 0, l = this.options.require.import.length; i < l; i++) { | ||
this.require(this.options.require.import[i]); | ||
} | ||
} | ||
} | ||
/** | ||
* @deprecated | ||
*/ | ||
call(method, ...args) { | ||
if ('function' === typeof method) { | ||
return method.apply(args); | ||
function NodeVM(options) { | ||
var j, k, len, len1, mod, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7; | ||
if (options == null) { | ||
options = {}; | ||
} | ||
this.natives = {}; | ||
this.options = { | ||
sandbox: (ref = options.sandbox) != null ? ref : null, | ||
console: (ref1 = options.console) != null ? ref1 : 'inherit', | ||
require: (ref2 = options.require) != null ? ref2 : false, | ||
language: (ref3 = options.language) != null ? ref3 : 'javascript', | ||
requireExternal: (ref4 = options.requireExternal) != null ? ref4 : false, | ||
requireNative: {}, | ||
requireRoot: (ref5 = options.requireRoot) != null ? ref5 : false, | ||
useStrict: (ref6 = options.useStrict) != null ? ref6 : true | ||
}; | ||
if (options.requireNative) { | ||
if (Array.isArray(options.requireNative)) { | ||
ref7 = options.requireNative; | ||
for (j = 0, len = ref7.length; j < len; j++) { | ||
mod = ref7[j]; | ||
if (indexOf.call(AVAILABLE_NATIVE_MODULES, mod) >= 0) { | ||
this.options.requireNative[mod] = true; | ||
} | ||
} | ||
} | ||
} else { | ||
for (k = 0, len1 = AVAILABLE_NATIVE_MODULES.length; k < len1; k++) { | ||
mod = AVAILABLE_NATIVE_MODULES[k]; | ||
this.options.requireNative[mod] = true; | ||
} | ||
} | ||
} | ||
} else { | ||
throw new VMError("Unrecognized method type."); | ||
} | ||
} | ||
/** | ||
* Require a module in VM and return it's exports. | ||
* | ||
* @return {*} Exported module. | ||
*/ | ||
require(module) { | ||
return this.run(`module.exports = require('${module}');`, 'vm.js'); | ||
} | ||
/** | ||
* Run the code in NodeVM. | ||
* | ||
* First time you run this method, code is executed same way like in node's regular `require` - it's executed with `module`, `require`, `exports`, `__dirname`, `__filename` variables and expect result in `module.exports'. | ||
* | ||
* @param {String} code Code to run. | ||
* @param {String} [filename] Filename that shows up in any stack traces produced from this script. | ||
* @return {*} Result of executed code. | ||
*/ | ||
run(code, filename) { | ||
if (this.options.compiler !== 'javascript') { | ||
code = _compileToJS(code, this.options.compiler); | ||
} | ||
if (filename) { | ||
filename = pa.resolve(filename); | ||
var dirname = pa.dirname(filename); | ||
} else { | ||
filename = null; | ||
var dirname = null; | ||
} | ||
/* | ||
Securely call method in VM. All arguments except functions are cloned during the process to prevent context leak. Functions are wrapped to secure closures. | ||
Buffers are copied! | ||
IMPORTANT: Method doesn't check for circular objects! If you send circular structure as an argument, you process will stuck in infinite loop. | ||
@param {Function} method Method to execute. | ||
@param {...*} argument Arguments. | ||
@return {*} Return value of executed method. | ||
*/ | ||
let module = vm.runInContext("({exports: {}})", this._context, { | ||
displayErrors: false | ||
}); | ||
let script = new vm.Script(`(function (exports, require, module, __filename, __dirname) { ${code} \n})`, { | ||
filename: filename || "vm.js", | ||
displayErrors: false | ||
}); | ||
try { | ||
let closure = script.runInContext(this._context, { | ||
filename: filename || "vm.js", | ||
displayErrors: false | ||
}); | ||
closure.call(this._context, module.exports, this._prepareRequire(dirname), module, filename, dirname); | ||
} catch (e) { | ||
throw this._internal.Decontextify.value(e); | ||
} | ||
NodeVM.prototype.call = function(method) { | ||
'use strict'; | ||
if (!this.running) { | ||
throw new VMError("VM is not running"); | ||
} | ||
if (typeof method === 'function') { | ||
return this.proxy.apply(this, arguments); | ||
} else { | ||
throw new VMError("Unrecognized method type"); | ||
} | ||
}; | ||
return this._internal.Decontextify.value(module.exports); | ||
} | ||
/** | ||
* Create NodeVM and run code inside it. | ||
* | ||
* @param {String} script Javascript code. | ||
* @param {String} [filename] File name (used in stack traces only). | ||
* @param {Object} [options] VM options. | ||
* @return {NodeVM} VM. | ||
*/ | ||
static code(script, filename, options) { | ||
if (filename != null) { | ||
if ('object' === typeof filename) { | ||
options = filename; | ||
filename = null; | ||
} else if ('string' === typeof filename) { | ||
filename = pa.resolve(filename); | ||
} else { | ||
throw new VMError("Invalid arguments."); | ||
} | ||
} | ||
if (arguments.length > 3) { | ||
throw new VMError("Invalid number of arguments."); | ||
} | ||
/* | ||
Run the code in NodeVM. | ||
First time you run this method, code is executed same way like in node's regular `require` - it's executed with `module`, `require`, `exports`, `__dirname`, `__filename` variables and expect result in `module.exports'. | ||
@param {String} code Code to run. | ||
@param {String} [filename] Filename that shows up in any stack traces produced from this script. | ||
@return {*} Result of executed code. | ||
*/ | ||
return new NodeVM(options).run(script, filename); | ||
} | ||
/** | ||
* Create NodeVM and run script from file inside it. | ||
* | ||
* @param {String} [filename] File name (used in stack traces only). | ||
* @param {Object} [options] VM options. | ||
* @return {NodeVM} VM. | ||
*/ | ||
static file(filename, options) { | ||
filename = pa.resolve(filename); | ||
if (!fs.existsSync(filename)) { | ||
throw new VMError(`Script '${filename}' not found.`); | ||
} | ||
if (fs.statSync(filename).isDirectory()) { | ||
throw new VMError("Script must be file, got directory."); | ||
} | ||
NodeVM.prototype.run = function(code, filename) { | ||
'use strict'; | ||
var closure, contextify, dirname, name, parent, ref, ref1, script, value; | ||
if (global.isVM) { | ||
throw new VMError("You can't nest VMs"); | ||
} | ||
if (this.options.language !== 'javascript') { | ||
code = _compileToJS(code, this.options.language); | ||
} | ||
if (filename) { | ||
filename = pa.resolve(filename); | ||
dirname = pa.dirname(filename); | ||
} else { | ||
filename = null; | ||
dirname = null; | ||
} | ||
if (this.running) { | ||
script = new vm.Script(code, { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
return script.runInContext(this.context, { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
} | ||
parent = { | ||
require: require, | ||
process: process, | ||
console: console, | ||
setTimeout: setTimeout, | ||
setInterval: setInterval, | ||
setImmediate: setImmediate, | ||
clearTimeout: clearTimeout, | ||
clearInterval: clearInterval, | ||
clearImmediate: clearImmediate | ||
}; | ||
if (global.DTRACE_HTTP_SERVER_RESPONSE) { | ||
parent.DTRACE_HTTP_SERVER_RESPONSE = global.DTRACE_HTTP_SERVER_RESPONSE; | ||
parent.DTRACE_HTTP_SERVER_REQUEST = global.DTRACE_HTTP_SERVER_REQUEST; | ||
parent.DTRACE_HTTP_CLIENT_RESPONSE = global.DTRACE_HTTP_CLIENT_RESPONSE; | ||
parent.DTRACE_HTTP_CLIENT_REQUEST = global.DTRACE_HTTP_CLIENT_REQUEST; | ||
parent.DTRACE_NET_STREAM_END = global.DTRACE_NET_STREAM_END; | ||
parent.DTRACE_NET_SERVER_CONNECTION = global.DTRACE_NET_SERVER_CONNECTION; | ||
parent.DTRACE_NET_SOCKET_READ = global.DTRACE_NET_SOCKET_READ; | ||
parent.DTRACE_NET_SOCKET_WRITE = global.DTRACE_NET_SOCKET_WRITE; | ||
} | ||
if (global.COUNTER_NET_SERVER_CONNECTION) { | ||
parent.COUNTER_NET_SERVER_CONNECTION = global.COUNTER_NET_SERVER_CONNECTION; | ||
parent.COUNTER_NET_SERVER_CONNECTION_CLOSE = global.COUNTER_NET_SERVER_CONNECTION_CLOSE; | ||
parent.COUNTER_HTTP_SERVER_REQUEST = global.COUNTER_HTTP_SERVER_REQUEST; | ||
parent.COUNTER_HTTP_SERVER_RESPONSE = global.COUNTER_HTTP_SERVER_RESPONSE; | ||
parent.COUNTER_HTTP_CLIENT_REQUEST = global.COUNTER_HTTP_CLIENT_REQUEST; | ||
parent.COUNTER_HTTP_CLIENT_RESPONSE = global.COUNTER_HTTP_CLIENT_RESPONSE; | ||
} | ||
this.context = vm.createContext(); | ||
contextify = vm.runInContext("(function(require) { " + cf + " \n})", this.context, { | ||
filename: "contextify.js", | ||
displayErrors: false | ||
}).call(this.context, require); | ||
closure = vm.runInContext("(function (vm, parent, contextify, __dirname, __filename) { " + sb + " \n})", this.context, { | ||
filename: "sandbox.js", | ||
displayErrors: false | ||
}); | ||
ref = closure.call(this.context, this, parent, contextify, dirname, filename), this.cache = ref.cache, this.module = ref.module, this.proxy = ref.proxy; | ||
this.cache[filename] = this.module; | ||
if (this.options.sandbox) { | ||
if (typeof this.options.sandbox !== 'object') { | ||
throw new VMError("Sandbox must be object"); | ||
} | ||
ref1 = this.options.sandbox; | ||
for (name in ref1) { | ||
value = ref1[name]; | ||
contextify(_prepareContextify(value), name); | ||
} | ||
} | ||
this.running = true; | ||
script = new vm.Script("(function (exports, require, module, __filename, __dirname) { " + code + " \n})", { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
closure = script.runInContext(this.context, { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
closure.call(this.context, this.module.exports, this.module.require, this.module, filename, dirname); | ||
return this.module.exports; | ||
}; | ||
return new NodeVM(options).run(fs.readFileSync(filename, 'utf8'), filename); | ||
} | ||
} | ||
/* | ||
Create NodeVM and run code inside it. | ||
@param {String} script Javascript code. | ||
@param {String} [filename] File name (used in stack traces only). | ||
@param {Object} [options] VM options. | ||
@return {NodeVM} VM. | ||
*/ | ||
NodeVM.code = function(script, filename, options) { | ||
var _vm; | ||
if (filename != null) { | ||
if (typeof filename === 'object') { | ||
options = filename; | ||
filename = null; | ||
} else if (typeof filename === 'string') { | ||
filename = pa.resolve(filename); | ||
} else { | ||
console.log(arguments); | ||
throw new VMError("Invalid arguments"); | ||
} | ||
} | ||
if (arguments.length > 3) { | ||
throw new VMError("Invalid number of arguments"); | ||
} | ||
_vm = new NodeVM(options); | ||
_vm.run(script, filename); | ||
return _vm; | ||
}; | ||
/* | ||
Create NodeVM and run script from file inside it. | ||
@param {String} [filename] File name (used in stack traces only). | ||
@param {Object} [options] VM options. | ||
@return {NodeVM} VM. | ||
*/ | ||
NodeVM.file = function(filename, options) { | ||
var _vm; | ||
_vm = new NodeVM(options); | ||
filename = pa.resolve(filename); | ||
if (!fs.existsSync(filename)) { | ||
throw new VMError("Script '" + filename + "' not found"); | ||
} | ||
if (fs.statSync(filename).isDirectory()) { | ||
throw new VMError("Script must be file, got directory"); | ||
} | ||
_vm.run(fs.readFileSync(filename, 'utf8'), filename); | ||
return _vm; | ||
}; | ||
return NodeVM; | ||
})(VM); | ||
/* | ||
VMError. | ||
@param {String} message Error message. | ||
@class | ||
@extends {Error} | ||
@property {String} stack Call stack. | ||
@property {String} message Error message. | ||
/** | ||
* VMError. | ||
* | ||
* @param {String} message Error message. | ||
* | ||
* @class | ||
* @extends {Error} | ||
* @property {String} stack Call stack. | ||
* @property {String} message Error message. | ||
*/ | ||
VMError = (function(superClass) { | ||
extend(VMError, superClass); | ||
class VMError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = 'VMError'; | ||
function VMError(message) { | ||
this.name = this.constructor.name; | ||
this.message = message; | ||
VMError.__super__.constructor.call(this); | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
} | ||
return VMError; | ||
})(Error); | ||
module.exports.VM = VM; | ||
module.exports.NodeVM = NodeVM; | ||
module.exports.VMError = VMError; | ||
exports.VMError = VMError; | ||
exports.NodeVM = NodeVM; | ||
exports.VM = VM; |
@@ -1,504 +0,406 @@ | ||
// Generated by CoffeeScript 1.10.0 | ||
var NATIVE_MODULES, Script, fakeHandlers, noop, | ||
slice = [].slice, | ||
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, | ||
hasProp = {}.hasOwnProperty; | ||
const {Script} = host.require('vm'); | ||
const fs = host.require('fs'); | ||
const pa = host.require('path'); | ||
const console = host.console; | ||
Script = parent.require('vm').Script; | ||
const BUILTIN_MODULES = host.process.binding('natives'); | ||
const JSON_PARSE = JSON.parse; | ||
noop = function() {}; | ||
/** | ||
* @param {Object} host Hosts's internal objects. | ||
*/ | ||
fakeHandlers = {}; | ||
return ((vm, host) => { | ||
'use strict'; | ||
const global = this; | ||
NATIVE_MODULES = parent.process.binding('natives'); | ||
const TIMERS = new host.WeakMap() | ||
const BUILTINS = {}; | ||
const CACHE = {}; | ||
const EXTENSIONS = { | ||
[".json"](module, filename) { | ||
try { | ||
var code = fs.readFileSync(filename, "utf8"); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
module.exports = JSON_PARSE(code); | ||
}, | ||
[".node"](module, filename) { | ||
if (vm.options.require.context === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.'); | ||
try { | ||
module.exports = Contextify.readonly(host.require(filename)); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
}, | ||
[".js"](module, filename, dirname) { | ||
if (vm.options.require.context !== 'sandbox') { | ||
try { | ||
module.exports = Contextify.readonly(host.require(filename)); | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
} else { | ||
try { | ||
// Load module | ||
var code = `(function (exports, require, module, __filename, __dirname) { 'use strict'; ${fs.readFileSync(filename, "utf8")} \n});`; | ||
// Precompile script | ||
let script = new Script(code, { | ||
filename: filename || "vm.js", | ||
displayErrors: false | ||
}); | ||
var closure = script.runInContext(global, { | ||
filename: filename || "vm.js", | ||
displayErrors: false | ||
}); | ||
} catch (ex) { | ||
throw Contextify.value(e); | ||
} | ||
// run script | ||
closure(module.exports, module.require, module, filename, dirname); | ||
} | ||
} | ||
}; | ||
/** | ||
* Resolve filename. | ||
*/ | ||
/* | ||
@param {Object} parent Parent's global object. | ||
*/ | ||
const _resolveFilename = function(path) { | ||
path = pa.resolve(path); | ||
let exists = fs.existsSync(path); | ||
let isdir = exists ? fs.statSync(path).isDirectory() : false; | ||
// direct file match | ||
if (exists && !isdir) return path; | ||
return (function(_this) { | ||
return function(vm, parent) { | ||
'use strict'; | ||
var EXTENSIONS, _prepareRequire, _requireNative, _resolveFilename, fs, global, pa, ref; | ||
EXTENSIONS = { | ||
".json": function(module, filename) { | ||
return module.exports = JSON.parse(fs.readFileSync(filename, "utf8")); | ||
} | ||
}; | ||
global = _this; | ||
global.global = global.GLOBAL = global.root = global; | ||
global.isVM = true; | ||
// load as file | ||
if (fs.existsSync(`${path}.js`)) return `${path}.js`; | ||
if (fs.existsSync(`${path}.node`)) return `${path}.node`; | ||
if (fs.existsSync(`${path}.json`)) return `${path}.json`; | ||
// load as directory | ||
if (fs.existsSync(`${path}/package.json`)) { | ||
try { | ||
var pkg = JSON.parse(fs.readFileSync(`${path}/package.json`, "utf8")); | ||
if (pkg.main == null) pkg.main = "index.js"; | ||
} catch (ex) { | ||
throw new VMError(`Module '${modulename}' has invalid package.json`, "EMODULEINVALID"); | ||
} | ||
return _resolveFilename(`${path}/${pkg.main}`); | ||
} | ||
if (fs.existsSync(`${path}/index.js`)) return `${path}/index.js`; | ||
if (fs.existsSync(`${path}/index.node`)) return `${path}/index.node`; | ||
return null; | ||
}; | ||
/** | ||
* Builtin require. | ||
*/ | ||
const _requireBuiltin = function(modulename) { | ||
if (modulename === 'buffer') return ({Buffer}); | ||
if (BUILTINS[modulename]) return BUILTINS[modulename].exports; // Only compiled builtins are stored here | ||
if (modulename === 'util') { | ||
return Contextify.readonly(host.require(modulename), { | ||
// Allows VM context to use util.inherits | ||
inherits: function(ctor, superCtor) { | ||
ctor.super_ = superCtor; | ||
Object.setPrototypeOf(ctor.prototype, superCtor.prototype); | ||
} | ||
}); | ||
} | ||
if (modulename === 'events') { | ||
try { | ||
let script = new Script(`(function (exports, require, module, process) { 'use strict'; ${BUILTIN_MODULES[modulename]} \n});`, { | ||
filename: `${modulename}.vm.js` | ||
}); | ||
// setup module scope | ||
let module = BUILTINS[modulename] = { | ||
exports: {}, | ||
require: _requireBuiltin | ||
}; | ||
// run script | ||
script.runInContext(global)(module.exports, module.require, module, host.process); | ||
return module.exports; | ||
} catch (e) { | ||
throw Contextify.value(e); | ||
} | ||
} | ||
return Contextify.readonly(host.require(modulename)); | ||
}; | ||
/** | ||
* Prepare require. | ||
*/ | ||
const _prepareRequire = function(current_dirname) { | ||
const _require = function(modulename) { | ||
if (vm.options.nesting && modulename === 'vm2') return {VM: Contextify.readonly(host.VM), NodeVM: Contextify.readonly(host.NodeVM)}; | ||
if (!vm.options.require) throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
if (modulename == null) throw new VMError("Module '' not found.", "ENOTFOUND"); | ||
if (typeof modulename !== 'string') throw new VMError(`Invalid module name '${modulename}'`, "EINVALIDNAME"); | ||
// Mock? | ||
if (vm.options.require.mock && vm.options.require.mock[modulename]) { | ||
return Contextify.readonly(vm.options.require.mock[modulename]); | ||
} | ||
// Builtin? | ||
if (BUILTIN_MODULES[modulename]) { | ||
if (host.Array.isArray(vm.options.require.builtin)) { | ||
if (vm.options.require.builtin.indexOf('*') >= 0) { | ||
if (vm.options.require.builtin.indexOf(`-${modulename}`) >= 0) { | ||
throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
} | ||
} else if (vm.options.require.builtin.indexOf(modulename) === -1) { | ||
throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
} | ||
} else if (vm.options.require.builtin) { | ||
if (!vm.options.require.builtin[modulename]) { | ||
throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
} | ||
} else { | ||
throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
} | ||
return _requireBuiltin(modulename); | ||
} | ||
// External? | ||
if (!vm.options.require.external) throw new VMError(`Access denied to require '${modulename}'`, "EDENIED"); | ||
/* | ||
Resolve filename. | ||
*/ | ||
_resolveFilename = function(path) { | ||
var error, ex, exists, isdir, pkg; | ||
path = pa.resolve(path); | ||
exists = fs.existsSync(path); | ||
isdir = exists ? fs.statSync(path).isDirectory() : false; | ||
if (exists && !isdir) { | ||
return path; | ||
} | ||
if (fs.existsSync(path + ".js")) { | ||
return path + ".js"; | ||
} | ||
if (fs.existsSync(path + ".node")) { | ||
return path + ".node"; | ||
} | ||
if (fs.existsSync(path + ".json")) { | ||
return path + ".json"; | ||
} | ||
if (fs.existsSync(path + "/package.json")) { | ||
try { | ||
pkg = JSON.parse(fs.readFileSync(path + "/package.json", "utf8")); | ||
if (pkg.main == null) { | ||
pkg.main = "index.js"; | ||
} | ||
} catch (error) { | ||
ex = error; | ||
throw new VMError("Module '" + modulename + "' has invalid package.json", "EMODULEINVALID"); | ||
} | ||
return _resolveFilename(path + "/" + pkg.main); | ||
} | ||
if (fs.existsSync(path + "/index.js")) { | ||
return path + "/index.js"; | ||
} | ||
if (fs.existsSync(path + "/index.node")) { | ||
return path + "/index.node"; | ||
} | ||
return null; | ||
}; | ||
if (/^(\.|\.\/|\.\.\/)/.exec(modulename)) { | ||
// Module is relative file, e.g. ./script.js or ../script.js | ||
if (!current_dirname) throw new VMError("You must specify script path to load relative modules.", "ENOPATH"); | ||
/* | ||
Prepare require. | ||
*/ | ||
_requireNative = function(modulename) { | ||
'use strict'; | ||
var module, script; | ||
if (vm.natives[modulename]) { | ||
return vm.natives[modulename].exports; | ||
} | ||
script = new Script("(function (exports, require, module, process) { 'use strict'; " + NATIVE_MODULES[modulename] + " \n});", { | ||
filename: modulename + ".sb.js" | ||
}); | ||
vm.natives[modulename] = module = { | ||
exports: {}, | ||
require: _requireNative | ||
}; | ||
script.runInContext(global)(module.exports, module.require, module, parent.process); | ||
return module.exports; | ||
}; | ||
_prepareRequire = function(current_dirname) { | ||
var _require; | ||
_require = function(modulename) { | ||
'use strict'; | ||
var closure, code, dirname, error, error1, error2, ex, extname, filename, module, path, paths, requiredPath, script, strictText; | ||
if (!vm.options.require) { | ||
throw new VMError("Access denied to require '" + modulename + "'", "EDENIED"); | ||
} | ||
if (modulename == null) { | ||
throw new VMError("Module '' not found.", "ENOTFOUND"); | ||
} | ||
if (typeof modulename !== 'string') { | ||
throw new VMError("Invalid module name '" + modulename + "'", "EINVALIDNAME"); | ||
} | ||
if (NATIVE_MODULES[modulename]) { | ||
if (vm.options.requireNative) { | ||
if (vm.options.requireNative[modulename]) { | ||
return _requireNative(modulename); | ||
} | ||
} | ||
throw new VMError("Access denied to require '" + modulename + "'", "EDENIED"); | ||
} | ||
if (!vm.options.requireExternal) { | ||
throw new VMError("Access denied to require '" + modulename + "'", "EDENIED"); | ||
} | ||
if (/^(\.\/|\.\.\/)/.exec(modulename)) { | ||
if (!current_dirname) { | ||
throw new VMError("You must specify script path to load relative modules.", "ENOPATH"); | ||
} | ||
filename = _resolveFilename(current_dirname + "/" + modulename); | ||
} else if (/^(\/|\\|[a-zA-Z]:\\)/.exec(modulename)) { | ||
filename = _resolveFilename(modulename); | ||
} else { | ||
if (!current_dirname) { | ||
throw new VMError("You must specify script path to load relative modules.", "ENOPATH"); | ||
} | ||
paths = current_dirname.split(pa.sep); | ||
while (paths.length) { | ||
path = paths.join(pa.sep); | ||
filename = _resolveFilename("" + path + pa.sep + "node_modules" + pa.sep + modulename); | ||
if (filename) { | ||
break; | ||
} | ||
paths.pop(); | ||
} | ||
} | ||
if (!filename) { | ||
throw new VMError("Module '" + modulename + "' not found", "ENOTFOUND"); | ||
} | ||
if (vm.cache[filename]) { | ||
return vm.cache[filename].exports; | ||
} | ||
dirname = pa.dirname(filename); | ||
extname = pa.extname(filename); | ||
if (vm.options.requireRoot) { | ||
requiredPath = pa.resolve(vm.options.requireRoot); | ||
if (dirname.indexOf(requiredPath) !== 0) { | ||
throw new VMError("Module '" + modulename + "' is not allowed to be required. The path is outside the border!", "EDENIED"); | ||
} | ||
} | ||
vm.cache[filename] = module = { | ||
filename: filename, | ||
exports: {}, | ||
require: _prepareRequire(dirname) | ||
}; | ||
if (EXTENSIONS[extname]) { | ||
try { | ||
EXTENSIONS[extname](module, filename); | ||
return module.exports; | ||
} catch (error) { | ||
ex = error; | ||
throw new VMError("Failed to load '" + filename + "': [" + ex.message + "]", "ELOADFAIL"); | ||
} | ||
} | ||
if (extname === '.node') { | ||
try { | ||
parent.process.dlopen(module, filename); | ||
return module.exports; | ||
} catch (error1) { | ||
ex = error1; | ||
throw new VMError("Failed to load '" + filename + "': [" + ex.message + "]", "ELOADFAIL"); | ||
} | ||
} | ||
try { | ||
strictText = vm.options.useStrict ? "'use strict'; " : ""; | ||
code = "(function (exports, require, module, __filename, __dirname) { " + strictText + (fs.readFileSync(filename, "utf8")) + " \n});"; | ||
} catch (error2) { | ||
ex = error2; | ||
throw new VMError("Failed to load '" + filename + "': [" + ex.message + "]", "ELOADFAIL"); | ||
} | ||
script = new Script(code, { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
closure = script.runInContext(global, { | ||
filename: filename != null ? filename : "vm", | ||
displayErrors: false | ||
}); | ||
closure(module.exports, module.require, module, filename, dirname); | ||
return module.exports; | ||
}; | ||
_require.cache = vm.cache; | ||
_require.extensions = EXTENSIONS; | ||
return _require; | ||
}; | ||
var filename = _resolveFilename(`${current_dirname}/${modulename}`); | ||
} else if (/^(\/|\\|[a-zA-Z]:\\)/.exec(modulename)) { | ||
// Module is absolute file, e.g. /script.js or //server/script.js or C:\script.js | ||
var filename = _resolveFilename(modulename); | ||
} else { | ||
// Check node_modules in path | ||
if (!current_dirname) throw new VMError("You must specify script path to load relative modules.", "ENOPATH"); | ||
/* | ||
Prepare sandbox. | ||
*/ | ||
global.setTimeout = function(callback) { | ||
var tmr; | ||
arguments[0] = function() { | ||
return callback.call(null); | ||
}; | ||
tmr = parent.setTimeout.apply(parent, arguments); | ||
return { | ||
ref: function() { | ||
return tmr.ref(); | ||
}, | ||
unref: function() { | ||
return tmr.unref(); | ||
} | ||
}; | ||
}; | ||
global.setInterval = function(callback) { | ||
arguments[0] = function() { | ||
return callback.call(null); | ||
}; | ||
parent.setInterval.apply(parent, arguments); | ||
return { | ||
ref: function() { | ||
return tmr.ref(); | ||
}, | ||
unref: function() { | ||
return tmr.unref(); | ||
} | ||
}; | ||
}; | ||
global.setImmediate = function(callback) { | ||
arguments[0] = function() { | ||
return callback.call(null); | ||
}; | ||
parent.setImmediate.apply(parent, arguments); | ||
return { | ||
ref: function() { | ||
return tmr.ref(); | ||
}, | ||
unref: function() { | ||
return tmr.unref(); | ||
} | ||
}; | ||
}; | ||
global.clearTimeout = function() { | ||
parent.clearTimeout.apply(parent, arguments); | ||
return null; | ||
}; | ||
global.clearInterval = function() { | ||
parent.clearInterval.apply(parent, arguments); | ||
return null; | ||
}; | ||
global.clearImmediate = function() { | ||
parent.clearImmediate.apply(parent, arguments); | ||
return null; | ||
}; | ||
global.process = { | ||
argv: [], | ||
title: parent.process.title, | ||
version: parent.process.version, | ||
versions: contextify(parent.process.versions), | ||
arch: parent.process.arch, | ||
platform: parent.process.platform, | ||
env: {}, | ||
pid: parent.process.pid, | ||
features: contextify(parent.process.features), | ||
nextTick: function(callback) { | ||
return parent.process.nextTick(function() { | ||
return callback.call(null); | ||
}); | ||
}, | ||
hrtime: function() { | ||
return parent.process.hrtime(); | ||
}, | ||
cwd: function() { | ||
return parent.process.cwd(); | ||
}, | ||
on: function(name, handler) { | ||
var fake; | ||
if (name !== 'beforeExit' && name !== 'exit') { | ||
throw new Error("Access denied to listen for '" + name + "' event."); | ||
} | ||
fake = function() { | ||
return handler.call(null); | ||
}; | ||
if (fakeHandlers[name] == null) { | ||
fakeHandlers[name] = new Map(); | ||
} | ||
fakeHandlers[name].set(handler, fake); | ||
parent.process.on(name, fake); | ||
return this; | ||
}, | ||
once: function(name, handler) { | ||
var fake, ref; | ||
if (name !== 'beforeExit' && name !== 'exit') { | ||
throw new Error("Access denied to listen for '" + name + "' event."); | ||
} | ||
if ((ref = fakeHandlers[name]) != null ? ref.has(handler) : void 0) { | ||
return this; | ||
} | ||
fake = function() { | ||
fakeHandlers[name]["delete"](handler); | ||
return handler.call(null); | ||
}; | ||
if (fakeHandlers[name] == null) { | ||
fakeHandlers[name] = new Map(); | ||
} | ||
fakeHandlers[name].set(handler, fake); | ||
parent.process.once(name, fake); | ||
return this; | ||
}, | ||
listeners: function(name) { | ||
var array; | ||
if (!fakeHandlers[name]) { | ||
return []; | ||
} | ||
array = []; | ||
fakeHandlers[name].forEach(function(value, key) { | ||
return array.push(key); | ||
}); | ||
return array; | ||
}, | ||
removeListener: function(name, handler) { | ||
var fake, ref; | ||
fake = (ref = fakeHandlers[name]) != null ? ref.get(handler) : void 0; | ||
if (fake == null) { | ||
return this; | ||
} | ||
fakeHandlers[name]["delete"](handler); | ||
parent.process.removeListener(name, fake); | ||
return this; | ||
}, | ||
umask: function() { | ||
if (arguments.length) { | ||
throw new Error("Access denied to set umask."); | ||
} | ||
return parent.process.umask(); | ||
} | ||
}; | ||
if (vm.options.console === 'inherit') { | ||
global.console = { | ||
log: function() { | ||
var ref; | ||
(ref = parent.console).log.apply(ref, arguments); | ||
return null; | ||
}, | ||
info: function() { | ||
var ref; | ||
(ref = parent.console).info.apply(ref, arguments); | ||
return null; | ||
}, | ||
warn: function() { | ||
var ref; | ||
(ref = parent.console).warn.apply(ref, arguments); | ||
return null; | ||
}, | ||
error: function() { | ||
var ref; | ||
(ref = parent.console).error.apply(ref, arguments); | ||
return null; | ||
}, | ||
dir: function() { | ||
var ref; | ||
(ref = parent.console).dir.apply(ref, arguments); | ||
return null; | ||
}, | ||
time: function() { | ||
var ref; | ||
(ref = parent.console).time.apply(ref, arguments); | ||
return null; | ||
}, | ||
timeEnd: function() { | ||
var ref; | ||
(ref = parent.console).timeEnd.apply(ref, arguments); | ||
return null; | ||
}, | ||
trace: function() { | ||
var ref; | ||
(ref = parent.console).trace.apply(ref, arguments); | ||
return null; | ||
} | ||
}; | ||
} else if (vm.options.console === 'redirect') { | ||
global.console = { | ||
log: function() { | ||
vm.emit.apply(vm, ['console.log'].concat(slice.call(arguments))); | ||
return null; | ||
}, | ||
info: function() { | ||
vm.emit.apply(vm, ['console.info'].concat(slice.call(arguments))); | ||
return null; | ||
}, | ||
warn: function() { | ||
vm.emit.apply(vm, ['console.warn'].concat(slice.call(arguments))); | ||
return null; | ||
}, | ||
error: function() { | ||
vm.emit.apply(vm, ['console.error'].concat(slice.call(arguments))); | ||
return null; | ||
}, | ||
dir: function() { | ||
vm.emit.apply(vm, ['console.dir'].concat(slice.call(arguments))); | ||
return null; | ||
}, | ||
time: noop, | ||
timeEnd: noop, | ||
trace: function() { | ||
vm.emit.apply(vm, ['console.trace'].concat(slice.call(arguments))); | ||
return null; | ||
} | ||
}; | ||
} | ||
if (parent.DTRACE_HTTP_SERVER_RESPONSE) { | ||
global.DTRACE_HTTP_SERVER_RESPONSE = function() { | ||
return parent.DTRACE_HTTP_SERVER_RESPONSE.apply(parent, arguments); | ||
}; | ||
global.DTRACE_HTTP_SERVER_REQUEST = function() { | ||
return parent.DTRACE_HTTP_SERVER_REQUEST.apply(parent, arguments); | ||
}; | ||
global.DTRACE_HTTP_CLIENT_RESPONSE = function() { | ||
return parent.DTRACE_HTTP_CLIENT_RESPONSE.apply(parent, arguments); | ||
}; | ||
global.DTRACE_HTTP_CLIENT_REQUEST = function() { | ||
return parent.DTRACE_HTTP_CLIENT_REQUEST.apply(parent, arguments); | ||
}; | ||
global.DTRACE_NET_STREAM_END = function() { | ||
return parent.DTRACE_NET_STREAM_END.apply(parent, arguments); | ||
}; | ||
global.DTRACE_NET_SERVER_CONNECTION = function() { | ||
return parent.DTRACE_NET_SERVER_CONNECTION.apply(parent, arguments); | ||
}; | ||
global.DTRACE_NET_SOCKET_READ = function() { | ||
return parent.DTRACE_NET_SOCKET_READ.apply(parent, arguments); | ||
}; | ||
global.DTRACE_NET_SOCKET_WRITE = function() { | ||
return parent.DTRACE_NET_SOCKET_WRITE.apply(parent, arguments); | ||
}; | ||
} | ||
if (parent.COUNTER_NET_SERVER_CONNECTION) { | ||
global.COUNTER_NET_SERVER_CONNECTION = function() { | ||
return parent.COUNTER_NET_SERVER_CONNECTION.apply(parent, arguments); | ||
}; | ||
global.COUNTER_NET_SERVER_CONNECTION_CLOSE = function() { | ||
return parent.COUNTER_NET_SERVER_CONNECTION_CLOSE.apply(parent, arguments); | ||
}; | ||
global.COUNTER_HTTP_SERVER_REQUEST = function() { | ||
return parent.COUNTER_HTTP_SERVER_REQUEST.apply(parent, arguments); | ||
}; | ||
global.COUNTER_HTTP_SERVER_RESPONSE = function() { | ||
return parent.COUNTER_HTTP_SERVER_RESPONSE.apply(parent, arguments); | ||
}; | ||
global.COUNTER_HTTP_CLIENT_REQUEST = function() { | ||
return parent.COUNTER_HTTP_CLIENT_REQUEST.apply(parent, arguments); | ||
}; | ||
global.COUNTER_HTTP_CLIENT_RESPONSE = function() { | ||
return parent.COUNTER_HTTP_CLIENT_RESPONSE.apply(parent, arguments); | ||
}; | ||
} | ||
if (vm.options.require && ((ref = vm.options.requireNative) != null ? ref['buffer'] : void 0) === true) { | ||
global.Buffer = _requireNative('buffer').Buffer; | ||
} | ||
fs = parent.require('fs'); | ||
pa = parent.require('path'); | ||
let paths = current_dirname.split(pa.sep); | ||
while (paths.length) { | ||
let path = paths.join(pa.sep); | ||
//console.log modulename, "#{path}#{pa.sep}node_modules#{pa.sep}#{modulename}" | ||
var filename = _resolveFilename(`${path}${pa.sep}node_modules${pa.sep}${modulename}`); | ||
if (filename) break; | ||
paths.pop(); | ||
} | ||
} | ||
if (!filename) throw new VMError(`Cannot find module '${modulename}'`, "ENOTFOUND"); | ||
// return cache whenever possible | ||
if (CACHE[filename]) return CACHE[filename].exports; | ||
/* | ||
VMError definition. | ||
*/ | ||
global.VMError = (function(superClass) { | ||
extend(VMError, superClass); | ||
let dirname = pa.dirname(filename); | ||
let extname = pa.extname(filename); | ||
if (vm.options.require.root) { | ||
let requiredPath = pa.resolve(vm.options.require.root); | ||
if (dirname.indexOf(requiredPath) !== 0) { | ||
throw new VMError(`Module '${modulename}' is not allowed to be required. The path is outside the border!`, "EDENIED"); | ||
} | ||
} | ||
let module = CACHE[filename] = { | ||
filename, | ||
exports: {}, | ||
require: _prepareRequire(dirname) | ||
}; | ||
// lookup extensions | ||
if (EXTENSIONS[extname]) { | ||
EXTENSIONS[extname](module, filename, dirname); | ||
return module.exports; | ||
} | ||
throw new VMError(`Failed to load '${modulename}': Unknown type.`, "ELOADFAIL"); | ||
}; | ||
return _require; | ||
}; | ||
function VMError(message, code) { | ||
this.name = this.constructor.name; | ||
this.message = message; | ||
this.code = code; | ||
VMError.__super__.constructor.call(this); | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
/** | ||
* Prepare sandbox. | ||
*/ | ||
global.setTimeout = function(callback, ...args) { | ||
let tmr = host.setTimeout(function() { | ||
callback.apply(null, args) | ||
}); | ||
let local = { | ||
ref() { return tmr.ref(); }, | ||
unref() { return tmr.unref(); } | ||
}; | ||
TIMERS.set(local, tmr); | ||
return local; | ||
}; | ||
global.setInterval = function(callback, ...args) { | ||
let tmr = host.setInterval(function() { | ||
callback.apply(null, args) | ||
}); | ||
let local = { | ||
ref() { return tmr.ref(); }, | ||
unref() { return tmr.unref(); } | ||
}; | ||
TIMERS.set(local, tmr); | ||
return local; | ||
}; | ||
global.setImmediate = function(callback, ...args) { | ||
let tmr = host.setImmediate(function() { | ||
callback.apply(null, args) | ||
}); | ||
let local = { | ||
ref() { return tmr.ref(); }, | ||
unref() { return tmr.unref(); } | ||
}; | ||
TIMERS.set(local, tmr); | ||
return local; | ||
}; | ||
global.clearTimeout = function(local) { | ||
host.clearTimeout(TIMERS.get(local)); | ||
return null; | ||
}; | ||
global.clearInterval = function(local) { | ||
host.clearInterval(TIMERS.get(local)); | ||
return null; | ||
}; | ||
global.clearImmediate = function(local) { | ||
host.clearImmediate(TIMERS.get(local)); | ||
return null; | ||
}; | ||
global.process = { | ||
argv: [], | ||
title: host.process.title, | ||
version: host.process.version, | ||
versions: Contextify.readonly(host.process.versions), | ||
arch: host.process.arch, | ||
platform: host.process.platform, | ||
env: {}, | ||
pid: host.process.pid, | ||
features: Contextify.readonly(host.process.features), | ||
nextTick(callback) { return host.process.nextTick(() => callback.call(null)); }, | ||
hrtime() { return host.process.hrtime(); }, | ||
cwd() { return host.process.cwd(); }, | ||
on(name, handler) { | ||
if (name !== 'beforeExit' && name !== 'exit') { | ||
throw new Error(`Access denied to listen for '${name}' event.`); | ||
} | ||
host.process.on(name, Decontextify.value(handler)); | ||
return this; | ||
}, | ||
once(name, handler) { | ||
if (name !== 'beforeExit' && name !== 'exit') { | ||
throw new Error(`Access denied to listen for '${name}' event.`); | ||
} | ||
return VMError; | ||
host.process.once(name, Decontextify.value(handler)); | ||
return this; | ||
}, | ||
listeners(name) { | ||
return Contextify.readonly(host.process.listeners(name)); | ||
}, | ||
removeListener(name, handler) { | ||
host.process.removeListener(name, Decontextify.value(handler)); | ||
return this; | ||
}, | ||
umask() { | ||
if (arguments.length) { | ||
throw new Error("Access denied to set umask."); | ||
} | ||
return host.process.umask(); | ||
} | ||
}; | ||
if (vm.options.console === 'inherit') { | ||
global.console = Contextify.readonly(host.console); | ||
} else if (vm.options.console === 'redirect') { | ||
global.console = { | ||
log(...args) { | ||
vm.emit('console.log', ...Decontextify.arguments(args)); | ||
return null; | ||
}, | ||
info(...args) { | ||
vm.emit('console.info', ...Decontextify.arguments(args)); | ||
return null; | ||
}, | ||
warn(...args) { | ||
vm.emit('console.warn', ...Decontextify.arguments(args)); | ||
return null; | ||
}, | ||
error(...args) { | ||
vm.emit('console.error', ...Decontextify.arguments(args)); | ||
return null; | ||
}, | ||
dir(...args) { | ||
vm.emit('console.dir', ...Decontextify.arguments(args)); | ||
return null; | ||
}, | ||
time: () => {}, | ||
timeEnd: () => {}, | ||
trace(...args) { | ||
vm.emit('console.trace', ...Decontextify.arguments(args)); | ||
return null; | ||
} | ||
}; | ||
} | ||
})(Error); | ||
return { | ||
/* | ||
Return contextized require. | ||
*/ | ||
/* | ||
Return contextized variables. | ||
*/ | ||
cache: {}, | ||
module: { | ||
filename: __filename, | ||
exports: {}, | ||
require: _prepareRequire(__dirname) | ||
}, | ||
proxy: function() { | ||
var arg, args, i, index, len, method; | ||
method = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; | ||
for (index = i = 0, len = args.length; i < len; index = ++i) { | ||
arg = args[index]; | ||
args[index] = contextify(arg); | ||
} | ||
return method.apply(null, args); | ||
} | ||
}; | ||
}; | ||
})(this)(vm, parent); | ||
return _prepareRequire; | ||
})(vm, host); |
{ | ||
"author": { | ||
"name": "Patrik Simek", | ||
"url": "https://patriksimek.cz" | ||
}, | ||
"name": "vm2", | ||
"description": "vm2 is a sandbox that can run untrusted code with whitelisted built-in node objects. Securely!", | ||
"keywords": [ | ||
"sandbox", | ||
"prison", | ||
"jail", | ||
"vm", | ||
"alcatraz", | ||
"contextify" | ||
], | ||
"version": "2.0.2", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/patriksimek/vm2" | ||
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
}, | ||
"devDependencies": { | ||
"coffee-script": "^1.8.0", | ||
"mocha": "^1.12.0" | ||
}, | ||
"engines": { | ||
"node": ">=0.11" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"bin": { | ||
"vm2": "./bin/vm2" | ||
} | ||
"author": { | ||
"name": "Patrik Simek", | ||
"url": "https://patriksimek.cz" | ||
}, | ||
"name": "vm2", | ||
"description": "vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. Securely!", | ||
"keywords": [ | ||
"sandbox", | ||
"prison", | ||
"jail", | ||
"vm", | ||
"alcatraz", | ||
"contextify" | ||
], | ||
"version": "3.0.0", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/patriksimek/vm2" | ||
}, | ||
"license": "MIT", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"mocha": "^2.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=6.0" | ||
}, | ||
"scripts": { | ||
"test": "mocha test" | ||
}, | ||
"bin": { | ||
"vm2": "./bin/vm2" | ||
} | ||
} |
166
README.md
@@ -1,6 +0,6 @@ | ||
# vm2 [![Dependency Status](https://david-dm.org/patriksimek/vm2.png)](https://david-dm.org/patriksimek/vm2) [![NPM version](https://badge.fury.io/js/vm2.png)](http://badge.fury.io/js/vm2) [![Build Status](https://secure.travis-ci.org/patriksimek/vm2.png)](http://travis-ci.org/patriksimek/vm2) | ||
# vm2 [![NPM Version][npm-image]][npm-url] [![Package Quality][quality-image]][quality-url] [![Travis CI][travis-image]][travis-url] | ||
vm2 is a sandbox that can run untrusted code with whitelisted built-in node objects. Securely! | ||
vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. Securely! | ||
## Features | ||
**Features** | ||
@@ -10,26 +10,27 @@ * Runs untrusted code securely in a single process with your code side by side | ||
* Sandbox has limited access to process's methods | ||
* Sandbox can require modules (native and external) | ||
* You can limit access to certain (or all) native modules | ||
* You can securely call methods inside sandbox with callbacks | ||
* Sandbox can require modules (builtin and external) | ||
* You can limit access to certain (or all) builtin modules | ||
* You can securely call methods and exchange data and callback between sandboxes | ||
* Is immune to `while (true) {}` (VM only, see docs) | ||
* Is immune to all known methods of attacks | ||
* Coffee-Script support | ||
* Transpilers support | ||
## How does it work | ||
**How does it work** | ||
* It uses internal VM module to create secure context | ||
* It compiles native modules inside a new context | ||
* It overrides native require to control access to modules | ||
* It forces modules (even native ones) to use `use strict` | ||
* It uses [Proxies](https://developer.mozilla.org/cs/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to prevent escaping the sandbox | ||
* It overrides builtin require to control access to modules | ||
## Installation | ||
**IMPORTANT**: Requires Node.js 6 or newer. | ||
npm install vm2 | ||
## Quick Examples | ||
## Quick Example | ||
```javascript | ||
var VM = require('vm2').VM; | ||
const {VM} = require('vm2'); | ||
var vm = new VM(); | ||
const vm = new VM(); | ||
vm.run("process.exit()"); | ||
@@ -40,16 +41,13 @@ ``` | ||
* [1.x to 2.x changes](#1x-to-2x-changes) | ||
* [VM](#vm) | ||
* [NodeVM](#nodevm) | ||
* [Calling VM's methods](#calling-vms-methods) | ||
* [Cross-sandbox relationships](#cross-sandbox-relationships) | ||
* [CLI](#cli) | ||
* [Known Issues](#known-issues) | ||
* [2.x to 3.x changes](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-changes) | ||
* [1.x and 2.x docs](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs) | ||
* [Contributing](https://github.com/patriksimek/vm2/wiki/Contributing) | ||
## 1.x to 2.x changes | ||
`Buffer` class is no longer globally available by default in NodeVM. To make `Buffer` accessible globaly, enable `require` option and make sure `buffer` module is whitelisted. More info in [Known Issues](#known-issues). | ||
## VM | ||
VM is a simple sandbox, without `require` feature, to synchronously run an untrusted code. Only JavaScript built-in objects are available. | ||
VM is a simple sandbox, without `require` feature, to synchronously run an untrusted code. Only JavaScript built-in objects + Buffer are available. | ||
@@ -60,13 +58,14 @@ **Options:** | ||
* `sandbox` - VM's global object. | ||
* `language` - `javascript` (default) or `coffeescript` | ||
* `compiler` - `javascript` (default) or `coffeescript` or custom compiler function. | ||
**IMPORTANT**: Timeout is only effective on code you run through `run`. Timeout is NOT effective on any method returned by VM. | ||
```javascript | ||
var VM = require('vm2').VM; | ||
const {VM} = require('vm2'); | ||
var options = { | ||
const vm = new VM({ | ||
timeout: 1000, | ||
sandbox: {} | ||
}; | ||
}); | ||
var vm = new VM(options); | ||
vm.run("process.exit()"); // throws ReferenceError: process is not defined | ||
@@ -78,6 +77,6 @@ ``` | ||
```javascript | ||
var number = vm.run("1337"); // returns 1337 | ||
let number = vm.run("1337"); // returns 1337 | ||
``` | ||
**IMPORTANT**: Timeout is only effective on code you run trough `run`. Timeout is NOT effective on any method returned by VM. | ||
**TIP**: See tests for more usage examples. | ||
@@ -90,51 +89,88 @@ ## NodeVM | ||
* `console` - `inherit` to enable console, `redirect` to redirect to events, `off` to disable console (default: `inherit`) | ||
* `sandbox` - VM's global object | ||
* `language` - `javascript` (default) or `coffeescript` | ||
* `require` - `true` to enable `require` method (default: `false`) | ||
* `requireExternal` - `true` to enable `require` of external modules (default: `false`) | ||
* `requireNative` - Array of allowed native modules. (default: all available) | ||
* `requireRoot` - Restricted path where local modules can be required (default: every path) | ||
* `useStrict` - Whether to add `use strict` directive to required modules (default: `true`) | ||
* `console` - `inherit` to enable console, `redirect` to redirect to events, `off` to disable console (default: `inherit`). | ||
* `sandbox` - VM's global object. | ||
* `compiler` - `javascript` (default) or `coffeescript` or custom compiler function. | ||
* `require` - `true` or object to enable `require` method (default: `false`). | ||
* `require.external` - `true` to enable `require` of external modules (default: `false`). | ||
* `require.builtin` - Array of allowed builtin modules (default: none). | ||
* `require.root` - Restricted path where local modules can be required (default: every path). | ||
* `require.mock` - Collection of mock modules (both external or builtin). | ||
* `require.context` - `host` (default) to require modules in host and proxy them to sandbox. `sandbox` to load, compile and require modules in sandbox. Builtin modules except `events` always required in host and proxied to sandbox. | ||
* `require.import` - Array of modules to be loaded into NodeVM on start. | ||
* `nesting` - `true` to enable VMs nesting (default: `false`). | ||
**Available modules:** `assert`, `buffer`, `child_process`, `constants`, `crypto`, `tls`, `dgram`, `dns`, `http`, `https`, `net`, `punycode`, `querystring`, `url`, `domain`, `events`, `fs`, `path`, `os`, `stream`, `string_decoder`, `timers`, `tty`, `util`, `sys`, `vm`, `zlib` | ||
**IMPORTANT**: Timeout is not effective for NodeVM so it is not immune to `while (true) {}` or similar evil. | ||
**REMEMBER**: The more modules you allow, the more fragile your sandbox becomes. | ||
**IMPORTANT**: Timeout is not effective for NodeVM so it is not immune to `while (true) {}` or similar evil. | ||
```javascript | ||
var NodeVM = require('vm2').NodeVM; | ||
const {NodeVM} = require('vm2'); | ||
var options = { | ||
const vm = new NodeVM({ | ||
console: 'inherit', | ||
sandbox: {}, | ||
require: true, | ||
requireExternal: true, | ||
requireNative: ['fs', 'path'], | ||
requireRoot : "./" | ||
}; | ||
require: { | ||
external: true, | ||
builtin: ['fs', 'path'], | ||
root: "./", | ||
mock: { | ||
fs: { | ||
readFileSync() { return 'Nice try!'; } | ||
} | ||
} | ||
} | ||
}); | ||
var vm = new NodeVM(options); | ||
var functionInSandbox = vm.run("module.exports = function(who) { console.log('hello '+ who); }"); | ||
let functionInSandbox = vm.run("module.exports = function(who) { console.log('hello '+ who); }"); | ||
functionInSandbox('world'); | ||
``` | ||
### Calling VM's methods | ||
**TIP**: See tests for more usage examples. | ||
Securely call method in sandbox. All arguments except functions are cloned during the process to prevent context leak. Functions are wrapped to secure closures. Buffers are copied. | ||
### Loading modules by relative path | ||
**IMPORTANT**: Method doesn't check for circular objects! If you send circular structure as an argument, your process will stuck in infinite loop. | ||
To load modules by relative path, you must pass full path of the script you're running as a second argument of vm's `run` method. Filename then also shows up in any stack traces produced from the script. | ||
**IMPORTANT**: Always use `vm.call` method to call methods or callbacks in sandbox. If you call it directly, you are exposing yourself a risk of main global context leakage! | ||
```javascript | ||
vm.call(functionInSandbox, 'world'); | ||
vm.run("require('foobar')", "/data/myvmscript.js"); | ||
``` | ||
### Loading modules by relative path | ||
## Cross-sandbox relationships | ||
To load modules by relative path, you must pass full path of the script you're running as a second argument of vm's `run` method. Filename then also shows up in any stack traces produced from the script. | ||
```javascript | ||
const assert = require('assert'); | ||
const {VM} = require('vm2'); | ||
```javascript | ||
vm.run("require('foobar')", "/data/myvmscript.js"); | ||
let sandbox = { | ||
object: new Object(), | ||
func: new Function(), | ||
buffer: new Buffer([0x01, 0x05]) | ||
} | ||
let vm = new VM({sandbox}); | ||
assert.ok(vm.run(`object`) === sandbox.object); | ||
assert.ok(vm.run(`object instanceof Object`)); | ||
assert.ok(vm.run(`object`) instanceof Object); | ||
assert.ok(vm.run(`object.__proto__ === Object.prototype`)); | ||
assert.ok(vm.run(`object`).__proto__ === Object.prototype); | ||
assert.ok(vm.run(`func`) === sandbox.func); | ||
assert.ok(vm.run(`func instanceof Function`)); | ||
assert.ok(vm.run(`func`) instanceof Function); | ||
assert.ok(vm.run(`func.__proto__ === Function.prototype`)); | ||
assert.ok(vm.run(`func`).__proto__ === Function.prototype); | ||
assert.ok(vm.run(`new func() instanceof func`)); | ||
assert.ok(vm.run(`new func()`) instanceof sandbox.func); | ||
assert.ok(vm.run(`new func().__proto__ === func.prototype`)); | ||
assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype); | ||
assert.ok(vm.run(`buffer`) === sandbox.buffer); | ||
assert.ok(vm.run(`buffer instanceof Buffer`)); | ||
assert.ok(vm.run(`buffer`) instanceof Buffer); | ||
assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`)); | ||
assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype); | ||
assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`)); | ||
assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer); | ||
``` | ||
@@ -152,8 +188,7 @@ | ||
Allowing `buffer` to be required inside NodeVM may crash your app with `TypeError: Invalid non-string/buffer chunk` errors (reported [here](https://github.com/patriksimek/vm2/issues/22) and [here](https://github.com/patriksimek/vm2/issues/7)). To prevent `buffer` from loading, disable `require` option or remove `buffer` from list of whitelisted native modules. Keep in mind that modules like `fs` or `stream` do require `buffer` internally. | ||
* It is not possible to define class that extends proxied class. | ||
<a name="license" /> | ||
## License | ||
Copyright (c) 2014-2015 Patrik Simek | ||
Copyright (c) 2014-2016 Patrik Simek | ||
@@ -167,1 +202,8 @@ The MIT License | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
[npm-image]: https://img.shields.io/npm/v/vm2.svg?style=flat-square | ||
[npm-url]: https://www.npmjs.com/package/vm2 | ||
[quality-image]: http://npm.packagequality.com/shield/vm2.svg?style=flat-square | ||
[quality-url]: http://packagequality.com/#?package=vm2 | ||
[travis-image]: https://img.shields.io/travis/patriksimek/vm2/master.svg?style=flat-square&label=unit | ||
[travis-url]: https://travis-ci.org/patriksimek/vm2 |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
1
203
64196
12
1657
1