vm 即将在渲染进程内被禁用,改用简单的沙箱执行命令

This commit is contained in:
fofolee 2022-04-18 17:41:04 +08:00
parent 24d0dd1e2a
commit a27fe4c338
12 changed files with 111 additions and 3422 deletions

53
plugin/lib/nodeFns.js Normal file
View File

@ -0,0 +1,53 @@
const nodeFns = {
AbortController,
AbortSignal,
Array,
ArrayBuffer,
Boolean,
Buffer,
DataView,
Date,
Error,
EvalError,
Float32Array,
Float64Array,
Function,
Infinity,
Int16Array,
Int32Array,
Int8Array,
Intl,
JSON,
Math,
NaN,
Number,
Object,
RangeError,
ReferenceError,
RegExp,
String,
SyntaxError,
TypeError,
URIError,
URL,
URLSearchParams,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray,
atob,
btoa,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
escape,
isFinite,
isNaN,
parseFloat,
parseInt,
require,
unescape,
};
module.exports = nodeFns

View File

@ -1,3 +0,0 @@
if (parseInt(process.versions.node.split('.')[0]) < 6) throw new Error('vm2 requires Node.js version 6 or newer.');
module.exports = require('./lib/main');

View File

@ -1,35 +0,0 @@
'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`);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +0,0 @@
'use strict';
// eslint-disable-next-line no-invalid-this, no-shadow
const {GeneratorFunction, AsyncFunction, AsyncGeneratorFunction, global, internal, host, hook} = this;
const {Contextify, Decontextify} = internal;
// eslint-disable-next-line no-shadow
const {Function, eval: eval_, Promise, Object, Reflect} = global;
const {getOwnPropertyDescriptor, defineProperty, assign} = Object;
const {apply: rApply, construct: rConstruct} = Reflect;
const FunctionHandler = {
__proto__: null,
apply(target, thiz, args) {
const type = this.type;
args = Decontextify.arguments(args);
try {
args = Contextify.value(hook(type, args));
} catch (e) {
throw Contextify.value(e);
}
return rApply(target, thiz, args);
},
construct(target, args, newTarget) {
const type = this.type;
args = Decontextify.arguments(args);
try {
args = Contextify.value(hook(type, args));
} catch (e) {
throw Contextify.value(e);
}
return rConstruct(target, args, newTarget);
}
};
function makeCheckFunction(type) {
return assign({
__proto__: null,
type
}, FunctionHandler);
}
function override(obj, prop, value) {
const desc = getOwnPropertyDescriptor(obj, prop);
desc.value = value;
defineProperty(obj, prop, desc);
}
const proxiedFunction = new host.Proxy(Function, makeCheckFunction('function'));
override(Function.prototype, 'constructor', proxiedFunction);
if (GeneratorFunction) {
Object.setPrototypeOf(GeneratorFunction, proxiedFunction);
override(GeneratorFunction.prototype, 'constructor', new host.Proxy(GeneratorFunction, makeCheckFunction('generator_function')));
}
if (AsyncFunction) {
Object.setPrototypeOf(AsyncFunction, proxiedFunction);
override(AsyncFunction.prototype, 'constructor', new host.Proxy(AsyncFunction, makeCheckFunction('async_function')));
}
if (AsyncGeneratorFunction) {
Object.setPrototypeOf(AsyncGeneratorFunction, proxiedFunction);
override(AsyncGeneratorFunction.prototype, 'constructor', new host.Proxy(AsyncGeneratorFunction, makeCheckFunction('async_generator_function')));
}
global.Function = proxiedFunction;
global.eval = new host.Proxy(eval_, makeCheckFunction('eval'));
if (Promise) {
Promise.prototype.then = new host.Proxy(Promise.prototype.then, makeCheckFunction('promise_then'));
// This seems not to work, and will produce
// UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object].
// This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object.
// Contextify.connect(host.Promise.prototype.then, Promise.prototype.then);
if (Promise.prototype.finally) {
Promise.prototype.finally = new host.Proxy(Promise.prototype.finally, makeCheckFunction('promise_finally'));
// Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally);
}
if (Promise.prototype.catch) {
Promise.prototype.catch = new host.Proxy(Promise.prototype.catch, makeCheckFunction('promise_catch'));
// Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch);
}
}

