API/api.medcify.app/node_modules/vm2/lib/nodevm.js

504 lines
16 KiB
JavaScript
Raw Normal View History

2022-09-26 06:11:44 +00:00
'use strict';
/**
* This callback will be called to resolve a module if it couldn't be found.
*
* @callback resolveCallback
* @param {string} moduleName - Name of the module used to resolve.
* @param {string} dirname - Name of the current directory.
* @return {(string|undefined)} The file or directory to use to load the requested module.
*/
/**
* This callback will be called to require a module instead of node's require.
*
* @callback customRequire
* @param {string} moduleName - Name of the module requested.
* @return {*} The required module object.
*/
const fs = require('fs');
const pa = require('path');
const {
Script
} = require('vm');
const {
VMError
} = require('./bridge');
const {
VMScript,
MODULE_PREFIX,
STRICT_MODULE_PREFIX,
MODULE_SUFFIX
} = require('./script');
const {
transformer
} = require('./transformer');
const {
VM
} = require('./vm');
const {
resolverFromOptions
} = require('./resolver-compat');
const objectDefineProperty = Object.defineProperty;
const objectDefineProperties = Object.defineProperties;
/**
* Host objects
*
* @private
*/
const HOST = Object.freeze({
__proto__: null,
version: parseInt(process.versions.node.split('.')[0]),
process,
console,
setTimeout,
setInterval,
setImmediate,
clearTimeout,
clearInterval,
clearImmediate
});
/**
* Compile a script.
*
* @private
* @param {string} filename - Filename of the script.
* @param {string} script - Script.
* @return {vm.Script} The compiled script.
*/
function compileScript(filename, script) {
return new Script(script, {
__proto__: null,
filename,
displayErrors: false
});
}
let cacheSandboxScript = null;
let cacheMakeNestingScript = null;
const NESTING_OVERRIDE = Object.freeze({
__proto__: null,
vm2: vm2NestingLoader
});
/**
* Event caused by a <code>console.debug</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.debug"
* @type {...*}
*/
/**
* Event caused by a <code>console.log</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.log"
* @type {...*}
*/
/**
* Event caused by a <code>console.info</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.info"
* @type {...*}
*/
/**
* Event caused by a <code>console.warn</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.warn"
* @type {...*}
*/
/**
* Event caused by a <code>console.error</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.error"
* @type {...*}
*/
/**
* Event caused by a <code>console.dir</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.dir"
* @type {...*}
*/
/**
* Event caused by a <code>console.trace</code> call if <code>options.console="redirect"</code> is specified.
*
* @public
* @event NodeVM."console.trace"
* @type {...*}
*/
/**
* Class NodeVM.
*
* @public
* @extends {VM}
* @extends {EventEmitter}
*/
class NodeVM extends VM {
/**
* Create a new NodeVM instance.<br>
*
* Unlike VM, NodeVM lets you use require same way like in regular node.<br>
*
* However, it does not use the timeout.
*
* @public
* @param {Object} [options] - VM options.
* @param {Object} [options.sandbox] - Objects that will be copied into the global object of the sandbox.
* @param {(string|compileCallback)} [options.compiler="javascript"] - The compiler to use.
* @param {boolean} [options.eval=true] - Allow the dynamic evaluation of code via eval(code) or Function(code)().<br>
* Only available for node v10+.
* @param {boolean} [options.wasm=true] - Allow to run wasm code.<br>
* Only available for node v10+.
* @param {("inherit"|"redirect"|"off")} [options.console="inherit"] - Sets the behavior of the console in the sandbox.
* <code>inherit</code> to enable console, <code>redirect</code> to redirect to events, <code>off</code> to disable console.
* @param {Object|boolean} [options.require=false] - Allow require inside the sandbox.
* @param {(boolean|string[]|Object)} [options.require.external=false] - <b>WARNING: When allowing require the option <code>options.require.root</code>
* should be set to restrict the script from requiring any module. Values can be true, an array of allowed external modules or an object.
* @param {(string[])} [options.require.external.modules] - Array of allowed external modules. Also supports wildcards, so specifying ['@scope/*-ver-??],
* for instance, will allow using all modules having a name of the form @scope/something-ver-aa, @scope/other-ver-11, etc.
* @param {boolean} [options.require.external.transitive=false] - Boolean which indicates if transitive dependencies of external modules are allowed.
* @param {string[]} [options.require.builtin=[]] - Array of allowed built-in modules, accepts ["*"] for all.
* @param {(string|string[])} [options.require.root] - Restricted path(s) where local modules can be required. If omitted every path is allowed.
* @param {Object} [options.require.mock] - Collection of mock modules (both external or built-in).
* @param {("host"|"sandbox")} [options.require.context="host"] - <code>host</code> to require modules in host and proxy them to sandbox.
* <code>sandbox</code> to load, compile and require modules in sandbox.
* Builtin modules except <code>events</code> always required in host and proxied to sandbox.
* @param {string[]} [options.require.import] - Array of modules to be loaded into NodeVM on start.
* @param {resolveCallback} [options.require.resolve] - An additional lookup function in case a module wasn't
* found in one of the traditional node lookup paths.
* @param {customRequire} [options.require.customRequire=require] - Custom require to require host and built-in modules.
* @param {boolean} [options.nesting=false] -
* <b>WARNING: Allowing this is a security risk as scripts can create a NodeVM which can require any host module.</b>
* Allow nesting of VMs.
* @param {("commonjs"|"none")} [options.wrapper="commonjs"] - <code>commonjs</code> to wrap script into CommonJS wrapper,
* <code>none</code> to retrieve value returned by the script.
* @param {string[]} [options.sourceExtensions=["js"]] - Array of file extensions to treat as source code.
* @param {string[]} [options.argv=[]] - Array of arguments passed to <code>process.argv</code>.
* This object will not be copied and the script can change this object.
* @param {Object} [options.env={}] - Environment map passed to <code>process.env</code>.
* This object will not be copied and the script can change this object.
* @param {boolean} [options.strict=false] - If modules should be loaded in strict mode.
* @throws {VMError} If the compiler is unknown.
*/
constructor(options = {}) {
const {
compiler,
eval: allowEval,
wasm,
console: consoleType = 'inherit',
require: requireOpts = false,
nesting = false,
wrapper = 'commonjs',
sourceExtensions = ['js'],
argv,
env,
strict = false,
sandbox
} = options;
// Throw this early
if (sandbox && 'object' !== typeof sandbox) {
throw new VMError('Sandbox must be an object.');
}
super({__proto__: null, compiler: compiler, eval: allowEval, wasm});
// This is only here for backwards compatibility.
objectDefineProperty(this, 'options', {__proto__: null, value: {
console: consoleType,
require: requireOpts,
nesting,
wrapper,
sourceExtensions,
strict
}});
const resolver = resolverFromOptions(this, requireOpts, nesting && NESTING_OVERRIDE, this._compiler);
objectDefineProperty(this, '_resolver', {__proto__: null, value: resolver});
if (!cacheSandboxScript) {
cacheSandboxScript = compileScript(`${__dirname}/setup-node-sandbox.js`,
`(function (host, data) { ${fs.readFileSync(`${__dirname}/setup-node-sandbox.js`, 'utf8')}\n})`);
}
const closure = this._runScript(cacheSandboxScript);
const extensions = {
__proto__: null
};
const loadJS = (mod, filename) => resolver.loadJS(this, mod, filename);
for (let i = 0; i < sourceExtensions.length; i++) {
extensions['.' + sourceExtensions[i]] = loadJS;
}
if (!extensions['.json']) extensions['.json'] = (mod, filename) => resolver.loadJSON(this, mod, filename);
if (!extensions['.node']) extensions['.node'] = (mod, filename) => resolver.loadNode(this, mod, filename);
this.readonly(HOST);
this.readonly(resolver);
this.readonly(this);
const {
Module,
jsonParse,
createRequireForModule,
requireImpl
} = closure(HOST, {
__proto__: null,
argv,
env,
console: consoleType,
vm: this,
resolver,
extensions
});
objectDefineProperties(this, {
__proto__: null,
_Module: {__proto__: null, value: Module},
_jsonParse: {__proto__: null, value: jsonParse},
_createRequireForModule: {__proto__: null, value: createRequireForModule},
_requireImpl: {__proto__: null, value: requireImpl},
_cacheRequireModule: {__proto__: null, value: null, writable: true}
});
resolver.init(this);
// prepare global sandbox
if (sandbox) {
this.setGlobals(sandbox);
}
if (requireOpts && requireOpts.import) {
if (Array.isArray(requireOpts.import)) {
for (let i = 0, l = requireOpts.import.length; i < l; i++) {
this.require(requireOpts.import[i]);
}
} else {
this.require(requireOpts.import);
}
}
}
/**
* @ignore
* @deprecated Just call the method yourself like <code>method(args);</code>
* @param {function} method - Function to invoke.
* @param {...*} args - Arguments to pass to the function.
* @return {*} Return value of the function.
* @todo Can we remove this function? It even had a bug that would use args as this parameter.
* @throws {*} Rethrows anything the method throws.
* @throws {VMError} If method is not a function.
* @throws {Error} If method is a class.
*/
call(method, ...args) {
if ('function' === typeof method) {
return method(...args);
} else {
throw new VMError('Unrecognized method type.');
}
}
/**
* Require a module in VM and return it's exports.
*
* @public
* @param {string} module - Module name.
* @return {*} Exported module.
* @throws {*} If the module couldn't be found or loading it threw an error.
*/
require(module) {
const path = this._resolver.pathResolve('.');
let mod = this._cacheRequireModule;
if (!mod || mod.path !== path) {
const filename = this._resolver.pathConcat(path, '/vm.js');
mod = new (this._Module)(filename, path);
this._resolver.registerModule(mod, filename, path, null, false);
this._cacheRequireModule = mod;
}
return this._requireImpl(mod, module, true);
}
/**
* 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|VMScript)} code - Code to run.
* @param {(string|Object)} [options] - Options map or filename.
* @param {string} [options.filename="vm.js"] - Filename that shows up in any stack traces produced from this script.<br>
* This is only used if code is a String.
* @param {boolean} [options.strict] - If modules should be loaded in strict mode. Defaults to NodeVM options.
* @param {("commonjs"|"none")} [options.wrapper] - <code>commonjs</code> to wrap script into CommonJS wrapper,
* <code>none</code> to retrieve value returned by the script. Defaults to NodeVM options.
* @return {*} Result of executed code.
* @throws {SyntaxError} If there is a syntax error in the script.
* @throws {*} If the script execution terminated with an exception it is propagated.
* @fires NodeVM."console.debug"
* @fires NodeVM."console.log"
* @fires NodeVM."console.info"
* @fires NodeVM."console.warn"
* @fires NodeVM."console.error"
* @fires NodeVM."console.dir"
* @fires NodeVM."console.trace"
*/
run(code, options) {
let script;
let filename;
if (typeof options === 'object') {
filename = options.filename;
} else {
filename = options;
options = {__proto__: null};
}
const {
strict = this.options.strict,
wrapper = this.options.wrapper,
module: customModule,
require: customRequire,
dirname: customDirname = null
} = options;
let sandboxModule = customModule;
let dirname = customDirname;
if (code instanceof VMScript) {
script = strict ? code._compileNodeVMStrict() : code._compileNodeVM();
if (!sandboxModule) {
const resolvedFilename = this._resolver.pathResolve(code.filename);
dirname = this._resolver.pathDirname(resolvedFilename);
sandboxModule = new (this._Module)(resolvedFilename, dirname);
this._resolver.registerModule(sandboxModule, resolvedFilename, dirname, null, false);
}
} else {
const unresolvedFilename = filename || 'vm.js';
if (!sandboxModule) {
if (filename) {
const resolvedFilename = this._resolver.pathResolve(filename);
dirname = this._resolver.pathDirname(resolvedFilename);
sandboxModule = new (this._Module)(resolvedFilename, dirname);
this._resolver.registerModule(sandboxModule, resolvedFilename, dirname, null, false);
} else {
sandboxModule = new (this._Module)(null, null);
sandboxModule.id = unresolvedFilename;
}
}
const prefix = strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX;
let scriptCode = this._compiler(code, unresolvedFilename);
scriptCode = transformer(null, scriptCode, false, false, unresolvedFilename).code;
script = new Script(prefix + scriptCode + MODULE_SUFFIX, {
__proto__: null,
filename: unresolvedFilename,
displayErrors: false
});
}
const closure = this._runScript(script);
const usedRequire = customRequire || this._createRequireForModule(sandboxModule);
const ret = Reflect.apply(closure, this.sandbox, [sandboxModule.exports, usedRequire, sandboxModule, filename, dirname]);
return wrapper === 'commonjs' ? sandboxModule.exports : ret;
}
/**
* Create NodeVM and run code inside it.
*
* @public
* @static
* @param {string} script - Code to execute.
* @param {string} [filename] - File name (used in stack traces only).
* @param {Object} [options] - VM options.
* @param {string} [options.filename] - File name (used in stack traces only). Used if <code>filename</code> is omitted.
* @return {*} Result of executed code.
* @see {@link NodeVM} for the options.
* @throws {SyntaxError} If there is a syntax error in the script.
* @throws {*} If the script execution terminated with an exception it is propagated.
*/
static code(script, filename, options) {
let unresolvedFilename;
if (filename != null) {
if ('object' === typeof filename) {
options = filename;
unresolvedFilename = options.filename;
} else if ('string' === typeof filename) {
unresolvedFilename = filename;
} else {
throw new VMError('Invalid arguments.');
}
} else if ('object' === typeof options) {
unresolvedFilename = options.filename;
}
if (arguments.length > 3) {
throw new VMError('Invalid number of arguments.');
}
const resolvedFilename = typeof unresolvedFilename === 'string' ? pa.resolve(unresolvedFilename) : undefined;
return new NodeVM(options).run(script, resolvedFilename);
}
/**
* Create NodeVM and run script from file inside it.
*
* @public
* @static
* @param {string} filename - Filename of file to load and execute in a NodeVM.
* @param {Object} [options] - NodeVM options.
* @return {*} Result of executed code.
* @see {@link NodeVM} for the options.
* @throws {Error} If filename is not a valid filename.
* @throws {SyntaxError} If there is a syntax error in the script.
* @throws {*} If the script execution terminated with an exception it is propagated.
*/
static file(filename, options) {
const resolvedFilename = pa.resolve(filename);
if (!fs.existsSync(resolvedFilename)) {
throw new VMError(`Script '${filename}' not found.`);
}
if (fs.statSync(resolvedFilename).isDirectory()) {
throw new VMError('Script must be file, got directory.');
}
return new NodeVM(options).run(fs.readFileSync(resolvedFilename, 'utf8'), resolvedFilename);
}
}
function vm2NestingLoader(resolver, vm, id) {
if (!cacheMakeNestingScript) {
cacheMakeNestingScript = compileScript('nesting.js', '(vm, nodevm) => ({VM: vm, NodeVM: nodevm})');
}
const makeNesting = vm._runScript(cacheMakeNestingScript);
return makeNesting(vm.readonly(VM), vm.readonly(NodeVM));
}
exports.NodeVM = NodeVM;