diff --git a/public/lib/vm2/index.js b/public/lib/vm2/index.js new file mode 100644 index 0000000..7c446f8 --- /dev/null +++ b/public/lib/vm2/index.js @@ -0,0 +1,3 @@ +if (parseInt(process.versions.node.split('.')[0]) < 6) throw new Error('vm2 requires Node.js version 6 or newer.'); + +module.exports = require('./lib/main'); diff --git a/public/lib/vm2/lib/cli.js b/public/lib/vm2/lib/cli.js new file mode 100644 index 0000000..0b62091 --- /dev/null +++ b/public/lib/vm2/lib/cli.js @@ -0,0 +1,35 @@ +'use strict'; + +const pa = require('path'); + +const {NodeVM, VMError} = require('../'); + +if (process.argv[2]) { + const path = pa.resolve(process.argv[2]); + + console.log(`\x1B[90m[vm] creating VM for ${path}\x1B[39m`); + const started = Date.now(); + + try { + NodeVM.file(path, { + verbose: true, + require: { + external: true + } + }); + + console.log(`\x1B[90m[vm] VM completed in ${Date.now() - started}ms\x1B[39m`); + } catch (ex) { + if (ex instanceof VMError) { + console.error(`\x1B[31m[vm:error] ${ex.message}\x1B[39m`); + } else { + const {stack} = ex; + + if (stack) { + console.error(`\x1B[31m[vm:error] ${stack}\x1B[39m`); + } else { + console.error(`\x1B[31m[vm:error] ${ex}\x1B[39m`); + } + } + } +} diff --git a/public/lib/vm2/lib/contextify.js b/public/lib/vm2/lib/contextify.js new file mode 100644 index 0000000..0ddd12c --- /dev/null +++ b/public/lib/vm2/lib/contextify.js @@ -0,0 +1,1147 @@ +/* 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 = host.Object.getOwnPropertyDescriptor(object, prop); + } catch (e) { + throw Decontextify.value(e); + } + + // Following code prevents V8 to throw + // TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '' + // 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 = 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 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; + + default: // string, number, boolean, symbol + return value; + } + } 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 { + // fix 'Unable to deserialize cloned data due to invalid or unsupported version' error of utools api + if (typeof context == 'object') { + var keys = Object.keys(context) + if (keys.includes('ubrowser') || keys.includes('goto')) { + args = args.map(a => (typeof a == 'object') && JSON.parse(JSON.stringify(a)) || a) + } + } + // end of fix + 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; + + default: // string, number, boolean, symbol + return value; + } + } 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 { + // 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 +}; + +exports.VMError = VMError; +exports.NodeVM = NodeVM; +exports.VM = VM; +exports.VMScript = VMScript; diff --git a/public/lib/vm2/lib/sandbox.js b/public/lib/vm2/lib/sandbox.js new file mode 100644 index 0000000..1a60eb5 --- /dev/null +++ b/public/lib/vm2/lib/sandbox.js @@ -0,0 +1,682 @@ +/* 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', [code]); + + // 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; + } + + return Contextify.readonly(host.require(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/public/package-lock.json b/public/package-lock.json new file mode 100644 index 0000000..beb565c --- /dev/null +++ b/public/package-lock.json @@ -0,0 +1,153 @@ +{ + "name": "public", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^0.24.0", + "iconv-lite": "^0.6.3", + "lodash": "^4.17.21", + "pinyin-match": "^1.2.2" + } + }, + "node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/pinyin-match": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pinyin-match/-/pinyin-match-1.2.2.tgz", + "integrity": "sha512-C0yOq4LkToJMkDHiQFKOY69El2GRcwdS2lVEjgWjIV8go3wE4mloGFNkVicGHFGYHDg523m2/lKzW8Hh+JR9nw==", + "dependencies": { + "rollup": "^2.44.0" + } + }, + "node_modules/rollup": { + "version": "2.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.63.0.tgz", + "integrity": "sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + } + }, + "dependencies": { + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "pinyin-match": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pinyin-match/-/pinyin-match-1.2.2.tgz", + "integrity": "sha512-C0yOq4LkToJMkDHiQFKOY69El2GRcwdS2lVEjgWjIV8go3wE4mloGFNkVicGHFGYHDg523m2/lKzW8Hh+JR9nw==", + "requires": { + "rollup": "^2.44.0" + } + }, + "rollup": { + "version": "2.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.63.0.tgz", + "integrity": "sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==", + "requires": { + "fsevents": "~2.3.2" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + } + } +} diff --git a/public/package.json b/public/package.json new file mode 100644 index 0000000..7ecb2a7 --- /dev/null +++ b/public/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "axios": "^0.24.0", + "iconv-lite": "^0.6.3", + "lodash": "^4.17.21", + "pinyin-match": "^1.2.2" + } +} diff --git a/public/plugin.json b/public/plugin.json new file mode 100644 index 0000000..5131fc0 --- /dev/null +++ b/public/plugin.json @@ -0,0 +1,40 @@ +{ + "pluginName": "快捷命令", + "description": "快速打开软件、网址及运行批处理、shell等脚本,免编写插件使用utools的api及实现UI交互", + "main": "index.html", + "homepage": "https://github.com/fofolee/uTools-quickcommand", + "publishPage": "https://yuanliao.info/d/424", + "version": "3.0.0", + "development": { + "main": "http://127.0.0.1:8080/" + }, + "author": "云之轩", + "unpack":"autopep8.py", + "logo": "logo.png", + "preload": "preload.js", + "pluginSetting": { + "single": false + }, + "features": [ + { + "code": "configuration", + "explain": "新建、编辑或获取快捷命令", + "cmds": [ "快捷命令", "QuickCommand"] + }, + { + "code": "code", + "explain": "运行代码", + "cmds": [ "运行代码", "RunCode"] + }, + { + "code": "newcommand", + "explain": "快速新建快捷命令", + "cmds": [ "新建快捷命令", "NewCommand", { + "type": "regex", + "label": "新建快捷命令", + "match": "/^\\{[\\s\\S]*\"program\" *: *\".*\"[\\s\\S]*\"cmd\" *: *\".*\"[\\s\\S]*\\}$/i", + "maxNum": 1 + }] + } + ] +} diff --git a/public/preload.js b/public/preload.js new file mode 100644 index 0000000..a1b4f82 --- /dev/null +++ b/public/preload.js @@ -0,0 +1,918 @@ +const fs = require('fs'); +const os = require('os'); +const child_process = require("child_process") +const iconv = require('iconv-lite') +const electron = require('electron') +const { NodeVM } = require('./lib/vm2') +const path = require("path") +const util = require("util") +const PinyinMatch = require('pinyin-match'); +const axios = require('axios'); + +_ = require("lodash") + +// axios.defaults.adapter = require('axios/lib/adapters/http') + +if (!utools.isWindows()) process.env.PATH += ':/usr/local/bin:/usr/local/sbin' + +// window.startTime = new Date().getTime() + +const shortCodes = [ + + open = path => { + utools.shellOpenItem(path) + }, + + locate = path => { + utools.shellShowItemInFolder(path); + }, + + visit = url => { + utools.shellOpenExternal(url); + }, + + system = cmd => { + child_process.exec(cmd); + }, + + message = msg => { + utools.showNotification(msg) + }, + + keyTap = (key, ...modifier) => utools.simulateKeyboardTap(key, ...modifier), + + copyTo = text => { + electron.clipboard.writeText(text) + }, + + send = text => { + copyTo(text); + quickcommand.simulatePaste(); + } +] + +ctlKey = utools.isMacOs() ? 'command' : 'control' + +quickcommand = { + // 模拟复制操作 + simulateCopy: function() { + utools.simulateKeyboardTap('c', ctlKey); + }, + + // 模拟粘贴操作 + simulatePaste: function() { + utools.simulateKeyboardTap('v', ctlKey); + }, + + // setTimout 不能在 vm2 中使用,同时在 electron 中有 bug + sleep: function(ms) { + var start = new Date().getTime() + try { + // node 16.13.1 + child_process.execSync(getSleepCodeByShell(ms), { + timeout: ms, + windowsHide: true + }) + } catch (ex) {} + var end = new Date().getTime() + return (end - start) + }, + + // 重写 setTimeout + setTimeout: function(callback, ms) { + var start = new Date().getTime() + child_process.exec(getSleepCodeByShell(ms), { + timeout: ms + }, (err, stdout, stderr) => { + var end = new Date().getTime() + callback(end - start) + }) + }, + + // 显示输入框 + showInputBox: function(placeHolders = [""], title = '') { + return new Promise((reslove, reject) => { + if (!(placeHolders instanceof Array)) placeHolders = [placeHolders.toString()] + utools.setExpendHeight(550) + var html = "" + var inputBoxNumbers = placeHolders.length + for (let i = 0; i < inputBoxNumbers; i++) { + html += `` + } + var result = [] + var options = { + onBeforeOpen: () => { + document.getElementById(`inputBox0`).focus() + $('.swal2-content').keydown(function(e) { + e.which == 13 && swal.clickConfirm() + }) + $(".output").is(":parent") ? utools.setExpendHeight(550) : modWindowHeight($('.swal2-popup').outerHeight() + 20) + }, + title: title, + html: html, + focusConfirm: false, + showCancelButton: true, + backdrop: utools.isDarkColors() ? '#ffffff26' : '#bbbbbb80', + preConfirm: () => { + for (let i = 0; i < inputBoxNumbers; i++) { + result.push(document.getElementById(`inputBox${i}`).value) + } + reslove(result) + } + } + swalOneByOne(options) + }); + }, + + // 显示选项按钮 + showButtonBox: function(buttons, title = '') { + return new Promise((reslove, reject) => { + if (!(buttons instanceof Array)) return reject(new TypeError(`应为 Array, 而非 ${typeof buttons}`)) + utools.setExpendHeight(550) + var html = `` + var buttonBoxNumbers = buttons.length + for (let i = 0; i < buttonBoxNumbers; i++) { + html += `` + } + var options = { + onBeforeOpen: () => { + clickButton = i => { + reslove({ + id: i, + text: buttons[i] + }) + swal.clickConfirm() + } + $(".output").is(":parent") && utools.setExpendHeight(550) || modWindowHeight($('.swal2-popup').outerHeight() + 20) + }, + html: html, + title: title, + backdrop: utools.isDarkColors() ? '#ffffff26' : '#bbbbbb80', + showConfirmButton: false + } + swalOneByOne(options) + }); + }, + + // 显示自动消失的提示框 + showMessageBox: function(title, icon = "success", time = 3000) { + var options = { + icon: icon, + title: title, + toast: true, + position: 'top', + timer: time, + showConfirmButton: false, + // onBeforeOpen: () => { + // setTimeout(() => { Swal.clickConfirm() }, time); + // } + } + swal.fire(options) + }, + + // 显示选项列表 + showSelectList: function(selects, opt = {}) { + return new Promise((reslove, reject) => { + if (!(selects instanceof Array)) return reject(new TypeError(`应为 Array, 而非 ${typeof selects}`)) + opt.optionType || (opt.optionType = 'plaintext') + typeof opt.placeholder == 'undefined' && (opt.placeholder = "搜索,支持拼音") + typeof opt.enableSearch == 'undefined' && (opt.enableSearch = true) + if ($('#quickselect').length) $('#quickselect').remove() + let cancelButton = opt.showCancelButton ? '' : '' + $("body").append(`
${cancelButton}
`) + let item, data = [] + selects.forEach((s, i) => { + item = {} + if (opt.optionType == 'json') { + item.text = '' + Object.keys(s).forEach(k => item[k] = s[k]) + item.id = i + s.icon && (item.text += `
`) + s.title && (item.text += `
${s.title}
`) + s.description && (item.text += `
${s.description}
`) + } else { + item = { + id: i, + text: s + } + } + data.push(item) + }) + $('#selectBox').data('options', data) + $('#selectBox').data('type', opt.optionType) + var prefer = { + // data: data, + width: "100%", + dropdownParent: $("#quickselect"), + closeOnSelect: false, + // 支持无限滚动 + ajax: { + transport: (params, success, failure) => { + let cont, pageSize = 50 + let term = (params.data.term || '').toLowerCase() + let page = (params.data.page || 1) + let items = $('#selectBox').data('options') + let results = items.filter(x => { + if (opt.optionType == 'json') cont = x.title + else if (opt.optionType == 'html') cont = x.text.replace(/<[^<>]+>/g, '') + else cont = x.text + return cont.toLowerCase().includes(term) || PinyinMatch.match(cont, term) + }) + let paged = results.slice((page - 1) * pageSize, page * pageSize) + let options = { + results: paged, + pagination: { + more: results.length >= page * pageSize + } + } + success(options) + } + }, + } + // 显示html时不转义标签 + if (opt.optionType != 'plaintext') prefer.escapeMarkup = markup => markup + $('#selectBox').select2(prefer) + $('#selectBox').val(null).trigger('change') + $('#selectBox').select2('open') + $("#quickselect .select2-search__field").focus() + $('#quickselect .select2').hide() + opt.optionType == 'plaintext' && $('.select2-results').css({ + 'line-height': '40px' + }) + modWindowHeight($('.select2-results').outerHeight()) + opt.enableSearch && utools.setSubInput(({ + text + }) => { + $("#quickselect .select2-search__field").val(text).trigger('input') + modWindowHeight($('.select2-results').outerHeight()) + }, opt.placeholder) + // 关闭列表 + let closeSelect = () => { + $('#selectBox').off('select2:select') + utools.removeSubInput() + $("#quickselect").remove() + } + $('#selectBox').on('select2:select', function(e) { + let result = $('#selectBox').data('options')[$(this).val()] + delete result.selected + closeSelect() + reslove(result) + }) + $('.circleButton').click(() => { + closeSelect() + reslove(false) + }) + }); + }, + + // 更新选项列表 + updateSelectList: function(opt, id) { + if (!$('#selectBox').length) throw '当前没有选择列表, 请结合 quickcommand.showSelectList 使用' + let data = $('#selectBox').data('options') + let num = data.length + typeof id == 'undefined' && (id = num) + if (id > num) throw 'id 不能大于当前列表数' + let optionType = $('#selectBox').data('type') + let item = { + id: id + } + if (optionType == 'json') { + item.text = '' + if (!(opt instanceof Object)) throw '更新的选项格式与当前的不一致' + Object.keys(opt).forEach(k => item[k] = opt[k]) + opt.icon && (item.text += `
`) + opt.title && (item.text += `
${opt.title}
`) + opt.description && (item.text += `
${opt.description}
`) + } else { + item.text = opt + } + data[id] && (data[id] = item) || data.push(item) + $('#selectBox').data('options', data).val(null).trigger('change') + $("#quickselect .select2-search__field").trigger('input') + modWindowHeight($('.select2-results').outerHeight()) + }, + + // 显示文本输入框 + showTextAera: function(placeholder = "", value = "") { + return new Promise((reslove, reject) => { + utools.setExpendHeight(550) + var html = ` +
+ + +
` + $("body").append(html) + $("#quicktextarea").addClass("fadeInUpWindow") + $(".circleButton").click(function() { + $("#quicktextarea").addClass("fadeOutDownWindow") + setTimeout(() => { + $("#quicktextarea").remove() + }, 300); + reslove($("#quicktextarea > textarea").val()) + }) + }); + }, + + showConfirmBox: async function(title) { + let options = { + text: title, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: '确定!', + cancelButtonText: '手抖...' + } + utools.setExpendHeight(550) + let result = await Swal.fire(options) + if (result.value) return true; + }, + + // 关闭进程 + kill: function(pid, signal = 'SIGTERM') { + process.kill(pid, signal) + }, + + // dom 解析 + htmlParse: function(html) { + return new DOMParser().parseFromString(html, 'text/html') + }, + + // 下载文件 + downloadFile: function(url, file = {}) { + return new Promise((reslove, reject) => { + if (file instanceof Object) file = utools.showSaveDialog(JSON.parse(JSON.stringify(file))) + axios({ + method: 'get', + url: url, + responseType: 'arraybuffer' + }).then(res => { + var filebuffer = Buffer.from(res.data) + fs.writeFile(file, filebuffer, err => { + if (err) reject(err) + else reslove(filebuffer) + }) + }).catch(err => { + reject(err) + }) + }) + }, + + // 上传文件 + uploadFile: function(url, file = {}, name = 'file', formData = {}) { + return new Promise((reslove, reject) => { + var objfile + if (file instanceof File) { + objfile = file + } else { + if (file instanceof Object) file = utools.showOpenDialog(JSON.parse(JSON.stringify(file)))[0] + if (!fs.existsSync(file)) return reject('文件不存在') + var arraybuffer = fs.readFileSync(file).buffer + var objfile = new File([arraybuffer], path.basename(file)) + } + var form = new FormData(); + form.append(name, objfile) + var keys = Object.keys(formData) + if (keys.length) keys.forEach(k => form.append(k, formData[k])) + axios.post(url, form, { + headers: { + 'accept': 'application/json', + 'Content-Type': `multipart/form-data; boundary=${formData._boundary}`, + } + }).then(res => { + reslove(res) + }).catch(err => { + reject(err) + }) + }) + }, + + // 载入在线资源 + loadRemoteScript: async function(url, forceUpdate = false) { + if (!/^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/.test(url)) throw 'url 不合法' + let remote = url + let root = path.join(os.tmpdir(), 'qcRemoteScript') + if (!fs.existsSync(root)) fs.mkdirSync(root) + let local = path.join(root, require('crypto').createHash('md5').update(url).digest('hex')) + if (forceUpdate || !fs.existsSync(local)) await this.downloadFile(remote, local) + return require(local) + } +} + +// 运行vbs脚本 +if (process.platform == 'win32') quickcommand.runVbs = function(script) { + return new Promise((reslove, reject) => { + var tempfile = path.join(os.tmpdir(), 'TempVBSScript.vbs') + fs.writeFile(tempfile, iconv.encode(script, 'gbk'), err => { + child_process.exec(`cscript.exe /nologo "${tempfile}"`, { + encoding: "buffer" + }, (err, stdout, stderr) => { + if (err) reject(iconv.decode(stderr, 'gbk')) + else reslove(iconv.decode(stdout, 'gbk')) + }); + }) + }) +} + + +// 在终端中执行 +if (process.platform !== 'linux') quickcommand.runInTerminal = function(cmdline, dir) { + let command = getCommandToLaunchTerminal(cmdline, dir) + child_process.exec(command) +} + +let getCommandToLaunchTerminal = (cmdline, dir) => { + let cd = '' + if (utools.isWindows()) { + let appPath = path.join(utools.getPath('home'), '/AppData/Local/Microsoft/WindowsApps/') + // 直接 existsSync wt.exe 无效 + if (fs.existsSync(appPath) && fs.readdirSync(appPath).includes('wt.exe')) { + cmdline = cmdline.replace(/"/g, `\\"`) + if (dir) cd = `-d "${dir.replace(/\\/g, '/')}"` + command = `${appPath}wt.exe ${cd} cmd /k "${cmdline}"` + } else { + cmdline = cmdline.replace(/"/g, `^"`) + if (dir) cd = `cd /d "${dir.replace(/\\/g, '/')}" &&` + command = `${cd} start "" cmd /k "${cmdline}"` + } + } else { + cmdline = cmdline.replace(/"/g, `\\"`) + if (dir) cd = `cd ${dir.replace(/ /g, `\\\\ `)} &&` + if (fs.existsSync('/Applications/iTerm.app')) { + command = `osascript -e 'tell application "iTerm" + create window with default profile + tell current session of current window to write text "clear && ${cd} ${cmdline}" + end tell'` + } else { + command = `osascript -e 'tell application "Terminal" + do script "clear && ${cd} ${cmdline}" + activate + end tell'` + } + } + console.log(command); + return command +} + +swalOneByOne = options => { + swal.getQueueStep() ? Swal.insertQueueStep(options) : Swal.queue([options]) +} + +pluginInfo = () => { + return JSON.parse(fs.readFileSync(path.join(__dirname, 'plugin.json'))) +} + + +let GetFilePath = (Path, File) => { + if (utools.isDev()) { + return path.join(__dirname, Path, File) + } else { + return path.join(__dirname.replace(/([a-zA-Z0-9\-]+\.asar)/, '$1.unpacked'), Path, File) + } +} + +let getSleepCodeByShell = ms => { + var cmd, tempFilePath + if (utools.isWindows()) { + tempFilePath = getQuickcommandTempFile('vbs') + cmd = `echo set ws=CreateObject("Wscript.Shell") > ${tempFilePath} && echo Wscript.sleep ${ms} >> ${tempFilePath} && cscript /nologo ${tempFilePath}` + } else { + cmd = `sleep ${ms / 1000}` + } + return cmd +} + +let modWindowHeight = height => { + $('#options').is(':hidden') && utools.setExpendHeight(height > 600 ? 600 : height); +} + +// 屏蔽危险函数 +getuToolsLite = () => { + var utoolsLite = Object.assign({}, utools) + if (utools.isDev()) return utoolsLite + // 数据库相关接口 + delete utoolsLite.db + delete utoolsLite.dbStorage + delete utoolsLite.removeFeature + delete utoolsLite.setFeature + // 支付相关接口 + delete utoolsLite.fetchUserServerTemporaryToken + delete utoolsLite.getUserServerTemporaryToken + delete utoolsLite.openPayment + delete utoolsLite.fetchUserPayments + return utoolsLite +} +console.error +let getSandboxFuns = () => { + var sandbox = { + utools: getuToolsLite(), + quickcommand: quickcommand, + electron: electron, + fs: fs, + path: path, + os: os, + child_process: child_process, + util: util, + TextDecoder: TextDecoder, + TextEncoder: TextEncoder, + URL: URL, + URLSearchParams: URLSearchParams, + axios: axios, + Audio: Audio, + fetch: fetch + } + shortCodes.forEach(f => { + sandbox[f.name] = f + }) + return sandbox +} + +let createNodeVM = (enterData = {}) => { + var sandbox = getSandboxFuns() + sandbox.quickcommand.enterData = enterData + sandbox.quickcommand.payload = enterData.payload + const vm = new NodeVM({ + require: { + external: true, + builtin: ["*"], + }, + console: 'redirect', + env: process.env, + sandbox: sandbox, + }); + return vm +} + +let stringifyAll = item => { + var cache = []; + var string = JSON.stringify(item, (key, value) => { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) return + cache.push(value); + } + return value; + }, '\t') + if (string != "{}") return string + else return item.toString() +} + +let parseItem = item => { + if (typeof item == "object") { + if (Buffer.isBuffer(item)) { + var bufferString = `[Buffer ${item.slice(0, 50).toString('hex').match(/\w{1,2}/g).join(" ")}` + if (item.length > 50) bufferString += `... ${(item.length / 1000).toFixed(2)}kb` + return bufferString + ']' + } else if (item instanceof ArrayBuffer) { + return `ArrayBuffer(${item.byteLength})` + } else if (item instanceof Blob) { + return `Blob {size: ${item.size}, type: "${item.type}"}` + } else { + try { + return stringifyAll(item) + } catch (error) {} + } + } else if (typeof item == "undefined") { + return "undefined" + } + return item.toString() +} + +// The vm module of Node.js is deprecated in the renderer process and will be removed +runCodeInVm = (cmd, cb, enterData = {}) => { + const vm = createNodeVM(enterData) + //重定向 console + vm.on('console.log', stdout => { + console.log(stdout); + cb(parseItem(stdout), null) + }); + + vm.on('console.error', stderr => { + cb(null, stderr.toString()) + }); + + let liteErr = e => { + return e.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() + } + + // 错误处理 + try { + vm.run(cmd, path.join(__dirname, 'preload.js')); + } catch (e) { + console.log('Error: ', e) + cb(null, liteErr(e)) + } + + let cbUnhandledError = e => { + removeAllListener() + console.log('UnhandledError: ', e) + cb(null, liteErr(e.error)) + } + + let cbUnhandledRejection = e => { + removeAllListener() + console.log('UnhandledRejection: ', e) + cb(null, liteErr(e.reason)) + } + + let removeAllListener = () => { + window.removeEventListener('error', cbUnhandledError) + window.removeEventListener('unhandledrejection', cbUnhandledRejection) + delete window.isWatchingError + } + + if (!window.isWatchingError) { + window.addEventListener('error', cbUnhandledError) + window.addEventListener('unhandledrejection', cbUnhandledRejection) + window.isWatchingError = true + } +} + +// shell代码提示,当前环境变量下的所有命令 +getShellCommand = () => { + var shellCommands = localStorage['shellCommands'] + if (shellCommands) return + localStorage['shellCommands'] = '[]' + if (utools.isWindows()) return + process.env.PATH.split(':').forEach(d => { + fs.readdir(d, (err, files) => { + if (!err) { + var commands = files.filter(x => x[0] != "." || x[0] != '[') + localStorage['shellCommands'] = JSON.stringify(JSON.parse(localStorage['shellCommands']).concat(commands)) + } + }) + }) +} + +// cmd代码提示,当前环境变量下的所有命令 +getCmdCommand = () => { + var cmdCommands = localStorage['cmdCommands'] + if (cmdCommands) return + localStorage['cmdCommands'] = '[]' + if (!utools.isWindows()) return + process.env.Path.split(';').forEach(d => { + fs.readdir(d, (err, files) => { + if (!err) { + var commands = [] + files.forEach(x => (x.length > 4 && x.slice(-4) == '.exe') && commands.push(x.slice(0, -4))) + localStorage['cmdCommands'] = JSON.stringify(JSON.parse(localStorage['cmdCommands']).concat(commands)) + } + }) + }) +} + +// python 代码提示,已安装的模块以及脚本内导入的模块的属性(方法) +getPythonMods = () => { + var pyModules = localStorage['pyModules'] + if (pyModules) return + localStorage['pyModules'] = '[]' + child_process.exec(`python -c "print(__import__('sys').path)"`, (err, stdout, stderr) => { + if (err) return + stdout = JSON.parse(stdout.replace(/'/g, `"`)).forEach(s => { + fs.readdir(s, (err, m) => { + if (!err) { + var mods = [] + m.forEach(d => (/\.py$|^[^-.]+$/.test(d)) && (d = d.split('.py')[0]) && (!mods.includes(d)) && mods.push(d)) + localStorage['pyModules'] = JSON.stringify(JSON.parse(localStorage['pyModules']).concat(mods)) + } + }) + }) + }) +} + +dirPythonMod = (mod, cb) => { + child_process.exec(`python -c "print(dir(__import__('${mod}')))"`, (err, stdout, stderr) => { + if (err) return cb([]) + cb(JSON.parse(stdout.replace(/'/g, `"`)).filter(x => x.slice(0, 2) != '__')) + }) +} + +// NodeJs 代码提示,所有在沙箱内支持的对象 +getNodeJsCommand = () => { + var obj = getSandboxFuns() + obj.Buffer = Buffer + obj.quickcommand.enterData = { + code: '', + type: '', + payload: '' + } + return obj +} + +htmlEncode = (value, raw = true) => { + return raw ? String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/ Buffer.from(text, 'utf8').toString('hex') +hexDecode = text => Buffer.from(text, 'hex').toString('utf8') + +py_beautify = (code, cb) => { + var file = getQuickcommandTempFile('py') + fs.writeFile(file, code, { + encoding: 'utf8' + }, err => { + var cmd = `python "${GetFilePath('assets/plugins', 'autopep8.py')}" "${file}"` + child_process.exec(cmd, { + encoding: "buffer" + }, (err, stdout, stderr) => { + var codec = utools.isWindows() ? 'cp936' : 'utf8' + cb(iconv.decode(stdout, codec).trim()) + }) + }) +} + +processPlatform = process.platform + +getQuickcommandTempFile = ext => { + return path.join(os.tmpdir(), `quickcommandTempFile.${ext}`) +} + +getBase64Ico = async filepath => { + let sourceImage, ext = path.extname(filepath).slice(1) + if (['png', 'jpg', 'jpeg', 'bmp', 'ico', 'gif', 'svg'].includes(ext)) { + if (ext == 'svg') ext = 'svg+xml' + sourceImage = `data:image/${ext};base64,` + fs.readFileSync(filepath, 'base64') + if (ext == 'png') return sourceImage + } else { + sourceImage = utools.getFileIcon(filepath) + return sourceImage + } + let compressedImage = await getCompressedIco(sourceImage) + return compressedImage +} + +getCompressedIco = async (img, width = 80) => { + let compressedImage = await pictureCompress({ + img: img, + width: width, + height: width, + type: 'png', + quality: 1 + }) + return compressedImage.img +} + +getDefaultCommands = () => { + let baseDir = path.join(__dirname, 'defaults') + let defaultCommands = {} + fs.readdirSync(baseDir).forEach(f => { + defaultCommands[f.slice(0, -5)] = path.join(baseDir, f) + }) + return defaultCommands +} + +getFileInfo = options => { + var file + if (options.type == 'file') { + file = options.argvs + } else if (options.type == 'dialog') { + var dialog = utools.showOpenDialog(options.argvs); + if (!dialog) return false + file = dialog[0] + } else { + return false + } + var information = { + name: path.basename(file), + ext: path.extname(file), + path: file + } + if (options.readfile) { + var codec = (information.ext == '.bat' || information == '.ps1') ? 'gbk' : 'utf8' + information.data = iconv.decode(fs.readFileSync(file), codec) + } + return information +} + +getCurrentFolderPathFix = () => { + let pwd = utools.getCurrentFolderPath() + let pwdFix = pwd ? pwd : path.join(utools.getPath('home'), 'desktop') + return pwdFix.replace(/\\/g, '\\\\') +} + +saveFile = (content, file) => { + if (file instanceof Object) { + file = utools.showSaveDialog(file) + } + file && fs.writeFileSync(file, content) +} + +yuQueClient = axios.create({ + baseURL: 'https://www.yuque.com/api/v2/', + headers: { + 'Content-Type': 'application/json', + // 只读权限 + 'X-Auth-Token': 'WNrd0Z4kfCZLFrGLVAaas93DZ7sbG6PirKq7VxBL' + } +}); + +getSelectFile = hwnd => + new Promise((reslove, reject) => { + if (utools.isWindows()) { + var cmd = `powershell.exe -NoProfile "(New-Object -COM 'Shell.Application').Windows() | Where-Object { $_.HWND -eq ${hwnd} } | Select-Object -Expand Document | select @{ n='SelectItems'; e={$_.SelectedItems()} } | select -Expand SelectItems | select -Expand Path "`; + child_process.exec(cmd, { + encoding: "buffer" + }, (err, stdout, stderr) => { + if (err) reject(stderr) + else reslove(iconv.decode(stdout, 'GBK').trim().replace(/\\/g, '/')); + }) + } else { + var cmd = `osascript -e 'tell application "Finder" to set selectedItems to selection as alias list + if selectedItems is {} then return + set parentPath to do shell script "dirname " & quoted form of POSIX path of (item 1 of selectedItems) + set pathData to "" + repeat with theItem in selectedItems + set pathData to pathData & POSIX path of theItem & linefeed + end repeat + ' + ` + child_process.exec(cmd, (err, stdout, stderr) => { + if (err) reject(stderr) + else reslove(stdout.trim()); + }); + } + }) + +clipboardReadText = () => { + return electron.clipboard.readText() + }, + + special = cmd => { + // 判断是否 windows 系统 + if (cmd.includes('{{isWin}}')) { + let repl = utools.isWindows() ? 1 : 0; + cmd = cmd.replace(/\{\{isWin\}\}/mg, repl) + } + // 获取本机唯一ID + if (cmd.includes('{{LocalId}}')) { + let repl = utools.getLocalId(); + cmd = cmd.replace(/\{\{LocalId\}\}/mg, repl) + } + // 获取浏览器当前链接 + if (cmd.includes('{{BrowserUrl}}')) { + let repl = utools.getCurrentBrowserUrl(); + cmd = cmd.replace(/\{\{BrowserUrl\}\}/mg, repl) + } + // 获取剪切板的文本 + if (cmd.includes('{{ClipText}}')) { + let repl = clipboardReadText(); + cmd = cmd.replace(/\{\{ClipText\}\}/mg, repl) + } + // 获取选中的文本 + // if (cmd.includes('{{SelectText}}')) { + // let repl = getSelectText(); + // cmd = cmd.replace(/\{\{SelectText\}\}/mg, repl) + // } + return cmd; + } + +runCodeFile = (cmd, option, terminal, callback) => { + var bin = option.bin, + argv = option.argv, + ext = option.ext, + charset = option.charset, + scptarg = option.scptarg || ""; + let script = getQuickcommandTempFile(ext) + // 批处理和 powershell 默认编码为 GBK, 解决批处理的换行问题 + if (charset.scriptCode) cmd = iconv.encode(cmd.replace(/\n/g, '\r\n'), charset.scriptCode); + fs.writeFileSync(script, cmd); + // var argvs = [script] + // if (argv) { + // argvs = argv.split(' ') + // argvs.push(script); + // } + var child, cmdline + if (bin.slice(-7) == 'csc.exe') { + cmdline = `${bin} ${argv} /out:"${script.slice(0, -2) + 'exe'}" "${script}" && "${script.slice(0, -2) + 'exe'}" ${scptarg}` + } else if (bin == 'gcc') { + var suffix = utools.isWindows() ? '.exe' : '' + cmdline = `${bin} ${argv} "${script.slice(0, -2)}" "${script}" && "${script.slice(0, -2) + suffix}" ${scptarg}` + } else if (utools.isWindows() && bin == 'bash') { + cmdline = `${bin} ${argv} "${script.replace(/\\/g, '/').replace(/C:/i, '/mnt/c')}" ${scptarg}` + } else { + cmdline = `${bin} ${argv} "${script}" ${scptarg}` + } + // 在终端中输出 + if (terminal) cmdline = getCommandToLaunchTerminal(cmdline) + child = child_process.spawn(cmdline, { + encoding: 'buffer', + shell: true + }) + // var chunks = [], + // err_chunks = []; + console.log('running: ' + cmdline); + child.stdout.on('data', chunk => { + if (charset.outputCode) chunk = iconv.decode(chunk, charset.outputCode) + callback(chunk.toString(), null) + // chunks.push(chunk) + }) + child.stderr.on('data', stderr => { + if (charset.outputCode) stderr = iconv.decode(stderr, charset.outputCode) + callback(null, stderr.toString()) + // err_chunks.push(err_chunk) + }) + // child.on('close', code => { + // let stdout = chunks.join(""); + // let stderr = err_chunks.join(""); + // callback(stdout, stderr) + // }) +}