You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					346 lines
				
				11 KiB
			
		
		
			
		
	
	
					346 lines
				
				11 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | // Translate the old options to the new Resolver functionality.
 | ||
|  | 
 | ||
|  | const fs = require('fs'); | ||
|  | const nmod = require('module'); | ||
|  | const {EventEmitter} = require('events'); | ||
|  | const util = require('util'); | ||
|  | 
 | ||
|  | const { | ||
|  | 	Resolver, | ||
|  | 	DefaultResolver | ||
|  | } = require('./resolver'); | ||
|  | const {VMScript} = require('./script'); | ||
|  | const {VM} = require('./vm'); | ||
|  | const {VMError} = require('./bridge'); | ||
|  | const {DefaultFileSystem} = require('./filesystem'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Require wrapper to be able to annotate require with webpackIgnore. | ||
|  |  * | ||
|  |  * @private | ||
|  |  * @param {string} moduleName - Name of module to load. | ||
|  |  * @return {*} Module exports. | ||
|  |  */ | ||
|  | function defaultRequire(moduleName) { | ||
|  | 	// Set module.parser.javascript.commonjsMagicComments=true in your webpack config.
 | ||
|  | 	// eslint-disable-next-line global-require
 | ||
|  | 	return require(/* webpackIgnore: true */ moduleName); | ||
|  | } | ||
|  | 
 | ||
|  | // source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
 | ||
|  | function escapeRegExp(string) { | ||
|  | 	return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
 | ||
|  | } | ||
|  | 
 | ||
|  | function makeExternalMatcherRegex(obj) { | ||
|  | 	return escapeRegExp(obj).replace(/\\\\|\//g, '[\\\\/]') | ||
|  | 		.replace(/\\\*\\\*/g, '.*').replace(/\\\*/g, '[^\\\\/]*').replace(/\\\?/g, '[^\\\\/]'); | ||
|  | } | ||
|  | 
 | ||
|  | function makeExternalMatcher(obj) { | ||
|  | 	const regexString = makeExternalMatcherRegex(obj); | ||
|  | 	return new RegExp(`[\\\\/]node_modules[\\\\/]${regexString}(?:[\\\\/](?!(?:.*[\\\\/])?node_modules[\\\\/]).*)?$`); | ||
|  | } | ||
|  | 
 | ||
|  | class LegacyResolver extends DefaultResolver { | ||
|  | 
 | ||
|  | 	constructor(fileSystem, builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler, strict, externals, allowTransitive) { | ||
|  | 		super(fileSystem, builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler, strict); | ||
|  | 		this.externals = externals; | ||
|  | 		this.currMod = undefined; | ||
|  | 		this.trustedMods = new WeakMap(); | ||
|  | 		this.allowTransitive = allowTransitive; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	isPathAllowed(path) { | ||
|  | 		return this.isPathAllowedForModule(path, this.currMod); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	isPathAllowedForModule(path, mod) { | ||
|  | 		if (!super.isPathAllowed(path)) return false; | ||
|  | 		if (mod) { | ||
|  | 			if (mod.allowTransitive) return true; | ||
|  | 			if (path.startsWith(mod.path)) { | ||
|  | 				const rem = path.slice(mod.path.length); | ||
|  | 				if (!/(?:^|[\\\\/])node_modules(?:$|[\\\\/])/.test(rem)) return true; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return this.externals.some(regex => regex.test(path)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	registerModule(mod, filename, path, parent, direct) { | ||
|  | 		const trustedParent = this.trustedMods.get(parent); | ||
|  | 		this.trustedMods.set(mod, { | ||
|  | 			filename, | ||
|  | 			path, | ||
|  | 			paths: this.genLookupPaths(path), | ||
|  | 			allowTransitive: this.allowTransitive && | ||
|  | 				((direct && trustedParent && trustedParent.allowTransitive) || this.externals.some(regex => regex.test(filename))) | ||
|  | 		}); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	resolveFull(mod, x, options, ext, direct) { | ||
|  | 		this.currMod = undefined; | ||
|  | 		if (!direct) return super.resolveFull(mod, x, options, ext, false); | ||
|  | 		const trustedMod = this.trustedMods.get(mod); | ||
|  | 		if (!trustedMod || mod.path !== trustedMod.path) return super.resolveFull(mod, x, options, ext, false); | ||
|  | 		const paths = [...mod.paths]; | ||
|  | 		if (paths.length === trustedMod.length) { | ||
|  | 			for (let i = 0; i < paths.length; i++) { | ||
|  | 				if (paths[i] !== trustedMod.paths[i]) { | ||
|  | 					return super.resolveFull(mod, x, options, ext, false); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		const extCopy = Object.assign({__proto__: null}, ext); | ||
|  | 		try { | ||
|  | 			this.currMod = trustedMod; | ||
|  | 			return super.resolveFull(trustedMod, x, undefined, extCopy, true); | ||
|  | 		} finally { | ||
|  | 			this.currMod = undefined; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	checkAccess(mod, filename) { | ||
|  | 		const trustedMod = this.trustedMods.get(mod); | ||
|  | 		if ((!trustedMod || trustedMod.filename !== filename) && !this.isPathAllowedForModule(filename, undefined)) { | ||
|  | 			throw new VMError(`Module '${filename}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	loadJS(vm, mod, filename) { | ||
|  | 		filename = this.pathResolve(filename); | ||
|  | 		this.checkAccess(mod, filename); | ||
|  | 		if (this.pathContext(filename, 'js') === 'sandbox') { | ||
|  | 			const trustedMod = this.trustedMods.get(mod); | ||
|  | 			const script = this.readScript(filename); | ||
|  | 			vm.run(script, {filename, strict: true, module: mod, wrapper: 'none', dirname: trustedMod ? trustedMod.path : mod.path}); | ||
|  | 		} else { | ||
|  | 			const m = this.hostRequire(filename); | ||
|  | 			mod.exports = vm.readonly(m); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | function defaultBuiltinLoader(resolver, vm, id) { | ||
|  | 	const mod = resolver.hostRequire(id); | ||
|  | 	return vm.readonly(mod); | ||
|  | } | ||
|  | 
 | ||
|  | const eventsModules = new WeakMap(); | ||
|  | 
 | ||
|  | function defaultBuiltinLoaderEvents(resolver, vm, id) { | ||
|  | 	return eventsModules.get(vm); | ||
|  | } | ||
|  | 
 | ||
|  | let cacheBufferScript; | ||
|  | 
 | ||
|  | function defaultBuiltinLoaderBuffer(resolver, vm, id) { | ||
|  | 	if (!cacheBufferScript) { | ||
|  | 		cacheBufferScript = new VMScript('return buffer=>({Buffer: buffer});', {__proto__: null, filename: 'buffer.js'}); | ||
|  | 	} | ||
|  | 	const makeBuffer = vm.run(cacheBufferScript, {__proto__: null, strict: true, wrapper: 'none'}); | ||
|  | 	return makeBuffer(Buffer); | ||
|  | } | ||
|  | 
 | ||
|  | let cacheUtilScript; | ||
|  | 
 | ||
|  | function defaultBuiltinLoaderUtil(resolver, vm, id) { | ||
|  | 	if (!cacheUtilScript) { | ||
|  | 		cacheUtilScript = new VMScript(`return function inherits(ctor, superCtor) {
 | ||
|  | 			ctor.super_ = superCtor; | ||
|  | 			Object.setPrototypeOf(ctor.prototype, superCtor.prototype); | ||
|  | 		}`, {__proto__: null, filename: 'util.js'});
 | ||
|  | 	} | ||
|  | 	const inherits = vm.run(cacheUtilScript, {__proto__: null, strict: true, wrapper: 'none'}); | ||
|  | 	const copy = Object.assign({}, util); | ||
|  | 	copy.inherits = inherits; | ||
|  | 	return vm.readonly(copy); | ||
|  | } | ||
|  | 
 | ||
|  | const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))).filter(s=>!s.startsWith('internal/')); | ||
|  | 
 | ||
|  | let EventEmitterReferencingAsyncResourceClass = null; | ||
|  | if (EventEmitter.EventEmitterAsyncResource) { | ||
|  | 	// eslint-disable-next-line global-require
 | ||
|  | 	const {AsyncResource} = require('async_hooks'); | ||
|  | 	const kEventEmitter = Symbol('kEventEmitter'); | ||
|  | 	class EventEmitterReferencingAsyncResource extends AsyncResource { | ||
|  | 		constructor(ee, type, options) { | ||
|  | 			super(type, options); | ||
|  | 			this[kEventEmitter] = ee; | ||
|  | 		} | ||
|  | 		get eventEmitter() { | ||
|  | 			return this[kEventEmitter]; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	EventEmitterReferencingAsyncResourceClass = EventEmitterReferencingAsyncResource; | ||
|  | } | ||
|  | 
 | ||
|  | let cacheEventsScript; | ||
|  | 
 | ||
|  | const SPECIAL_MODULES = { | ||
|  | 	events(vm) { | ||
|  | 		if (!cacheEventsScript) { | ||
|  | 			const eventsSource = fs.readFileSync(`${__dirname}/events.js`, 'utf8'); | ||
|  | 			cacheEventsScript = new VMScript(`(function (fromhost) { const module = {}; module.exports={};{ ${eventsSource}
 | ||
|  | } return module.exports;})`, {filename: 'events.js'});
 | ||
|  | 		} | ||
|  | 		const closure = VM.prototype.run.call(vm, cacheEventsScript); | ||
|  | 		const eventsInstance = closure(vm.readonly({ | ||
|  | 			kErrorMonitor: EventEmitter.errorMonitor, | ||
|  | 			once: EventEmitter.once, | ||
|  | 			on: EventEmitter.on, | ||
|  | 			getEventListeners: EventEmitter.getEventListeners, | ||
|  | 			EventEmitterReferencingAsyncResource: EventEmitterReferencingAsyncResourceClass | ||
|  | 		})); | ||
|  | 		eventsModules.set(vm, eventsInstance); | ||
|  | 		vm._addProtoMapping(EventEmitter.prototype, eventsInstance.EventEmitter.prototype); | ||
|  | 		return defaultBuiltinLoaderEvents; | ||
|  | 	}, | ||
|  | 	buffer(vm) { | ||
|  | 		return defaultBuiltinLoaderBuffer; | ||
|  | 	}, | ||
|  | 	util(vm) { | ||
|  | 		return defaultBuiltinLoaderUtil; | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | function addDefaultBuiltin(builtins, key, vm) { | ||
|  | 	if (builtins[key]) return; | ||
|  | 	const special = SPECIAL_MODULES[key]; | ||
|  | 	builtins[key] = special ? special(vm) : defaultBuiltinLoader; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | function genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override) { | ||
|  | 	const builtins = {__proto__: null}; | ||
|  | 	if (mockOpt) { | ||
|  | 		const keys = Object.getOwnPropertyNames(mockOpt); | ||
|  | 		for (let i = 0; i < keys.length; i++) { | ||
|  | 			const key = keys[i]; | ||
|  | 			builtins[key] = (resolver, tvm, id) => tvm.readonly(mockOpt[key]); | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if (override) { | ||
|  | 		const keys = Object.getOwnPropertyNames(override); | ||
|  | 		for (let i = 0; i < keys.length; i++) { | ||
|  | 			const key = keys[i]; | ||
|  | 			builtins[key] = override[key]; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if (Array.isArray(builtinOpt)) { | ||
|  | 		const def = builtinOpt.indexOf('*') >= 0; | ||
|  | 		if (def) { | ||
|  | 			for (let i = 0; i < BUILTIN_MODULES.length; i++) { | ||
|  | 				const name = BUILTIN_MODULES[i]; | ||
|  | 				if (builtinOpt.indexOf(`-${name}`) === -1) { | ||
|  | 					addDefaultBuiltin(builtins, name, vm); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} else { | ||
|  | 			for (let i = 0; i < BUILTIN_MODULES.length; i++) { | ||
|  | 				const name = BUILTIN_MODULES[i]; | ||
|  | 				if (builtinOpt.indexOf(name) !== -1) { | ||
|  | 					addDefaultBuiltin(builtins, name, vm); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} else if (builtinOpt) { | ||
|  | 		for (let i = 0; i < BUILTIN_MODULES.length; i++) { | ||
|  | 			const name = BUILTIN_MODULES[i]; | ||
|  | 			if (builtinOpt[name]) { | ||
|  | 				addDefaultBuiltin(builtins, name, vm); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return builtins; | ||
|  | } | ||
|  | 
 | ||
|  | function defaultCustomResolver() { | ||
|  | 	return undefined; | ||
|  | } | ||
|  | 
 | ||
|  | const DEFAULT_FS = new DefaultFileSystem(); | ||
|  | 
 | ||
|  | const DENY_RESOLVER = new Resolver(DEFAULT_FS, {__proto__: null}, [], id => { | ||
|  | 	throw new VMError(`Access denied to require '${id}'`, 'EDENIED'); | ||
|  | }); | ||
|  | 
 | ||
|  | function resolverFromOptions(vm, options, override, compiler) { | ||
|  | 	if (!options) { | ||
|  | 		if (!override) return DENY_RESOLVER; | ||
|  | 		const builtins = genBuiltinsFromOptions(vm, undefined, undefined, override); | ||
|  | 		return new Resolver(DEFAULT_FS, builtins, [], defaultRequire); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	const { | ||
|  | 		builtin: builtinOpt, | ||
|  | 		mock: mockOpt, | ||
|  | 		external: externalOpt, | ||
|  | 		root: rootPaths, | ||
|  | 		resolve: customResolver, | ||
|  | 		customRequire: hostRequire = defaultRequire, | ||
|  | 		context = 'host', | ||
|  | 		strict = true, | ||
|  | 		fs: fsOpt = DEFAULT_FS, | ||
|  | 	} = options; | ||
|  | 
 | ||
|  | 	const builtins = genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override); | ||
|  | 
 | ||
|  | 	if (!externalOpt) return new Resolver(fsOpt, builtins, [], hostRequire); | ||
|  | 
 | ||
|  | 	let checkPath; | ||
|  | 	if (rootPaths) { | ||
|  | 		const checkedRootPaths = (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f => fsOpt.resolve(f)); | ||
|  | 		checkPath = (filename) => { | ||
|  | 			return checkedRootPaths.some(path => { | ||
|  | 				if (!filename.startsWith(path)) return false; | ||
|  | 				const len = path.length; | ||
|  | 				if (filename.length === len || (len > 0 && fsOpt.isSeparator(path[len-1]))) return true; | ||
|  | 				return fsOpt.isSeparator(filename[len]); | ||
|  | 			}); | ||
|  | 		}; | ||
|  | 	} else { | ||
|  | 		checkPath = () => true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	let newCustomResolver = defaultCustomResolver; | ||
|  | 	let externals = undefined; | ||
|  | 	let external = undefined; | ||
|  | 	if (customResolver) { | ||
|  | 		let externalCache; | ||
|  | 		newCustomResolver = (resolver, x, path, extList) => { | ||
|  | 			if (external && !(resolver.pathIsAbsolute(x) || resolver.pathIsRelative(x))) { | ||
|  | 				if (!externalCache) { | ||
|  | 					externalCache = external.map(ext => new RegExp(makeExternalMatcherRegex(ext))); | ||
|  | 				} | ||
|  | 				if (!externalCache.some(regex => regex.test(x))) return undefined; | ||
|  | 			} | ||
|  | 			const resolved = customResolver(x, path); | ||
|  | 			if (!resolved) return undefined; | ||
|  | 			if (externals) externals.push(new RegExp('^' + escapeRegExp(resolved))); | ||
|  | 			return resolver.loadAsFileOrDirecotry(resolved, extList); | ||
|  | 		}; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (typeof externalOpt !== 'object') { | ||
|  | 		return new DefaultResolver(fsOpt, builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler, strict); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	let transitive = false; | ||
|  | 	if (Array.isArray(externalOpt)) { | ||
|  | 		external = externalOpt; | ||
|  | 	} else { | ||
|  | 		external = externalOpt.modules; | ||
|  | 		transitive = context === 'sandbox' && externalOpt.transitive; | ||
|  | 	} | ||
|  | 	externals = external.map(makeExternalMatcher); | ||
|  | 	return new LegacyResolver(fsOpt, builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler, strict, externals, transitive); | ||
|  | } | ||
|  | 
 | ||
|  | exports.resolverFromOptions = resolverFromOptions; |