UNPKG

shadow-function

Version:

ioing lib - shadow Function, worker Function

230 lines (204 loc) 7.58 kB
'use strict' import { getObjectType } from '../objectType/index' import { Sandbox } from '../sandbox/index' import allowProtoProperties from './allowProtoProperties.config' // ShadowFunction class ShadowFunction { public sandbox = new Sandbox(false, this['allow properties'] || ['*']) public shadowWindow = this.sandbox.shadowWindow as ShadowWindow public ShadowFunction = this.shadowWindow.Function as FunctionConstructor private shadowHasOwnProperty = this.shadowWindow.Object.hasOwnProperty private ShadowObject = this.shadowWindow.Object private ShadowProxy = this.shadowWindow.Proxy private shadowFunction!: Function private allowProtoProperties = allowProtoProperties public run: Function = () => null public log (e: object): string | undefined | void { return console.log('Event Log:', e) } private tracker = (e: { origin: object, name: string | number | symbol, action: string, value?: any}) => { if (typeof (e.name) === 'symbol' || !isNaN(Number(e.name))) return 'no' switch (e.name) { case 'length': case '__proto__': break default: return this.log(e) } return 'no' } constructor () { this.freezeShadowProto(this.shadowWindow) } static debugger (isee: string, model = { 'no-Proxy': false }) { let msg = 'I know the danger!' if (isee === msg) { this.prototype['open debugger'] = true if (model['no-Proxy']) { this.prototype['open debugger: no-Proxy'] = true } if (Sandbox.prototype['unsafe-context'] !== true) { console.warn(`%c ShadowFunction: Opening the 'debugger' will result in a risk of losing security!`, 'font-size: 32px; color: red') } } else { console.log(`ShadowFunction: Opening 'debugger' input '${msg}'`) } } static assignContextProperties (names = ['*']) { this.prototype['allow properties'] = names } public setAllowProtoProperties (allowProperties: object | Array<string>) { Object.assign(this.allowProtoProperties, allowProperties) return this.preShadowFunction.bind(this) } private freezeShadowProto (object: Object) { const Object = this.ShadowObject const propNames = Object.getOwnPropertyNames(object) // Freeze properties before freezing self for (let name of propNames) { let value = object[name] if (!value) break if (!(Object.getOwnPropertyDescriptor(object, name) || {}).writable) break typeof value === 'object' && this.freezeShadowProto(value) } if (!Object.isFrozen(object['prototype'])) Object.freeze(object['prototype']) } private getAllowProperties (object: object) { return this.getAllowProtoProperties(object).concat(Object.getOwnPropertyNames(object)) } private getAllowProtoProperties (object: object) { let propertyNames = this.allowProtoProperties[getObjectType(object)] || [] if (object['__proto__']) return propertyNames.concat(this.getAllowProtoProperties(object['__proto__'] as object)) return propertyNames } private isElementObject (value: any): boolean { if (/HTML(\w+)?Element/.exec(getObjectType(value))) return true return false } private isFixedObject (value: any): boolean { if (this.isElementObject(value)) return false return typeof value === 'object' } private safeGetter (origin: object, target: object, name: string | number | symbol) { if (this['open debugger'] && name === '...') { console.warn(`ShadowFunction: Unsafe return in debug mode!`) return origin } if (this.getAllowProperties(origin).indexOf(name as string) === -1) { this.tracker({ origin, name, action: 'read' }) return target[name] } if (origin[name] && !this.isFixedObject(origin[name]) && origin[name].hasOwnProperty === this.shadowHasOwnProperty) { return origin[name] } return this.proxy(origin[name], origin) } private safeSetter (origin: object, target: object, name: string | number | symbol, value: any): boolean { target[name] = this.proxy(value, target) if (this.tracker({ origin, name, action: 'write', value }) === `allow set ${name as string}`) origin[name] = target[name] return true } private puppet (origin: object) { const Proxy = this.ShadowProxy const propNames = Object.getOwnPropertyNames(origin) const puppet = new this.ShadowObject() propNames.map((name) => { if ((Object.getOwnPropertyDescriptor(puppet, name) || { writable: true }).writable) puppet[name] = '...' }) return new Proxy(puppet, { get: (target: object, name: string | number | symbol) => { return this.safeGetter(origin, target, name) }, set: (target: any, name: string | number | symbol, value: any) => { return this.safeSetter(origin, target, name, value) } }) } private proxy (target: object, self: object | null = null) { if (this['open debugger: no-Proxy'] || !target) return target switch (typeof(target)) { case 'string': case 'number': case 'boolean': case 'undefined': return target case 'function': return this.safeReturnFunction(target as Function, self) case 'object': return this.puppet(target) } return } private safeReturnFunction (fn: Function, origin: object | null) { const shadowFunction = new this.ShadowFunction('fn', 'origin', 'proxy', ` return (function () { return function () { return proxy(fn.apply(origin, proxy(arguments))) } })() `)(fn, origin, this.proxy.bind(this)) const propNames = Object.getOwnPropertyNames(fn) for (let name of propNames) { if ((Object.getOwnPropertyDescriptor(fn, name) || { writable: true }).writable) { if (name === 'prototype') continue shadowFunction[name] = this.proxy({ 0: fn[name] })[0] } } return shadowFunction } public preShadowFunction (scriptStr: string) { this.shadowFunction = new this.ShadowFunction(` (function () { with (arguments[0].window || {}) { with (arguments[0].context || {}) { (function () {` + scriptStr + `}).bind(Object.assign(arguments[0].window || {}, arguments[0].context || {}))() } } })(this)`) return this.runShadowFunction.bind(this) } public runShadowFunction (origin: object) { const target = this.proxy(origin) const shadowFunction = this.shadowFunction shadowFunction.apply(this.ShadowObject.assign(new this.ShadowObject(), { window: this.shadowWindow, context: target })) return { run: this.preShadowFunction.bind(this), proxy: this.proxy.bind(this), sandbox: this.sandbox } } } const shadowFunction = (code: object | string, setting?: object, log?: (e: object) => void): void => { const sfnc = new ShadowFunction() if (log) sfnc.log = log if (setting) { sfnc.run = sfnc.setAllowProtoProperties(setting)(code as string) } else { switch (typeof (code)) { case 'object': sfnc.run = sfnc.setAllowProtoProperties(code as object) break case 'string': sfnc.run = sfnc.preShadowFunction(code as string) break default: throw new Error('ShadowFunction: Uncaught SyntaxError: Unexpected identifier.') } } return sfnc.run as unknown as void } shadowFunction.debugger = ShadowFunction.debugger.bind(ShadowFunction) export { shadowFunction as ShadowFunction }