shadow-function
Version:
ioing lib - shadow Function, worker Function
230 lines (204 loc) • 7.58 kB
text/typescript
'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
}