View File

@ -1,12 +0,0 @@
// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function match(wildcard, s) {
const regexString = escapeRegExp(wildcard).replace(/\\\*/g, '\\S*').replace(/\\\?/g, '.');
const regex = new RegExp(regexString);
return regex.test(s);
}
module.exports = {match};

File diff suppressed because it is too large Load Diff

View File

@ -1,682 +0,0 @@
/* eslint-disable no-shadow, no-invalid-this */
/* global vm, host, Contextify, Decontextify, VMError, options */
'use strict';
const {Script} = host.require('vm');
const fs = host.require('fs');
const pa = host.require('path');
const BUILTIN_MODULES = host.process.binding('natives');
const parseJSON = JSON.parse;
const importModuleDynamically = () => {
// We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object.
// eslint-disable-next-line no-throw-literal
throw 'Dynamic imports are not allowed.';
};
/**
* @param {Object} host Hosts's internal objects.
*/
return ((vm, host) => {
'use strict';
const global = this;
const TIMERS = new host.WeakMap(); // Contains map of timers created inside sandbox
const BUILTINS = {__proto__: null};
const CACHE = {__proto__: null};
const EXTENSIONS = {
__proto__: null,
['.json'](module, filename) {
try {
const code = fs.readFileSync(filename, 'utf8');
module.exports = parseJSON(code);
} catch (e) {
throw Contextify.value(e);
}
},
['.node'](module, filename) {
if (vm.options.require.context === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.');
try {
module.exports = Contextify.readonly(host.require(filename));
} catch (e) {
throw Contextify.value(e);
}
}
};
for (let i = 0; i < vm.options.sourceExtensions.length; i++) {
const ext = vm.options.sourceExtensions[i];
EXTENSIONS['.' + ext] = (module, filename, dirname) => {
if (vm.options.require.context !== 'sandbox') {
try {
module.exports = Contextify.readonly(host.require(filename));
} catch (e) {
throw Contextify.value(e);
}
} else {
let script;
try {
// Load module
let contents = fs.readFileSync(filename, 'utf8');
contents = vm._compiler(contents, filename);
const code = host.STRICT_MODULE_PREFIX + contents + host.MODULE_SUFFIX;
const ccode = vm._hook('run', [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);

View File

@ -3,15 +3,11 @@ const os = require('os');
const child_process = require("child_process") const child_process = require("child_process")
const iconv = require('iconv-lite') const iconv = require('iconv-lite')
const electron = require('electron') const electron = require('electron')
const {
NodeVM,
VM
} = require('./lib/vm2')
const path = require("path") const path = require("path")
const axios = require('axios'); const axios = require('axios');
const http = require('http'); const http = require('http');
const url = require('url') const url = require('url')
const nodeFns = require("./lib/nodeFns")
window._ = require("lodash") window._ = require("lodash")
window.yuQueClient = axios.create({ window.yuQueClient = axios.create({
baseURL: 'https://www.yuque.com/api/v2/', baseURL: 'https://www.yuque.com/api/v2/',
@ -26,8 +22,6 @@ window.yuQueClient = axios.create({
if (!utools.isWindows()) process.env.PATH += ':/usr/local/bin:/usr/local/sbin' if (!utools.isWindows()) process.env.PATH += ':/usr/local/bin:/usr/local/sbin'
// window.startTime = new Date().getTime()
const shortCodes = [ const shortCodes = [
open = path => { open = path => {
@ -199,7 +193,6 @@ window.temporaryStoreSoldOut = () => {
} }
} }
// python -c // python -c
window.runPythonCommand = py => { window.runPythonCommand = py => {
try { try {
@ -280,7 +273,6 @@ window.hexEncode = text => Buffer.from(text, 'utf8').toString('hex')
window.hexDecode = text => Buffer.from(text, 'hex').toString('utf8') window.hexDecode = text => Buffer.from(text, 'hex').toString('utf8')
window.base64Decode = text => Buffer.from(text, 'base64').toString('utf8') window.base64Decode = text => Buffer.from(text, 'base64').toString('utf8')
window.processPlatform = process.platform window.processPlatform = process.platform
window.joinPath = path.join window.joinPath = path.join
@ -401,6 +393,15 @@ window.getSelectFile = hwnd => {
} }
} }
window.showHelpPage = path => {
utools.ubrowser
.goto("https://www.yuque.com/fofolee-awga0/cpbg1m/bg31vl" + path)
.run({
width: 1380,
height: 750
});
}
window.clipboardReadText = () => electron.clipboard.readText() window.clipboardReadText = () => electron.clipboard.readText()
window.convertFilePathToUtoolsPayload = files => files.map(file => { window.convertFilePathToUtoolsPayload = files => files.map(file => {
@ -480,64 +481,58 @@ let getSandboxFuns = () => {
shortCodes.forEach(f => { shortCodes.forEach(f => {
sandbox[f.name] = f sandbox[f.name] = f
}) })
Object.assign(sandbox, nodeFns)
return sandbox return sandbox
} }
let createNodeVM = (userVars) => { // 简化报错信息
var sandbox = getSandboxFuns() let liteErr = e => {
Object.assign(userVars, sandbox)
const vm = new NodeVM({
require: {
external: true,
builtin: ["*"],
},
console: 'redirect',
env: process.env,
sandbox: userVars,
});
return vm
}
window.showHelpPage = path => {
utools.ubrowser
.goto("https://www.yuque.com/fofolee-awga0/cpbg1m/bg31vl" + path)
.run({
width: 1380,
height: 750
});
}
window.VmEval = (cmd, sandbox = {}) => new VM({
sandbox: sandbox
}).run(cmd)
let isWatchingError = false
// The vm module of Node.js is deprecated in the renderer process and will be removed
window.runCodeInVm = (cmd, callback, userVars = {}) => {
const vm = createNodeVM(userVars)
//重定向 console
vm.on('console.log', (...stdout) => {
console.log(stdout);
callback(parseStdout(stdout), null)
});
vm.on('console.error', stderr => {
callback(null, stderr.toString())
});
let liteErr = e => {
if (!e) return if (!e) return
return e.error ? e.error.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() : e.message return e.error ? e.error.stack.replace(/([ ] +at.+)|(.+\.js:\d+)/g, '').trim() : e.message
} }
// 错误处理 // vm 模块将无法在渲染进程中使用,改用简单的沙箱来执行代码
let createSandbox = (code, sandbox, async = false) => {
if (!async) code = `return (${code})`
const sandFn = new Function('sandbox', `with(sandbox){${code}}`);
const proxy = new Proxy(sandbox, {
has(target, key) {
return true;
}
});
return sandFn(proxy);
}
window.evalCodeInSandbox = (code, userVars = {}) => {
let sandbox = getSandboxFuns()
let sandboxWithUV = Object.assign(userVars, sandbox)
try { try {
vm.run(cmd, path.join(__dirname, 'preload.js')); return createSandbox(code, sandboxWithUV);
} catch (error) {
throw liteErr(error)
}
}
let isWatchingError = false
window.runCodeInSandbox = (code, callback, userVars = {}) => {
let sandbox = getSandboxFuns()
sandbox.console = {
log: (...stdout) => {
console.log(stdout);
callback(parseStdout(stdout), null)
},
error: (...stderr) => {
callback(null, parseStdout(stderr))
}
}
let sandboxWithUV = Object.assign(userVars, sandbox)
try {
createSandbox(code, sandboxWithUV, true)
} catch (e) { } catch (e) {
console.log('Error: ', e) console.log('Error: ', e)
callback(null, liteErr(e)) callback(null, liteErr(e))
} }
// 自动捕捉错误
let cbUnhandledError = e => { let cbUnhandledError = e => {
removeAllListener() removeAllListener()
console.log('UnhandledError: ', e) console.log('UnhandledError: ', e)
@ -619,7 +614,7 @@ let httpServer
window.quickcommandHttpServer = () => { window.quickcommandHttpServer = () => {
let run = (cmd = '', port = 33442) => { let run = (cmd = '', port = 33442) => {
let httpResponse = (res, code, result) => { let httpResponse = (res, code, result) => {
// 因为无法判断 vm2 是否执行完毕,故只收受一次 console.log接收后就关闭连接 // 只收受一次 console.log接收后就关闭连接
if (res.finished) return if (res.finished) return
res.writeHead(code, { res.writeHead(code, {
'Content-Type': 'text/html' 'Content-Type': 'text/html'
@ -630,7 +625,7 @@ window.quickcommandHttpServer = () => {
let runUserCode = (res, cmd, userVars) => { let runUserCode = (res, cmd, userVars) => {
// 不需要返回输出的提前关闭连接 // 不需要返回输出的提前关闭连接
if (!cmd.includes('console.log')) httpResponse(res, 200) if (!cmd.includes('console.log')) httpResponse(res, 200)
window.runCodeInVm(cmd, (stdout, stderr) => { window.runCodeInSandbox(cmd, (stdout, stderr) => {
// 错误返回 500 // 错误返回 500
if (stderr) return httpResponse(res, 500, stderr) if (stderr) return httpResponse(res, 500, stderr)
return httpResponse(res, 200, stdout) return httpResponse(res, 200, stdout)

View File

@ -97,7 +97,7 @@ export default {
utools.outPlugin(); utools.outPlugin();
}, 500); }, 500);
if (currentCommand.program === "quickcommand") { if (currentCommand.program === "quickcommand") {
window.runCodeInVm(currentCommand.cmd, (stdout, stderr) => { window.runCodeInSandbox(currentCommand.cmd, (stdout, stderr) => {
if (stderr) { if (stderr) {
return quitBeforeShowResult return quitBeforeShowResult
? alert(stderr) ? alert(stderr)

View File

@ -10,21 +10,21 @@ let escapeItem = item => {
let handlingJsonVar = (jsonVar, name) => { let handlingJsonVar = (jsonVar, name) => {
try { try {
return escapeItem(window.VmEval(jsonVar.slice(2, -2), { return escapeItem(window.evalCodeInSandbox(jsonVar.slice(2, -2), {
[name]: quickcommand.enterData.payload [name]: quickcommand.enterData.payload
})) }))
} catch { } catch (e) {
return "" return utools.showNotification(e)
} }
} }
let handlingJsExpression = js => { let handlingJsExpression = js => {
try { try {
return window.VmEval(js.slice(5, -2), { return window.evalCodeInSandbox(js.slice(5, -2), {
utools: window.getuToolsLite(), utools: window.getuToolsLite(),
}) })
} catch { } catch (e) {
return "" return utools.showNotification(e)
} }
} }
@ -131,7 +131,7 @@ const specialVars = {
name: "js", name: "js",
label: "{{js:}}", label: "{{js:}}",
desc: "获取js表达式的值如{{js:utools.isMacOs()}}", desc: "获取js表达式的值如{{js:utools.isMacOs()}}",
tooltip: "注意必须为表达式而非语句类似Vue的文本插值。不支持异步/Node", tooltip: "注意必须为表达式而非语句类似Vue的文本插值",
type: "command", type: "command",
match: /{{js:(.*?)}}/mg, match: /{{js:(.*?)}}/mg,
repl: js => handlingJsExpression(js) repl: js => handlingJsExpression(js)

View File

@ -88,16 +88,3 @@ declare var console: {
log(message?: any): void, log(message?: any): void,
error(message?: any): void error(message?: any): void
} }
// process
declare var process: {
argv: any[];
title: string;
version: string;
versions: object;
arch: string;
platform: string;
env: object;
pid: number;
features: object
}