From 2353ce60351c50379b8d1daab05812c4db634162 Mon Sep 17 00:00:00 2001 From: XmiliaH Date: Tue, 8 Feb 2022 14:08:09 +0100 Subject: [PATCH 1/2] Internal changes and security improvements --- .editorconfig | 3 + .eslintignore | 4 +- CHANGELOG.md | 10 +- LICENSE.md | 2 +- README.md | 174 +-- index.d.ts | 20 +- lib/bridge.js | 957 ++++++++++++++ lib/compiler.js | 87 ++ lib/contextify.js | 1153 ----------------- lib/events.js | 977 ++++++++++++++ lib/fixasync.js | 83 -- lib/helpers.js | 12 - lib/main.js | 1391 +------------------- lib/nodevm.js | 497 ++++++++ lib/resolver-compat.js | 317 +++++ lib/resolver.js | 882 +++++++++++++ lib/sandbox.js | 686 ---------- lib/script.js | 388 ++++++ lib/setup-node-sandbox.js | 463 +++++++ lib/setup-sandbox.js | 452 +++++++ lib/transformer.js | 113 ++ lib/vm.js | 539 ++++++++ package-lock.json | 1909 +++++++++++++++++++++++++++- package.json | 8 +- test/helpers.js | 14 - test/node_modules/require/index.js | 1 + test/nodevm.js | 93 +- test/vm.js | 173 ++- 28 files changed, 7932 insertions(+), 3476 deletions(-) create mode 100644 lib/bridge.js create mode 100644 lib/compiler.js delete mode 100644 lib/contextify.js create mode 100644 lib/events.js delete mode 100644 lib/fixasync.js delete mode 100644 lib/helpers.js create mode 100644 lib/nodevm.js create mode 100644 lib/resolver-compat.js create mode 100644 lib/resolver.js delete mode 100644 lib/sandbox.js create mode 100644 lib/script.js create mode 100644 lib/setup-node-sandbox.js create mode 100644 lib/setup-sandbox.js create mode 100644 lib/transformer.js create mode 100644 lib/vm.js delete mode 100644 test/helpers.js create mode 100644 test/node_modules/require/index.js diff --git a/.editorconfig b/.editorconfig index 42060ef..2f76435 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,6 @@ indent_size = 4 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true + +[/lib/events.js] +indent_size = 2 diff --git a/.eslintignore b/.eslintignore index 6f00010..7e0189b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,3 @@ -/test.js \ No newline at end of file +/test.js +/node-* +/lib/events.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3725fdc..09f0d25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ +v3.9.6 (TBD) +------------------- +[fix] Security fixes (XmiliaH) + v3.9.5 (2021-10-17) ------------------- -[new] Editor config (aubelsb2) -[fix] Fix for Promise.then breaking -[fix] Fix for missing properties on CallSite +[new] Editor config (aubelsb2) +[fix] Fix for Promise.then breaking +[fix] Fix for missing properties on CallSite v3.9.4 (2021-10-12) ------------------- diff --git a/LICENSE.md b/LICENSE.md index 573ea1f..f0900b3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2021 Patrik Simek and contributors +Copyright (c) 2014-2022 Patrik Simek and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 9ef8dad..3625d91 100644 --- a/README.md +++ b/README.md @@ -2,34 +2,34 @@ vm2 is a sandbox that can run untrusted code with whitelisted Node's built-in modules. Securely! -**Features** +## Features * Runs untrusted code securely in a single process with your code side by side -* Full control over sandbox's console output -* Sandbox has limited access to process's methods -* Sandbox can require modules (builtin and external) -* You can limit access to certain (or all) builtin modules +* Full control over the sandbox's console output +* The sandbox has limited access to the process's methods +* It is possible to require modules (built-in and external) from the sandbox +* You can limit access to certain (or all) built-in modules * You can securely call methods and exchange data and callbacks between sandboxes * Is immune to all known methods of attacks -* Transpilers support +* Transpiler support -**How does it work** +## How does it work -* It uses internal VM module to create secure context -* It uses [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to prevent escaping the sandbox -* It overrides builtin require to control access to modules +* It uses the internal VM module to create a secure context. +* It uses [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to prevent escaping from the sandbox. +* It overrides the built-in require to control access to modules. -**What is the difference between Node's vm and vm2?** +## What is the difference between Node's vm and vm2? Try it yourself: -```javascript +```js const vm = require('vm'); vm.runInNewContext('this.constructor.constructor("return process")().exit()'); console.log('Never gets executed.'); ``` -```javascript +```js const {VM} = require('vm2'); new VM().run('this.constructor.constructor("return process")().exit()'); // Throws ReferenceError: process is not defined @@ -37,24 +37,27 @@ new VM().run('this.constructor.constructor("return process")().exit()'); ## Installation -**IMPORTANT**: Requires Node.js 6 or newer. +**IMPORTANT**: VM2 requires Node.js 6 or newer. - npm install vm2 +```sh +npm install vm2 +``` ## Quick Example -```javascript +```js const {VM} = require('vm2'); const vm = new VM(); vm.run(`process.exit()`); // TypeError: process.exit is not a function ``` -```javascript +```js const {NodeVM} = require('vm2'); const vm = new NodeVM({ require: { - external: true + external: true, + root: './' } }); @@ -63,9 +66,9 @@ vm.run(` request('http://www.google.com', function (error, response, body) { console.error(error); if (!error && response.statusCode == 200) { - console.log(body) // Show the HTML for the Google homepage. + console.log(body); // Show the HTML for the Google homepage. } - }) + }); `, 'vm.js'); ``` @@ -86,61 +89,62 @@ vm.run(` ## VM -VM is a simple sandbox, without `require` feature, to synchronously run an untrusted code. Only JavaScript built-in objects + Buffer are available. Scheduling functions (`setInterval`, `setTimeout` and `setImmediate`) are not available by default. +VM is a simple sandbox to synchronously run untrusted code without the `require` feature. Only JavaScript built-in objects and Node's `Buffer` are available. Scheduling functions (`setInterval`, `setTimeout` and `setImmediate`) are not available by default. **Options:** -* `timeout` - Script timeout in milliseconds. +* `timeout` - Script timeout in milliseconds. **WARNING**: You might want to use this option together with `allowAsync=false`. Further, operating on returned objects from the sandbox can run arbitrary code and circumvent the timeout. One should test if the returned object is a primitive with `typeof` and fully discard it (doing logging or creating error messages with such an object might also run arbitrary code again) in the other case. * `sandbox` - VM's global object. * `compiler` - `javascript` (default) or `coffeescript` or custom compiler function. The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`. -* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc) will throw an `EvalError` (default: `true`). +* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc.) will throw an `EvalError` (default: `true`). * `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`). -* `fixAsync` - If set to `true` any attempt to run code using async will throw a `VMError` (default: `false`). +* `allowAsync` - If set to `false` any attempt to run code using `async` will throw a `VMError` (default: `true`). -**IMPORTANT**: Timeout is only effective on synchronous code you run through `run`. Timeout is NOT effective on any method returned by VM. There're some situations when timeout doesn't work - see [#244](https://github.com/patriksimek/vm2/pull/244). +**IMPORTANT**: Timeout is only effective on synchronous code that you run through `run`. Timeout does **NOT** work on any method returned by VM. There are some situations when timeout doesn't work - see [#244](https://github.com/patriksimek/vm2/pull/244). -```javascript +```js const {VM} = require('vm2'); const vm = new VM({ timeout: 1000, + allowAsync: false, sandbox: {} }); -vm.run("process.exit()"); // throws ReferenceError: process is not defined +vm.run('process.exit()'); // throws ReferenceError: process is not defined ``` You can also retrieve values from VM. -```javascript -let number = vm.run("1337"); // returns 1337 +```js +let number = vm.run('1337'); // returns 1337 ``` **TIP**: See tests for more usage examples. ## NodeVM -Unlike `VM`, `NodeVM` lets you require modules same way like in regular Node's context. +Unlike `VM`, `NodeVM` allows you to require modules in the same way that you would in the regular Node's context. **Options:** * `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 (which receives the code, and it's filepath). The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`. -* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc) will throw an `EvalError` (default: `true`). +* `compiler` - `javascript` (default) or `coffeescript` or custom compiler function (which receives the code, and it's file path). The library expects you to have coffee-script pre-installed if the compiler is set to `coffeescript`. +* `eval` - If set to `false` any calls to `eval` or function constructors (`Function`, `GeneratorFunction`, etc.) will throw an `EvalError` (default: `true`). * `wasm` - If set to `false` any attempt to compile a WebAssembly module will throw a `WebAssembly.CompileError` (default: `true`). * `sourceExtensions` - Array of file extensions to treat as source code (default: `['js']`). * `require` - `true` or object to enable `require` method (default: `false`). -* `require.external` - `true`, an array of allowed external modules or an object (default: `false`). -* `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. -* `require.external.transitive` - Boolean which indicates if transitive dependencies of external modules are allowed (default: `false`). -* `require.builtin` - Array of allowed builtin modules, accepts ["*"] for all (default: none). +* `require.external` - Values can be `true`, an array of allowed external modules, or an object (default: `false`). All paths matching `/node_modules/${any_allowed_external_module}/(?!/node_modules/)` are allowed to be required. +* `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. The `*` wildcard does not match path separators. +* `require.external.transitive` - Boolean which indicates if transitive dependencies of external modules are allowed (default: `false`). **WARNING**: When a module is required transitively, any module is then able to require it normally, even if this was not possible before it was loaded. +* `require.builtin` - Array of allowed built-in modules, accepts ["*"] for all (default: none). **WARNING**: "*" can be dangerous as new built-ins can be added. * `require.root` - Restricted path(s) 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. +* `require.mock` - Collection of mock modules (both external or built-in). +* `require.context` - `host` (default) to require modules in the host and proxy them into the sandbox. `sandbox` to load, compile, and require modules in the sandbox. Except for `events`, built-in modules are always required in the host and proxied into the sandbox. +* `require.import` - An array of modules to be loaded into NodeVM on start. * `require.resolve` - An additional lookup function in case a module wasn't found in one of the traditional node lookup paths. -* `nesting` - `true` to enable VMs nesting (default: `false`). +* `nesting` - **WARNING**: Allowing this is a security risk as scripts can create a NodeVM which can require any host module. `true` to enable VMs nesting (default: `false`). * `wrapper` - `commonjs` (default) to wrap script into CommonJS wrapper, `none` to retrieve value returned by the script. * `argv` - Array to be passed to `process.argv`. * `env` - Object to be passed to `process.env`. @@ -150,7 +154,7 @@ Unlike `VM`, `NodeVM` lets you require modules same way like in regular Node's c **REMEMBER**: The more modules you allow, the more fragile your sandbox becomes. -```javascript +```js const {NodeVM} = require('vm2'); const vm = new NodeVM({ @@ -159,10 +163,10 @@ const vm = new NodeVM({ require: { external: true, builtin: ['fs', 'path'], - root: "./", + root: './', mock: { fs: { - readFileSync() { return 'Nice try!'; } + readFileSync: () => 'Nice try!' } } } @@ -170,12 +174,12 @@ const vm = new NodeVM({ // Sync -let 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'); // Async -let functionWithCallbackInSandbox = vm.run("module.exports = function(who, callback) { callback('hello '+ who); }"); +let functionWithCallbackInSandbox = vm.run('module.exports = function(who, callback) { callback("hello "+ who); }'); functionWithCallbackInSandbox('world', (greeting) => { console.log(greeting); }); @@ -183,7 +187,7 @@ functionWithCallbackInSandbox('world', (greeting) => { When `wrapper` is set to `none`, `NodeVM` behaves more like `VM` for synchronous code. -```javascript +```js assert.ok(vm.run('return true') === true); ``` @@ -191,52 +195,52 @@ assert.ok(vm.run('return true') === true); ### Loading modules by relative path -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 if the script is a string. Filename then also shows up in any stack traces produced from the script. +To load modules by relative path, you must pass the full path of the script you're running as a second argument to vm's `run` method if the script is a string. The filename is then displayed in any stack traces generated by the script. -```javascript -vm.run("require('foobar')", "/data/myvmscript.js"); +```js +vm.run('require("foobar")', '/data/myvmscript.js'); ``` -If the script you are running is an VMScript, the path is given in the VMScript constructor. +If the script you are running is a VMScript, the path is given in the VMScript constructor. -```javascript -const script = new VMScript("require('foobar')", {filename: "/data/myvmscript.js"}); +```js +const script = new VMScript('require("foobar")', {filename: '/data/myvmscript.js'}); vm.run(script); ``` ## VMScript -You can increase performance by using pre-compiled scripts. The pre-compiled VMScript can be run later multiple times. It is important to note that the code is not bound to any VM (context); rather, it is bound before each run, just for that run. +You can increase performance by using precompiled scripts. The precompiled VMScript can be run multiple times. It is important to note that the code is not bound to any VM (context); rather, it is bound before each run, just for that run. -```javascript +```js const {VM, VMScript} = require('vm2'); const vm = new VM(); -const script = new VMScript("Math.random()"); +const script = new VMScript('Math.random()'); console.log(vm.run(script)); console.log(vm.run(script)); ``` -Works for both `VM` and `NodeVM`. +It works for both `VM` and `NodeVM`. -```javascript +```js const {NodeVM, VMScript} = require('vm2'); const vm = new NodeVM(); -const script = new VMScript("module.exports = Math.random()"); +const script = new VMScript('module.exports = Math.random()'); console.log(vm.run(script)); console.log(vm.run(script)); ``` -Code is compiled automatically first time you run it. You can compile the code anytime with `script.compile()`. Once the code is compiled, the method has no effect. +Code is compiled automatically the first time it runs. One can compile the code anytime with `script.compile()`. Once the code is compiled, the method has no effect. ## Error handling -Errors in code compilation and synchronous code execution can be handled by `try`/`catch`. Errors in asynchronous code execution can be handled by attaching `uncaughtException` event handler to Node's `process`. +Errors in code compilation and synchronous code execution can be handled by `try-catch`. Errors in asynchronous code execution can be handled by attaching `uncaughtException` event handler to Node's `process`. -```javascript +```js try { - var script = new VMScript("Math.random()").compile(); + var script = new VMScript('Math.random()').compile(); } catch (err) { console.error('Failed to compile script.', err); } @@ -249,21 +253,22 @@ try { process.on('uncaughtException', (err) => { console.error('Asynchronous error caught.', err); -}) +}); ``` ## Debugging a sandboxed code -You can debug/inspect code running in the sandbox as if it was running in a normal process. +You can debug or inspect code running in the sandbox as if it was running in a normal process. -- You can use breakpoints (requires you to specify a script file name) -- You can use `debugger` keyword. -- You can use step-in to step inside the code running in the sandbox. +* You can use breakpoints (which requires you to specify a script file name) +* You can use `debugger` keyword. +* You can use step-in to step inside the code running in the sandbox. -**Example** +### Example /tmp/main.js: -```javascript + +```js const {VM, VMScript} = require('.'); const fs = require('fs'); const file = `${__dirname}/sandbox.js`; @@ -275,21 +280,22 @@ new VM().run(script); ``` /tmp/sandbox.js -```javascript + +```js const foo = 'ahoj'; -// Debugger keyword works just fine anywhere. +// The debugger keyword works just fine everywhere. // Even without specifying a file name to the VMScript object. debugger; ``` ## Read-only objects (experimental) -To prevent sandboxed script to add/change/delete properties to/from the proxied objects, you can use `freeze` methods to make the object read-only. This is only effective inside VM. Frozen objects are affected deeply. Primitive types can not be frozen. +To prevent sandboxed scripts from adding, changing, or deleting properties from the proxied objects, you can use `freeze` methods to make the object read-only. This is only effective inside VM. Frozen objects are affected deeply. Primitive types cannot be frozen. **Example without using `freeze`:** -```javascript +```js const util = { add: (a, b) => a + b } @@ -304,25 +310,25 @@ console.log(util.add(1, 1)); // returns 0 **Example with using `freeze`:** -```javascript -const vm = new VM(); // Objects specified in sandbox can not be frozen. +```js +const vm = new VM(); // Objects specified in the sandbox cannot be frozen. vm.freeze(util, 'util'); // Second argument adds object to global. vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode. console.log(util.add(1, 1)); // returns 2 ``` -**IMPORTANT:** It is not possible to freeze objects that has already been proxied to the VM. +**IMPORTANT:** It is not possible to freeze objects that have already been proxied to the VM. ## Protected objects (experimental) -Unlike `freeze`, this method allows sandboxed script to add/modify/delete properties on object with one exception - it is not possible to attach functions. Sandboxed script is therefore not able to modify methods like `toJSON`, `toString` or `inspect`. +Unlike `freeze`, this method allows sandboxed scripts to add, change, or delete properties on objects, with one exception - it is not possible to attach functions. Sandboxed scripts are therefore not able to modify methods like `toJSON`, `toString` or `inspect`. -**IMPORTANT:** It is not possible to protect objects that has already been proxied to the VM. +**IMPORTANT:** It is not possible to protect objects that have already been proxied to the VM. ## Cross-sandbox relationships -```javascript +```js const assert = require('assert'); const {VM} = require('vm2'); @@ -362,19 +368,19 @@ assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer); ## CLI -Before you can use vm2 in command line, install it globally with `npm install vm2 -g`. +Before you can use vm2 in the command line, install it globally with `npm install vm2 -g`. -``` -$ vm2 ./script.js +```sh +vm2 ./script.js ``` ## Known Issues -* It is not possible to define class that extends proxied class. +* It is not possible to define a class that extends a proxied class. ## Deployment -1. Update the CHANGELOG +1. Update the `CHANGELOG.md` 2. Update the `package.json` version number 3. Commit the changes 4. Run `npm publish` diff --git a/index.d.ts b/index.d.ts index dacff55..be5983f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -21,6 +21,8 @@ export interface VMRequire { mock?: any; /* An additional lookup function in case a module wasn't found in one of the traditional node lookup paths. */ resolve?: (moduleName: string, parentDirname: string) => string; + /** Custom require to require host and built-in modules. */ + customRequire?: (id: string) => any; } /** @@ -56,8 +58,14 @@ export interface VMOptions { wasm?: boolean; /** * If set to `true` any attempt to run code using async will throw a `VMError` (default: `false`). + * @deprecated Use ``allowAsync` instead */ fixAsync?: boolean; + + /** + * If set to `false` any attempt to run code using async will throw a `VMError` (default: `true`). + */ + allowAsync?: boolean; } /** @@ -84,6 +92,8 @@ export interface NodeVMOptions extends VMOptions { * This object will not be copied and the script can change this object. */ env?: any; + /** Run modules in strict mode. Required modules are always strict. */ + strict?: boolean; } /** @@ -98,9 +108,7 @@ export class VM { /** Timeout to use for the run methods */ timeout?: number; /** Runs the code */ - run(js: string, path?: string): any; - /** Runs the VMScript object */ - run(script: VMScript): any; + run(script: string|VMScript, options?: string|{filename?: string}): any; /** Runs the code in the specific file */ runFile(filename: string): any; /** Loads all the values into the global object with the same names */ @@ -146,9 +154,7 @@ export class NodeVM extends EventEmitter implements VM { /** Only here because of implements VM. Does nothing. */ timeout?: number; /** Runs the code */ - run(js: string, path?: string): any; - /** Runs the VMScript object */ - run(script: VMScript): any; + run(js: string|VMScript, options?: string|{filename?: string, wrapper?: "commonjs" | "none", strict?: boolean}): any; /** Runs the code in the specific file */ runFile(filename: string): any; /** Loads all the values into the global object with the same names */ @@ -159,6 +165,8 @@ export class NodeVM extends EventEmitter implements VM { getGlobal(name: string): any; /** Freezes the object inside VM making it read-only. Not available for primitive values. */ freeze(object: any, name?: string): any; + /** Freezes the object inside VM making it read-only. Not available for primitive values. */ + readonly(object: any): any; /** Protects the object inside VM making impossible to set functions as it's properties. Not available for primitive values */ protect(object: any, name?: string): any; } diff --git a/lib/bridge.js b/lib/bridge.js new file mode 100644 index 0000000..520e5af --- /dev/null +++ b/lib/bridge.js @@ -0,0 +1,957 @@ +'use strict'; + +/** + * __ ___ ____ _ _ ___ _ _ ____ + * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| + * \ \ /\ / / _ \ | |_) | \| || || \| | | _ + * \ V V / ___ \| _ <| |\ || || |\ | |_| | + * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| + * + * This file is critical for vm2. It implements the bridge between the host and the sandbox. + * If you do not know exactly what you are doing, you should NOT edit this file. + * + * The file is loaded in the host and sandbox to handle objects in both directions. + * This is done to ensure that RangeErrors are from the correct context. + * The boundary between the sandbox and host might throw RangeErrors from both contexts. + * Therefore, thisFromOther and friends can handle objects from both domains. + * + * Method parameters have comments to tell from which context they came. + * + */ + +const globalsList = [ + 'Number', + 'String', + 'Boolean', + 'Date', + 'RegExp', + 'Map', + 'WeakMap', + 'Set', + 'WeakSet', + 'Promise', + 'Object', + 'Function' +]; + +const errorsList = [ + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'EvalError', + 'URIError', + 'Error' +]; + +const OPNA = 'Operation not allowed on contextified object.'; + +const thisGlobalPrototypes = { + __proto__: null, + Array: Array.prototype +}; + +for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +const { + getPrototypeOf: thisReflectGetPrototypeOf, + setPrototypeOf: thisReflectSetPrototypeOf, + defineProperty: thisReflectDefineProperty, + deleteProperty: thisReflectDeleteProperty, + getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, + isExtensible: thisReflectIsExtensible, + preventExtensions: thisReflectPreventExtensions, + apply: thisReflectApply, + construct: thisReflectConstruct, + set: thisReflectSet, + get: thisReflectGet, + has: thisReflectHas, + ownKeys: thisReflectOwnKeys, + enumerate: thisReflectEnumerate, +} = Reflect; + +const thisObject = Object; +const { + freeze: thisObjectFreeze, + prototype: thisObjectPrototype +} = thisObject; +const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; +const ThisProxy = Proxy; +const ThisWeakMap = WeakMap; +const { + get: thisWeakMapGet, + set: thisWeakMapSet +} = ThisWeakMap.prototype; +const ThisMap = Map; +const thisMapGet = ThisMap.prototype.get; +const thisMapSet = ThisMap.prototype.set; +const thisFunction = Function; +const thisFunctionBind = thisFunction.prototype.bind; +const thisArrayIsArray = Array.isArray; +const thisErrorCaptureStackTrace = Error.captureStackTrace; + +const thisSymbolToString = Symbol.prototype.toString; +const thisSymbolToStringTag = Symbol.toStringTag; + +/** + * VMError. + * + * @public + * @extends {Error} + */ +class VMError extends Error { + + /** + * Create VMError instance. + * + * @public + * @param {string} message - Error message. + * @param {string} code - Error code. + */ + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + thisErrorCaptureStackTrace(this, this.constructor); + } +} + +thisGlobalPrototypes['VMError'] = VMError.prototype; + +function thisUnexpected() { + return new VMError('Unexpected'); +} + +if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); + +function thisSafeGetOwnPropertyDescriptor(obj, key) { + const desc = thisReflectGetOwnPropertyDescriptor(obj, key); + if (!desc) return desc; + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + return desc; +} + +function thisThrowCallerCalleeArgumentsAccess(key) { + 'use strict'; + thisThrowCallerCalleeArgumentsAccess[key]; + return thisUnexpected(); +} + +function thisIdMapping(factory, other) { + return other; +} + +const thisThrowOnKeyAccessHandler = thisObjectFreeze({ + __proto__: null, + get(target, key, receiver) { + if (typeof key === 'symbol') { + key = thisReflectApply(thisSymbolToString, key, []); + } + throw new VMError(`Unexpected access to key '${key}'`); + } +}); + +const emptyForzenObject = thisObjectFreeze({ + __proto__: null +}); + +const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); + +function SafeBase() {} + +if (!thisReflectDefineProperty(SafeBase, 'prototype', { + __proto__: null, + value: thisThrowOnKeyAccess +})) throw thisUnexpected(); + +function SHARED_FUNCTION() {} + +const TEST_PROXY_HANDLER = thisObjectFreeze({ + __proto__: thisThrowOnKeyAccess, + construct() { + return this; + } +}); + +function thisIsConstructor(obj) { + // Note: obj@any(unsafe) + const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); + try { + // eslint-disable-next-line no-new + new Func(); + return true; + } catch (e) { + return false; + } +} + +function thisCreateTargetObject(obj, proto) { + // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) + let base; + if (typeof obj === 'function') { + if (thisIsConstructor(obj)) { + // Bind the function since bound functions do not have a prototype property. + base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); + } else { + base = () => {}; + } + } else if (thisArrayIsArray(obj)) { + base = []; + } else { + return {__proto__: proto}; + } + if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); + return base; +} + +function createBridge(otherInit, registerProxy) { + + const mappingOtherToThis = new ThisWeakMap(); + const protoMappings = new ThisMap(); + const protoName = new ThisMap(); + + function thisAddProtoMapping(proto, other, name) { + // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => thisProxyOther(factory, object, proto)]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + + function thisAddProtoMappingFactory(protoFactory, other, name) { + // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + let proto; + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => { + if (!proto) { + proto = protoFactory(); + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + return thisProxyOther(factory, object, proto); + }]); + } + + const result = { + __proto__: null, + globalPrototypes: thisGlobalPrototypes, + safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, + fromArguments: thisFromOtherArguments, + from: thisFromOther, + fromWithFactory: thisFromOtherWithFactory, + ensureThis: thisEnsureThis, + mapping: mappingOtherToThis, + connect: thisConnect, + reflectSet: thisReflectSet, + reflectGet: thisReflectGet, + reflectDefineProperty: thisReflectDefineProperty, + reflectDeleteProperty: thisReflectDeleteProperty, + reflectApply: thisReflectApply, + reflectConstruct: thisReflectConstruct, + reflectHas: thisReflectHas, + reflectOwnKeys: thisReflectOwnKeys, + reflectEnumerate: thisReflectEnumerate, + reflectGetPrototypeOf: thisReflectGetPrototypeOf, + reflectIsExtensible: thisReflectIsExtensible, + reflectPreventExtensions: thisReflectPreventExtensions, + objectHasOwnProperty: thisObjectHasOwnProperty, + weakMapSet: thisWeakMapSet, + addProtoMapping: thisAddProtoMapping, + addProtoMappingFactory: thisAddProtoMappingFactory, + defaultFactory, + protectedFactory, + readonlyFactory, + VMError + }; + + const isHost = typeof otherInit !== 'object'; + + if (isHost) { + otherInit = otherInit(result, registerProxy); + } + + result.other = otherInit; + + const { + globalPrototypes: otherGlobalPrototypes, + safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, + fromArguments: otherFromThisArguments, + from: otherFromThis, + mapping: mappingThisToOther, + reflectSet: otherReflectSet, + reflectGet: otherReflectGet, + reflectDefineProperty: otherReflectDefineProperty, + reflectDeleteProperty: otherReflectDeleteProperty, + reflectApply: otherReflectApply, + reflectConstruct: otherReflectConstruct, + reflectHas: otherReflectHas, + reflectOwnKeys: otherReflectOwnKeys, + reflectEnumerate: otherReflectEnumerate, + reflectGetPrototypeOf: otherReflectGetPrototypeOf, + reflectIsExtensible: otherReflectIsExtensible, + reflectPreventExtensions: otherReflectPreventExtensions, + objectHasOwnProperty: otherObjectHasOwnProperty, + weakMapSet: otherWeakMapSet + } = otherInit; + + function thisOtherHasOwnProperty(object, key) { + // Note: object@other(safe) key@prim throws@this(unsafe) + try { + return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + } + + function thisDefaultGet(handler, object, key, desc) { + // Note: object@other(unsafe) key@prim desc@other(safe) + let ret; // @other(unsafe) + if (desc.get || desc.set) { + const getter = desc.get; + if (!getter) return undefined; + try { + ret = otherReflectApply(getter, object, [key]); + } catch (e) { + throw thisFromOther(e); + } + } else { + ret = desc.value; + } + return handler.fromOtherWithContext(ret); + } + + function otherFromThisIfAvailable(to, from, key) { + // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) + if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; + try { + to[key] = otherFromThis(from[key]); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return true; + } + + class BaseHandler extends SafeBase { + + constructor(object) { + // Note: object@other(unsafe) throws@this(unsafe) + super(); + this.object = object; + } + + getFactory() { + return defaultFactory; + } + + fromOtherWithContext(other) { + // Note: other@other(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(this.getFactory(), other); + } + + doPreventExtensions(target, object, factory) { + // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) + let keys; // @other(safe-array-of-prim) + try { + keys = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; // @prim + let desc; + try { + desc = otherSafeGetOwnPropertyDescriptor(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + if (!desc) continue; + if (!desc.configurable) { + const current = thisSafeGetOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = this.fromOtherWithContext(desc.get); + desc.set = this.fromOtherWithContext(desc.set); + } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { + desc.value = null; + } else { + desc.value = this.fromOtherWithContext(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); + } + if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + switch (key) { + case 'constructor': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + const proto = thisReflectGetPrototypeOf(target); + return proto === null ? undefined : proto.constructor; + } + case '__proto__': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + return thisReflectGetPrototypeOf(target); + } + case thisSymbolToStringTag: + if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { + const proto = thisReflectGetPrototypeOf(target); + const name = thisReflectApply(thisMapGet, protoName, [proto]); + if (name) return name; + } + break; + case 'arguments': + case 'caller': + case 'callee': + if (thisOtherHasOwnProperty(object, key)) throw thisThrowCallerCalleeArgumentsAccess(key); + break; + } + let ret; // @other(unsafe) + try { + ret = otherReflectGet(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return this.fromOtherWithContext(ret); + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { + return this.setPrototypeOf(target, value); + } + try { + value = otherFromThis(value); + return otherReflectSet(object, key, value) === true; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + } + + getPrototypeOf(target) { + // Note: target@this(unsafe) + return thisReflectGetPrototypeOf(target); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + throw new VMError(OPNA); + } + + apply(target, context, args) { + // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) + const object = this.object; // @other(unsafe) + let ret; // @other(unsafe) + try { + context = otherFromThis(context); + args = otherFromThisArguments(args); + ret = otherReflectApply(object, context, args); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return thisFromOther(ret); + } + + construct(target, args, newTarget) { + // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + let ret; // @other(unsafe) + try { + args = otherFromThisArguments(args); + ret = otherReflectConstruct(object, args); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); + } + + getOwnPropertyDescriptorDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) + const object = this.object; // @other(unsafe) + if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; + return desc; + } + + getOwnPropertyDescriptor(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.object; // @other(unsafe) + let desc; // @other(safe) + try { + desc = otherSafeGetOwnPropertyDescriptor(object, prop); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + + desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); + + if (!desc) return undefined; + + let thisDesc; + if (desc.get || desc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(desc.get), + set: this.fromOtherWithContext(desc.set), + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(desc.value), + writable: desc.writable === true, + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } + if (!thisDesc.configurable) { + const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); + if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + } + return thisDesc; + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + return desc; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + + desc = this.definePropertyDesc(target, prop, desc); + + if (!desc) return false; + + let otherDesc = {__proto__: null}; + let hasFunc = true; + let hasValue = true; + let hasBasic = true; + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); + + try { + if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; + if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { + otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); + } + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + + if (!otherDesc.configurable) { + let thisDesc; + if (otherDesc.get || otherDesc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(otherDesc.get), + set: this.fromOtherWithContext(otherDesc.set), + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(otherDesc.value), + writable: otherDesc.writable, + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + return true; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.object; // @other(unsafe) + try { + return otherReflectDeleteProperty(object, prop) === true; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + } + + has(target, key) { + // Note: target@this(unsafe) key@prim throws@this(unsafe) + const object = this.object; // @other(unsafe) + try { + return otherReflectHas(object, key) === true; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + try { + if (otherReflectIsExtensible(object)) return true; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return false; + } + + ownKeys(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return thisFromOther(res); + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + try { + if (!otherReflectPreventExtensions(object)) return false; + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return true; + } + + enumerate(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectEnumerate(object); + } catch (e) { // @other(unsafe) + throw thisFromOther(e); + } + return this.fromOtherWithContext(res); + } + + } + + function defaultFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new BaseHandler(object); + } + + class ProtectedHandler extends BaseHandler { + + getFactory() { + return protectedFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + if (typeof value === 'function') { + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }) === true; + } + return super.set(target, key, value, receiver); + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; + return desc; + } + + } + + function protectedFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ProtectedHandler(object); + } + + class ReadOnlyHandler extends BaseHandler { + + getFactory() { + return readonlyFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + return false; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + return false; + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + } + + function readonlyFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ReadOnlyHandler(object); + } + + class ReadOnlyMockHandler extends ReadOnlyHandler { + + constructor(object, mock) { + // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) + super(object); + this.mock = mock; + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.object; // @other(unsafe) + const mock = this.mock; + if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { + return mock[key]; + } + return super.get(target, key, receiver); + } + + } + + function thisFromOther(other) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(defaultFactory, other); + } + + function thisProxyOther(factory, other, proto) { + const target = thisCreateTargetObject(other, proto); + const handler = factory(other); + const proxy = new ThisProxy(target, handler); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); + registerProxy(proxy, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + if (!isHost) { + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); + return proxy; + } + const proxy2 = new ThisProxy(proxy, emptyForzenObject); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); + registerProxy(proxy2, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); + return proxy2; + } + + function thisEnsureThis(other) { + const type = typeof other; + switch (type) { + case 'object': + case 'function': + if (other === null) { + return null; + } else { + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { + return other; + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; + } + + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherWithFactory(factory, other, proto) { + for (let loop = 0; loop < 10; loop++) { + const type = typeof other; + switch (type) { + case 'object': + case 'function': + if (other === null) { + return null; + } else { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + other = e; + break; + } + } + return thisProxyOther(factory, other, thisObjectPrototype); + } + + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + factory = defaultFactory; + proto = undefined; + } + throw new VMError('Exception recursion depth'); + } + + function thisFromOtherArguments(args) { + // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) + const arr = []; + for (let i = 0; i < args.length; i++) { + const value = thisFromOther(args[i]); + thisReflectDefineProperty(arr, i, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + return arr; + } + + function thisConnect(obj, other) { + // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); + } + + thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); + + for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, key); + } + + for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, 'Error'); + } + + thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); + + result.BaseHandler = BaseHandler; + result.ProtectedHandler = ProtectedHandler; + result.ReadOnlyHandler = ReadOnlyHandler; + result.ReadOnlyMockHandler = ReadOnlyMockHandler; + + return result; +} + +exports.createBridge = createBridge; +exports.VMError = VMError; diff --git a/lib/compiler.js b/lib/compiler.js new file mode 100644 index 0000000..35b2e28 --- /dev/null +++ b/lib/compiler.js @@ -0,0 +1,87 @@ +'use strict'; + +const { + VMError +} = require('./bridge'); + +let cacheCoffeeScriptCompiler; + +/** + * Returns the cached coffee script compiler or loads it + * if it is not found in the cache. + * + * @private + * @return {compileCallback} The coffee script compiler. + * @throws {VMError} If the coffee-script module can't be found. + */ +function getCoffeeScriptCompiler() { + if (!cacheCoffeeScriptCompiler) { + try { + // The warning generated by webpack can be disabled by setting: + // ignoreWarnings[].message = /Can't resolve 'coffee-script'/ + /* eslint-disable-next-line global-require */ + const coffeeScript = require('coffee-script'); + cacheCoffeeScriptCompiler = (code, filename) => { + return coffeeScript.compile(code, {header: false, bare: true}); + }; + } catch (e) { + throw new VMError('Coffee-Script compiler is not installed.'); + } + } + return cacheCoffeeScriptCompiler; +} + +/** + * Remove the shebang from source code. + * + * @private + * @param {string} code - Code from which to remove the shebang. + * @return {string} code without the shebang. + */ +function removeShebang(code) { + if (!code.startsWith('#!')) return code; + return '//' + code.substring(2); +} + + +/** + * The JavaScript compiler, just a identity function. + * + * @private + * @type {compileCallback} + * @param {string} code - The JavaScript code. + * @param {string} filename - Filename of this script. + * @return {string} The code. + */ +function jsCompiler(code, filename) { + return removeShebang(code); +} + +/** + * Look up the compiler for a specific name. + * + * @private + * @param {(string|compileCallback)} compiler - A compile callback or the name of the compiler. + * @return {compileCallback} The resolved compiler. + * @throws {VMError} If the compiler is unknown or the coffee script module was needed and couldn't be found. + */ +function lookupCompiler(compiler) { + if ('function' === typeof compiler) return compiler; + switch (compiler) { + case 'coffeescript': + case 'coffee-script': + case 'cs': + case 'text/coffeescript': + return getCoffeeScriptCompiler(); + case 'javascript': + case 'java-script': + case 'js': + case 'text/javascript': + return jsCompiler; + default: + throw new VMError(`Unsupported compiler '${compiler}'.`); + } +} + +exports.removeShebang = removeShebang; +exports.lookupCompiler = lookupCompiler; diff --git a/lib/contextify.js b/lib/contextify.js deleted file mode 100644 index 84fa63c..0000000 --- a/lib/contextify.js +++ /dev/null @@ -1,1153 +0,0 @@ -/* global host */ -/* eslint-disable block-spacing, no-multi-spaces, brace-style, no-array-constructor, new-cap, no-use-before-define */ - -'use strict'; - -// eslint-disable-next-line no-invalid-this, no-shadow -const global = this; - -const local = host.Object.create(null); -local.Object = Object; -local.Array = Array; -local.Reflect = host.Object.create(null); -local.Reflect.ownKeys = Reflect.ownKeys; -local.Reflect.enumerate = Reflect.enumerate; -local.Reflect.getPrototypeOf = Reflect.getPrototypeOf; -local.Reflect.construct = Reflect.construct; -local.Reflect.apply = Reflect.apply; -local.Reflect.set = Reflect.set; -local.Reflect.deleteProperty = Reflect.deleteProperty; -local.Reflect.has = Reflect.has; -local.Reflect.defineProperty = Reflect.defineProperty; -local.Reflect.setPrototypeOf = Reflect.setPrototypeOf; -local.Reflect.isExtensible = Reflect.isExtensible; -local.Reflect.preventExtensions = Reflect.preventExtensions; -local.Reflect.getOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; - -function uncurryThis(func) { - return (thiz, args) => local.Reflect.apply(func, thiz, args); -} - -const FunctionBind = uncurryThis(Function.prototype.bind); - -// global is originally prototype of host.Object so it can be used to climb up from the sandbox. -Object.setPrototypeOf(global, Object.prototype); - -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 captureStackTrace = Error.captureStackTrace; - -const RETURN_FALSE = () => false; - -const FROZEN_TRAPS = { - __proto__: null, - set(target, key, value, receiver) { - return local.Reflect.defineProperty(receiver, key, { - __proto__: null, - value: value, - writable: true, - enumerable: true, - configurable: true - }); - }, - setPrototypeOf: RETURN_FALSE, - defineProperty: RETURN_FALSE, - deleteProperty: RETURN_FALSE, - isExtensible: RETURN_FALSE, - preventExtensions: RETURN_FALSE -}; - -// Map of contextified objects to original objects -const Contextified = new host.WeakMap(); -const Decontextified = new host.WeakMap(); - -// We can't use host's hasInstance method -const ObjectHasInstance = uncurryThis(local.Object[Symbol.hasInstance]); -function instanceOf(value, construct) { - try { - return ObjectHasInstance(construct, [value]); - } catch (ex) { - // Never pass the handled exception through! - throw new VMError('Unable to perform instanceOf check.'); - // This exception actually never get to the user. It only instructs the caller to return null because we wasn't able to perform instanceOf check. - } -} - -const SHARED_OBJECT = {__proto__: null}; -function SHARED_FUNCTION() {} - -function createBaseObject(obj) { - let base; - if (typeof obj === 'function') { - try { - // eslint-disable-next-line no-new - new new host.Proxy(obj, { - __proto__: null, - construct() { - return this; - } - })(); - // Bind the function since bound functions do not have a prototype property. - base = FunctionBind(SHARED_FUNCTION, [null]); - } catch (e) { - base = () => {}; - } - } else if (host.Array.isArray(obj)) { - base = []; - } else { - return {__proto__: null}; - } - if (!local.Reflect.setPrototypeOf(base, null)) { - // Should not happen - return null; - } - return base; -} - -/** - * VMError definition. - */ - -class VMError extends Error { - constructor(message, code) { - super(message); - - this.name = 'VMError'; - this.code = code; - - captureStackTrace(this, this.constructor); - } -} - -global.VMError = VMError; - -/* - * This function will throw a TypeError for accessing properties - * on a strict mode function - */ -function throwCallerCalleeArgumentsAccess(key) { - 'use strict'; - throwCallerCalleeArgumentsAccess[key]; - return new VMError('Unreachable'); -} - -function unexpected() { - throw new VMError('Should not happen'); -} - -function doPreventExtensions(target, object, doProxy) { - const keys = local.Reflect.ownKeys(object); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - let desc = local.Reflect.getOwnPropertyDescriptor(object, key); - if (!desc) continue; - if (!local.Reflect.setPrototypeOf(desc, null)) unexpected(); - if (!desc.configurable) { - const current = local.Reflect.getOwnPropertyDescriptor(target, key); - if (current && !current.configurable) continue; - if (desc.get || desc.set) { - desc.get = doProxy(desc.get); - desc.set = doProxy(desc.set); - } else { - desc.value = doProxy(desc.value); - } - } else { - if (desc.get || desc.set) { - desc = { - __proto__: null, - configurable: true, - enumerable: desc.enumerable, - writable: true, - value: null - }; - } else { - desc.value = null; - } - } - if (!local.Reflect.defineProperty(target, key, desc)) unexpected(); - } - if (!local.Reflect.preventExtensions(target)) unexpected(); -} - -/** - * Decontextify. - */ - -const Decontextify = host.Object.create(null); -Decontextify.proxies = new host.WeakMap(); - -Decontextify.arguments = args => { - if (!host.Array.isArray(args)) return new host.Array(); - - try { - const arr = new host.Array(); - for (let i = 0, l = args.length; i < l; i++) arr[i] = Decontextify.value(args[i]); - return arr; - } catch (e) { - // Never pass the handled exception through! - return new host.Array(); - } -}; -Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { - if (typeof instance === 'function') return Decontextify.function(instance); - - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return instance; - if (key === 'isVMProxy') return true; - if (key === 'constructor') return klass; - if (key === '__proto__') return klass.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; - if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; - - try { - return Decontextify.value(instance[key], null, deepTraps, flags); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return klass && klass.prototype; - }; - - return Decontextify.object(instance, base, deepTraps, flags); -}; -Decontextify.function = (fnc, traps, deepTraps, flags, mock) => { - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - // eslint-disable-next-line prefer-const - let proxy; - - base.apply = (target, context, args) => { - context = Contextify.value(context); - - // Set context of all arguments to vm's context. - args = Contextify.arguments(args); - - try { - return Decontextify.value(fnc.apply(context, args)); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.construct = (target, args, newTarget) => { - args = Contextify.arguments(args); - - try { - return Decontextify.instance(new fnc(...args), proxy, deepTraps, flags); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return fnc; - if (key === 'isVMProxy') return true; - if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; - if (key === 'constructor') return host.Function; - if (key === '__proto__') return host.Function.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; - - try { - return Decontextify.value(fnc[key], null, deepTraps, flags); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return host.Function.prototype; - }; - - proxy = Decontextify.object(fnc, host.Object.assign(base, traps), deepTraps); - return proxy; -}; -Decontextify.object = (object, traps, deepTraps, flags, mock) => { - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return object; - if (key === 'isVMProxy') return true; - if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; - if (key === 'constructor') return host.Object; - if (key === '__proto__') return host.Object.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; - - try { - return Decontextify.value(object[key], null, deepTraps, flags); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.set = (target, key, value, receiver) => { - value = Contextify.value(value); - - try { - return local.Reflect.set(object, key, value); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.getOwnPropertyDescriptor = (target, prop) => { - let def; - - try { - def = local.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 '' - // which is either non-existant or configurable in the proxy target - - let desc; - if (!def) { - return undefined; - } else if (def.get || def.set) { - desc = { - __proto__: null, - get: Decontextify.value(def.get) || undefined, - set: Decontextify.value(def.set) || undefined, - enumerable: def.enumerable === true, - configurable: def.configurable === true - }; - } else { - desc = { - __proto__: null, - value: Decontextify.value(def.value), - writable: def.writable === true, - enumerable: def.enumerable === true, - configurable: def.configurable === true - }; - } - if (!desc.configurable) { - try { - def = local.Object.getOwnPropertyDescriptor(target, prop); - if (!def || def.configurable || def.writable !== desc.writable) { - local.Reflect.defineProperty(target, prop, desc); - } - } catch (e) { - // Should not happen. - } - } - return desc; - }; - base.defineProperty = (target, key, descriptor) => { - let success = false; - try { - success = local.Reflect.setPrototypeOf(descriptor, null); - } catch (e) { - // Should not happen - } - if (!success) return false; - // There's a chance accessing a property throws an error so we must not access them - // in try catch to prevent contextifying local objects. - - const propertyDescriptor = host.Object.create(null); - if (descriptor.get || descriptor.set) { - propertyDescriptor.get = Contextify.value(descriptor.get, null, deepTraps, flags) || undefined; - propertyDescriptor.set = Contextify.value(descriptor.set, null, deepTraps, flags) || undefined; - propertyDescriptor.enumerable = descriptor.enumerable === true; - propertyDescriptor.configurable = descriptor.configurable === true; - } else { - propertyDescriptor.value = Contextify.value(descriptor.value, null, deepTraps, flags); - propertyDescriptor.writable = descriptor.writable === true; - propertyDescriptor.enumerable = descriptor.enumerable === true; - propertyDescriptor.configurable = descriptor.configurable === true; - } - - try { - success = local.Reflect.defineProperty(object, key, propertyDescriptor); - } catch (e) { - throw Decontextify.value(e); - } - if (success && !descriptor.configurable) { - try { - local.Reflect.defineProperty(target, key, descriptor); - } catch (e) { - // This should not happen. - return false; - } - } - return success; - }; - base.deleteProperty = (target, prop) => { - try { - return Decontextify.value(local.Reflect.deleteProperty(object, prop)); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return host.Object.prototype; - }; - base.setPrototypeOf = (target) => { - throw new host.Error(OPNA); - }; - base.has = (target, key) => { - try { - return Decontextify.value(local.Reflect.has(object, key)); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.isExtensible = target => { - let result; - try { - result = local.Reflect.isExtensible(object); - } catch (e) { - throw Decontextify.value(e); - } - if (!result) { - try { - if (local.Reflect.isExtensible(target)) { - doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); - } - } catch (e) { - // Should not happen - } - } - return result; - }; - base.ownKeys = target => { - try { - return Decontextify.value(local.Reflect.ownKeys(object)); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.preventExtensions = target => { - let success; - try { - success = local.Reflect.preventExtensions(object); - } catch (e) { - throw Decontextify.value(e); - } - if (success) { - try { - if (local.Reflect.isExtensible(target)) { - doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); - } - } catch (e) { - // Should not happen - } - } - return success; - }; - base.enumerate = target => { - try { - return Decontextify.value(local.Reflect.enumerate(object)); - } catch (e) { - throw Decontextify.value(e); - } - }; - - host.Object.assign(base, traps, deepTraps); - - let shallow; - if (host.Array.isArray(object)) { - const origGet = base.get; - shallow = { - __proto__: null, - ownKeys: base.ownKeys, - // TODO this get will call getOwnPropertyDescriptor of target all the time. - get: origGet - }; - base.ownKeys = target => { - try { - const keys = local.Reflect.ownKeys(object); - // Do this hack so that console.log(decontextify([1,2,3])) doesn't write the properties twice - // a la [1,2,3,'0':1,'1':2,'2':3] - return Decontextify.value(keys.filter(key=>typeof key!=='string' || !key.match(/^\d+$/))); - } catch (e) { - throw Decontextify.value(e); - } - }; - base.get = (target, key, receiver) => { - if (key === host.Symbol.toStringTag) return; - return origGet(target, key, receiver); - }; - } else { - shallow = SHARED_OBJECT; - } - - const proxy = new host.Proxy(createBaseObject(object), base); - Decontextified.set(proxy, object); - // We need two proxies since nodes inspect just removes one. - const proxy2 = new host.Proxy(proxy, shallow); - Decontextify.proxies.set(object, proxy2); - Decontextified.set(proxy2, object); - return proxy2; -}; -Decontextify.value = (value, traps, deepTraps, flags, mock) => { - try { - 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 (instanceOf(value, Number)) { return Decontextify.instance(value, host.Number, deepTraps, flags, 'Number'); - } else if (instanceOf(value, String)) { return Decontextify.instance(value, host.String, deepTraps, flags, 'String'); - } else if (instanceOf(value, Boolean)) { return Decontextify.instance(value, host.Boolean, deepTraps, flags, 'Boolean'); - } else if (instanceOf(value, Date)) { return Decontextify.instance(value, host.Date, deepTraps, flags, 'Date'); - } else if (instanceOf(value, RangeError)) { return Decontextify.instance(value, host.RangeError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, ReferenceError)) { return Decontextify.instance(value, host.ReferenceError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, SyntaxError)) { return Decontextify.instance(value, host.SyntaxError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, TypeError)) { return Decontextify.instance(value, host.TypeError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, VMError)) { return Decontextify.instance(value, host.VMError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, EvalError)) { return Decontextify.instance(value, host.EvalError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, URIError)) { return Decontextify.instance(value, host.URIError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, Error)) { return Decontextify.instance(value, host.Error, deepTraps, flags, 'Error'); - } else if (instanceOf(value, Array)) { return Decontextify.instance(value, host.Array, deepTraps, flags, 'Array'); - } else if (instanceOf(value, RegExp)) { return Decontextify.instance(value, host.RegExp, deepTraps, flags, 'RegExp'); - } else if (instanceOf(value, Map)) { return Decontextify.instance(value, host.Map, deepTraps, flags, 'Map'); - } else if (instanceOf(value, WeakMap)) { return Decontextify.instance(value, host.WeakMap, deepTraps, flags, 'WeakMap'); - } else if (instanceOf(value, Set)) { return Decontextify.instance(value, host.Set, deepTraps, flags, 'Set'); - } else if (instanceOf(value, WeakSet)) { return Decontextify.instance(value, host.WeakSet, deepTraps, flags, 'WeakSet'); - } else if (typeof Promise === 'function' && instanceOf(value, Promise)) { - return Decontextify.instance(value, host.Promise, deepTraps, flags, 'Promise'); - } else if (local.Reflect.getPrototypeOf(value) === null) { - return Decontextify.instance(value, null, deepTraps, flags); - } else { - return Decontextify.object(value, traps, deepTraps, flags, mock); - } - case 'function': - return Decontextify.function(value, traps, deepTraps, flags, mock); - - case 'undefined': - return undefined; - - case 'string': - case 'number': - case 'boolean': - case 'symbol': - case 'bigint': - return value; - - default: // new, unknown types can be dangerous - return null; - } - } catch (ex) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } -}; - -/** - * Contextify. - */ - -const Contextify = host.Object.create(null); -Contextify.proxies = new host.WeakMap(); - -Contextify.arguments = args => { - if (!host.Array.isArray(args)) return new local.Array(); - - try { - const arr = new local.Array(); - for (let i = 0, l = args.length; i < l; i++) arr[i] = Contextify.value(args[i]); - return arr; - } catch (e) { - // Never pass the handled exception through! - return new local.Array(); - } -}; -Contextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { - if (typeof instance === 'function') return Contextify.function(instance); - - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return instance; - if (key === 'isVMProxy') return true; - if (key === 'constructor') return klass; - if (key === '__proto__') return klass.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; - if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; - - try { - return Contextify.value(host.Reflect.get(instance, key), null, deepTraps, flags); - } catch (e) { - throw Contextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return klass && klass.prototype; - }; - - return Contextify.object(instance, base, deepTraps, flags); -}; -Contextify.function = (fnc, traps, deepTraps, flags, mock) => { - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - // eslint-disable-next-line prefer-const - let proxy; - - base.apply = (target, context, args) => { - // Fixes buffer unsafe allocation for node v6/7 - if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { - args[0] = new local.Array(args[0]).fill(0); - } - - context = Decontextify.value(context); - - // Set context of all arguments to host's context. - args = Decontextify.arguments(args); - - try { - return Contextify.value(fnc.apply(context, args)); - } catch (e) { - throw Contextify.value(e); - } - }; - base.construct = (target, args, newTarget) => { - // Fixes buffer unsafe allocation for node v6/7 - if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { - args[0] = new local.Array(args[0]).fill(0); - } - - args = Decontextify.arguments(args); - - try { - return Contextify.instance(new fnc(...args), proxy, deepTraps, flags); - } catch (e) { - throw Contextify.value(e); - } - }; - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return fnc; - if (key === 'isVMProxy') return true; - if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; - if (key === 'constructor') return Function; - if (key === '__proto__') return Function.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; - - if (key === 'caller' || key === 'callee' || key === 'arguments') throw throwCallerCalleeArgumentsAccess(key); - - try { - return Contextify.value(host.Reflect.get(fnc, key), null, deepTraps, flags); - } catch (e) { - throw Contextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return Function.prototype; - }; - - proxy = Contextify.object(fnc, host.Object.assign(base, traps), deepTraps); - return proxy; -}; -Contextify.object = (object, traps, deepTraps, flags, mock) => { - // We must not use normal object because there's a chance object already contains malicious code in the prototype - const base = host.Object.create(null); - - base.get = (target, key, receiver) => { - try { - if (key === 'vmProxyTarget' && DEBUG) return object; - if (key === 'isVMProxy') return true; - if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; - if (key === 'constructor') return Object; - if (key === '__proto__') return Object.prototype; - } catch (e) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } - - if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; - if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; - if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; - if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; - - try { - return Contextify.value(host.Reflect.get(object, key), null, deepTraps, flags); - } catch (e) { - throw Contextify.value(e); - } - }; - base.set = (target, key, value, receiver) => { - if (key === '__proto__') return false; - if (flags && flags.protected && typeof value === 'function') return false; - - value = Decontextify.value(value); - - try { - return host.Reflect.set(object, key, value); - } catch (e) { - throw Contextify.value(e); - } - }; - base.getOwnPropertyDescriptor = (target, prop) => { - let def; - - try { - 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 '' - // which is either non-existant or configurable in the proxy target - - let desc; - if (!def) { - return undefined; - } else if (def.get || def.set) { - desc = { - __proto__: null, - get: Contextify.value(def.get, null, deepTraps, flags) || undefined, - set: Contextify.value(def.set, null, deepTraps, flags) || undefined, - enumerable: def.enumerable === true, - configurable: def.configurable === true - }; - } else { - desc = { - __proto__: null, - value: Contextify.value(def.value, null, deepTraps, flags), - writable: def.writable === true, - enumerable: def.enumerable === true, - configurable: def.configurable === true - }; - } - if (!desc.configurable) { - try { - def = host.Object.getOwnPropertyDescriptor(target, prop); - if (!def || def.configurable || def.writable !== desc.writable) { - local.Reflect.defineProperty(target, prop, desc); - } - } catch (e) { - // Should not happen. - } - } - return desc; - }; - base.defineProperty = (target, key, descriptor) => { - let success = false; - try { - success = local.Reflect.setPrototypeOf(descriptor, null); - } catch (e) { - // Should not happen - } - if (!success) return false; - // There's a chance accessing a property throws an error so we must not access them - // in try catch to prevent contextifying local objects. - - const descGet = descriptor.get; - const descSet = descriptor.set; - const descValue = descriptor.value; - - if (flags && flags.protected) { - if (descGet || descSet || typeof descValue === 'function') return false; - } - - const propertyDescriptor = host.Object.create(null); - if (descGet || descSet) { - propertyDescriptor.get = Decontextify.value(descGet, null, deepTraps, flags) || undefined; - propertyDescriptor.set = Decontextify.value(descSet, null, deepTraps, flags) || undefined; - propertyDescriptor.enumerable = descriptor.enumerable === true; - propertyDescriptor.configurable = descriptor.configurable === true; - } else { - propertyDescriptor.value = Decontextify.value(descValue, null, deepTraps, flags); - propertyDescriptor.writable = descriptor.writable === true; - propertyDescriptor.enumerable = descriptor.enumerable === true; - propertyDescriptor.configurable = descriptor.configurable === true; - } - - try { - success = host.Reflect.defineProperty(object, key, propertyDescriptor); - } catch (e) { - throw Contextify.value(e); - } - if (success && !descriptor.configurable) { - try { - local.Reflect.defineProperty(target, key, descriptor); - } catch (e) { - // This should not happen. - return false; - } - } - return success; - }; - base.deleteProperty = (target, prop) => { - try { - return Contextify.value(host.Reflect.deleteProperty(object, prop)); - } catch (e) { - throw Contextify.value(e); - } - }; - base.getPrototypeOf = (target) => { - return local.Object.prototype; - }; - base.setPrototypeOf = (target) => { - throw new VMError(OPNA); - }; - base.has = (target, key) => { - try { - return Contextify.value(host.Reflect.has(object, key)); - } catch (e) { - throw Contextify.value(e); - } - }; - base.isExtensible = target => { - let result; - try { - result = host.Reflect.isExtensible(object); - } catch (e) { - throw Contextify.value(e); - } - if (!result) { - try { - if (local.Reflect.isExtensible(target)) { - doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); - } - } catch (e) { - // Should not happen - } - } - return result; - }; - base.ownKeys = target => { - try { - return Contextify.value(host.Reflect.ownKeys(object)); - } catch (e) { - throw Contextify.value(e); - } - }; - base.preventExtensions = target => { - let success; - try { - success = local.Reflect.preventExtensions(object); - } catch (e) { - throw Contextify.value(e); - } - if (success) { - try { - if (local.Reflect.isExtensible(target)) { - doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); - } - } catch (e) { - // Should not happen - } - } - return success; - }; - base.enumerate = target => { - try { - return Contextify.value(host.Reflect.enumerate(object)); - } catch (e) { - throw Contextify.value(e); - } - }; - - const proxy = new host.Proxy(createBaseObject(object), host.Object.assign(base, traps, deepTraps)); - Contextify.proxies.set(object, proxy); - Contextified.set(proxy, object); - return proxy; -}; -Contextify.value = (value, traps, deepTraps, flags, mock) => { - try { - 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 (instanceOf(value, host.Number)) { return Contextify.instance(value, Number, deepTraps, flags, 'Number'); - } else if (instanceOf(value, host.String)) { return Contextify.instance(value, String, deepTraps, flags, 'String'); - } else if (instanceOf(value, host.Boolean)) { return Contextify.instance(value, Boolean, deepTraps, flags, 'Boolean'); - } else if (instanceOf(value, host.Date)) { return Contextify.instance(value, Date, deepTraps, flags, 'Date'); - } else if (instanceOf(value, host.RangeError)) { return Contextify.instance(value, RangeError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.ReferenceError)) { return Contextify.instance(value, ReferenceError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.SyntaxError)) { return Contextify.instance(value, SyntaxError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.TypeError)) { return Contextify.instance(value, TypeError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.VMError)) { return Contextify.instance(value, VMError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.EvalError)) { return Contextify.instance(value, EvalError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.URIError)) { return Contextify.instance(value, URIError, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.Error)) { return Contextify.instance(value, Error, deepTraps, flags, 'Error'); - } else if (instanceOf(value, host.Array)) { return Contextify.instance(value, Array, deepTraps, flags, 'Array'); - } else if (instanceOf(value, host.RegExp)) { return Contextify.instance(value, RegExp, deepTraps, flags, 'RegExp'); - } else if (instanceOf(value, host.Map)) { return Contextify.instance(value, Map, deepTraps, flags, 'Map'); - } else if (instanceOf(value, host.WeakMap)) { return Contextify.instance(value, WeakMap, deepTraps, flags, 'WeakMap'); - } else if (instanceOf(value, host.Set)) { return Contextify.instance(value, Set, deepTraps, flags, 'Set'); - } else if (instanceOf(value, host.WeakSet)) { return Contextify.instance(value, WeakSet, deepTraps, flags, 'WeakSet'); - } else if (typeof Promise === 'function' && instanceOf(value, host.Promise)) { - return Contextify.instance(value, Promise, deepTraps, flags, 'Promise'); - } else if (instanceOf(value, host.Buffer)) { return Contextify.instance(value, LocalBuffer, deepTraps, flags, 'Uint8Array'); - } else if (host.Reflect.getPrototypeOf(value) === null) { - return Contextify.instance(value, null, deepTraps, flags); - } else { - return Contextify.object(value, traps, deepTraps, flags, mock); - } - case 'function': - return Contextify.function(value, traps, deepTraps, flags, mock); - - case 'undefined': - return undefined; - - case 'string': - case 'number': - case 'boolean': - case 'symbol': - case 'bigint': - return value; - - default: // new, unknown types can be dangerous - return null; - } - } catch (ex) { - // Never pass the handled exception through! This block can't throw an exception under normal conditions. - return null; - } -}; -Contextify.setGlobal = (name, value) => { - const prop = Contextify.value(name); - try { - global[prop] = Contextify.value(value); - } catch (e) { - throw Decontextify.value(e); - } -}; -Contextify.getGlobal = (name) => { - const prop = Contextify.value(name); - try { - return Decontextify.value(global[prop]); - } catch (e) { - throw Decontextify.value(e); - } -}; -Contextify.readonly = (value, mock) => { - return Contextify.value(value, null, FROZEN_TRAPS, null, mock); -}; -Contextify.protected = (value, mock) => { - return Contextify.value(value, null, null, {protected: true}, mock); -}; -Contextify.connect = (outer, inner) => { - Decontextified.set(outer, inner); - Contextified.set(inner, outer); -}; -Contextify.makeModule = ()=>({exports: {}}); -Contextify.isVMProxy = (obj) => Decontextified.has(obj); - -const BufferMock = host.Object.create(null); -BufferMock.allocUnsafe = function allocUnsafe(size) { - return this.alloc(size); -}; -BufferMock.allocUnsafeSlow = function allocUnsafeSlow(size) { - return this.alloc(size); -}; -const BufferOverride = host.Object.create(null); -BufferOverride.inspect = function inspect(recurseTimes, ctx) { - // Mimic old behavior, could throw but didn't pass a test. - const max = host.INSPECT_MAX_BYTES; - const actualMax = Math.min(max, this.length); - const remaining = this.length - max; - let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); - if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; - return `<${this.constructor.name} ${str}>`; -}; -const LocalBuffer = global.Buffer = Contextify.readonly(host.Buffer, BufferMock); -Contextify.connect(host.Buffer.prototype.inspect, BufferOverride.inspect); -Contextify.connect(host.Function.prototype.bind, Function.prototype.bind); - -const oldPrepareStackTraceDesc = Reflect.getOwnPropertyDescriptor(Error, 'prepareStackTrace'); - -let currentPrepareStackTrace = Error.prepareStackTrace; -const wrappedPrepareStackTrace = new host.WeakMap(); -if (typeof currentPrepareStackTrace === 'function') { - wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); -} - -let OriginalCallSite; -Error.prepareStackTrace = (e, sst) => { - OriginalCallSite = sst[0].constructor; -}; -new Error().stack; -if (typeof OriginalCallSite === 'function') { - Error.prepareStackTrace = undefined; - - function makeCallSiteGetters(list) { - const callSiteGetters = []; - for (let i=0; i { - return local.Reflect.apply(func, thiz, []); - } - }; - } - return callSiteGetters; - } - - function applyCallSiteGetters(callSite, getters) { - const properties = {__proto__: null}; - for (let i=0; i { - if (host.Array.isArray(sst)) { - for (let i=0; i ReflectApply(func, thiz, args); +} + +const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); +const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); + +const kRejection = SymbolFor('nodejs.rejection'); + +function inspect(obj) { + return typeof obj === 'symbol' ? obj.toString() : `${obj}`; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function assert(what, message) { + if (!what) throw new Error(message); +} + +function E(key, msg, Base) { + return function NodeError(...args) { + const error = new Base(); + const message = ReflectApply(msg, error, args); + ObjectDefineProperties(error, { + message: { + value: message, + enumerable: false, + writable: true, + configurable: true, + }, + toString: { + value() { + return `${this.name} [${key}]: ${this.message}`; + }, + enumerable: false, + writable: true, + configurable: true, + }, + }); + error.code = key; + return error; + }; +} + + +const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', + (name, expected, actual) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (StringPrototypeEndsWith(name, ' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (ArrayPrototypeIncludes(kTypes, value)) { + ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); + } else if (RegExpPrototypeTest(classRegExp, value)) { + ArrayPrototypePush(instances, value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + ArrayPrototypePush(other, value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = ArrayPrototypeIndexOf(types, 'object'); + if (pos !== -1) { + ArrayPrototypeSplice(types, pos, 1); + ArrayPrototypePush(instances, 'Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = ArrayPrototypePop(types); + msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = ArrayPrototypePop(instances); + msg += + `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = ArrayPrototypePop(other); + msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (StringPrototypeToLowerCase(other[0]) !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; + }, TypeError); + +const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); + +const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', + (str, range, input, replaceDefaultBoolean = false) => { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean ? str : + `The value of "${str}" is out of range.`; + const received = inspect(input); + msg += ` It must be ${range}. Received ${received}`; + return msg; + }, RangeError); + +const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', + err => { + const msg = 'Unhandled error.'; + if (err === undefined) return msg; + return `${msg} (${err})`; + }, Error); + +function validateBoolean(value, name) { + if (typeof value !== 'boolean') + throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); +} + +function validateFunction(value, name) { + if (typeof value !== 'function') + throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); +} + +function validateString(value, name) { + if (typeof value !== 'string') + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); +} + +function nc(cond, e) { + return cond === undefined || cond === null ? e : cond; +} + +function oc(base, key) { + return base === undefined || base === null ? undefined : base[key]; +} + +const kCapture = Symbol('kCapture'); +const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); +const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); +const kMaxEventTargetListenersWarned = + Symbol('events.maxEventTargetListenersWarned'); + +const kIsEventTarget = SymbolFor('nodejs.event_target'); + +function isEventTarget(obj) { + return oc(oc(obj, 'constructor'), kIsEventTarget); +} + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @constructs {EventEmitter} + */ +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +module.exports = EventEmitter; +if (host.once) module.exports.once = host.once; +if (host.on) module.exports.on = host.on; +if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, 'EventEmitter.captureRejections'); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +if (host.EventEmitterReferencingAsyncResource) { + const kAsyncResource = Symbol('kAsyncResource'); + const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; + + class EventEmitterAsyncResource extends EventEmitter { + /** + * @param {{ + * name?: string, + * triggerAsyncId?: number, + * requireManualDestroy?: boolean, + * }} [options] + */ + constructor(options = undefined) { + let name; + if (typeof options === 'string') { + name = options; + options = undefined; + } else { + if (new.target === EventEmitterAsyncResource) { + validateString(oc(options, 'name'), 'options.name'); + } + name = oc(options, 'name') || new.target.name; + } + super(options); + + this[kAsyncResource] = + new EventEmitterReferencingAsyncResource(this, name, options); + } + + /** + * @param {symbol,string} event + * @param {...any} args + * @returns {boolean} + */ + emit(event, ...args) { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + const { asyncResource } = this; + ArrayPrototypeUnshift(args, super.emit, this, event); + return ReflectApply(asyncResource.runInAsyncScope, asyncResource, + args); + } + + /** + * @returns {void} + */ + emitDestroy() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + this.asyncResource.emitDestroy(); + } + + /** + * @type {number} + */ + get asyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.asyncId(); + } + + /** + * @type {number} + */ + get triggerAsyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.triggerAsyncId(); + } + + /** + * @type {EventEmitterReferencingAsyncResource} + */ + get asyncResource() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this[kAsyncResource]; + } + } + EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; +} + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, 'listener'); +} + +ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new ERR_OUT_OF_RANGE('defaultMaxListeners', + 'a non-negative number', + arg); + } + defaultMaxListeners = arg; + } +}); + +ObjectDefineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + } +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +EventEmitter.setMaxListeners = + function(n = defaultMaxListeners, ...eventTargets) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (isEventTarget(target)) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === 'function') { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + 'eventTargets', + ['EventEmitter', 'EventTarget'], + target); + } + } + } + }; + +// If you're updating this function definition, please also update any +// re-definitions, such as the one in the Domain module (lib/domain.js). +EventEmitter.init = function(opts) { + + if (this._events === undefined || + this._events === ObjectGetPrototypeOf(this)._events) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + + if (oc(opts, 'captureRejections')) { + validateBoolean(opts.captureRejections, 'options.captureRejections'); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catchable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = (type === 'error'); + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) + this.emit(kErrorMonitor, ...args); + doError = (doError && events.error === undefined); + } else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + try { + const capture = {}; + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); + } catch (e) {} + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch (e) { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = ObjectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + nc(listener.listener, listener)); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error('Possible EventEmitter memory leak detected. ' + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + 'emitter.setMaxListeners() to increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + checkListener(listener); + + const events = this._events; + if (events === undefined) + return this; + + const list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + const events = this._events; + if (events === undefined) + return this; + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else + delete events[type]; + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of ReflectOwnKeys(events)) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = ObjectCreate(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) + return []; + + const evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } + return emitter.listenerCount(type); +}; + +EventEmitter.prototype.listenerCount = listenerCount; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return ArrayPrototypeSlice(arr); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === 'function') + ret[i] = orig; + } + return ret; +} diff --git a/lib/fixasync.js b/lib/fixasync.js deleted file mode 100644 index d4e474b..0000000 --- a/lib/fixasync.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -// eslint-disable-next-line no-invalid-this, no-shadow -const {GeneratorFunction, AsyncFunction, AsyncGeneratorFunction, global, internal, host, hook} = this; -const {Contextify, Decontextify} = internal; -// eslint-disable-next-line no-shadow -const {Function, eval: eval_, Promise, Object, Reflect} = global; -const {getOwnPropertyDescriptor, defineProperty, assign} = Object; -const {apply: rApply, construct: rConstruct} = Reflect; - -const FunctionHandler = { - __proto__: null, - apply(target, thiz, args) { - const type = this.type; - args = Decontextify.arguments(args); - try { - args = Contextify.value(hook(type, args)); - } catch (e) { - throw Contextify.value(e); - } - return rApply(target, thiz, args); - }, - construct(target, args, newTarget) { - const type = this.type; - args = Decontextify.arguments(args); - try { - args = Contextify.value(hook(type, args)); - } catch (e) { - throw Contextify.value(e); - } - return rConstruct(target, args, newTarget); - } -}; - -function makeCheckFunction(type) { - return assign({ - __proto__: null, - type - }, FunctionHandler); -} - -function override(obj, prop, value) { - const desc = getOwnPropertyDescriptor(obj, prop); - desc.value = value; - defineProperty(obj, prop, desc); -} - -const proxiedFunction = new host.Proxy(Function, makeCheckFunction('function')); -override(Function.prototype, 'constructor', proxiedFunction); -if (GeneratorFunction) { - Object.setPrototypeOf(GeneratorFunction, proxiedFunction); - override(GeneratorFunction.prototype, 'constructor', new host.Proxy(GeneratorFunction, makeCheckFunction('generator_function'))); -} -if (AsyncFunction) { - Object.setPrototypeOf(AsyncFunction, proxiedFunction); - override(AsyncFunction.prototype, 'constructor', new host.Proxy(AsyncFunction, makeCheckFunction('async_function'))); -} -if (AsyncGeneratorFunction) { - Object.setPrototypeOf(AsyncGeneratorFunction, proxiedFunction); - override(AsyncGeneratorFunction.prototype, 'constructor', new host.Proxy(AsyncGeneratorFunction, makeCheckFunction('async_generator_function'))); -} - -global.Function = proxiedFunction; -global.eval = new host.Proxy(eval_, makeCheckFunction('eval')); - -if (Promise) { - - Promise.prototype.then = new host.Proxy(Promise.prototype.then, makeCheckFunction('promise_then')); - // This seems not to work, and will produce - // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. - // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. - // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); - - if (Promise.prototype.finally) { - Promise.prototype.finally = new host.Proxy(Promise.prototype.finally, makeCheckFunction('promise_finally')); - // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); - } - if (Promise.prototype.catch) { - Promise.prototype.catch = new host.Proxy(Promise.prototype.catch, makeCheckFunction('promise_catch')); - // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); - } - -} diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index 9c78ab8..0000000 --- a/lib/helpers.js +++ /dev/null @@ -1,12 +0,0 @@ -// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping -function escapeRegExp(string) { - return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -function match(wildcard, s) { - const regexString = escapeRegExp(wildcard).replace(/\\\*/g, '\\S*').replace(/\\\?/g, '.'); - const regex = new RegExp(regexString); - return regex.test(s); -} - -module.exports = {match}; diff --git a/lib/main.js b/lib/main.js index 1b0d867..ae78444 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,1384 +1,19 @@ -/* eslint-disable global-require, no-use-before-define */ - 'use strict'; -/** - * This callback will be called to transform a script to JavaScript. - * - * @callback compileCallback - * @param {string} code - Script code to transform to JavaScript. - * @param {string} filename - Filename of this script. - * @return {string} JavaScript code that represents the script code. - */ - -/** - * This callback will be called to resolve a module if it couldn't be found. - * - * @callback resolveCallback - * @param {string} moduleName - Name of the module to resolve. - * @param {string} dirname - Name of the current directory. - * @return {(string|undefined)} The file or directory to use to load the requested module. - */ - -const fs = require('fs'); -const vm = require('vm'); -const pa = require('path'); -const {EventEmitter} = require('events'); -const {INSPECT_MAX_BYTES} = require('buffer'); -const helpers = require('./helpers.js'); -const importModuleDynamically = () => { - // We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object. - // eslint-disable-next-line no-throw-literal - throw 'Dynamic imports are not allowed.'; -}; - -const MODULE_PREFIX = '(function (exports, require, module, __filename, __dirname) { '; -const STRICT_MODULE_PREFIX = MODULE_PREFIX + '"use strict"; '; -const MODULE_SUFFIX = '\n});'; - -/** - * Load a script from a file and compile it. - * - * @private - * @param {string} filename - File to load and compile to a script. - * @param {string} prefix - Prefix for the script. - * @param {string} suffix - Suffix for the script. - * @return {vm.Script} The compiled script. - */ -function loadAndCompileScript(filename, prefix, suffix) { - const data = fs.readFileSync(filename, 'utf8'); - return new vm.Script(prefix + data + suffix, { - filename, - displayErrors: false, - importModuleDynamically - }); -} - -/** - * Cache where we can cache some things - * - * @private - * @property {?compileCallback} coffeeScriptCompiler - The coffee script compiler or null if not yet used. - * @property {?Object} timeoutContext - The context used for the timeout functionality of null if not yet used. - * @property {?vm.Script} timeoutScript - The compiled script used for the timeout functionality of null if not yet used. - * @property {vm.Script} contextifyScript - The compiled script used to setup a sandbox. - * @property {?vm.Script} sandboxScript - The compiled script used to setup the NodeVM require mechanism of null if not yet used. - * @property {?vm.Script} hookScript - The compiled script used to setup the async hooking functionality. - * @property {?vm.Script} getGlobalScript - The compiled script used to get the global sandbox object. - * @property {?vm.Script} getGeneratorFunctionScript - The compiled script used to get the generator function constructor. - * @property {?vm.Script} getAsyncFunctionScript - The compiled script used to get the async function constructor. - * @property {?vm.Script} getAsyncGeneratorFunctionScript - The compiled script used to get the async generator function constructor. - */ -const CACHE = { - coffeeScriptCompiler: null, - timeoutContext: null, - timeoutScript: null, - contextifyScript: loadAndCompileScript(`${__dirname}/contextify.js`, '(function(require, host) { ', '\n})'), - sandboxScript: null, - hookScript: null, - getGlobalScript: null, - getGeneratorFunctionScript: null, - getAsyncFunctionScript: null, - getAsyncGeneratorFunctionScript: null, -}; - -/** - * Default run options for vm.Script.runInContext - * - * @private - */ -const DEFAULT_RUN_OPTIONS = {displayErrors: false, importModuleDynamically}; - -/** - * Returns the cached coffee script compiler or loads it - * if it is not found in the cache. - * - * @private - * @return {compileCallback} The coffee script compiler. - * @throws {VMError} If the coffee-script module can't be found. - */ -function getCoffeeScriptCompiler() { - if (!CACHE.coffeeScriptCompiler) { - try { - const coffeeScript = require('coffee-script'); - CACHE.coffeeScriptCompiler = (code, filename) => { - return coffeeScript.compile(code, {header: false, bare: true}); - }; - } catch (e) { - throw new VMError('Coffee-Script compiler is not installed.'); - } - } - return CACHE.coffeeScriptCompiler; -} - -/** - * The JavaScript compiler, just a identity function. - * - * @private - * @type {compileCallback} - * @param {string} code - The JavaScript code. - * @param {string} filename - Filename of this script. - * @return {string} The code. - */ -function jsCompiler(code, filename) { - return removeShebang(code); -} - -/** - * Look up the compiler for a specific name. - * - * @private - * @param {(string|compileCallback)} compiler - A compile callback or the name of the compiler. - * @return {compileCallback} The resolved compiler. - * @throws {VMError} If the compiler is unknown or the coffee script module was needed and couldn't be found. - */ -function lookupCompiler(compiler) { - if ('function' === typeof compiler) return compiler; - switch (compiler) { - case 'coffeescript': - case 'coffee-script': - case 'cs': - case 'text/coffeescript': - return getCoffeeScriptCompiler(); - case 'javascript': - case 'java-script': - case 'js': - case 'text/javascript': - return jsCompiler; - default: - throw new VMError(`Unsupported compiler '${compiler}'.`); - } -} - -/** - * Remove the shebang from source code. - * - * @private - * @param {string} code - Code from which to remove the shebang. - * @return {string} code without the shebang. - */ -function removeShebang(code) { - if (!code.startsWith('#!')) return code; - return '//' + code.substr(2); -} - -/** - * Class Script - * - * @public - */ -class VMScript { - - /** - * The script code with wrapping. If set will invalidate the cache.
- * Writable only for backwards compatibility. - * - * @public - * @readonly - * @member {string} code - * @memberOf VMScript# - */ - - /** - * The filename used for this script. - * - * @public - * @readonly - * @since v3.9.0 - * @member {string} filename - * @memberOf VMScript# - */ - - /** - * The line offset use for stack traces. - * - * @public - * @readonly - * @since v3.9.0 - * @member {number} lineOffset - * @memberOf VMScript# - */ - - /** - * The column offset use for stack traces. - * - * @public - * @readonly - * @since v3.9.0 - * @member {number} columnOffset - * @memberOf VMScript# - */ - - /** - * The compiler to use to get the JavaScript code. - * - * @public - * @readonly - * @since v3.9.0 - * @member {(string|compileCallback)} compiler - * @memberOf VMScript# - */ - - /** - * The prefix for the script. - * - * @private - * @member {string} _prefix - * @memberOf VMScript# - */ - - /** - * The suffix for the script. - * - * @private - * @member {string} _suffix - * @memberOf VMScript# - */ - - /** - * The compiled vm.Script for the VM or if not compiled null. - * - * @private - * @member {?vm.Script} _compiledVM - * @memberOf VMScript# - */ - - /** - * The compiled vm.Script for the NodeVM or if not compiled null. - * - * @private - * @member {?vm.Script} _compiledNodeVM - * @memberOf VMScript# - */ - - /** - * The compiled vm.Script for the NodeVM in strict mode or if not compiled null. - * - * @private - * @member {?vm.Script} _compiledNodeVMStrict - * @memberOf VMScript# - */ - - /** - * The resolved compiler to use to get the JavaScript code. - * - * @private - * @readonly - * @member {compileCallback} _compiler - * @memberOf VMScript# - */ - - /** - * The script to run without wrapping. - * - * @private - * @member {string} _code - * @memberOf VMScript# - */ - - /** - * Create VMScript instance. - * - * @public - * @param {string} 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. - * @param {number} [options.lineOffset=0] - Passed to vm.Script options. - * @param {number} [options.columnOffset=0] - Passed to vm.Script options. - * @param {(string|compileCallback)} [options.compiler="javascript"] - The compiler to use. - * @throws {VMError} If the compiler is unknown or if coffee-script was requested but the module not found. - */ - constructor(code, options) { - const sCode = `${code}`; - let useFileName; - let useOptions; - if (arguments.length === 2) { - if (typeof options === 'object' && options.toString === Object.prototype.toString) { - useOptions = options || {}; - useFileName = useOptions.filename; - } else { - useOptions = {}; - useFileName = options; - } - } else if (arguments.length > 2) { - // We do it this way so that there are no more arguments in the function. - // eslint-disable-next-line prefer-rest-params - useOptions = arguments[2] || {}; - useFileName = options || useOptions.filename; - } else { - useOptions = {}; - } - - const { - compiler = 'javascript', - lineOffset = 0, - columnOffset = 0 - } = useOptions; - - // Throw if the compiler is unknown. - const resolvedCompiler = lookupCompiler(compiler); - - Object.defineProperties(this, { - code: { - // Put this here so that it is enumerable, and looks like a property. - get() { - return this._prefix + this._code + this._suffix; - }, - set(value) { - const strNewCode = String(value); - if (strNewCode === this._code && this._prefix === '' && this._suffix === '') return; - this._code = strNewCode; - this._prefix = ''; - this._suffix = ''; - this._compiledVM = null; - this._compiledNodeVM = null; - this._compiledCode = null; - }, - enumerable: true - }, - filename: { - value: useFileName || 'vm.js', - enumerable: true - }, - lineOffset: { - value: lineOffset, - enumerable: true - }, - columnOffset: { - value: columnOffset, - enumerable: true - }, - compiler: { - value: compiler, - enumerable: true - }, - _code: { - value: sCode, - writable: true - }, - _prefix: { - value: '', - writable: true - }, - _suffix: { - value: '', - writable: true - }, - _compiledVM: { - value: null, - writable: true - }, - _compiledNodeVM: { - value: null, - writable: true - }, - _compiledNodeVMStrict: { - value: null, - writable: true - }, - _compiledCode: { - value: null, - writable: true - }, - _compiler: {value: resolvedCompiler} - }); - } - - /** - * Wraps the code.
- * This will replace the old wrapping.
- * Will invalidate the code cache. - * - * @public - * @deprecated Since v3.9.0. Wrap your code before passing it into the VMScript object. - * @param {string} prefix - String that will be appended before the script code. - * @param {script} suffix - String that will be appended behind the script code. - * @return {this} This for chaining. - * @throws {TypeError} If prefix or suffix is a Symbol. - */ - wrap(prefix, suffix) { - const strPrefix = `${prefix}`; - const strSuffix = `${suffix}`; - if (this._prefix === strPrefix && this._suffix === strSuffix) return this; - this._prefix = strPrefix; - this._suffix = strSuffix; - this._compiledVM = null; - this._compiledNodeVM = null; - this._compiledNodeVMStrict = null; - return this; - } - - /** - * Compile this script.
- * This is useful to detect syntax errors in the script. - * - * @public - * @return {this} This for chaining. - * @throws {SyntaxError} If there is a syntax error in the script. - */ - compile() { - this._compileVM(); - return this; - } - - /** - * Get the compiled code. - * - * @private - * @return {string} The code. - */ - getCompiledCode() { - if (!this._compiledCode) { - this._compiledCode = this._compiler(this._prefix + removeShebang(this._code) + this._suffix, this.filename); - } - return this._compiledCode; - } - - /** - * Compiles this script to a vm.Script. - * - * @private - * @param {string} prefix - JavaScript code that will be used as prefix. - * @param {string} suffix - JavaScript code that will be used as suffix. - * @return {vm.Script} The compiled vm.Script. - * @throws {SyntaxError} If there is a syntax error in the script. - */ - _compile(prefix, suffix) { - return new vm.Script(prefix + this.getCompiledCode() + suffix, { - filename: this.filename, - displayErrors: false, - lineOffset: this.lineOffset, - columnOffset: this.columnOffset, - importModuleDynamically - }); - } - - /** - * Will return the cached version of the script intended for VM or compile it. - * - * @private - * @return {vm.Script} The compiled script - * @throws {SyntaxError} If there is a syntax error in the script. - */ - _compileVM() { - let script = this._compiledVM; - if (!script) { - this._compiledVM = script = this._compile('', ''); - } - return script; - } - - /** - * Will return the cached version of the script intended for NodeVM or compile it. - * - * @private - * @return {vm.Script} The compiled script - * @throws {SyntaxError} If there is a syntax error in the script. - */ - _compileNodeVM() { - let script = this._compiledNodeVM; - if (!script) { - this._compiledNodeVM = script = this._compile(MODULE_PREFIX, MODULE_SUFFIX); - } - return script; - } - - /** - * Will return the cached version of the script intended for NodeVM in strict mode or compile it. - * - * @private - * @return {vm.Script} The compiled script - * @throws {SyntaxError} If there is a syntax error in the script. - */ - _compileNodeVMStrict() { - let script = this._compiledNodeVMStrict; - if (!script) { - this._compiledNodeVMStrict = script = this._compile(STRICT_MODULE_PREFIX, MODULE_SUFFIX); - } - return script; - } - -} - -/** - * - * This callback will be called and has a specific time to finish.
- * No parameters will be supplied.
- * If parameters are required, use a closure. - * - * @private - * @callback runWithTimeout - * @return {*} - * - */ - -/** - * Run a function with a specific timeout. - * - * @private - * @param {runWithTimeout} fn - Function to run with the specific timeout. - * @param {number} timeout - The amount of time to give the function to finish. - * @return {*} The value returned by the function. - * @throws {Error} If the function took to long. - */ -function doWithTimeout(fn, timeout) { - let ctx = CACHE.timeoutContext; - let script = CACHE.timeoutScript; - if (!ctx) { - CACHE.timeoutContext = ctx = vm.createContext(); - CACHE.timeoutScript = script = new vm.Script('fn()', { - filename: 'timeout_bridge.js', - displayErrors: false, - importModuleDynamically - }); - } - ctx.fn = fn; - try { - return script.runInContext(ctx, { - displayErrors: false, - importModuleDynamically, - timeout - }); - } finally { - ctx.fn = null; - } -} - -function tryCompile(args) { - const code = args[args.length - 1]; - const params = args.slice(0, -1); - vm.compileFunction(code, params); -} - -function makeCheckHook(checkAsync, checkImport) { - if (!checkAsync && !checkImport) return null; - return (hook, args) => { - if (hook === 'function' || hook === 'generator_function' || hook === 'eval' || hook === 'run' || - (!checkAsync && (hook === 'async_function' || hook === 'async_generator_function'))) { - if (hook === 'eval') { - const script = args[0]; - args = [script]; - if (typeof(script) !== 'string') return args; - } else { - // Next line throws on Symbol, this is the same behavior as function constructor calls - args = args.map(arg => `${arg}`); - } - const hasAsync = checkAsync && args.findIndex(arg => /\basync\b/.test(arg)) !== -1; - const hasImport = checkImport && args.findIndex(arg => /\bimport\b/.test(arg)) !== -1; - if (!hasAsync && !hasImport) return args; - const mapped = args.map(arg => { - if (hasAsync) arg = arg.replace(/async/g, 'a\\u0073ync'); - if (hasImport) arg = arg.replace(/import/g, 'i\\u006dport'); - return arg; - }); - try { - tryCompile(mapped); - } catch (u) { - // Some random syntax error or error because of async or import. - - // First report real syntax errors - tryCompile(args); - - if (hasAsync && hasImport) { - const mapped2 = args.map(arg => arg.replace(/async/g, 'a\\u0073ync')); - try { - tryCompile(mapped2); - } catch (e) { - throw new VMError('Async not available'); - } - throw new VMError('Dynamic Import not supported'); - } - if (hasAsync) { - // Then async error - throw new VMError('Async not available'); - } - throw new VMError('Dynamic Import not supported'); - } - return args; - } - if (checkAsync) throw new VMError('Async not available'); - return args; - }; -} - -/** - * Class VM. - * - * @public - */ -class VM extends EventEmitter { - - /** - * The timeout for {@link VM#run} calls. - * - * @public - * @since v3.9.0 - * @member {number} timeout - * @memberOf VM# - */ - - /** - * Get the global sandbox object. - * - * @public - * @readonly - * @since v3.9.0 - * @member {Object} sandbox - * @memberOf VM# - */ - - /** - * The compiler to use to get the JavaScript code. - * - * @public - * @readonly - * @since v3.9.0 - * @member {(string|compileCallback)} compiler - * @memberOf VM# - */ - - /** - * The context for this sandbox. - * - * @private - * @readonly - * @member {Object} _context - * @memberOf VM# - */ - - /** - * The internal methods for this sandbox. - * - * @private - * @readonly - * @member {{Contextify: Object, Decontextify: Object, Buffer: Object, sandbox:Object}} _internal - * @memberOf VM# - */ - - /** - * The resolved compiler to use to get the JavaScript code. - * - * @private - * @readonly - * @member {compileCallback} _compiler - * @memberOf VM# - */ - - /** - * The hook called when some events occurs. - * - * @private - * @readonly - * @since v3.9.2 - * @member {Function} _hook - * @memberOf VM# - */ - - /** - * Create a new VM instance. - * - * @public - * @param {Object} [options] - VM options. - * @param {number} [options.timeout] - The amount of time until a call to {@link VM#run} will timeout. - * @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)().
- * Only available for node v10+. - * @param {boolean} [options.wasm=true] - Allow to run wasm code.
- * Only available for node v10+. - * @param {boolean} [options.fixAsync=false] - Filters for async functions. - * @throws {VMError} If the compiler is unknown. - */ - constructor(options = {}) { - super(); - - // Read all options - const { - timeout, - sandbox, - compiler = 'javascript' - } = options; - const allowEval = options.eval !== false; - const allowWasm = options.wasm !== false; - const fixAsync = !!options.fixAsync; - - // Early error if sandbox is not an object. - if (sandbox && 'object' !== typeof sandbox) { - throw new VMError('Sandbox must be object.'); - } - - // Early error if compiler can't be found. - const resolvedCompiler = lookupCompiler(compiler); - - // Create a new context for this vm. - const _context = vm.createContext(undefined, { - codeGeneration: { - strings: allowEval, - wasm: allowWasm - } - }); - - // Create the bridge between the host and the sandbox. - const _internal = CACHE.contextifyScript.runInContext(_context, DEFAULT_RUN_OPTIONS).call(_context, require, HOST); - - const hook = makeCheckHook(fixAsync, true); - - // Define the properties of this object. - // Use Object.defineProperties here to be able to - // hide and set properties write only. - Object.defineProperties(this, { - timeout: { - value: timeout, - writable: true, - enumerable: true - }, - compiler: { - value: compiler, - enumerable: true - }, - sandbox: { - value: _internal.sandbox, - enumerable: true - }, - _context: {value: _context}, - _internal: {value: _internal}, - _compiler: {value: resolvedCompiler}, - _hook: {value: hook} - }); - - if (hook) { - if (!CACHE.hookScript) { - CACHE.hookScript = loadAndCompileScript(`${__dirname}/fixasync.js`, '(function() { ', '\n})'); - CACHE.getGlobalScript = new vm.Script('this', { - filename: 'get_global.js', - displayErrors: false, - importModuleDynamically - }); - try { - CACHE.getGeneratorFunctionScript = new vm.Script('(function*(){}).constructor', { - filename: 'get_generator_function.js', - displayErrors: false, - importModuleDynamically - }); - } catch (ex) {} - try { - CACHE.getAsyncFunctionScript = new vm.Script('(async function(){}).constructor', { - filename: 'get_async_function.js', - displayErrors: false, - importModuleDynamically - }); - } catch (ex) {} - try { - CACHE.getAsyncGeneratorFunctionScript = new vm.Script('(async function*(){}).constructor', { - filename: 'get_async_generator_function.js', - displayErrors: false, - importModuleDynamically - }); - } catch (ex) {} - } - const internal = { - __proto__: null, - global: CACHE.getGlobalScript.runInContext(_context, DEFAULT_RUN_OPTIONS), - internal: _internal, - host: HOST, - hook - }; - if (CACHE.getGeneratorFunctionScript) { - try { - internal.GeneratorFunction = CACHE.getGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); - } catch (ex) {} - } - if (CACHE.getAsyncFunctionScript) { - try { - internal.AsyncFunction = CACHE.getAsyncFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); - } catch (ex) {} - } - if (CACHE.getAsyncGeneratorFunctionScript) { - try { - internal.AsyncGeneratorFunction = CACHE.getAsyncGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); - } catch (ex) {} - } - CACHE.hookScript.runInContext(_context, DEFAULT_RUN_OPTIONS).call(internal); - } - - // prepare global sandbox - if (sandbox) { - this.setGlobals(sandbox); - } - } - - /** - * Adds all the values to the globals. - * - * @public - * @since v3.9.0 - * @param {Object} values - All values that will be added to the globals. - * @return {this} This for chaining. - * @throws {*} If the setter of a global throws an exception it is propagated. And the remaining globals will not be written. - */ - setGlobals(values) { - for (const name in values) { - if (Object.prototype.hasOwnProperty.call(values, name)) { - this._internal.Contextify.setGlobal(name, values[name]); - } - } - return this; - } - - /** - * Set a global value. - * - * @public - * @since v3.9.0 - * @param {string} name - The name of the global. - * @param {*} value - The value of the global. - * @return {this} This for chaining. - * @throws {*} If the setter of the global throws an exception it is propagated. - */ - setGlobal(name, value) { - this._internal.Contextify.setGlobal(name, value); - return this; - } - - /** - * Get a global value. - * - * @public - * @since v3.9.0 - * @param {string} name - The name of the global. - * @return {*} The value of the global. - * @throws {*} If the getter of the global throws an exception it is propagated. - */ - getGlobal(name) { - return this._internal.Contextify.getGlobal(name); - } - - /** - * Freezes the object inside VM making it read-only. Not available for primitive values. - * - * @public - * @param {*} value - Object to freeze. - * @param {string} [globalName] - Whether to add the object to global. - * @return {*} Object to freeze. - * @throws {*} If the setter of the global throws an exception it is propagated. - */ - freeze(value, globalName) { - this._internal.Contextify.readonly(value); - if (globalName) this._internal.Contextify.setGlobal(globalName, value); - return value; - } - - /** - * Protects the object inside VM making impossible to set functions as it's properties. Not available for primitive values. - * - * @public - * @param {*} value - Object to protect. - * @param {string} [globalName] - Whether to add the object to global. - * @return {*} Object to protect. - * @throws {*} If the setter of the global throws an exception it is propagated. - */ - protect(value, globalName) { - this._internal.Contextify.protected(value); - if (globalName) this._internal.Contextify.setGlobal(globalName, value); - return value; - } - - /** - * Run the code in VM. - * - * @public - * @param {(string|VMScript)} code - Code to run. - * @param {string} [filename="vm.js"] - Filename that shows up in any stack traces produced from this script.
- * This is only used if code is a String. - * @return {*} Result of executed code. - * @throws {SyntaxError} If there is a syntax error in the script. - * @throws {Error} An error is thrown when the script took to long and there is a timeout. - * @throws {*} If the script execution terminated with an exception it is propagated. - */ - run(code, filename) { - let script; - if (code instanceof VMScript) { - if (this._hook) { - const scriptCode = code.getCompiledCode(); - const changed = this._hook('run', [scriptCode])[0]; - if (changed === scriptCode) { - script = code._compileVM(); - } else { - script = new vm.Script(changed, { - filename: code.filename, - displayErrors: false, - importModuleDynamically - }); - } - } else { - script = code._compileVM(); - } - } else { - const useFileName = filename || 'vm.js'; - let scriptCode = this._compiler(code, useFileName); - if (this._hook) { - scriptCode = this._hook('run', [scriptCode])[0]; - } - // Compile the script here so that we don't need to create a instance of VMScript. - script = new vm.Script(scriptCode, { - filename: useFileName, - displayErrors: false, - importModuleDynamically - }); - } - - if (!this.timeout) { - // If no timeout is given, directly run the script. - try { - return this._internal.Decontextify.value(script.runInContext(this._context, DEFAULT_RUN_OPTIONS)); - } catch (e) { - throw this._internal.Decontextify.value(e); - } - } - - return doWithTimeout(()=>{ - try { - return this._internal.Decontextify.value(script.runInContext(this._context, DEFAULT_RUN_OPTIONS)); - } catch (e) { - throw this._internal.Decontextify.value(e); - } - }, this.timeout); - } - - /** - * Run the code in VM. - * - * @public - * @since v3.9.0 - * @param {string} filename - Filename of file to load and execute in a NodeVM. - * @return {*} Result of executed code. - * @throws {Error} If filename is not a valid filename. - * @throws {SyntaxError} If there is a syntax error in the script. - * @throws {Error} An error is thrown when the script took to long and there is a timeout. - * @throws {*} If the script execution terminated with an exception it is propagated. - */ - runFile(filename) { - 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 this.run(fs.readFileSync(resolvedFilename, 'utf8'), resolvedFilename); - } - -} - -/** - * Event caused by a console.debug call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.debug" - * @type {...*} - */ - -/** - * Event caused by a console.log call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.log" - * @type {...*} - */ - -/** - * Event caused by a console.info call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.info" - * @type {...*} - */ - -/** - * Event caused by a console.warn call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.warn" - * @type {...*} - */ - -/** - * Event caused by a console.error call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.error" - * @type {...*} - */ - -/** - * Event caused by a console.dir call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.dir" - * @type {...*} - */ - -/** - * Event caused by a console.trace call if options.console="redirect" is specified. - * - * @public - * @event NodeVM."console.trace" - * @type {...*} - */ - -/** - * Class NodeVM. - * - * @public - * @extends {VM} - * @extends {EventEmitter} - */ -class NodeVM extends VM { - - /** - * Create a new NodeVM instance.
- * - * Unlike VM, NodeVM lets you use require same way like in regular node.
- * - * 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)().
- * Only available for node v10+. - * @param {boolean} [options.wasm=true] - Allow to run wasm code.
- * Only available for node v10+. - * @param {("inherit"|"redirect"|"off")} [options.console="inherit"] - Sets the behavior of the console in the sandbox. - * inherit to enable console, redirect to redirect to events, off to disable console. - * @param {Object|boolean} [options.require=false] - Allow require inside the sandbox. - * @param {(boolean|string[]|Object)} [options.require.external=false] - 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 builtin 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 builtin). - * @param {("host"|"sandbox")} [options.require.context="host"] - host 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. - * @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 {boolean} [options.nesting=false] - Allow nesting of VMs. - * @param {("commonjs"|"none")} [options.wrapper="commonjs"] - commonjs to wrap script into CommonJS wrapper, - * none 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 process.argv. - * This object will not be copied and the script can change this object. - * @param {Object} [options.env={}] - Environment map passed to process.env. - * 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 sandbox = options.sandbox; - - // Throw this early - if (sandbox && 'object' !== typeof sandbox) { - throw new VMError('Sandbox must be object.'); - } - - super({compiler: options.compiler, eval: options.eval, wasm: options.wasm}); - - // defaults - Object.defineProperty(this, 'options', {value: { - console: options.console || 'inherit', - require: options.require || false, - nesting: options.nesting || false, - wrapper: options.wrapper || 'commonjs', - sourceExtensions: options.sourceExtensions || ['js'], - strict: options.strict || false - }}); - - let sandboxScript = CACHE.sandboxScript; - if (!sandboxScript) { - CACHE.sandboxScript = sandboxScript = loadAndCompileScript(`${__dirname}/sandbox.js`, - '(function (vm, host, Contextify, Decontextify, Buffer, options) { ', '\n})'); - } - - const closure = sandboxScript.runInContext(this._context, DEFAULT_RUN_OPTIONS); - - Object.defineProperty(this, '_prepareRequire', { - value: closure.call(this._context, this, HOST, this._internal.Contextify, this._internal.Decontextify, this._internal.Buffer, options) - }); - - // prepare global sandbox - if (sandbox) { - this.setGlobals(sandbox); - } - - if (this.options.require && this.options.require.import) { - if (Array.isArray(this.options.require.import)) { - for (let i = 0, l = this.options.require.import.length; i < l; i++) { - this.require(this.options.require.import[i]); - } - } else { - this.require(this.options.require.import); - } - } - } - - /** - * @ignore - * @deprecated Just call the method yourself like method(args); - * @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) { - 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|VMScript)} code - Code to run. - * @param {string} [filename] - Filename that shows up in any stack traces produced from this script.
- * This is only used if code is a String. - * @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, filename) { - let dirname; - let resolvedFilename; - let script; - - if (code instanceof VMScript) { - if (this._hook) { - const prefix = this.options.strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX; - const scriptCode = prefix + code.getCompiledCode() + MODULE_SUFFIX; - const changed = this._hook('run', [scriptCode])[0]; - if (changed === scriptCode) { - script = this.options.strict ? code._compileNodeVMStrict() : code._compileNodeVM(); - } else { - script = new vm.Script(changed, { - filename: code.filename, - displayErrors: false, - importModuleDynamically - }); - } - } else { - script = this.options.strict ? code._compileNodeVMStrict() : code._compileNodeVM(); - } - resolvedFilename = pa.resolve(code.filename); - dirname = pa.dirname(resolvedFilename); - } else { - const unresolvedFilename = filename || 'vm.js'; - if (filename) { - resolvedFilename = pa.resolve(filename); - dirname = pa.dirname(resolvedFilename); - } else { - resolvedFilename = null; - dirname = null; - } - const prefix = this.options.strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX; - let scriptCode = prefix + this._compiler(code, unresolvedFilename) + MODULE_SUFFIX; - if (this._hook) { - scriptCode = this._hook('run', [scriptCode])[0]; - } - script = new vm.Script(scriptCode, { - filename: unresolvedFilename, - displayErrors: false, - importModuleDynamically - }); - } - - const wrapper = this.options.wrapper; - const module = this._internal.Contextify.makeModule(); - - try { - const closure = script.runInContext(this._context, DEFAULT_RUN_OPTIONS); - - const returned = closure.call(this._context, module.exports, this._prepareRequire(dirname), module, resolvedFilename, dirname); - - return this._internal.Decontextify.value(wrapper === 'commonjs' ? module.exports : returned); - } catch (e) { - throw this._internal.Decontextify.value(e); - } - - } - - /** - * 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 filename 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); - } -} - -/** - * VMError. - * - * @public - * @extends {Error} - */ -class VMError extends Error { - - /** - * Create VMError instance. - * - * @public - * @param {string} message - Error message. - */ - constructor(message) { - super(message); - - this.name = 'VMError'; - - Error.captureStackTrace(this, this.constructor); - } -} - -/** - * Host objects - * - * @private - */ -const HOST = { - version: parseInt(process.versions.node.split('.')[0]), - require, - process, - console, - setTimeout, - setInterval, - setImmediate, - clearTimeout, - clearInterval, - clearImmediate, - String, - Number, - Buffer, - Boolean, - Array, - Date, - Error, - EvalError, - RangeError, - ReferenceError, - SyntaxError, - TypeError, - URIError, - RegExp, - Function, - Object, - VMError, - Proxy, - Reflect, - Map, - WeakMap, - Set, - WeakSet, - Promise, - Symbol, - INSPECT_MAX_BYTES, - VM, - NodeVM, - helpers, - MODULE_PREFIX, - STRICT_MODULE_PREFIX, - MODULE_SUFFIX -}; +const { + VMError +} = require('./bridge'); +const { + VMScript +} = require('./script'); +const { + VM +} = require('./vm'); +const { + NodeVM +} = require('./nodevm'); exports.VMError = VMError; +exports.VMScript = VMScript; exports.NodeVM = NodeVM; exports.VM = VM; -exports.VMScript = VMScript; diff --git a/lib/nodevm.js b/lib/nodevm.js new file mode 100644 index 0000000..6192ca9 --- /dev/null +++ b/lib/nodevm.js @@ -0,0 +1,497 @@ +'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 console.debug call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.debug" + * @type {...*} + */ + +/** + * Event caused by a console.log call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.log" + * @type {...*} + */ + +/** + * Event caused by a console.info call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.info" + * @type {...*} + */ + +/** + * Event caused by a console.warn call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.warn" + * @type {...*} + */ + +/** + * Event caused by a console.error call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.error" + * @type {...*} + */ + +/** + * Event caused by a console.dir call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.dir" + * @type {...*} + */ + +/** + * Event caused by a console.trace call if options.console="redirect" is specified. + * + * @public + * @event NodeVM."console.trace" + * @type {...*} + */ + +/** + * Class NodeVM. + * + * @public + * @extends {VM} + * @extends {EventEmitter} + */ +class NodeVM extends VM { + + /** + * Create a new NodeVM instance.
+ * + * Unlike VM, NodeVM lets you use require same way like in regular node.
+ * + * 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)().
+ * Only available for node v10+. + * @param {boolean} [options.wasm=true] - Allow to run wasm code.
+ * Only available for node v10+. + * @param {("inherit"|"redirect"|"off")} [options.console="inherit"] - Sets the behavior of the console in the sandbox. + * inherit to enable console, redirect to redirect to events, off to disable console. + * @param {Object|boolean} [options.require=false] - Allow require inside the sandbox. + * @param {(boolean|string[]|Object)} [options.require.external=false] - WARNING: When allowing require the option options.require.root + * 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"] - host 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. + * @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] - + * WARNING: Allowing this is a security risk as scripts can create a NodeVM which can require any host module. + * Allow nesting of VMs. + * @param {("commonjs"|"none")} [options.wrapper="commonjs"] - commonjs to wrap script into CommonJS wrapper, + * none 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 process.argv. + * This object will not be copied and the script can change this object. + * @param {Object} [options.env={}] - Environment map passed to process.env. + * 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 + } = 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}, + _cacheRequireModule: {__proto__: null, value: null, writable: true} + }); + + + resolver.init(this, ()=>true); + + // 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 method(args); + * @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) { + mod = new (this._Module)(this._resolver.pathConcat(path, '/vm.js'), path); + this._cacheRequireModule = mod; + } + return mod.require(module); + } + + /** + * 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.
+ * 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] - commonjs to wrap script into CommonJS wrapper, + * none 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); + } + } 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); + } else { + sandboxModule = new (this._Module)(null, null); + sandboxModule.id = unresolvedFilename; + } + } + const prefix = strict ? STRICT_MODULE_PREFIX : MODULE_PREFIX; + let scriptCode = prefix + this._compiler(code, unresolvedFilename) + MODULE_SUFFIX; + scriptCode = transformer(null, scriptCode, false, false).code; + script = new Script(scriptCode, { + __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 filename 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; diff --git a/lib/resolver-compat.js b/lib/resolver-compat.js new file mode 100644 index 0000000..663a7ce --- /dev/null +++ b/lib/resolver-compat.js @@ -0,0 +1,317 @@ +'use strict'; + +// Translate the old options to the new Resolver functionality. + +const fs = require('fs'); +const pa = require('path'); +const nmod = require('module'); +const {EventEmitter} = require('events'); +const util = require('util'); + +const { + Resolver, + DefaultResolver +} = require('./resolver'); +const {VMScript} = require('./script'); +const {VM} = require('./vm'); +const {VMError} = require('./bridge'); + +/** + * Require wrapper to be able to annotate require with webpackIgnore. + * + * @private + * @param {string} moduleName - Name of module to load. + * @return {*} Module exports. + */ +function defaultRequire(moduleName) { + // Set module.parser.javascript.commonjsMagicComments=true in your webpack config. + // eslint-disable-next-line global-require + return require(/* webpackIgnore: true */ moduleName); +} + +// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping +function escapeRegExp(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +function makeExternalMatcher(obj) { + const regexString = escapeRegExp(obj).replace(/\\\\|\//g, '[\\\\/]') + .replace(/\\\*\\\*/g, '.*').replace(/\\\*/g, '[^\\\\/]*').replace(/\\\?/g, '[^\\\\/]'); + return new RegExp(`[\\\\/]node_modules[\\\\/]${regexString}(?:[\\\\/](?!(?:.*[\\\\/])?node_modules[\\\\/]).*)?$`); +} + +class TransitiveResolver extends DefaultResolver { + + constructor(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler, externals) { + super(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler); + this.externals = externals; + this.currMod = undefined; + this.trustedMods = new WeakMap(); + } + + isPathAllowed(path) { + return this.isPathAllowedForModule(path, this.currMod); + } + + isPathAllowedForModule(path, mod) { + if (!super.isPathAllowed(path)) return false; + if (mod && (mod.allowTransitive || path.startsWith(mod.path))) return true; + return this.externals.some(regex => regex.test(path)); + } + + registerModule(mod, filename, path, parent, direct) { + const trustedParent = this.trustedMods.get(parent); + this.trustedMods.set(mod, { + filename, + path, + paths: this.genLookupPaths(path), + allowTransitive: (direct && trustedParent && trustedParent.allowTransitive) || this.externals.some(regex => regex.test(filename)) + }); + } + + resolveFull(mod, x, options, ext, direct) { + this.currMod = undefined; + if (!direct) return super.resolveFull(mod, x, options, ext, false); + const trustedMod = this.trustedMods.get(mod); + if (!trustedMod || mod.path !== trustedMod.path) return super.resolveFull(mod, x, options, ext, false); + const paths = [...mod.paths]; + if (paths.length === trustedMod.length) { + for (let i = 0; i < paths.length; i++) { + if (paths[i] !== trustedMod.paths[i]) { + return super.resolveFull(mod, x, options, ext, false); + } + } + } + const extCopy = Object.assign({__proto__: null}, ext); + try { + this.currMod = trustedMod; + return super.resolveFull(trustedMod, x, undefined, extCopy, true); + } finally { + this.currMod = undefined; + } + } + + checkAccess(mod, filename) { + const trustedMod = this.trustedMods.get(mod); + if ((!trustedMod || trustedMod.filename !== filename) && !this.isPathAllowedForModule(filename, undefined)) { + throw new VMError(`Module '${filename}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); + } + } + + loadJS(vm, mod, filename) { + filename = this.pathResolve(filename); + this.checkAccess(mod, filename); + const trustedMod = this.trustedMods.get(mod); + const script = this.readScript(filename); + vm.run(script, {filename, strict: true, module: mod, wrapper: 'none', dirname: trustedMod ? trustedMod.path : mod.path}); + } + +} + +function defaultBuiltinLoader(resolver, vm, id) { + const mod = resolver.hostRequire(id); + return vm.readonly(mod); +} + +const eventsModules = new WeakMap(); + +function defaultBuiltinLoaderEvents(resolver, vm, id) { + return eventsModules.get(vm); +} + +let cacheBufferScript; + +function defaultBuiltinLoaderBuffer(resolver, vm, id) { + if (!cacheBufferScript) { + cacheBufferScript = new VMScript('return buffer=>({Buffer: buffer});', {__proto__: null, filename: 'buffer.js'}); + } + const makeBuffer = vm.run(cacheBufferScript, {__proto__: null, strict: true, wrapper: 'none'}); + return makeBuffer(Buffer); +} + +let cacheUtilScript; + +function defaultBuiltinLoaderUtil(resolver, vm, id) { + if (!cacheUtilScript) { + cacheUtilScript = new VMScript(`return function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + Object.setPrototypeOf(ctor.prototype, superCtor.prototype); + }`, {__proto__: null, filename: 'util.js'}); + } + const inherits = vm.run(cacheUtilScript, {__proto__: null, strict: true, wrapper: 'none'}); + const copy = Object.assign({}, util); + copy.inherits = inherits; + return vm.readonly(copy); +} + +const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))).filter(s=>!s.startsWith('internal/')); + +let EventEmitterReferencingAsyncResourceClass = null; +if (EventEmitter.EventEmitterAsyncResource) { + // eslint-disable-next-line global-require + const {AsyncResource} = require('async_hooks'); + const kEventEmitter = Symbol('kEventEmitter'); + class EventEmitterReferencingAsyncResource extends AsyncResource { + constructor(ee, type, options) { + super(type, options); + this[kEventEmitter] = ee; + } + get eventEmitter() { + return this[kEventEmitter]; + } + } + EventEmitterReferencingAsyncResourceClass = EventEmitterReferencingAsyncResource; +} + +let cacheEventsScript; + +const SPECIAL_MODULES = { + events(vm) { + if (!cacheEventsScript) { + const eventsSource = fs.readFileSync(`${__dirname}/events.js`, 'utf8'); + cacheEventsScript = new VMScript(`(function (fromhost) { const module = {}; module.exports={};{ ${eventsSource} +} return module.exports;})`, {filename: 'events.js'}); + } + const closure = VM.prototype.run.call(vm, cacheEventsScript); + const eventsInstance = closure(vm.readonly({ + kErrorMonitor: EventEmitter.errorMonitor, + once: EventEmitter.once, + on: EventEmitter.on, + getEventListeners: EventEmitter.getEventListeners, + EventEmitterReferencingAsyncResource: EventEmitterReferencingAsyncResourceClass + })); + eventsModules.set(vm, eventsInstance); + vm._addProtoMapping(EventEmitter.prototype, eventsInstance.EventEmitter.prototype); + return defaultBuiltinLoaderEvents; + }, + buffer(vm) { + return defaultBuiltinLoaderBuffer; + }, + util(vm) { + return defaultBuiltinLoaderUtil; + } +}; + +function addDefaultBuiltin(builtins, key, vm) { + if (builtins[key]) return; + const special = SPECIAL_MODULES[key]; + builtins[key] = special ? special(vm) : defaultBuiltinLoader; +} + + +function genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override) { + const builtins = {__proto__: null}; + if (mockOpt) { + const keys = Object.getOwnPropertyNames(mockOpt); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + builtins[key] = (resolver, tvm, id) => tvm.readonly(mockOpt[key]); + } + } + if (override) { + const keys = Object.getOwnPropertyNames(override); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + builtins[key] = override[key]; + } + } + if (Array.isArray(builtinOpt)) { + const def = builtinOpt.indexOf('*') >= 0; + if (def) { + for (let i = 0; i < BUILTIN_MODULES.length; i++) { + const name = BUILTIN_MODULES[i]; + if (builtinOpt.indexOf(`-${name}`) === -1) { + addDefaultBuiltin(builtins, name, vm); + } + } + } else { + for (let i = 0; i < BUILTIN_MODULES.length; i++) { + const name = BUILTIN_MODULES[i]; + if (builtinOpt.indexOf(name) !== -1) { + addDefaultBuiltin(builtins, name, vm); + } + } + } + } else if (builtinOpt) { + for (let i = 0; i < BUILTIN_MODULES.length; i++) { + const name = BUILTIN_MODULES[i]; + if (builtinOpt[name]) { + addDefaultBuiltin(builtins, name, vm); + } + } + } + return builtins; +} + +function defaultCustomResolver() { + return undefined; +} + +const DENY_RESOLVER = new Resolver({__proto__: null}, [], id => { + throw new VMError(`Access denied to require '${id}'`, 'EDENIED'); +}); + +function resolverFromOptions(vm, options, override, compiler) { + if (!options) { + if (!override) return DENY_RESOLVER; + const builtins = genBuiltinsFromOptions(vm, undefined, undefined, override); + return new Resolver(builtins, [], defaultRequire); + } + + const { + builtin: builtinOpt, + mock: mockOpt, + external: externalOpt, + root: rootPaths, + resolve: customResolver, + customRequire: hostRequire = defaultRequire, + context = 'host' + } = options; + + const builtins = genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override); + + if (!externalOpt) return new Resolver(builtins, [], hostRequire); + + let checkPath; + if (rootPaths) { + const checkedRootPaths = (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f => pa.resolve(f)); + checkPath = (filename) => { + return checkedRootPaths.some(path => filename.startsWith(path)); + }; + } else { + checkPath = () => true; + } + + let newCustomResolver = defaultCustomResolver; + let externals = undefined; + if (customResolver) { + newCustomResolver = (resolver, x, path, extList) => { + const resolved = customResolver(x, path); + if (!resolved) return undefined; + if (externals) externals.push(new RegExp('^' + escapeRegExp(resolved))); + return resolver.loadAsFileOrDirecotry(resolved, extList); + }; + } + + if (typeof externalOpt !== 'object') { + return new DefaultResolver(builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler); + } + + let external; + let transitive = false; + if (Array.isArray(externalOpt)) { + external = externalOpt; + } else { + external = externalOpt.modules; + transitive = context === 'sandbox' && externalOpt.transitive; + } + externals = external.map(makeExternalMatcher); + if (transitive) return new TransitiveResolver(builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler, externals); + const nextCheckPath = checkPath; + checkPath = (filename) => { + return nextCheckPath(filename) && externals.some(regex => regex.test(filename)); + }; + return new DefaultResolver(builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler); +} + +exports.resolverFromOptions = resolverFromOptions; diff --git a/lib/resolver.js b/lib/resolver.js new file mode 100644 index 0000000..008104c --- /dev/null +++ b/lib/resolver.js @@ -0,0 +1,882 @@ +'use strict'; + +// The Resolver is currently experimental and might be exposed to users in the future. + +const pa = require('path'); +const fs = require('fs'); + +const { + VMError +} = require('./bridge'); +const { VMScript } = require('./script'); + +// This should match. Note that '\', '%' are invalid characters +// 1. name/.* +// 2. @scope/name/.* +const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^/\\%]+)(\/.*)?$/; + +// See https://tc39.es/ecma262/#integer-index +function isArrayIndex(key) { + const keyNum = +key; + if (`${keyNum}` !== key) return false; + return keyNum >= 0 && keyNum < 0xFFFFFFFF; +} + +class Resolver { + + constructor(builtinModules, globalPaths, hostRequire) { + this.builtinModules = builtinModules; + this.globalPaths = globalPaths; + this.hostRequire = hostRequire; + } + + init(vm) { + + } + + pathResolve(path) { + return pa.resolve(path); + } + + pathIsRelative(path) { + if (path === '' || path[0] !== '.') return false; + if (path.length === 1) return true; + const idx = path[1] === '.' ? 2 : 1; + if (path.length <= idx) return false; + return path[idx] === '/' || path[idx] === pa.sep; + } + + pathIsAbsolute(path) { + return pa.isAbsolute(path); + } + + pathConcat(...paths) { + return pa.join(...paths); + } + + pathBasename(path) { + return pa.basename(path); + } + + pathDirname(path) { + return pa.dirname(path); + } + + lookupPaths(mod, id) { + if (typeof id === 'string') throw new Error('Id is not a string'); + if (this.pathIsRelative(id)) return [mod.path || '.']; + return [...mod.paths, ...this.globalPaths]; + } + + getBuiltinModulesList() { + return Object.getOwnPropertyNames(this.builtinModules); + } + + loadBuiltinModule(vm, id) { + const handler = this.builtinModules[id]; + return handler && handler(this, vm, id); + } + + loadJS(vm, mod, filename) { + throw new VMError(`Access denied to require '${filename}'`, 'EDENIED'); + } + + loadJSON(vm, mod, filename) { + throw new VMError(`Access denied to require '${filename}'`, 'EDENIED'); + } + + loadNode(vm, mod, filename) { + throw new VMError(`Access denied to require '${filename}'`, 'EDENIED'); + } + + registerModule(mod, filename, path, parent, direct) { + + } + + resolve(mod, x, options, ext, direct) { + if (typeof x !== 'string') throw new Error('Id is not a string'); + + if (x.startsWith('node:') || this.builtinModules[x]) { + // a. return the core module + // b. STOP + return x; + } + + return this.resolveFull(mod, x, options, ext, direct); + } + + resolveFull(mod, x, options, ext, direct) { + // 7. THROW "not found" + throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND'); + } + + // NODE_MODULES_PATHS(START) + genLookupPaths(path) { + // 1. let PARTS = path split(START) + // 2. let I = count of PARTS - 1 + // 3. let DIRS = [] + const dirs = []; + // 4. while I >= 0, + while (true) { + const name = this.pathBasename(path); + // a. if PARTS[I] = "node_modules" CONTINUE + if (name !== 'node_modules') { + // b. DIR = path join(PARTS[0 .. I] + "node_modules") + // c. DIRS = DIR + DIRS // Note: this seems wrong. Should be DIRS + DIR + dirs.push(this.pathConcat(path, 'node_modules')); + } + const dir = this.pathDirname(path); + if (dir == path) break; + // d. let I = I - 1 + path = dir; + } + + return dirs; + // This is done later on + // 5. return DIRS + GLOBAL_FOLDERS + } + +} + +class DefaultResolver extends Resolver { + + constructor(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler) { + super(builtinModules, globalPaths, hostRequire); + this.checkPath = checkPath; + this.pathContext = pathContext; + this.customResolver = customResolver; + this.compiler = compiler; + this.packageCache = {__proto__: null}; + this.scriptCache = {__proto__: null}; + } + + isPathAllowed(path) { + return this.checkPath(path); + } + + pathTestIsDirectory(path) { + try { + const stat = fs.statSync(path, {__proto__: null, throwIfNoEntry: false}); + return stat && stat.isDirectory(); + } catch (e) { + return false; + } + } + + pathTestIsFile(path) { + try { + const stat = fs.statSync(path, {__proto__: null, throwIfNoEntry: false}); + return stat && stat.isFile(); + } catch (e) { + return false; + } + } + + readFile(path) { + return fs.readFileSync(path, {encoding: 'utf8'}); + } + + readFileWhenExists(path) { + return this.pathTestIsFile(path) ? this.readFile(path) : undefined; + } + + readScript(filename) { + let script = this.scriptCache[filename]; + if (!script) { + script = new VMScript(this.readFile(filename), {filename, compiler: this.compiler}); + this.scriptCache[filename] = script; + } + return script; + } + + checkAccess(mod, filename) { + if (!this.isPathAllowed(filename)) { + throw new VMError(`Module '${filename}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); + } + } + + loadJS(vm, mod, filename) { + filename = this.pathResolve(filename); + this.checkAccess(mod, filename); + if (this.pathContext(filename, 'js') === 'sandbox') { + const script = this.readScript(filename); + vm.run(script, {filename, strict: true, module: mod, wrapper: 'none', dirname: mod.path}); + } else { + const m = this.hostRequire(filename); + mod.exports = vm.readonly(m); + } + } + + loadJSON(vm, mod, filename) { + filename = this.pathResolve(filename); + this.checkAccess(mod, filename); + const json = this.readFile(filename); + mod.exports = vm._jsonParse(json); + } + + loadNode(vm, mod, filename) { + filename = this.pathResolve(filename); + this.checkAccess(mod, filename); + if (this.pathContext(filename, 'node') === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.'); + const m = this.hostRequire(filename); + mod.exports = vm.readonly(m); + } + + // require(X) from module at path Y + resolveFull(mod, x, options, ext, direct) { + // Note: core module handled by caller + + const extList = Object.getOwnPropertyNames(ext); + const path = mod.path || '.'; + + // 5. LOAD_PACKAGE_SELF(X, dirname(Y)) + let f = this.loadPackageSelf(x, path, extList); + if (f) return f; + + // 4. If X begins with '#' + if (x[0] === '#') { + // a. LOAD_PACKAGE_IMPORTS(X, dirname(Y)) + f = this.loadPackageImports(x, path, extList); + if (f) return f; + } + + // 2. If X begins with '/' + if (this.pathIsAbsolute(x)) { + // a. set Y to be the filesystem root + f = this.loadAsFileOrDirecotry(x, extList); + if (f) return f; + + // c. THROW "not found" + throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND'); + + // 3. If X begins with './' or '/' or '../' + } else if (this.pathIsRelative(x)) { + if (typeof options === 'object' && options !== null) { + const paths = options.paths; + if (Array.isArray(paths)) { + for (let i = 0; i < paths.length; i++) { + // a. LOAD_AS_FILE(Y + X) + // b. LOAD_AS_DIRECTORY(Y + X) + f = this.loadAsFileOrDirecotry(this.pathConcat(paths[i], x), extList); + if (f) return f; + } + } else if (paths === undefined) { + // a. LOAD_AS_FILE(Y + X) + // b. LOAD_AS_DIRECTORY(Y + X) + f = this.loadAsFileOrDirecotry(this.pathConcat(path, x), extList); + if (f) return f; + } else { + throw new VMError('Invalid options.paths option.'); + } + } else { + // a. LOAD_AS_FILE(Y + X) + // b. LOAD_AS_DIRECTORY(Y + X) + f = this.loadAsFileOrDirecotry(this.pathConcat(path, x), extList); + if (f) return f; + } + + // c. THROW "not found" + throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND'); + } + + let dirs; + if (typeof options === 'object' && options !== null) { + const paths = options.paths; + if (Array.isArray(paths)) { + dirs = []; + + for (let i = 0; i < paths.length; i++) { + const lookups = this.genLookupPaths(paths[i]); + for (let j = 0; j < lookups.length; j++) { + if (!dirs.includes(lookups[j])) dirs.push(lookups[j]); + } + if (i === 0) { + const globalPaths = this.globalPaths; + for (let j = 0; j < globalPaths.length; j++) { + if (!dirs.includes(globalPaths[j])) dirs.push(globalPaths[j]); + } + } + } + } else if (paths === undefined) { + dirs = [...mod.paths, ...this.globalPaths]; + } else { + throw new VMError('Invalid options.paths option.'); + } + } else { + dirs = [...mod.paths, ...this.globalPaths]; + } + + // 6. LOAD_NODE_MODULES(X, dirname(Y)) + f = this.loadNodeModules(x, dirs, extList); + if (f) return f; + + f = this.customResolver(this, x, path, extList); + if (f) return f; + + return super.resolveFull(mod, x, options, ext, direct); + } + + loadAsFileOrDirecotry(x, extList) { + // a. LOAD_AS_FILE(X) + const f = this.loadAsFile(x, extList); + if (f) return f; + // b. LOAD_AS_DIRECTORY(X) + return this.loadAsDirectory(x, extList); + } + + tryFile(x) { + x = this.pathResolve(x); + return this.isPathAllowed(x) && this.pathTestIsFile(x) ? x : undefined; + } + + tryWithExtension(x, extList) { + for (let i = 0; i < extList.length; i++) { + const ext = extList[i]; + if (ext !== this.pathBasename(ext)) continue; + const f = this.tryFile(x + ext); + if (f) return f; + } + return undefined; + } + + readPackage(path) { + const packagePath = this.pathResolve(this.pathConcat(path, 'package.json')); + + const cache = this.packageCache[packagePath]; + if (cache !== undefined) return cache; + + if (!this.isPathAllowed(packagePath)) return undefined; + const content = this.readFileWhenExists(packagePath); + if (!content) { + this.packageCache[packagePath] = false; + return false; + } + + let parsed; + try { + parsed = JSON.parse(content); + } catch (e) { + e.path = packagePath; + e.message = 'Error parsing ' + packagePath + ': ' + e.message; + throw e; + } + + const filtered = { + name: parsed.name, + main: parsed.main, + exports: parsed.exports, + imports: parsed.imports, + type: parsed.type + }; + this.packageCache[packagePath] = filtered; + return filtered; + } + + readPackageScope(path) { + while (true) { + const dir = this.pathDirname(path); + if (dir === path) break; + const basename = this.pathBasename(dir); + if (basename === 'node_modules') break; + const pack = this.readPackage(dir); + if (pack) return {data: pack, scope: dir}; + path = dir; + } + return {data: undefined, scope: undefined}; + } + + // LOAD_AS_FILE(X) + loadAsFile(x, extList) { + // 1. If X is a file, load X as its file extension format. STOP + const f = this.tryFile(x); + if (f) return f; + // 2. If X.js is a file, load X.js as JavaScript text. STOP + // 3. If X.json is a file, parse X.json to a JavaScript Object. STOP + // 4. If X.node is a file, load X.node as binary addon. STOP + return this.tryWithExtension(x, extList); + } + + // LOAD_INDEX(X) + loadIndex(x, extList) { + // 1. If X/index.js is a file, load X/index.js as JavaScript text. STOP + // 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP + // 3. If X/index.node is a file, load X/index.node as binary addon. STOP + return this.tryWithExtension(this.pathConcat(x, 'index'), extList); + } + + // LOAD_AS_DIRECTORY(X) + loadAsPackage(x, pack, extList) { + // 1. If X/package.json is a file, + // already done. + if (pack) { + // a. Parse X/package.json, and look for "main" field. + // b. If "main" is a falsy value, GOTO 2. + if (typeof pack.main === 'string') { + // c. let M = X + (json main field) + const m = this.pathConcat(x, pack.main); + // d. LOAD_AS_FILE(M) + let f = this.loadAsFile(m, extList); + if (f) return f; + // e. LOAD_INDEX(M) + f = this.loadIndex(m, extList); + if (f) return f; + // f. LOAD_INDEX(X) DEPRECATED + f = this.loadIndex(x, extList); + if (f) return f; + // g. THROW "not found" + throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND'); + } + } + + // 2. LOAD_INDEX(X) + return this.loadIndex(x, extList); + } + + // LOAD_AS_DIRECTORY(X) + loadAsDirectory(x, extList) { + // 1. If X/package.json is a file, + const pack = this.readPackage(x); + return this.loadAsPackage(x, pack, extList); + } + + // LOAD_NODE_MODULES(X, START) + loadNodeModules(x, dirs, extList) { + // 1. let DIRS = NODE_MODULES_PATHS(START) + // This step is already done. + + // 2. for each DIR in DIRS: + for (let i = 0; i < dirs.length; i++) { + const dir = dirs[i]; + // a. LOAD_PACKAGE_EXPORTS(X, DIR) + let f = this.loadPackageExports(x, dir, extList); + if (f) return f; + // b. LOAD_AS_FILE(DIR/X) + f = this.loadAsFile(dir + '/' + x, extList); + if (f) return f; + // c. LOAD_AS_DIRECTORY(DIR/X) + f = this.loadAsDirectory(dir + '/' + x, extList); + if (f) return f; + } + + return undefined; + } + + // LOAD_PACKAGE_IMPORTS(X, DIR) + loadPackageImports(x, dir, extList) { + // 1. Find the closest package scope SCOPE to DIR. + const {data, scope} = this.readPackageScope(dir); + // 2. If no scope was found, return. + if (!data) return undefined; + // 3. If the SCOPE/package.json "imports" is null or undefined, return. + if (typeof data.imports !== 'object' || data.imports === null || Array.isArray(data.imports)) return undefined; + // 4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), + // ["node", "require"]) defined in the ESM resolver. + + // PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions) + // 1. Assert: specifier begins with "#". + // 2. If specifier is exactly equal to "#" or starts with "#/", then + if (x === '#' || x.startsWith('#/')) { + // a. Throw an Invalid Module Specifier error. + throw new VMError(`Invalid module specifier '${x}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // 3. Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL). + // Note: packageURL === parentURL === scope + // 4. If packageURL is not null, then + // Always true + // a. Let pjson be the result of READ_PACKAGE_JSON(packageURL). + // pjson === data + // b. If pjson.imports is a non-null Object, then + // Already tested + // x. Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions). + const match = this.packageImportsExportsResolve(x, data.imports, scope, true, ['node', 'require'], extList); + // y. If resolved is not null or undefined, return resolved. + if (!match) { + // 5. Throw a Package Import Not Defined error. + throw new VMError(`Package import not defined for '${x}'`, 'ERR_PACKAGE_IMPORT_NOT_DEFINED'); + } + // END PACKAGE_IMPORTS_RESOLVE + + // 5. RESOLVE_ESM_MATCH(MATCH). + return this.resolveEsmMatch(match, x, extList); + } + + // LOAD_PACKAGE_EXPORTS(X, DIR) + loadPackageExports(x, dir, extList) { + // 1. Try to interpret X as a combination of NAME and SUBPATH where the name + // may have a @scope/ prefix and the subpath begins with a slash (`/`). + const res = x.match(EXPORTS_PATTERN); + // 2. If X does not match this pattern or DIR/NAME/package.json is not a file, + // return. + if (!res) return undefined; + const scope = this.pathConcat(dir, res[0]); + const pack = this.readPackage(scope); + if (!pack) return undefined; + // 3. Parse DIR/NAME/package.json, and look for "exports" field. + // 4. If "exports" is null or undefined, return. + if (!pack.exports) return undefined; + // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, + // `package.json` "exports", ["node", "require"]) defined in the ESM resolver. + const match = this.packageExportsResolve(scope, '.' + res[1], pack.exports, ['node', 'require'], extList); + // 6. RESOLVE_ESM_MATCH(MATCH) + return this.resolveEsmMatch(match, x, extList); + } + + // LOAD_PACKAGE_SELF(X, DIR) + loadPackageSelf(x, dir, extList) { + // 1. Find the closest package scope SCOPE to DIR. + const {data, scope} = this.readPackageScope(dir); + // 2. If no scope was found, return. + if (!data) return undefined; + // 3. If the SCOPE/package.json "exports" is null or undefined, return. + if (!data.exports) return undefined; + // 4. If the SCOPE/package.json "name" is not the first segment of X, return. + if (x !== data.name && !x.startsWith(data.name + '/')) return undefined; + // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE), + // "." + X.slice("name".length), `package.json` "exports", ["node", "require"]) + // defined in the ESM resolver. + const match = this.packageExportsResolve(scope, '.' + x.slice(data.name.length), data.exports, ['node', 'require'], extList); + // 6. RESOLVE_ESM_MATCH(MATCH) + return this.resolveEsmMatch(match, x, extList); + } + + // RESOLVE_ESM_MATCH(MATCH) + resolveEsmMatch(match, x, extList) { + // 1. let { RESOLVED, EXACT } = MATCH + const resolved = match; + const exact = true; + // 2. let RESOLVED_PATH = fileURLToPath(RESOLVED) + const resolvedPath = resolved; + let f; + // 3. If EXACT is true, + if (exact) { + // a. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension + // format. STOP + f = this.tryFile(resolvedPath); + // 4. Otherwise, if EXACT is false, + } else { + // a. LOAD_AS_FILE(RESOLVED_PATH) + // b. LOAD_AS_DIRECTORY(RESOLVED_PATH) + f = this.loadAsFileOrDirecotry(resolvedPath, extList); + } + if (f) return f; + // 5. THROW "not found" + throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND'); + } + + // PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions) + packageExportsResolve(packageURL, subpath, rexports, conditions, extList) { + // 1. If exports is an Object with both a key starting with "." and a key not starting with ".", throw an Invalid Package Configuration error. + let hasDots = false; + if (typeof rexports === 'object' && !Array.isArray(rexports)) { + const keys = Object.getOwnPropertyNames(rexports); + if (keys.length > 0) { + hasDots = keys[0][0] === '.'; + for (let i = 0; i < keys.length; i++) { + if (hasDots !== (keys[i][0] === '.')) { + throw new VMError('Invalid package configuration', 'ERR_INVALID_PACKAGE_CONFIGURATION'); + } + } + } + } + // 2. If subpath is equal to ".", then + if (subpath === '.') { + // a. Let mainExport be undefined. + let mainExport = undefined; + // b. If exports is a String or Array, or an Object containing no keys starting with ".", then + if (typeof rexports === 'string' || Array.isArray(rexports) || !hasDots) { + // x. Set mainExport to exports. + mainExport = rexports; + // c. Otherwise if exports is an Object containing a "." property, then + } else if (hasDots) { + // x. Set mainExport to exports["."]. + mainExport = rexports['.']; + } + // d. If mainExport is not undefined, then + if (mainExport) { + // x. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, mainExport, "", false, false, conditions). + const resolved = this.packageTargetResolve(packageURL, mainExport, '', false, false, conditions, extList); + // y. If resolved is not null or undefined, return resolved. + if (resolved) return resolved; + } + // 3. Otherwise, if exports is an Object and all keys of exports start with ".", then + } else if (hasDots) { + // a. Let matchKey be the string "./" concatenated with subpath. + // Note: Here subpath starts already with './' + // b. Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( matchKey, exports, packageURL, false, conditions). + const resolved = this.packageImportsExportsResolve(subpath, rexports, packageURL, false, conditions, extList); + // c. If resolved is not null or undefined, return resolved. + if (resolved) return resolved; + } + // 4. Throw a Package Path Not Exported error. + throw new VMError(`Package path '${subpath}' is not exported`, 'ERR_PACKAGE_PATH_NOT_EXPORTED'); + } + + // PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions) + packageImportsExportsResolve(matchKey, matchObj, packageURL, isImports, conditions, extList) { + // 1. If matchKey is a key of matchObj and does not contain "*", then + let target = matchObj[matchKey]; + if (target && matchKey.indexOf('*') === -1) { + // a. Let target be the value of matchObj[matchKey]. + // b. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, "", false, isImports, conditions). + return this.packageTargetResolve(packageURL, target, '', false, isImports, conditions, extList); + } + // 2. Let expansionKeys be the list of keys of matchObj containing only a single "*", + // sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity. + const expansionKeys = Object.getOwnPropertyNames(matchObj); + let bestKey = ''; + let bestSubpath; + // 3. For each key expansionKey in expansionKeys, do + for (let i = 0; i < expansionKeys.length; i++) { + const expansionKey = expansionKeys[i]; + if (matchKey.length < expansionKey.length) continue; + // a. Let patternBase be the substring of expansionKey up to but excluding the first "*" character. + const star = expansionKey.indexOf('*'); + if (star === -1) continue; // Note: expansionKeys was not filtered + const patternBase = expansionKey.slice(0, star); + // b. If matchKey starts with but is not equal to patternBase, then + if (matchKey.startsWith(patternBase) && expansionKey.indexOf('*', star + 1) === -1) { // Note: expansionKeys was not filtered + // 1. Let patternTrailer be the substring of expansionKey from the index after the first "*" character. + const patternTrailer = expansionKey.slice(star + 1); + // 2. If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or + // equal to the length of expansionKey, then + if (matchKey.endsWith(patternTrailer) && this.patternKeyCompare(bestKey, expansionKey) === 1) { // Note: expansionKeys was not sorted + // a. Let target be the value of matchObj[expansionKey]. + target = matchObj[expansionKey]; + // b. Let subpath be the substring of matchKey starting at the index of the length of patternBase up to the length of + // matchKey minus the length of patternTrailer. + bestKey = expansionKey; + bestSubpath = matchKey.slice(patternBase.length, matchKey.length - patternTrailer.length); + } + } + } + if (bestSubpath) { // Note: expansionKeys was not sorted + // c. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, subpath, true, isImports, conditions). + return this.packageTargetResolve(packageURL, target, bestSubpath, true, isImports, conditions, extList); + } + // 4. Return null. + return null; + } + + // PATTERN_KEY_COMPARE(keyA, keyB) + patternKeyCompare(keyA, keyB) { + // 1. Assert: keyA ends with "/" or contains only a single "*". + // 2. Assert: keyB ends with "/" or contains only a single "*". + // 3. Let baseLengthA be the index of "*" in keyA plus one, if keyA contains "*", or the length of keyA otherwise. + const baseAStar = keyA.indexOf('*'); + const baseLengthA = baseAStar === -1 ? keyA.length : baseAStar + 1; + // 4. Let baseLengthB be the index of "*" in keyB plus one, if keyB contains "*", or the length of keyB otherwise. + const baseBStar = keyB.indexOf('*'); + const baseLengthB = baseBStar === -1 ? keyB.length : baseBStar + 1; + // 5. If baseLengthA is greater than baseLengthB, return -1. + if (baseLengthA > baseLengthB) return -1; + // 6. If baseLengthB is greater than baseLengthA, return 1. + if (baseLengthB > baseLengthA) return 1; + // 7. If keyA does not contain "*", return 1. + if (baseAStar === -1) return 1; + // 8. If keyB does not contain "*", return -1. + if (baseBStar === -1) return -1; + // 9. If the length of keyA is greater than the length of keyB, return -1. + if (keyA.length > keyB.length) return -1; + // 10. If the length of keyB is greater than the length of keyA, return 1. + if (keyB.length > keyA.length) return 1; + // 11. Return 0. + return 0; + } + + // PACKAGE_TARGET_RESOLVE(packageURL, target, subpath, pattern, internal, conditions) + packageTargetResolve(packageURL, target, subpath, pattern, internal, conditions, extList) { + // 1. If target is a String, then + if (typeof target === 'string') { + // a. If pattern is false, subpath has non-zero length and target does not end with "/", throw an Invalid Module Specifier error. + if (!pattern && subpath.length > 0 && !target.endsWith('/')) { + throw new VMError(`Invalid package specifier '${subpath}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // b. If target does not start with "./", then + if (!target.startsWith('./')) { + // 1. If internal is true and target does not start with "../" or "/" and is not a valid URL, then + if (internal && !target.startsWith('../') && !target.startsWith('/')) { + let isURL = false; + try { + // eslint-disable-next-line no-new + new URL(target); + isURL = true; + } catch (e) {} + if (!isURL) { + // a. If pattern is true, then + if (pattern) { + // 1. Return PACKAGE_RESOLVE(target with every instance of "*" replaced by subpath, packageURL + "/"). + return this.packageResolve(target.replace(/\*/g, subpath), packageURL, conditions, extList); + } + // b. Return PACKAGE_RESOLVE(target + subpath, packageURL + "/"). + return this.packageResolve(this.pathConcat(target, subpath), packageURL, conditions, extList); + } + } + // Otherwise, throw an Invalid Package Target error. + throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET'); + } + target = decodeURI(target); + // c. If target split on "/" or "\" contains any ".", ".." or "node_modules" segments after the first segment, case insensitive + // and including percent encoded variants, throw an Invalid Package Target error. + if (target.split(/[/\\]/).slice(1).findIndex(x => x === '.' || x === '..' || x.toLowerCase() === 'node_modules') !== -1) { + throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET'); + } + // d. Let resolvedTarget be the URL resolution of the concatenation of packageURL and target. + const resolvedTarget = this.pathConcat(packageURL, target); + // e. Assert: resolvedTarget is contained in packageURL. + subpath = decodeURI(subpath); + // f. If subpath split on "/" or "\" contains any ".", ".." or "node_modules" segments, case insensitive and including percent + // encoded variants, throw an Invalid Module Specifier error. + if (subpath.split(/[/\\]/).findIndex(x => x === '.' || x === '..' || x.toLowerCase() === 'node_modules') !== -1) { + throw new VMError(`Invalid package specifier '${subpath}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // g. If pattern is true, then + if (pattern) { + // 1. Return the URL resolution of resolvedTarget with every instance of "*" replaced with subpath. + return resolvedTarget.replace(/\*/g, subpath); + } + // h. Otherwise, + // 1. Return the URL resolution of the concatenation of subpath and resolvedTarget. + return this.pathConcat(resolvedTarget, subpath); + // 3. Otherwise, if target is an Array, then + } else if (Array.isArray(target)) { + // a. If target.length is zero, return null. + if (target.length === 0) return null; + let lastException = undefined; + // b. For each item targetValue in target, do + for (let i = 0; i < target.length; i++) { + const targetValue = target[i]; + // 1. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions), + // continuing the loop on any Invalid Package Target error. + let resolved; + try { + resolved = this.packageTargetResolve(packageURL, targetValue, subpath, pattern, internal, conditions, extList); + } catch (e) { + if (e.code !== 'ERR_INVALID_PACKAGE_TARGET') throw e; + lastException = e; + continue; + } + // 2. If resolved is undefined, continue the loop. + // 3. Return resolved. + if (resolved !== undefined) return resolved; + if (resolved === null) { + lastException = null; + } + } + // c. Return or throw the last fallback resolution null return or error. + if (lastException === undefined || lastException === null) return lastException; + throw lastException; + // 2. Otherwise, if target is a non-null Object, then + } else if (typeof target === 'object' && target !== null) { + const keys = Object.getOwnPropertyNames(target); + // a. If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error. + for (let i = 0; i < keys.length; i++) { + const p = keys[i]; + if (isArrayIndex(p)) throw new VMError(`Invalid package configuration for '${subpath}'`, 'ERR_INVALID_PACKAGE_CONFIGURATION'); + } + // b. For each property p of target, in object insertion order as, + for (let i = 0; i < keys.length; i++) { + const p = keys[i]; + // 1. If p equals "default" or conditions contains an entry for p, then + if (p === 'default' || conditions.contains(p)) { + // a. Let targetValue be the value of the p property in target. + const targetValue = target[p]; + // b. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions). + const resolved = this.packageTargetResolve(packageURL, targetValue, subpath, pattern, internal, conditions, extList); + // c. If resolved is equal to undefined, continue the loop. + // d. Return resolved. + if (resolved !== undefined) return resolved; + } + } + // c. Return undefined. + return undefined; + // 4. Otherwise, if target is null, return null. + } else if (target == null) { + return null; + } + // Otherwise throw an Invalid Package Target error. + throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET'); + } + + // PACKAGE_RESOLVE(packageSpecifier, parentURL) + packageResolve(packageSpecifier, parentURL, conditions, extList) { + // 1. Let packageName be undefined. + let packageName = undefined; + // 2. If packageSpecifier is an empty string, then + if (packageSpecifier === '') { + // a. Throw an Invalid Module Specifier error. + throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // 3. If packageSpecifier is a Node.js builtin module name, then + if (this.builtinModules[packageSpecifier]) { + // a. Return the string "node:" concatenated with packageSpecifier. + return 'node:' + packageSpecifier; + } + let idx = packageSpecifier.indexOf('/'); + // 5. Otherwise, + if (packageSpecifier[0] === '@') { + // a. If packageSpecifier does not contain a "/" separator, then + if (idx === -1) { + // x. Throw an Invalid Module Specifier error. + throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // b. Set packageName to the substring of packageSpecifier until the second "/" separator or the end of the string. + idx = packageSpecifier.indexOf('/', idx + 1); + } + // else + // 4. If packageSpecifier does not start with "@", then + // a. Set packageName to the substring of packageSpecifier until the first "/" separator or the end of the string. + packageName = idx === -1 ? packageSpecifier : packageSpecifier.slice(0, idx); + // 6. If packageName starts with "." or contains "\" or "%", then + if (idx !== 0 && (packageName[0] === '.' || packageName.indexOf('\\') >= 0 || packageName.indexOf('%') >= 0)) { + // a. Throw an Invalid Module Specifier error. + throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // 7. Let packageSubpath be "." concatenated with the substring of packageSpecifier from the position at the length of packageName. + const packageSubpath = '.' + packageSpecifier.slice(packageName.length); + // 8. If packageSubpath ends in "/", then + if (packageSubpath[packageSubpath.length - 1] === '/') { + // a. Throw an Invalid Module Specifier error. + throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER'); + } + // 9. Let selfUrl be the result of PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL). + const selfUrl = this.packageSelfResolve(packageName, packageSubpath, parentURL); + // 10. If selfUrl is not undefined, return selfUrl. + if (selfUrl) return selfUrl; + // 11. While parentURL is not the file system root, + let packageURL; + while (true) { + // a. Let packageURL be the URL resolution of "node_modules/" concatenated with packageSpecifier, relative to parentURL. + packageURL = this.pathResolve(this.pathConcat(parentURL, 'node_modules', packageSpecifier)); + // b. Set parentURL to the parent folder URL of parentURL. + const parentParentURL = this.pathDirname(parentURL); + // c. If the folder at packageURL does not exist, then + if (this.isPathAllowed(packageURL) && this.pathTestIsDirectory(packageURL)) break; + // 1. Continue the next loop iteration. + if (parentParentURL === parentURL) { + // 12. Throw a Module Not Found error. + throw new VMError(`Cannot find module '${packageSpecifier}'`, 'ENOTFOUND'); + } + parentURL = parentParentURL; + } + // d. Let pjson be the result of READ_PACKAGE_JSON(packageURL). + const pack = this.readPackage(packageURL); + // e. If pjson is not null and pjson.exports is not null or undefined, then + if (pack && pack.exports) { + // 1. Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions). + return this.packageExportsResolve(packageURL, packageSubpath, pack.exports, conditions, extList); + } + // f. Otherwise, if packageSubpath is equal to ".", then + if (packageSubpath === '.') { + // 1. If pjson.main is a string, then + // a. Return the URL resolution of main in packageURL. + return this.loadAsPackage(packageSubpath, pack, extList); + } + // g. Otherwise, + // 1. Return the URL resolution of packageSubpath in packageURL. + return this.pathConcat(packageURL, packageSubpath); + } + +} + +exports.Resolver = Resolver; +exports.DefaultResolver = DefaultResolver; diff --git a/lib/sandbox.js b/lib/sandbox.js deleted file mode 100644 index 508cc0e..0000000 --- a/lib/sandbox.js +++ /dev/null @@ -1,686 +0,0 @@ -/* eslint-disable no-shadow, no-invalid-this */ -/* global vm, host, Contextify, Decontextify, VMError, options */ - -'use strict'; - -const {Script} = host.require('vm'); -const fs = host.require('fs'); -const pa = host.require('path'); - -const BUILTIN_MODULES = host.process.binding('natives'); -const parseJSON = JSON.parse; -const importModuleDynamically = () => { - // We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object. - // eslint-disable-next-line no-throw-literal - throw 'Dynamic imports are not allowed.'; -}; - -/** - * @param {Object} host Hosts's internal objects. - */ - -return ((vm, host) => { - 'use strict'; - - const global = this; - - const TIMERS = new host.WeakMap(); // Contains map of timers created inside sandbox - const BUILTINS = {__proto__: null}; - const CACHE = {__proto__: null}; - const EXTENSIONS = { - __proto__: null, - ['.json'](module, filename) { - try { - const code = fs.readFileSync(filename, 'utf8'); - module.exports = parseJSON(code); - } catch (e) { - throw Contextify.value(e); - } - }, - ['.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); - } - } - }; - - for (let i = 0; i < vm.options.sourceExtensions.length; i++) { - const ext = vm.options.sourceExtensions[i]; - - EXTENSIONS['.' + ext] = (module, filename, dirname) => { - if (vm.options.require.context !== 'sandbox') { - try { - module.exports = Contextify.readonly(host.require(filename)); - } catch (e) { - throw Contextify.value(e); - } - } else { - let script; - - try { - // Load module - let contents = fs.readFileSync(filename, 'utf8'); - contents = vm._compiler(contents, filename); - - const code = host.STRICT_MODULE_PREFIX + contents + host.MODULE_SUFFIX; - - const ccode = vm._hook('run', host.Array.of(code))[0]; - - // Precompile script - script = new Script(ccode, { - __proto__: null, - filename: filename || 'vm.js', - displayErrors: false, - importModuleDynamically - }); - - } catch (ex) { - throw Contextify.value(ex); - } - - const closure = script.runInContext(global, { - __proto__: null, - filename: filename || 'vm.js', - displayErrors: false, - importModuleDynamically - }); - - // run the script - closure(module.exports, module.require, module, filename, dirname); - } - }; - } - - const _parseExternalOptions = (options) => { - if (host.Array.isArray(options)) { - return { - __proto__: null, - external: options, - transitive: false - }; - } - - return { - __proto__: null, - external: options.modules, - transitive: options.transitive - }; - }; - - /** - * Resolve filename. - */ - - const _resolveFilename = (path) => { - if (!path) return null; - let hasPackageJson; - try { - path = pa.resolve(path); - - const exists = fs.existsSync(path); - const isdir = exists ? fs.statSync(path).isDirectory() : false; - - // direct file match - if (exists && !isdir) return path; - - // load as file - - for (let i = 0; i < vm.options.sourceExtensions.length; i++) { - const ext = vm.options.sourceExtensions[i]; - if (fs.existsSync(`${path}.${ext}`)) return `${path}.${ext}`; - } - if (fs.existsSync(`${path}.json`)) return `${path}.json`; - if (fs.existsSync(`${path}.node`)) return `${path}.node`; - - // load as module - - hasPackageJson = fs.existsSync(`${path}/package.json`); - } catch (e) { - throw Contextify.value(e); - } - - if (hasPackageJson) { - let pkg; - try { - pkg = fs.readFileSync(`${path}/package.json`, 'utf8'); - } catch (e) { - throw Contextify.value(e); - } - try { - pkg = parseJSON(pkg); - } catch (ex) { - throw new VMError(`Module '${path}' has invalid package.json`, 'EMODULEINVALID'); - } - - let main; - if (pkg && pkg.main) { - main = _resolveFilename(`${path}/${pkg.main}`); - if (!main) main = _resolveFilename(`${path}/index`); - } else { - main = _resolveFilename(`${path}/index`); - } - - return main; - } - - // load as directory - - try { - for (let i = 0; i < vm.options.sourceExtensions.length; i++) { - const ext = vm.options.sourceExtensions[i]; - if (fs.existsSync(`${path}/index.${ext}`)) return `${path}/index.${ext}`; - } - - if (fs.existsSync(`${path}/index.json`)) return `${path}/index.json`; - if (fs.existsSync(`${path}/index.node`)) return `${path}/index.node`; - } catch (e) { - throw Contextify.value(e); - } - - return null; - }; - - /** - * Builtin require. - */ - - const _requireBuiltin = (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 - __proto__: null, - inherits: (ctor, superCtor) => { - ctor.super_ = superCtor; - Object.setPrototypeOf(ctor.prototype, superCtor.prototype); - } - }); - } - - if (moduleName === 'events' || moduleName === 'internal/errors') { - let script; - try { - script = new Script(`(function (exports, require, module, process, internalBinding) { - 'use strict'; - const primordials = global; - ${BUILTIN_MODULES[moduleName]} - \n - });`, { - filename: `${moduleName}.vm.js` - }); - - } catch (e) { - throw Contextify.value(e); - } - - // setup module scope - const module = BUILTINS[moduleName] = { - exports: {}, - require: _requireBuiltin - }; - - // run script - try { - // FIXME binding should be contextified - script.runInContext(global)(module.exports, module.require, module, host.process, host.process.binding); - } catch (e) { - // e could be from inside or outside of sandbox - throw new VMError(`Error loading '${moduleName}'`); - } - return module.exports; - } - - try { - return Contextify.readonly(host.require(moduleName)); - } catch (e) { - throw new VMError(`Error loading '${moduleName}'`); - } - }; - - /** - * Prepare require. - */ - - const _prepareRequire = (currentDirname, parentAllowsTransitive = false) => { - const _require = moduleName => { - let requireObj; - try { - const optionsObj = vm.options; - if (optionsObj.nesting && moduleName === 'vm2') return {VM: Contextify.readonly(host.VM), NodeVM: Contextify.readonly(host.NodeVM)}; - requireObj = optionsObj.require; - } catch (e) { - throw Contextify.value(e); - } - - if (!requireObj) 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'); - - let filename; - let allowRequireTransitive = false; - - // Mock? - - try { - const {mock} = requireObj; - if (mock) { - const mockModule = mock[moduleName]; - if (mockModule) { - return Contextify.readonly(mockModule); - } - } - } catch (e) { - throw Contextify.value(e); - } - - // Builtin? - - if (BUILTIN_MODULES[moduleName]) { - let allowed; - try { - const builtinObj = requireObj.builtin; - if (host.Array.isArray(builtinObj)) { - if (builtinObj.indexOf('*') >= 0) { - allowed = builtinObj.indexOf(`-${moduleName}`) === -1; - } else { - allowed = builtinObj.indexOf(moduleName) >= 0; - } - } else if (builtinObj) { - allowed = builtinObj[moduleName]; - } else { - allowed = false; - } - } catch (e) { - throw Contextify.value(e); - } - if (!allowed) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); - - return _requireBuiltin(moduleName); - } - - // External? - - let externalObj; - try { - externalObj = requireObj.external; - } catch (e) { - throw Contextify.value(e); - } - - if (!externalObj) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); - - if (/^(\.|\.\/|\.\.\/)/.exec(moduleName)) { - // Module is relative file, e.g. ./script.js or ../script.js - - if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); - - filename = _resolveFilename(`${currentDirname}/${moduleName}`); - } else if (/^(\/|\\|[a-zA-Z]:\\)/.exec(moduleName)) { - // Module is absolute file, e.g. /script.js or //server/script.js or C:\script.js - - filename = _resolveFilename(moduleName); - } else { - // Check node_modules in path - - if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); - - if (typeof externalObj === 'object') { - let isWhitelisted; - try { - const { external, transitive } = _parseExternalOptions(externalObj); - - isWhitelisted = external.some(ext => host.helpers.match(ext, moduleName)) || (transitive && parentAllowsTransitive); - } catch (e) { - throw Contextify.value(e); - } - if (!isWhitelisted) { - throw new VMError(`The module '${moduleName}' is not whitelisted in VM.`, 'EDENIED'); - } - - allowRequireTransitive = true; - } - - // FIXME the paths array has side effects - const paths = currentDirname.split(pa.sep); - - while (paths.length) { - const path = paths.join(pa.sep); - - // console.log moduleName, "#{path}#{pa.sep}node_modules#{pa.sep}#{moduleName}" - - filename = _resolveFilename(`${path}${pa.sep}node_modules${pa.sep}${moduleName}`); - if (filename) break; - - paths.pop(); - } - } - - if (!filename) { - let resolveFunc; - try { - resolveFunc = requireObj.resolve; - } catch (e) { - throw Contextify.value(e); - } - if (resolveFunc) { - let resolved; - try { - resolved = requireObj.resolve(moduleName, currentDirname); - } catch (e) { - throw Contextify.value(e); - } - filename = _resolveFilename(resolved); - } - } - if (!filename) throw new VMError(`Cannot find module '${moduleName}'`, 'ENOTFOUND'); - - // return cache whenever possible - if (CACHE[filename]) return CACHE[filename].exports; - - const dirname = pa.dirname(filename); - const extname = pa.extname(filename); - - let allowedModule = true; - try { - const rootObj = requireObj.root; - if (rootObj) { - const rootPaths = host.Array.isArray(rootObj) ? rootObj : host.Array.of(rootObj); - allowedModule = rootPaths.some(path => host.String.prototype.startsWith.call(dirname, pa.resolve(path))); - } - } catch (e) { - throw Contextify.value(e); - } - - if (!allowedModule) { - throw new VMError(`Module '${moduleName}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); - } - - const module = CACHE[filename] = { - filename, - exports: {}, - require: _prepareRequire(dirname, allowRequireTransitive) - }; - - // 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; - }; - - /** - * Prepare sandbox. - */ - - // This is a function and not an arrow function, since the original is also a function - global.setTimeout = function setTimeout(callback, delay, ...args) { - if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); - let tmr; - try { - tmr = host.setTimeout(Decontextify.value(() => { - // FIXME ...args has side effects - callback(...args); - }), Decontextify.value(delay)); - } catch (e) { - throw Contextify.value(e); - } - const local = Contextify.value(tmr); - - TIMERS.set(local, tmr); - return local; - }; - - global.setInterval = function setInterval(callback, interval, ...args) { - if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); - let tmr; - try { - tmr = host.setInterval(Decontextify.value(() => { - // FIXME ...args has side effects - callback(...args); - }), Decontextify.value(interval)); - } catch (e) { - throw Contextify.value(e); - } - - const local = Contextify.value(tmr); - - TIMERS.set(local, tmr); - return local; - }; - - global.setImmediate = function setImmediate(callback, ...args) { - if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); - let tmr; - try { - tmr = host.setImmediate(Decontextify.value(() => { - // FIXME ...args has side effects - callback(...args); - })); - } catch (e) { - throw Contextify.value(e); - } - - const local = Contextify.value(tmr); - - TIMERS.set(local, tmr); - return local; - }; - - global.clearTimeout = function clearTimeout(local) { - try { - host.clearTimeout(TIMERS.get(local)); - } catch (e) { - throw Contextify.value(e); - } - }; - - global.clearInterval = function clearInterval(local) { - try { - host.clearInterval(TIMERS.get(local)); - } catch (e) { - throw Contextify.value(e); - } - }; - - global.clearImmediate = function clearImmediate(local) { - try { - host.clearImmediate(TIMERS.get(local)); - } catch (e) { - throw Contextify.value(e); - } - }; - - function addListener(name, handler) { - if (name !== 'beforeExit' && name !== 'exit') { - throw new Error(`Access denied to listen for '${name}' event.`); - } - - try { - host.process.on(name, Decontextify.value(handler)); - } catch (e) { - throw Contextify.value(e); - } - - return this; - } - - const {argv: optionArgv, env: optionsEnv} = options; - - // FIXME wrong class structure - global.process = { - argv: optionArgv !== undefined ? Contextify.value(optionArgv) : [], - title: host.process.title, - version: host.process.version, - versions: Contextify.readonly(host.process.versions), - arch: host.process.arch, - platform: host.process.platform, - env: optionsEnv !== undefined ? Contextify.value(optionsEnv) : {}, - pid: host.process.pid, - features: Contextify.readonly(host.process.features), - nextTick: function nextTick(callback, ...args) { - if (typeof callback !== 'function') { - throw new Error('Callback must be a function.'); - } - - try { - host.process.nextTick(Decontextify.value(() => { - // FIXME ...args has side effects - callback(...args); - })); - } catch (e) { - throw Contextify.value(e); - } - }, - hrtime: function hrtime(time) { - try { - return Contextify.value(host.process.hrtime(Decontextify.value(time))); - } catch (e) { - throw Contextify.value(e); - } - }, - cwd: function cwd() { - try { - return Contextify.value(host.process.cwd()); - } catch (e) { - throw Contextify.value(e); - } - }, - addListener, - on: addListener, - - once: function once(name, handler) { - if (name !== 'beforeExit' && name !== 'exit') { - throw new Error(`Access denied to listen for '${name}' event.`); - } - - try { - host.process.once(name, Decontextify.value(handler)); - } catch (e) { - throw Contextify.value(e); - } - - return this; - }, - - listeners: function listeners(name) { - if (name !== 'beforeExit' && name !== 'exit') { - // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. - return []; - } - - // Filter out listeners, which were not created in this sandbox - try { - return Contextify.value(host.process.listeners(name).filter(listener => Contextify.isVMProxy(listener))); - } catch (e) { - throw Contextify.value(e); - } - }, - - removeListener: function removeListener(name, handler) { - if (name !== 'beforeExit' && name !== 'exit') { - return this; - } - - try { - host.process.removeListener(name, Decontextify.value(handler)); - } catch (e) { - throw Contextify.value(e); - } - - return this; - }, - - umask: function umask() { - if (arguments.length) { - throw new Error('Access denied to set umask.'); - } - - try { - return Contextify.value(host.process.umask()); - } catch (e) { - throw Contextify.value(e); - } - } - }; - - if (vm.options.console === 'inherit') { - global.console = Contextify.readonly(host.console); - } else if (vm.options.console === 'redirect') { - global.console = { - debug(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.debug', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - log(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.log', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - info(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.info', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - warn(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.warn', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - error(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.error', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - dir(...args) { - try { - vm.emit('console.dir', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - }, - time() {}, - timeEnd() {}, - trace(...args) { - try { - // FIXME ...args has side effects - vm.emit('console.trace', ...Decontextify.arguments(args)); - } catch (e) { - throw Contextify.value(e); - } - } - }; - } - - /* - Return contextified require. - */ - - return _prepareRequire; -})(vm, host); diff --git a/lib/script.js b/lib/script.js new file mode 100644 index 0000000..088fa52 --- /dev/null +++ b/lib/script.js @@ -0,0 +1,388 @@ +'use strict'; + +const {Script} = require('vm'); +const { + lookupCompiler, + removeShebang +} = require('./compiler'); +const { + transformer +} = require('./transformer'); + +const objectDefineProperties = Object.defineProperties; + +const MODULE_PREFIX = '(function (exports, require, module, __filename, __dirname) { '; +const STRICT_MODULE_PREFIX = MODULE_PREFIX + '"use strict"; '; +const MODULE_SUFFIX = '\n});'; + +/** + * Class Script + * + * @public + */ +class VMScript { + + /** + * The script code with wrapping. If set will invalidate the cache.
+ * Writable only for backwards compatibility. + * + * @public + * @readonly + * @member {string} code + * @memberOf VMScript# + */ + + /** + * The filename used for this script. + * + * @public + * @readonly + * @since v3.9.0 + * @member {string} filename + * @memberOf VMScript# + */ + + /** + * The line offset use for stack traces. + * + * @public + * @readonly + * @since v3.9.0 + * @member {number} lineOffset + * @memberOf VMScript# + */ + + /** + * The column offset use for stack traces. + * + * @public + * @readonly + * @since v3.9.0 + * @member {number} columnOffset + * @memberOf VMScript# + */ + + /** + * The compiler to use to get the JavaScript code. + * + * @public + * @readonly + * @since v3.9.0 + * @member {(string|compileCallback)} compiler + * @memberOf VMScript# + */ + + /** + * The prefix for the script. + * + * @private + * @member {string} _prefix + * @memberOf VMScript# + */ + + /** + * The suffix for the script. + * + * @private + * @member {string} _suffix + * @memberOf VMScript# + */ + + /** + * The compiled vm.Script for the VM or if not compiled null. + * + * @private + * @member {?vm.Script} _compiledVM + * @memberOf VMScript# + */ + + /** + * The compiled vm.Script for the NodeVM or if not compiled null. + * + * @private + * @member {?vm.Script} _compiledNodeVM + * @memberOf VMScript# + */ + + /** + * The compiled vm.Script for the NodeVM in strict mode or if not compiled null. + * + * @private + * @member {?vm.Script} _compiledNodeVMStrict + * @memberOf VMScript# + */ + + /** + * The resolved compiler to use to get the JavaScript code. + * + * @private + * @readonly + * @member {compileCallback} _compiler + * @memberOf VMScript# + */ + + /** + * The script to run without wrapping. + * + * @private + * @member {string} _code + * @memberOf VMScript# + */ + + /** + * Whether or not the script contains async functions. + * + * @private + * @member {boolean} _hasAsync + * @memberOf VMScript# + */ + + /** + * Create VMScript instance. + * + * @public + * @param {string} 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. + * @param {number} [options.lineOffset=0] - Passed to vm.Script options. + * @param {number} [options.columnOffset=0] - Passed to vm.Script options. + * @param {(string|compileCallback)} [options.compiler="javascript"] - The compiler to use. + * @throws {VMError} If the compiler is unknown or if coffee-script was requested but the module not found. + */ + constructor(code, options) { + const sCode = `${code}`; + let useFileName; + let useOptions; + if (arguments.length === 2) { + if (typeof options === 'object') { + useOptions = options || {__proto__: null}; + useFileName = useOptions.filename; + } else { + useOptions = {__proto__: null}; + useFileName = options; + } + } else if (arguments.length > 2) { + // We do it this way so that there are no more arguments in the function. + // eslint-disable-next-line prefer-rest-params + useOptions = arguments[2] || {__proto__: null}; + useFileName = options || useOptions.filename; + } else { + useOptions = {__proto__: null}; + } + + const { + compiler = 'javascript', + lineOffset = 0, + columnOffset = 0 + } = useOptions; + + // Throw if the compiler is unknown. + const resolvedCompiler = lookupCompiler(compiler); + + objectDefineProperties(this, { + __proto__: null, + code: { + __proto__: null, + // Put this here so that it is enumerable, and looks like a property. + get() { + return this._prefix + this._code + this._suffix; + }, + set(value) { + const strNewCode = String(value); + if (strNewCode === this._code && this._prefix === '' && this._suffix === '') return; + this._code = strNewCode; + this._prefix = ''; + this._suffix = ''; + this._compiledVM = null; + this._compiledNodeVM = null; + this._compiledCode = null; + }, + enumerable: true + }, + filename: { + __proto__: null, + value: useFileName || 'vm.js', + enumerable: true + }, + lineOffset: { + __proto__: null, + value: lineOffset, + enumerable: true + }, + columnOffset: { + __proto__: null, + value: columnOffset, + enumerable: true + }, + compiler: { + __proto__: null, + value: compiler, + enumerable: true + }, + _code: { + __proto__: null, + value: sCode, + writable: true + }, + _prefix: { + __proto__: null, + value: '', + writable: true + }, + _suffix: { + __proto__: null, + value: '', + writable: true + }, + _compiledVM: { + __proto__: null, + value: null, + writable: true + }, + _compiledNodeVM: { + __proto__: null, + value: null, + writable: true + }, + _compiledNodeVMStrict: { + __proto__: null, + value: null, + writable: true + }, + _compiledCode: { + __proto__: null, + value: null, + writable: true + }, + _hasAsync: { + __proto__: null, + value: false, + writable: true + }, + _compiler: {__proto__: null, value: resolvedCompiler} + }); + } + + /** + * Wraps the code.
+ * This will replace the old wrapping.
+ * Will invalidate the code cache. + * + * @public + * @deprecated Since v3.9.0. Wrap your code before passing it into the VMScript object. + * @param {string} prefix - String that will be appended before the script code. + * @param {script} suffix - String that will be appended behind the script code. + * @return {this} This for chaining. + * @throws {TypeError} If prefix or suffix is a Symbol. + */ + wrap(prefix, suffix) { + const strPrefix = `${prefix}`; + const strSuffix = `${suffix}`; + if (this._prefix === strPrefix && this._suffix === strSuffix) return this; + this._prefix = strPrefix; + this._suffix = strSuffix; + this._compiledVM = null; + this._compiledNodeVM = null; + this._compiledNodeVMStrict = null; + return this; + } + + /** + * Compile this script.
+ * This is useful to detect syntax errors in the script. + * + * @public + * @return {this} This for chaining. + * @throws {SyntaxError} If there is a syntax error in the script. + */ + compile() { + this._compileVM(); + return this; + } + + /** + * Get the compiled code. + * + * @private + * @return {string} The code. + */ + getCompiledCode() { + if (!this._compiledCode) { + const comp = this._compiler(this._prefix + removeShebang(this._code) + this._suffix, this.filename); + const res = transformer(null, comp, false, false); + this._compiledCode = res.code; + this._hasAsync = res.hasAsync; + } + return this._compiledCode; + } + + /** + * Compiles this script to a vm.Script. + * + * @private + * @param {string} prefix - JavaScript code that will be used as prefix. + * @param {string} suffix - JavaScript code that will be used as suffix. + * @return {vm.Script} The compiled vm.Script. + * @throws {SyntaxError} If there is a syntax error in the script. + */ + _compile(prefix, suffix) { + return new Script(prefix + this.getCompiledCode() + suffix, { + __proto__: null, + filename: this.filename, + displayErrors: false, + lineOffset: this.lineOffset, + columnOffset: this.columnOffset + }); + } + + /** + * Will return the cached version of the script intended for VM or compile it. + * + * @private + * @return {vm.Script} The compiled script + * @throws {SyntaxError} If there is a syntax error in the script. + */ + _compileVM() { + let script = this._compiledVM; + if (!script) { + this._compiledVM = script = this._compile('', ''); + } + return script; + } + + /** + * Will return the cached version of the script intended for NodeVM or compile it. + * + * @private + * @return {vm.Script} The compiled script + * @throws {SyntaxError} If there is a syntax error in the script. + */ + _compileNodeVM() { + let script = this._compiledNodeVM; + if (!script) { + this._compiledNodeVM = script = this._compile(MODULE_PREFIX, MODULE_SUFFIX); + } + return script; + } + + /** + * Will return the cached version of the script intended for NodeVM in strict mode or compile it. + * + * @private + * @return {vm.Script} The compiled script + * @throws {SyntaxError} If there is a syntax error in the script. + */ + _compileNodeVMStrict() { + let script = this._compiledNodeVMStrict; + if (!script) { + this._compiledNodeVMStrict = script = this._compile(STRICT_MODULE_PREFIX, MODULE_SUFFIX); + } + return script; + } + +} + +exports.MODULE_PREFIX = MODULE_PREFIX; +exports.STRICT_MODULE_PREFIX = STRICT_MODULE_PREFIX; +exports.MODULE_SUFFIX = MODULE_SUFFIX; +exports.VMScript = VMScript; diff --git a/lib/setup-node-sandbox.js b/lib/setup-node-sandbox.js new file mode 100644 index 0000000..f65b27e --- /dev/null +++ b/lib/setup-node-sandbox.js @@ -0,0 +1,463 @@ +/* global host, data, VMError */ + +'use strict'; + +const LocalError = Error; +const LocalTypeError = TypeError; +const LocalWeakMap = WeakMap; + +const { + apply: localReflectApply, + defineProperty: localReflectDefineProperty +} = Reflect; + +const { + set: localWeakMapSet, + get: localWeakMapGet +} = LocalWeakMap.prototype; + +const { + isArray: localArrayIsArray +} = Array; + +function uncurryThis(func) { + return (thiz, ...args) => localReflectApply(func, thiz, args); +} + +const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); +const localArrayPrototypePush = uncurryThis(Array.prototype.push); +const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); +const localStringPrototypeSlice = uncurryThis(String.prototype.slice); +const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); + +const { + argv: optionArgv, + env: optionEnv, + console: optionConsole, + vm, + resolver, + extensions +} = data; + +function ensureSandboxArray(a) { + return localArrayPrototypeSlice(a); +} + +const globalPaths = ensureSandboxArray(resolver.globalPaths); + +class Module { + + constructor(id, path, parent) { + this.id = id; + this.filename = id; + this.path = path; + this.parent = parent; + this.loaded = false; + this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; + this.children = []; + this.exports = {}; + } + + _updateChildren(child, isNew) { + const children = this.children; + if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { + localArrayPrototypePush(children, child); + } + } + + require(id) { + return requireImpl(this, id, false); + } + +} + +const originalRequire = Module.prototype.require; +const cacheBuiltins = {__proto__: null}; + +function requireImpl(mod, id, direct) { + if (direct && mod.require !== originalRequire) { + return mod.require(id); + } + const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); + if (localStringPrototypeStartsWith(filename, 'node:')) { + id = localStringPrototypeSlice(filename, 5); + let nmod = cacheBuiltins[id]; + if (!nmod) { + nmod = resolver.loadBuiltinModule(vm, id); + if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); + cacheBuiltins[id] = nmod; + } + return nmod; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + mod._updateChildren(cachedModule, false); + return cachedModule.exports; + } + + let nmod = cacheBuiltins[id]; + if (nmod) return nmod; + nmod = resolver.loadBuiltinModule(vm, id); + if (nmod) { + cacheBuiltins[id] = nmod; + return nmod; + } + + const path = resolver.pathDirname(filename); + const module = new Module(filename, path, mod); + resolver.registerModule(module, filename, path, mod, direct); + mod._updateChildren(module, true); + try { + Module._cache[filename] = module; + const handler = findBestExtensionHandler(filename); + handler(module, filename); + module.loaded = true; + } catch (e) { + delete Module._cache[filename]; + const children = mod.children; + if (localArrayIsArray(children)) { + const index = localArrayPrototypeIndexOf(children, module); + if (index !== -1) { + localArrayPrototypeSplice(children, index, 1); + } + } + throw e; + } + + return module.exports; +} + +Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); +Module.globalPaths = globalPaths; +Module._extensions = {__proto__: null}; +Module._cache = {__proto__: null}; + +{ + const keys = Object.getOwnPropertyNames(extensions); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const handler = extensions[key]; + Module._extensions[key] = (mod, filename) => handler(mod, filename); + } +} + +function findBestExtensionHandler(filename) { + const name = resolver.pathBasename(filename); + for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { + const ext = localStringPrototypeSlice(name, i + 1); + const handler = Module._extensions[ext]; + if (handler) return handler; + } + const js = Module._extensions['.js']; + if (js) return js; + const keys = Object.getOwnPropertyNames(Module._extensions); + if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); + return Module._extensions[keys[0]]; +} + +function createRequireForModule(mod) { + // eslint-disable-next-line no-shadow + function require(id) { + return requireImpl(mod, id, true); + } + function resolve(id, options) { + return resolver.resolve(mod, id, options, Module._extensions, true); + } + require.resolve = resolve; + function paths(id) { + return ensureSandboxArray(resolver.lookupPaths(mod, id)); + } + resolve.paths = paths; + + require.extensions = Module._extensions; + + require.cache = Module._cache; + + return require; +} + +/** + * Prepare sandbox. + */ + +const TIMERS = new LocalWeakMap(); + +class Timeout { +} + +class Interval { +} + +class Immediate { +} + +function clearTimer(timer) { + const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); + if (obj) { + obj.clear(obj.value); + } +} + +// This is a function and not an arrow function, since the original is also a function +// eslint-disable-next-line no-shadow +global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Timeout(callback, args); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setTimeout(cb, delay); + + const ref = { + __proto__: null, + clear: host.clearTimeout, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Interval(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setInterval(cb, interval); + + const ref = { + __proto__: null, + clear: host.clearInterval, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Immediate(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setImmediate(cb); + + const ref = { + __proto__: null, + clear: host.clearImmediate, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.clearTimeout = function clearTimeout(timeout) { + clearTimer(timeout); +}; + +// eslint-disable-next-line no-shadow +global.clearInterval = function clearInterval(interval) { + clearTimer(interval); +}; + +// eslint-disable-next-line no-shadow +global.clearImmediate = function clearImmediate(immediate) { + clearTimer(immediate); +}; + +const localProcess = host.process; + +function vmEmitArgs(event, args) { + const allargs = [event]; + for (let i = 0; i < args.length; i++) { + if (!localReflectDefineProperty(allargs, i + 1, { + __proto__: null, + value: args[i], + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + } + return localReflectApply(vm.emit, vm, allargs); +} + +const LISTENERS = new LocalWeakMap(); +const LISTENER_HANDLER = new LocalWeakMap(); + +/** + * + * @param {*} name + * @param {*} handler + * @this process + * @return {this} + */ +function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (!cb) { + cb = () => { + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); + } + + localProcess.on(name, cb); + + return this; +} + +/** + * + * @this process + * @return {this} + */ +// eslint-disable-next-line no-shadow +function process() { + return this; +} + +// FIXME wrong class structure +global.process = { + __proto__: process.prototype, + argv: optionArgv !== undefined ? optionArgv : [], + title: localProcess.title, + version: localProcess.version, + versions: localProcess.versions, + arch: localProcess.arch, + platform: localProcess.platform, + env: optionEnv !== undefined ? optionEnv : {}, + pid: localProcess.pid, + features: localProcess.features, + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new LocalError('Callback must be a function.'); + } + + localProcess.nextTick(()=>{ + localReflectApply(callback, null, args); + }); + }, + hrtime: function hrtime(time) { + return localProcess.hrtime(time); + }, + cwd: function cwd() { + return localProcess.cwd(); + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let triggered = false; + const cb = () => { + if (triggered) return; + triggered = true; + localProcess.removeListener(name, cb); + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + + localProcess.on(name, cb); + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + const all = localProcess.listeners(name); + const filtered = []; + let j = 0; + for (let i = 0; i < all.length; i++) { + const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); + if (h) { + if (!localReflectDefineProperty(filtered, j, { + __proto__: null, + value: h, + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + j++; + } + } + return filtered; + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (cb) localProcess.removeListener(name, cb); + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new LocalError('Access denied to set umask.'); + } + + return localProcess.umask(); + } +}; + +if (optionConsole === 'inherit') { + global.console = host.console; +} else if (optionConsole === 'redirect') { + global.console = { + debug(...args) { + vmEmitArgs('console.debug', args); + }, + log(...args) { + vmEmitArgs('console.log', args); + }, + info(...args) { + vmEmitArgs('console.info', args); + }, + warn(...args) { + vmEmitArgs('console.warn', args); + }, + error(...args) { + vmEmitArgs('console.error', args); + }, + dir(...args) { + vmEmitArgs('console.dir', args); + }, + time() {}, + timeEnd() {}, + trace(...args) { + vmEmitArgs('console.trace', args); + } + }; +} + +return { + __proto__: null, + Module, + jsonParse: JSON.parse, + createRequireForModule +}; diff --git a/lib/setup-sandbox.js b/lib/setup-sandbox.js new file mode 100644 index 0000000..de49fdd --- /dev/null +++ b/lib/setup-sandbox.js @@ -0,0 +1,452 @@ +/* global host, bridge, data, context */ + +'use strict'; + +const { + Object: localObject, + Array: localArray, + Error: LocalError, + Reflect: localReflect, + Proxy: LocalProxy, + WeakMap: LocalWeakMap, + Function: localFunction, + Promise: localPromise, + eval: localEval +} = global; + +const { + freeze: localObjectFreeze +} = localObject; + +const { + getPrototypeOf: localReflectGetPrototypeOf, + apply: localReflectApply, + deleteProperty: localReflectDeleteProperty, + has: localReflectHas, + defineProperty: localReflectDefineProperty, + setPrototypeOf: localReflectSetPrototypeOf, + getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor +} = localReflect; + +const { + isArray: localArrayIsArray +} = localArray; + +const { + ensureThis, + ReadOnlyHandler, + from, + fromWithFactory, + readonlyFactory, + connect, + addProtoMapping, + VMError, + ReadOnlyMockHandler +} = bridge; + +const { + allowAsync, + GeneratorFunction, + AsyncFunction, + AsyncGeneratorFunction +} = data; + +const localWeakMapGet = LocalWeakMap.prototype.get; + +function localUnexpected() { + return new VMError('Should not happen'); +} + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); + +Object.defineProperties(global, { + global: {value: global, writable: true, configurable: true, enumerable: true}, + globalThis: {value: global, writable: true, configurable: true}, + GLOBAL: {value: global, writable: true, configurable: true}, + root: {value: global, writable: true, configurable: true} +}); + +if (!localReflectDefineProperty(global, 'VMError', { + __proto__: null, + value: VMError, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +// Fixes buffer unsafe allocation +/* eslint-disable no-use-before-define */ +class BufferHandler extends ReadOnlyHandler { + + apply(target, thiz, args) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + + construct(target, args, newTarget) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + +} +/* eslint-enable no-use-before-define */ + +const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); + + +if (!localReflectDefineProperty(global, 'Buffer', { + __proto__: null, + value: LocalBuffer, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafe(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafe, host.Buffer.allocUnsafe); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafeSlow(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); + +/** + * Replacement for Buffer inspect + * + * @param {*} recurseTimes + * @param {*} ctx + * @this LocalBuffer + * @return {string} + */ +function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +} + +connect(inspect, host.Buffer.prototype.inspect); + +connect(localFunction.prototype.bind, host.Function.prototype.bind); + +connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); +connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); +connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); +connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); + +/* + * PrepareStackTrace sanitization + */ + +const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); + +let currentPrepareStackTrace = LocalError.prepareStackTrace; +const wrappedPrepareStackTrace = new LocalWeakMap(); +if (typeof currentPrepareStackTrace === 'function') { + wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); +} + +let OriginalCallSite; +LocalError.prepareStackTrace = (e, sst) => { + OriginalCallSite = sst[0].constructor; +}; +new LocalError().stack; +if (typeof OriginalCallSite === 'function') { + LocalError.prepareStackTrace = undefined; + + function makeCallSiteGetters(list) { + const callSiteGetters = []; + for (let i=0; i { + return localReflectApply(func, thiz, []); + } + }; + } + return callSiteGetters; + } + + function applyCallSiteGetters(thiz, callSite, getters) { + for (let i=0; i { + if (localArrayIsArray(sst)) { + for (let i=0; i < sst.length; i++) { + const cs = sst[i]; + if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { + sst[i] = new CallSite(cs); + } + } + } + return value(error, sst); + }; + wrappedPrepareStackTrace.set(value, newWrapped); + wrappedPrepareStackTrace.set(newWrapped, newWrapped); + currentPrepareStackTrace = newWrapped; + } + })) throw localUnexpected(); +} else if (oldPrepareStackTraceDesc) { + localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); +} else { + localReflectDeleteProperty(LocalError, 'prepareStackTrace'); +} + +/* + * Exception sanitization + */ + +const withProxy = localObjectFreeze({ + __proto__: null, + has(target, key) { + if (key === host.INTERNAL_STATE_NAME) return false; + return localReflectHas(target, key); + } +}); + +const interanState = localObjectFreeze({ + __proto__: null, + wrapWith(x) { + return new LocalProxy(x, withProxy); + }, + handleException: ensureThis, + import(what) { + throw new VMError('Dynamic Import not supported'); + } +}); + +if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { + __proto__: null, + configurable: false, + enumerable: false, + writable: false, + value: interanState +})) throw localUnexpected(); + +/* + * Eval sanitization + */ + +function throwAsync() { + return new VMError('Async not available'); +} + +function makeFunction(inputArgs, isAsync, isGenerator) { + const lastArgs = inputArgs.length - 1; + let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; + let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; + for (let i = 1; i < lastArgs; i++) { + args += `,${inputArgs[i]}`; + } + try { + code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); +} + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + return makeFunction(args, this.isAsync, this.isGenerator); + }, + construct(target, args, newTarget) { + return makeFunction(args, this.isAsync, this.isGenerator); + } +}; + +const EvalHandler = { + __proto__: null, + apply(target, thiz, args) { + if (args.length === 0) return undefined; + let code = `${args[0]}`; + try { + code = host.transformAndCheck(null, code, false, false, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); + } +}; + +const AsyncErrorHandler = { + __proto__: null, + apply(target, thiz, args) { + throw throwAsync(); + }, + construct(target, args, newTarget) { + throw throwAsync(); + } +}; + +function makeCheckFunction(isAsync, isGenerator) { + if (isAsync && !allowAsync) return AsyncErrorHandler; + return { + __proto__: FunctionHandler, + isAsync, + isGenerator + }; +} + +function overrideWithProxy(obj, prop, value, handler) { + const proxy = new LocalProxy(value, handler); + if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); + return proxy; +} + +const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); +if (GeneratorFunction) { + if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); +} +if (AsyncFunction) { + if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); +} +if (AsyncGeneratorFunction) { + if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); +} + +global.Function = proxiedFunction; +global.eval = new LocalProxy(localEval, EvalHandler); + +/* + * Promise sanitization + */ + +if (localPromise && !allowAsync) { + + const PromisePrototype = localPromise.prototype; + + overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); + // This seems not to work, and will produce + // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. + // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. + // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (PromisePrototype.finally) { + overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} + +function readonly(other, mock) { + // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) + if (!mock) return fromWithFactory(readonlyFactory, other); + const tmock = from(mock); + return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); +} + +return { + __proto__: null, + readonly, + global +}; diff --git a/lib/transformer.js b/lib/transformer.js new file mode 100644 index 0000000..b1115dc --- /dev/null +++ b/lib/transformer.js @@ -0,0 +1,113 @@ + +const {parse: acornParse} = require('acorn'); +const {full: acornWalkFull} = require('acorn-walk'); + +const INTERNAL_STATE_NAME = 'VM2_INTERNAL_STATE_DO_NOT_USE_OR_PROGRAM_WILL_FAIL'; + +function assertType(node, type) { + if (!node) throw new Error(`None existent node expected '${type}'`); + if (node.type !== type) throw new Error(`Invalid node type '${node.type}' expected '${type}'`); + return node; +} + +function transformer(args, body, isAsync, isGenerator) { + let code; + let argsOffset; + if (args === null) { + code = body; + } else { + code = isAsync ? '(async function' : '(function'; + if (isGenerator) code += '*'; + code += ' anonymous('; + code += args; + argsOffset = code.length; + code += '\n) {\n'; + code += body; + code += '\n})'; + } + + const ast = acornParse(code, { + __proto__: null, + ecmaVersion: 2020, + allowAwaitOutsideFunction: args === null && isAsync, + allowReturnOutsideFunction: args === null + }); + + if (args !== null) { + const pBody = assertType(ast, 'Program').body; + if (pBody.length !== 1) throw new Error('Invalid arguments'); + const expr = pBody[0]; + if (expr.type !== 'ExpressionStatement') throw new Error('Invalid arguments'); + const func = expr.expression; + if (func.type !== 'FunctionExpression') throw new Error('Invalid arguments'); + if (func.body.start !== argsOffset + 3) throw new Error('Invalid arguments'); + } + + const insertions = []; + let hasAsync = false; + + const RIGHT = -100; + const LEFT = 100; + + acornWalkFull(ast, (node, state, type) => { + if (type === 'CatchClause') { + const param = node.param; + if (param) { + const name = assertType(param, 'Identifier').name; + const cBody = assertType(node.body, 'BlockStatement'); + if (cBody.body.length > 0) { + insertions.push({ + __proto__: null, + pos: cBody.body[0].start, + order: RIGHT, + code: `${name}=${INTERNAL_STATE_NAME}.handleException(${name});` + }); + } + } + } else if (type === 'WithStatement') { + insertions.push({ + __proto__: null, + pos: node.object.start, + order: RIGHT, + code: INTERNAL_STATE_NAME + '.wrapWith(' + }); + insertions.push({ + __proto__: null, + pos: node.object.end, + order: LEFT, + code: ')' + }); + } else if (type === 'Identifier') { + if (node.name === INTERNAL_STATE_NAME) { + throw new Error('Use of internal vm2 state variable'); + } + } else if (type === 'ImportExpression') { + insertions.push({ + __proto__: null, + pos: node.start, + order: LEFT, + code: INTERNAL_STATE_NAME + '.' + }); + } else if (type === 'Function') { + if (node.async) hasAsync = true; + } + }); + + if (insertions.length === 0) return {__proto__: null, code, hasAsync}; + + insertions.sort((a, b) => (a.pos == b.pos ? a.order - b.order : a.pos - b.pos)); + + let ncode = ''; + let curr = 0; + for (let i = 0; i < insertions.length; i++) { + const change = insertions[i]; + ncode += code.substring(curr, change.pos) + change.code; + curr = change.pos; + } + ncode += code.substring(curr); + + return {__proto__: null, code: ncode, hasAsync}; +} + +exports.INTERNAL_STATE_NAME = INTERNAL_STATE_NAME; +exports.transformer = transformer; diff --git a/lib/vm.js b/lib/vm.js new file mode 100644 index 0000000..fea6f92 --- /dev/null +++ b/lib/vm.js @@ -0,0 +1,539 @@ +'use strict'; + +/** + * This callback will be called to transform a script to JavaScript. + * + * @callback compileCallback + * @param {string} code - Script code to transform to JavaScript. + * @param {string} filename - Filename of this script. + * @return {string} JavaScript code that represents the script code. + */ + +/** + * This callback will be called to resolve a module if it couldn't be found. + * + * @callback resolveCallback + * @param {string} moduleName - Name of the modulusedRequiree to resolve. + * @param {string} dirname - Name of the current directory. + * @return {(string|undefined)} The file or directory to use to load the requested module. + */ + +const fs = require('fs'); +const pa = require('path'); +const { + Script, + createContext +} = require('vm'); +const { + EventEmitter +} = require('events'); +const { + INSPECT_MAX_BYTES +} = require('buffer'); +const { + createBridge, + VMError +} = require('./bridge'); +const { + transformer, + INTERNAL_STATE_NAME +} = require('./transformer'); +const { + lookupCompiler +} = require('./compiler'); +const { + VMScript +} = require('./script'); + +const objectDefineProperties = Object.defineProperties; + +/** + * Host objects + * + * @private + */ +const HOST = Object.freeze({ + Buffer, + Function, + Object, + transformAndCheck, + INSPECT_MAX_BYTES, + INTERNAL_STATE_NAME +}); + +/** + * 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 + }); +} + +/** + * Default run options for vm.Script.runInContext + * + * @private + */ +const DEFAULT_RUN_OPTIONS = Object.freeze({__proto__: null, displayErrors: false}); + +function checkAsync(allow) { + if (!allow) throw new VMError('Async not available'); +} + +function transformAndCheck(args, code, isAsync, isGenerator, allowAsync) { + const ret = transformer(args, code, isAsync, isGenerator); + checkAsync(allowAsync || !ret.hasAsync); + return ret.code; +} + +/** + * + * This callback will be called and has a specific time to finish.
+ * No parameters will be supplied.
+ * If parameters are required, use a closure. + * + * @private + * @callback runWithTimeout + * @return {*} + * + */ + +let cacheTimeoutContext = null; +let cacheTimeoutScript = null; + +/** + * Run a function with a specific timeout. + * + * @private + * @param {runWithTimeout} fn - Function to run with the specific timeout. + * @param {number} timeout - The amount of time to give the function to finish. + * @return {*} The value returned by the function. + * @throws {Error} If the function took to long. + */ +function doWithTimeout(fn, timeout) { + if (!cacheTimeoutContext) { + cacheTimeoutContext = createContext(); + cacheTimeoutScript = new Script('fn()', { + __proto__: null, + filename: 'timeout_bridge.js', + displayErrors: false + }); + } + cacheTimeoutContext.fn = fn; + try { + return cacheTimeoutScript.runInContext(cacheTimeoutContext, { + __proto__: null, + displayErrors: false, + timeout + }); + } finally { + cacheTimeoutContext.fn = null; + } +} + +const bridgeScript = compileScript(`${__dirname}/bridge.js`, + `(function(global) {"use strict"; const exports = {};${fs.readFileSync(`${__dirname}/bridge.js`, 'utf8')}\nreturn exports;})`); +const setupSandboxScript = compileScript(`${__dirname}/setup-sandbox.js`, + `(function(global, host, bridge, data, context) { ${fs.readFileSync(`${__dirname}/setup-sandbox.js`, 'utf8')}\n})`); +const getGlobalScript = compileScript('get_global.js', 'this'); + +let getGeneratorFunctionScript = null; +let getAsyncFunctionScript = null; +let getAsyncGeneratorFunctionScript = null; +try { + getGeneratorFunctionScript = compileScript('get_generator_function.js', '(function*(){}).constructor'); +} catch (ex) {} +try { + getAsyncFunctionScript = compileScript('get_async_function.js', '(async function(){}).constructor'); +} catch (ex) {} +try { + getAsyncGeneratorFunctionScript = compileScript('get_async_generator_function.js', '(async function*(){}).constructor'); +} catch (ex) {} + +/** + * Class VM. + * + * @public + */ +class VM extends EventEmitter { + + /** + * The timeout for {@link VM#run} calls. + * + * @public + * @since v3.9.0 + * @member {number} timeout + * @memberOf VM# + */ + + /** + * Get the global sandbox object. + * + * @public + * @readonly + * @since v3.9.0 + * @member {Object} sandbox + * @memberOf VM# + */ + + /** + * The compiler to use to get the JavaScript code. + * + * @public + * @readonly + * @since v3.9.0 + * @member {(string|compileCallback)} compiler + * @memberOf VM# + */ + + /** + * The resolved compiler to use to get the JavaScript code. + * + * @private + * @readonly + * @member {compileCallback} _compiler + * @memberOf VM# + */ + + /** + * Create a new VM instance. + * + * @public + * @param {Object} [options] - VM options. + * @param {number} [options.timeout] - The amount of time until a call to {@link VM#run} will timeout. + * @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)().
+ * Only available for node v10+. + * @param {boolean} [options.wasm=true] - Allow to run wasm code.
+ * Only available for node v10+. + * @param {boolean} [options.allowAsync=true] - Allows for async functions. + * @throws {VMError} If the compiler is unknown. + */ + constructor(options = {}) { + super(); + + // Read all options + const { + timeout, + sandbox, + compiler = 'javascript', + allowAsync: optAllowAsync = true + } = options; + const allowEval = options.eval !== false; + const allowWasm = options.wasm !== false; + const allowAsync = optAllowAsync && !options.fixAsync; + + // Early error if sandbox is not an object. + if (sandbox && 'object' !== typeof sandbox) { + throw new VMError('Sandbox must be object.'); + } + + // Early error if compiler can't be found. + const resolvedCompiler = lookupCompiler(compiler); + + // Create a new context for this vm. + const _context = createContext(undefined, { + __proto__: null, + codeGeneration: { + __proto__: null, + strings: allowEval, + wasm: allowWasm + } + }); + + const sandboxGlobal = getGlobalScript.runInContext(_context, DEFAULT_RUN_OPTIONS); + + // Initialize the sandbox bridge + const { + createBridge: sandboxCreateBridge + } = bridgeScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal); + + // Initialize the bridge + const bridge = createBridge(sandboxCreateBridge, () => {}); + + const data = { + __proto__: null, + allowAsync + }; + + if (getGeneratorFunctionScript) { + data.GeneratorFunction = getGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); + } + if (getAsyncFunctionScript) { + data.AsyncFunction = getAsyncFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); + } + if (getAsyncGeneratorFunctionScript) { + data.AsyncGeneratorFunction = getAsyncGeneratorFunctionScript.runInContext(_context, DEFAULT_RUN_OPTIONS); + } + + // Create the bridge between the host and the sandbox. + const internal = setupSandboxScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal, HOST, bridge.other, data, _context); + + const runScript = (script) => { + // This closure is intentional to hide _context and bridge since the allow to access the sandbox directly which is unsafe. + let ret; + try { + ret = script.runInContext(_context, DEFAULT_RUN_OPTIONS); + } catch (e) { + throw bridge.from(e); + } + return bridge.from(ret); + }; + + const makeReadonly = (value, mock) => { + try { + internal.readonly(value, mock); + } catch (e) { + throw bridge.from(e); + } + return value; + }; + + const makeProtected = (value) => { + const sandboxBridge = bridge.other; + try { + sandboxBridge.fromWithFactory(sandboxBridge.protectedFactory, value); + } catch (e) { + throw bridge.from(e); + } + return value; + }; + + const addProtoMapping = (hostProto, sandboxProto) => { + const sandboxBridge = bridge.other; + let otherProto; + try { + otherProto = sandboxBridge.from(sandboxProto); + sandboxBridge.addProtoMapping(otherProto, hostProto); + } catch (e) { + throw bridge.from(e); + } + bridge.addProtoMapping(hostProto, otherProto); + }; + + const addProtoMappingFactory = (hostProto, sandboxProtoFactory) => { + const sandboxBridge = bridge.other; + const factory = () => { + const proto = sandboxProtoFactory(this); + bridge.addProtoMapping(hostProto, proto); + return proto; + }; + try { + const otherProtoFactory = sandboxBridge.from(factory); + sandboxBridge.addProtoMappingFactory(otherProtoFactory, hostProto); + } catch (e) { + throw bridge.from(e); + } + }; + + // Define the properties of this object. + // Use Object.defineProperties here to be able to + // hide and set properties read-only. + objectDefineProperties(this, { + __proto__: null, + timeout: { + __proto__: null, + value: timeout, + writable: true, + enumerable: true + }, + compiler: { + __proto__: null, + value: compiler, + enumerable: true + }, + sandbox: { + __proto__: null, + value: bridge.from(sandboxGlobal), + enumerable: true + }, + _runScript: {__proto__: null, value: runScript}, + _makeReadonly: {__proto__: null, value: makeReadonly}, + _makeProtected: {__proto__: null, value: makeProtected}, + _addProtoMapping: {__proto__: null, value: addProtoMapping}, + _addProtoMappingFactory: {__proto__: null, value: addProtoMappingFactory}, + _compiler: {__proto__: null, value: resolvedCompiler}, + _allowAsync: {__proto__: null, value: allowAsync} + }); + + // prepare global sandbox + if (sandbox) { + this.setGlobals(sandbox); + } + } + + /** + * Adds all the values to the globals. + * + * @public + * @since v3.9.0 + * @param {Object} values - All values that will be added to the globals. + * @return {this} This for chaining. + * @throws {*} If the setter of a global throws an exception it is propagated. And the remaining globals will not be written. + */ + setGlobals(values) { + for (const name in values) { + if (Object.prototype.hasOwnProperty.call(values, name)) { + this.sandbox[name] = values[name]; + } + } + return this; + } + + /** + * Set a global value. + * + * @public + * @since v3.9.0 + * @param {string} name - The name of the global. + * @param {*} value - The value of the global. + * @return {this} This for chaining. + * @throws {*} If the setter of the global throws an exception it is propagated. + */ + setGlobal(name, value) { + this.sandbox[name] = value; + return this; + } + + /** + * Get a global value. + * + * @public + * @since v3.9.0 + * @param {string} name - The name of the global. + * @return {*} The value of the global. + * @throws {*} If the getter of the global throws an exception it is propagated. + */ + getGlobal(name) { + return this.sandbox[name]; + } + + /** + * Freezes the object inside VM making it read-only. Not available for primitive values. + * + * @public + * @param {*} value - Object to freeze. + * @param {string} [globalName] - Whether to add the object to global. + * @return {*} Object to freeze. + * @throws {*} If the setter of the global throws an exception it is propagated. + */ + freeze(value, globalName) { + this.readonly(value); + if (globalName) this.sandbox[globalName] = value; + return value; + } + + /** + * Freezes the object inside VM making it read-only. Not available for primitive values. + * + * @public + * @param {*} value - Object to freeze. + * @param {*} [mock] - When the object does not have a property the mock is used before prototype lookup. + * @return {*} Object to freeze. + */ + readonly(value, mock) { + return this._makeReadonly(value, mock); + } + + /** + * Protects the object inside VM making impossible to set functions as it's properties. Not available for primitive values. + * + * @public + * @param {*} value - Object to protect. + * @param {string} [globalName] - Whether to add the object to global. + * @return {*} Object to protect. + * @throws {*} If the setter of the global throws an exception it is propagated. + */ + protect(value, globalName) { + this._makeProtected(value); + if (globalName) this.sandbox[globalName] = value; + return value; + } + + /** + * Run the code in VM. + * + * @public + * @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.
+ * This is only used if code is a String. + * @return {*} Result of executed code. + * @throws {SyntaxError} If there is a syntax error in the script. + * @throws {Error} An error is thrown when the script took to long and there is a timeout. + * @throws {*} If the script execution terminated with an exception it is propagated. + */ + run(code, options) { + let script; + let filename; + + if (typeof options === 'object') { + filename = options.filename; + } else { + filename = options; + } + + if (code instanceof VMScript) { + script = code._compileVM(); + checkAsync(this._allowAsync || !code._hasAsync); + } else { + const useFileName = filename || 'vm.js'; + let scriptCode = this._compiler(code, useFileName); + const ret = transformer(null, scriptCode, false, false); + scriptCode = ret.code; + checkAsync(this._allowAsync || !ret.hasAsync); + // Compile the script here so that we don't need to create a instance of VMScript. + script = new Script(scriptCode, { + __proto__: null, + filename: useFileName, + displayErrors: false + }); + } + + if (!this.timeout) { + return this._runScript(script); + } + + return doWithTimeout(() => { + return this._runScript(script); + }, this.timeout); + } + + /** + * Run the code in VM. + * + * @public + * @since v3.9.0 + * @param {string} filename - Filename of file to load and execute in a NodeVM. + * @return {*} Result of executed code. + * @throws {Error} If filename is not a valid filename. + * @throws {SyntaxError} If there is a syntax error in the script. + * @throws {Error} An error is thrown when the script took to long and there is a timeout. + * @throws {*} If the script execution terminated with an exception it is propagated. + */ + runFile(filename) { + 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 this.run(fs.readFileSync(resolvedFilename, 'utf8'), resolvedFilename); + } + +} + +exports.VM = VM; diff --git a/package-lock.json b/package-lock.json index f24583c..e357673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,1881 @@ { "name": "vm2", - "version": "3.9.3", - "lockfileVersion": 1, + "version": "3.9.5", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "vm2", + "version": "3.9.5", + "license": "MIT", + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "devDependencies": { + "eslint": "^5.16.0", + "eslint-config-integromat": "^1.5.0", + "mocha": "^6.2.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.8.3" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/eslint-config-integromat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-integromat/-/eslint-config-integromat-1.5.0.tgz", + "integrity": "sha512-ndV5LeuRfWOVysz6sb4i03s6k2Xmxhy3s2yvUzZR6hn4lXtJu08lmrx+v8UKOp1G56LmiD9gFMpvy4DBJPwJ0g==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.7.0" + } + }, + "node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "dependencies": { + "estraverse": "^4.0.0" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "dependencies": { + "estraverse": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "deprecated": "Fixed a prototype pollution security issue in 4.1.0, please upgrade to ^4.1.1 or ^5.0.1.", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", + "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mocha/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/mocha/node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-async": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", + "dev": true, + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.8.3", @@ -31,16 +1904,21 @@ } }, "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, "ajv": { "version": "6.12.0", @@ -375,7 +2253,8 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/eslint-config-integromat/-/eslint-config-integromat-1.5.0.tgz", "integrity": "sha512-ndV5LeuRfWOVysz6sb4i03s6k2Xmxhy3s2yvUzZR6hn4lXtJu08lmrx+v8UKOp1G56LmiD9gFMpvy4DBJPwJ0g==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "4.0.3", @@ -411,6 +2290,14 @@ "acorn": "^6.0.7", "acorn-jsx": "^5.0.0", "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + } } }, "esprima": { diff --git a/package.json b/package.json index d519a1e..422416d 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,15 @@ "alcatraz", "contextify" ], - "version": "3.9.5", + "version": "3.9.6", "main": "index.js", + "sideEffects": false, "repository": "github:patriksimek/vm2", "license": "MIT", - "dependencies": {}, + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, "devDependencies": { "eslint": "^5.16.0", "eslint-config-integromat": "^1.5.0", diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index 4e72742..0000000 --- a/test/helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env mocha */ - -const {match} = require('../lib/helpers'); -const assert = require('assert'); - -describe('wildcard matching', () => { - it('handles * correctly', () => { - assert.strictEqual(match('s*th*g', 'something'), true); - }); - - it('handles ? correctly', () => { - assert.strictEqual(match('someth??g', 'something'), true); - }); -}); diff --git a/test/node_modules/require/index.js b/test/node_modules/require/index.js new file mode 100644 index 0000000..2457758 --- /dev/null +++ b/test/node_modules/require/index.js @@ -0,0 +1 @@ +exports.require = require; diff --git a/test/nodevm.js b/test/nodevm.js index 02e912f..5df40ab 100644 --- a/test/nodevm.js +++ b/test/nodevm.js @@ -6,10 +6,11 @@ const fs = require('fs'); const path = require('path'); const assert = require('assert'); +const {EventEmitter} = require('events'); const {NodeVM, VMScript} = require('..'); -const NODE_VERSION = parseInt(process.versions.node.split('.')[0]); +// const NODE_VERSION = parseInt(process.versions.node.split('.')[0]); -global.isVM = false; +global.isHost = true; describe('NodeVM', () => { let vm; @@ -26,7 +27,7 @@ describe('NodeVM', () => { it('globals', () => { const ex = vm.run('module.exports = global'); - assert.equal(ex.isVM, true); + assert.equal(ex.isHost, undefined); }); it('options', ()=>{ @@ -49,7 +50,7 @@ describe('NodeVM', () => { }); it('global attack', () => { - assert.equal(vm.run("module.exports = console.log.constructor('return (function(){return this})().isVM')()"), true); + assert.equal(vm.run("module.exports = console.log.constructor('return (function(){return this})().isHost')()"), undefined); }); it('shebang', () => { @@ -80,7 +81,7 @@ describe('modules', () => { } }); - assert.equal(vm.run(`module.exports = require('${__dirname}/data/json.json')`).working, true); + assert.equal(vm.run(`module.exports = require('./data/json.json')`, `${__dirname}/vm.js`).working, true); }); it.skip('run coffee-script', () => { @@ -122,7 +123,7 @@ describe('modules', () => { it('disabled require', () => { const vm = new NodeVM; - assert.throws(() => vm.run("require('fs')"), /Access denied to require 'fs'/); + assert.throws(() => vm.run("require('fs')"), /Cannot find module 'fs'/); }); it('disable setters on builtin modules', () => { @@ -192,7 +193,7 @@ describe('modules', () => { assert.throws(() => vm.run("require('mocha')", __filename), err => { assert.equal(err.name, 'VMError'); - assert.equal(err.message, 'Access denied to require \'mocha\''); + assert.equal(err.message, 'Cannot find module \'mocha\''); return true; }); }); @@ -207,7 +208,7 @@ describe('modules', () => { assert.ok(vm.run("require('mocha')", __filename)); assert.throws(() => vm.run("require('unknown')", __filename), err => { assert.equal(err.name, 'VMError'); - assert.equal(err.message, "The module 'unknown' is not whitelisted in VM."); + assert.equal(err.message, "Cannot find module 'unknown'"); return true; }); }); @@ -303,7 +304,7 @@ describe('modules', () => { } }); - assert.throws(() => vm.run("var test = require('../package.json')", __filename), /Module '\.\.\/package.json' is not allowed to be required\. The path is outside the border!/); + assert.throws(() => vm.run("var test = require('../package.json')", __filename), /Cannot find module '\.\.\/package.json'/); }); it('process events', () => { @@ -380,7 +381,7 @@ describe('modules', () => { const vm = new NodeVM(); // https://github.com/patriksimek/vm2/issues/276 - assert.strictEqual(vm.run('module.exports = setTimeout(()=>{}).ref().constructor.constructor === Function'), true); + assert.strictEqual(vm.run('const timeout = setTimeout(()=>{});module.exports = !timeout.ref || timeout.ref().constructor.constructor === Function'), true); // https://github.com/patriksimek/vm2/issues/285 assert.strictEqual(vm.run(`try { @@ -392,21 +393,67 @@ describe('modules', () => { }); - if (NODE_VERSION < 12) { - // Doesn't work under Node 12, fix pending - it('native event emitter', done => { - const vm = new NodeVM({ - require: { - builtin: ['events'] - }, - sandbox: { - done - } - }); + it('native event emitter', () => { + const vm = new NodeVM({ + require: { + builtin: ['events'] + } + }); + + assert.ok(vm.run(`const {EventEmitter} = require('events'); const ee = new EventEmitter(); let tr; ee.on('test', ()=>{tr = true;}); ee.emit('test'); return tr`, {wrapper: 'none'})); + assert.ok(vm.run('const {EventEmitter} = require("events"); return new EventEmitter()', {wrapper: 'none'}) instanceof EventEmitter); + assert.ok(vm.run('return nei => nei instanceof require("events").EventEmitter', {wrapper: 'none'})(new EventEmitter())); + assert.ok(vm.run(` + const {EventEmitter} = require('events'); + class EEE extends EventEmitter { + test() {return true;} + } + return new EEE().test(); + `, {wrapper: 'none'})); + + }); + + it('cache modules', () => { + const vm = new NodeVM({ + require: { + context: 'sandbox', + external: ['module1', 'module2', 'require'], + builtin: ['*'] + } + }); + assert.ok(vm.run('return require("module1") === require("module2")', {filename: `${__dirname}/vm.js`, wrapper: 'none'})); + assert.ok(vm.run('return require("require").require("fs") === require("fs")', {filename: `${__dirname}/vm.js`, wrapper: 'none'})); + assert.ok(vm.run('return require("require").require("buffer") === require("buffer")', {filename: `${__dirname}/vm.js`, wrapper: 'none'})); + assert.ok(vm.run('return require("require").require("util") === require("util")', {filename: `${__dirname}/vm.js`, wrapper: 'none'})); + }); + + it('strict module name checks', () => { + const vm = new NodeVM({ + require: { + external: ['module'] + } + }); + assert.throws(()=>vm.run('require("module1")', `${__dirname}/vm.js`), /Cannot find module 'module1'/); + }); + + it('module name globs', () => { + const vm = new NodeVM({ + require: { + external: ['mo?ule1', 'm*e2'] + } + }); + assert.doesNotThrow(()=>vm.run('require("module1");require("module2")', `${__dirname}/vm.js`)); + }); - vm.run(`let {EventEmitter} = require('events'); const ee = new EventEmitter(); ee.on('test', done); ee.emit('test');`); + it('module name glob escape', () => { + const vm = new NodeVM({ + require: { + external: ['module1*'] + } }); - } + assert.throws(()=>vm.run('require("module1/../module2")', `${__dirname}/vm.js`), /Cannot find module 'module1\/..\/module2'/); + }); + }); describe('nesting', () => { diff --git a/test/vm.js b/test/vm.js index b67dfab..b7ccd72 100644 --- a/test/vm.js +++ b/test/vm.js @@ -8,7 +8,77 @@ const {VM, VMScript} = require('..'); const NODE_VERSION = parseInt(process.versions.node.split('.')[0]); const {inspect} = require('util'); -global.isVM = false; +global.isHost = true; + +function makeHelpers() { + function isVMProxy(obj) { + const key = {}; + const proto = Object.getPrototypeOf(obj); + if (!proto) return undefined; + proto.isVMProxy = key; + const proxy = obj.isVMProxy !== key; + delete proto.isVMProxy; + return proxy; + } + + function isLocal(obj) { + if (obj instanceof Object || obj === Object.prototype) return true; + const ctor = obj.constructor; + if (ctor && ctor.prototype === obj && ctor instanceof ctor && !isVMProxy(ctor)) return false; + return true; + } + + function collectAll(obj) { + const toVisit = []; + const visited = new Map(); + function addObj(o, path) { + if (o && (typeof o === 'object' || typeof o === 'function') && !visited.has(o)) { + visited.set(o, path); + toVisit.push(o); + } + } + addObj(obj, 'obj'); + function addProp(o, name, path) { + const prop = Object.getOwnPropertyDescriptor(o, name); + if (typeof name === 'symbol') name = '!' + name.toString(); + Object.setPrototypeOf(prop, null); + addObj(prop.get, `${path}>${name}`); + addObj(prop.set, `${path}<${name}`); + addObj(prop.value, `${path}.${name}`); + } + function addAllFrom(o) { + const path = visited.get(o); + const names = Object.getOwnPropertyNames(o); + for (let i = 0; i < names.length; i++) { + addProp(o, names[i], path); + } + const symbols = Object.getOwnPropertySymbols(o); + for (let i = 0; i < symbols.length; i++) { + addProp(o, symbols[i], path); + } + addObj(Object.getPrototypeOf(o), path + '@'); + } + while (toVisit.length > 0) { + addAllFrom(toVisit.pop()); + } + return visited; + } + + function checkAllLocal(obj) { + const wrong = []; + collectAll(obj).forEach((v, k) => { + if (!isLocal(k)) wrong.push(v); + }); + return wrong.length === 0 ? undefined : wrong; + } + + return {isVMProxy, checkAllLocal}; +} + +const { + isVMProxy, + checkAllLocal +} = makeHelpers(); describe('node', () => { let vm; @@ -20,7 +90,7 @@ describe('node', () => { before(() => { vm = new VM(); }); - it('inspect', () => { + it.skip('inspect', () => { assert.throws(() => inspect(doubleProxy), /Expected/); if (NODE_VERSION !== 10) { // This failes on node 10 since they do not unwrap proxys. @@ -107,22 +177,22 @@ describe('contextify', () => { assert.strictEqual(sandbox.test.object.y === sandbox.test.object.y.valueOf(), true); assert.strictEqual(vm.run('test.object.y instanceof Function'), true); assert.strictEqual(vm.run('test.object.y.valueOf() instanceof Function'), true); - assert.strictEqual(vm.run('test.object.y').isVMProxy, void 0); - assert.strictEqual(vm.run('test.object.y.valueOf()').isVMProxy, void 0); + assert.strictEqual(isVMProxy(vm.run('test.object.y')), false); + assert.strictEqual(isVMProxy(vm.run('test.object.y.valueOf()')), false); assert.strictEqual(vm.run('test.object.y') === vm.run('test.object.y.valueOf()'), true); assert.strictEqual(vm.run('test.object.y === test.object.y.valueOf()'), true); assert.strictEqual(vm.run('test.object').y instanceof Function, true); assert.strictEqual(vm.run('test.object').y.valueOf() instanceof Function, true); - assert.strictEqual(vm.run('test.object').y.isVMProxy, void 0); - assert.strictEqual(vm.run('test.object').y.valueOf().isVMProxy, void 0); + assert.strictEqual(isVMProxy(vm.run('test.object').y), false); + assert.strictEqual(isVMProxy(vm.run('test.object').y.valueOf()), false); assert.strictEqual(vm.run('test.object').y === vm.run('test.object').y.valueOf(), true); assert.strictEqual(vm.run('test.valueOf()') === vm.run('test').valueOf(), true); assert.strictEqual(vm.run('test.object.y.constructor instanceof Function'), true); - assert.strictEqual(vm.run("test.object.y.constructor('return (function(){return this})().isVM')()"), true); + assert.strictEqual(vm.run("test.object.y.constructor('return (function(){return this})() === global')()"), true); assert.strictEqual(vm.run('test.object.valueOf() instanceof Object'), true); assert.strictEqual(vm.run('test.object.valueOf().y instanceof Function'), true); assert.strictEqual(vm.run('test.object.valueOf().y.constructor instanceof Function'), true); - assert.strictEqual(vm.run("test.object.valueOf().y.constructor('return (function(){return this})().isVM')()"), true); + assert.strictEqual(vm.run("test.object.valueOf().y.constructor('return (function(){return this})() === global')()"), true); assert.strictEqual(Object.prototype.toString.call(vm.run(`[]`)), '[object Array]'); assert.strictEqual(Object.prototype.toString.call(vm.run(`new Date`)), '[object Date]'); @@ -156,15 +226,20 @@ describe('contextify', () => { assert.strictEqual(o.a === sandbox.test.date, true); o = vm.run('let y = new Date(); let z = {a: y, b: y};z'); - assert.strictEqual(o.isVMProxy, true); + assert.strictEqual(isVMProxy(o), true); assert.strictEqual(o instanceof Object, true); assert.strictEqual(o.a instanceof Date, true); assert.strictEqual(o.b instanceof Date, true); assert.strictEqual(o.a === o.b, true); + + assert.strictEqual(checkAllLocal(vm), undefined); + + o = vm.run(`(${makeHelpers})().checkAllLocal(global)`); + assert.strictEqual(o, undefined); }); it('class', () => { - assert.strictEqual(vm.run('new test.klass()').isVMProxy, undefined); + assert.strictEqual(isVMProxy(vm.run('new test.klass()')), false); assert.strictEqual(vm.run('new test.klass()').greet('friend'), 'hello friend'); assert.strictEqual(vm.run('new test.klass()') instanceof TestClass, true); @@ -250,7 +325,7 @@ describe('contextify', () => { assert.strictEqual(vm.run('test.object.y()({})'), true, '#4'); assert.strictEqual(vm.run('test.object.z({}) instanceof Object'), true, '#5'); assert.strictEqual(vm.run("Object.getOwnPropertyDescriptor(test.object, 'y').hasOwnProperty instanceof Function"), true, '#6'); - assert.strictEqual(vm.run("Object.getOwnPropertyDescriptor(test.object, 'y').hasOwnProperty.constructor('return (function(){return this})().isVM')()"), true, '#7'); + assert.strictEqual(vm.run("Object.getOwnPropertyDescriptor(test.object, 'y').hasOwnProperty.constructor('return (function(){return this})().isHost')()"), undefined, '#7'); }); it('null', () => { @@ -330,7 +405,7 @@ describe('VM', () => { assert.throws(() => vm.run('function test(){ return await Promise.resolve(); };'), err => { assert.ok(err instanceof Error); assert.equal(err.name, 'SyntaxError'); - assert.match(err.message, /await is only valid in async function/); + // assert.match(err.message, /await is only valid in async function/); // Changed due to acorn return true; }); } @@ -375,6 +450,15 @@ describe('VM', () => { if (Promise.prototype.catch) assert.throws(() => vm2.run('Promise.resolve().catch(function(){})'), /Async not available/, '#7'); assert.throws(() => vm2.run('eval("(as"+"ync function(){})")'), /Async not available/, '#8'); assert.throws(() => vm2.run('Function')('(async function(){})'), /Async not available/, '#9'); + assert.doesNotThrow(() => vm2.run(` + let a = {import: 1} + let b = {import : {"import": 2}}; + let c = { import : 1}; + let d = a.import; + let e = a. import; + let f = a.import-1; + let g = a.import.import; + `)); }); } @@ -564,11 +648,9 @@ describe('VM', () => { new Buffer(100).toString('hex'); `), '00'.repeat(100), '#5'); - if (NODE_VERSION < 8) { - assert.strictEqual(vm2.run(` - Buffer(100).toString('hex'); - `), '00'.repeat(100), '#6'); - } + assert.strictEqual(vm2.run(` + Buffer(100).toString('hex'); + `), '00'.repeat(100), '#6'); assert.strictEqual(vm2.run(` class MyBuffer2 extends Buffer {}; new MyBuffer2(100).toString('hex'); @@ -596,7 +678,9 @@ describe('VM', () => { }); `); } catch (ex) { - assert.strictEqual(ex, null); + assert.throws(()=>{ + ex(()=>{}); + }, /process is not defined/); } }); @@ -738,6 +822,8 @@ describe('VM', () => { process.mainModule.require("child_process").execSync("whoami").toString() `), /e is not a function/, '#5'); + + /* TODO internal have changed too much for this to still work vm2 = new VM(); assert.throws(() => vm2.run(` @@ -763,6 +849,7 @@ describe('VM', () => { } process.mainModule.require("child_process").execSync("whoami").toString() `), /e is not a function/, '#6'); + */ vm2 = new VM(); @@ -906,7 +993,7 @@ describe('VM', () => { return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString(); } })() - `), /e is not a function/); + `), /process is not defined/); }); if (NODE_VERSION >= 10) { @@ -927,6 +1014,54 @@ describe('VM', () => { assert.throws(()=>vm2.run('sst=>sst[0].getThis().constructor.constructor')(sst), /TypeError: Cannot read propert.*constructor/); }); + it('Node internal prepareStackTrace attack', () => { + const vm2 = new VM(); + + assert.throws(()=>vm2.run(` + function stack() { + new Error().stack; + stack(); + } + try { + stack(); + } catch (e) { + e.constructor.constructor("return process")() + } + `), /process is not defined/); + + }); + + it('Monkey patching attack', () => { + const vm2 = new VM(); + assert.doesNotThrow(() => { + const f = vm2.run(` + function onget() {throw new Error();} + function onset() {throw new Error();} + const desc = {__proto__: null, get: onget, set: onset}; + Object.defineProperties(Object.prototype, { + __proto__: null, + '0': desc, + get: desc, + set: desc, + apply: desc, + call: desc, + '1': desc, + 'length': desc, + }); + Object.defineProperties(Function.prototype, { + __proto__: null, + call: desc, + apply: desc, + bind: desc, + }); + function passer(a, b, c) { + return a(b, c); + } + `); + f((a, b) => b, {}, {}); + }); + }); + after(() => { vm = null; }); From 94bf9183b208c85c0946f40085819b8244cd482c Mon Sep 17 00:00:00 2001 From: XmiliaH Date: Tue, 8 Feb 2022 14:14:43 +0100 Subject: [PATCH 2/2] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f0d25..a24ec6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v3.9.6 (TBD) +v3.9.6 (2022-02-08) ------------------- [fix] Security fixes (XmiliaH)