123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- 'use strict';
- 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;
- const HOST = Object.freeze({
- Buffer,
- Function,
- Object,
- transformAndCheck,
- INSPECT_MAX_BYTES,
- INTERNAL_STATE_NAME
- });
- function compileScript(filename, script) {
- return new Script(script, {
- __proto__: null,
- filename,
- displayErrors: false
- });
- }
- 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, undefined);
- checkAsync(allowAsync || !ret.hasAsync);
- return ret.code;
- }
- let cacheTimeoutContext = null;
- let cacheTimeoutScript = null;
- 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 extends EventEmitter {
-
-
-
-
-
- constructor(options = {}) {
- super();
-
- const {
- timeout,
- sandbox,
- compiler = 'javascript',
- allowAsync: optAllowAsync = true
- } = options;
- const allowEval = options.eval !== false;
- const allowWasm = options.wasm !== false;
- const allowAsync = optAllowAsync && !options.fixAsync;
-
- if (sandbox && 'object' !== typeof sandbox) {
- throw new VMError('Sandbox must be object.');
- }
-
- const resolvedCompiler = lookupCompiler(compiler);
-
- const _context = createContext(undefined, {
- __proto__: null,
- codeGeneration: {
- __proto__: null,
- strings: allowEval,
- wasm: allowWasm
- }
- });
- const sandboxGlobal = getGlobalScript.runInContext(_context, DEFAULT_RUN_OPTIONS);
-
- const {
- createBridge: sandboxCreateBridge
- } = bridgeScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal);
-
- 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);
- }
-
- const internal = setupSandboxScript.runInContext(_context, DEFAULT_RUN_OPTIONS)(sandboxGlobal, HOST, bridge.other, data, _context);
- const runScript = (script) => {
-
- 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);
- }
- };
-
-
-
- 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}
- });
-
- if (sandbox) {
- this.setGlobals(sandbox);
- }
- }
-
- setGlobals(values) {
- for (const name in values) {
- if (Object.prototype.hasOwnProperty.call(values, name)) {
- this.sandbox[name] = values[name];
- }
- }
- return this;
- }
-
- setGlobal(name, value) {
- this.sandbox[name] = value;
- return this;
- }
-
- getGlobal(name) {
- return this.sandbox[name];
- }
-
- freeze(value, globalName) {
- this.readonly(value);
- if (globalName) this.sandbox[globalName] = value;
- return value;
- }
-
- readonly(value, mock) {
- return this._makeReadonly(value, mock);
- }
-
- protect(value, globalName) {
- this._makeProtected(value);
- if (globalName) this.sandbox[globalName] = value;
- return value;
- }
-
- 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, useFileName);
- scriptCode = ret.code;
- checkAsync(this._allowAsync || !ret.hasAsync);
-
- 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);
- }
-
- 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;
|