UNPKG

deno-vm

Version:

A VM module that provides a secure runtime environment via Deno.

466 lines (449 loc) 14.1 kB
import { encodeBase64, decodeBase64, } from 'https://deno.land/std/encoding/base64.ts'; import { Transferrable } from './MessageTarget.ts'; import { MessagePort, MessageChannel } from './MessageChannel.ts'; const HAS_CIRCULAR_REF_OR_TRANSFERRABLE = Symbol('hasCircularRef'); /** * Serializes the given value into a new object that is flat and contains no circular references. * * The returned object contains a root which is the entry point to the data structure and optionally * contains a refs property which is a flat map of references. * * If the refs property is defined, then the data structure was circular. * * @param value The value to serialize. * @param transferrable The transferrable list. */ export function serializeStructure( value: unknown, transferrable?: Transferrable[] ): Structure | StructureWithRefs { if ( (typeof value !== 'object' && typeof value !== 'bigint') || value === null ) { return { root: value, }; } else { let map = new Map<any, MapRef>(); const result = _serializeObject(value, map); if ((<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] === true) { let refs = {} as any; for (let [key, ref] of map) { refs[ref.id] = ref.obj; } return { root: result, refs: refs, }; } return { root: value, }; } } export function deserializeStructure( value: Structure | StructureWithRefs ): DeserializedStructure { if ('refs' in value) { let map = new Map<string, any>(); let transferred = [] as Transferrable[]; const result = _deserializeRef(value, value.root[0], map, transferred); return { data: result, transferred: transferred, }; } else { return { data: value.root, transferred: [], }; } } function _serializeObject(value: unknown, map: Map<any, MapRef>) { if (typeof value !== 'object' && typeof value !== 'bigint') { return value; } const ref = map.get(value); if (ref) { (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; return [ref.id]; } let id = '$' + map.size; if ( value instanceof Uint8Array || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array ) { let ref = { root: encodeBase64( new Uint8Array(value.buffer, value.byteOffset, value.byteLength) ), type: value.constructor.name, } as Ref; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj: ref, }); return [id]; } else if (value instanceof ArrayBuffer) { let ref = { root: encodeBase64(new Uint8Array(value)), type: value.constructor.name, } as Ref; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj: ref, }); return [id]; } else if (typeof value === 'bigint') { const root = value.toString(); const ref = { root, type: 'BigInt', } as const; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj: ref, }); return [id]; } else if (Array.isArray(value)) { let root = [] as any[]; let ref = { root, } as Ref; map.set(value, { id, obj: ref, }); for (let prop of value) { root.push(_serializeObject(prop, map)); } return [id]; } else if (value instanceof Date) { const obj = { root: value.toISOString(), type: 'Date', } as const; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); return [id]; } else if (value instanceof RegExp) { const obj = { root: { source: value.source, flags: value.flags, }, type: 'RegExp', } as const; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); return [id]; } else if (value instanceof Map) { let root = [] as any[]; let obj = { root, type: 'Map', } as Ref; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); for (let prop of value) { root.push(_serializeObject(prop, map)); } return [id]; } else if (value instanceof Set) { let root = [] as any[]; let obj = { root, type: 'Set', } as Ref; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); for (let prop of value) { root.push(_serializeObject(prop, map)); } return [id]; } else if (value instanceof Error) { let obj = { root: { name: value.name, message: value.message, stack: value.stack, }, type: 'Error', } as const; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); return [id]; } else if (value instanceof MessagePort) { if (!value.transferred) { throw new Error( 'Port must be transferred before serialization. Did you forget to add it to the transfer list?' ); } let obj = { root: { channel: value.channelID, }, type: 'MessagePort', } as const; (<any>map)[HAS_CIRCULAR_REF_OR_TRANSFERRABLE] = true; map.set(value, { id, obj, }); return [id]; } else if (value instanceof Object) { let root = {} as any; let ref = { root, } as Ref; map.set(value, { id, obj: ref, }); for (let prop in value) { if (Object.hasOwnProperty.call(value, prop)) { root[prop] = _serializeObject((<any>value)[prop], map); } } return [id]; } } function _deserializeRef( structure: StructureWithRefs, ref: string, map: Map<string, any>, transfered: Transferrable[] ): any { if (map.has(ref)) { return map.get(ref); } const refData = structure.refs[ref]; if (!refData) { throw new Error('Missing reference'); } if ('type' in refData && !!refData.type) { const types = [ 'ArrayBuffer', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array', ]; if (types.indexOf(refData.type) >= 0) { const bytes = new Uint8Array(decodeBase64(refData.root)); const final = refData.type == 'Uint8Array' ? bytes : refData.type === 'ArrayBuffer' ? bytes.buffer.slice( bytes.byteOffset, bytes.byteOffset + bytes.byteLength ) : refData.type === 'Int8Array' ? new Int8Array( bytes.buffer, bytes.byteOffset, bytes.byteLength / Int8Array.BYTES_PER_ELEMENT ) : refData.type == 'Int16Array' ? new Int16Array( bytes.buffer, bytes.byteOffset, bytes.byteLength / Int16Array.BYTES_PER_ELEMENT ) : refData.type == 'Int32Array' ? new Int32Array( bytes.buffer, bytes.byteOffset, bytes.byteLength / Int32Array.BYTES_PER_ELEMENT ) : refData.type == 'Uint16Array' ? new Uint16Array( bytes.buffer, bytes.byteOffset, bytes.byteLength / Uint16Array.BYTES_PER_ELEMENT ) : refData.type == 'Uint32Array' ? new Uint32Array( bytes.buffer, bytes.byteOffset, bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT ) : null; map.set(ref, final); return final; } else if (refData.type === 'BigInt') { const final = BigInt(refData.root); map.set(ref, final); return final; } else if (refData.type === 'Date') { const final = new Date(refData.root); map.set(ref, final); return final; } else if (refData.type === 'RegExp') { const final = new RegExp(refData.root.source, refData.root.flags); map.set(ref, final); return final; } else if (refData.type === 'Map') { let final = new Map(); map.set(ref, final); for (let value of refData.root) { const [key, val] = _deserializeRef( structure, value[0], map, transfered ); final.set(key, val); } return final; } else if (refData.type === 'Set') { let final = new Set(); map.set(ref, final); for (let value of refData.root) { const val = Array.isArray(value) ? _deserializeRef(structure, value[0], map, transfered) : value; final.add(val); } return final; } else if (refData.type === 'Error') { let proto = Error.prototype; if (refData.root.name === 'EvalError') { proto = EvalError.prototype; } else if (refData.root.name === 'RangeError') { proto = RangeError.prototype; } else if (refData.root.name === 'ReferenceError') { proto = ReferenceError.prototype; } else if (refData.root.name === 'SyntaxError') { proto = SyntaxError.prototype; } else if (refData.root.name === 'TypeError') { proto = TypeError.prototype; } else if (refData.root.name === 'URIError') { proto = URIError.prototype; } let final = Object.create(proto); if (typeof refData.root.message !== 'undefined') { Object.defineProperty(final, 'message', { value: refData.root.message, writable: true, enumerable: false, configurable: true, }); } if (typeof refData.root.stack !== 'undefined') { Object.defineProperty(final, 'stack', { value: refData.root.stack, writable: true, enumerable: false, configurable: true, }); } return final; } else if (refData.type === 'MessagePort') { let final = new MessageChannel(refData.root.channel); map.set(ref, final.port1); transfered.push(final.port2); return final.port1; } } else if (Array.isArray(refData.root)) { let arr = [] as any[]; map.set(ref, arr); for (let value of refData.root) { arr.push( Array.isArray(value) ? _deserializeRef(structure, value[0], map, transfered) : value ); } return arr; } else if (typeof refData.root === 'object') { let obj = {} as any; map.set(ref, obj); for (let prop in refData.root) { if (Object.hasOwnProperty.call(refData.root, prop)) { const value = refData.root[prop]; obj[prop] = Array.isArray(value) ? _deserializeRef(structure, value[0], map, transfered) : value; } } return obj; } map.set(ref, refData.root); return refData.root; } export interface Structure { root: any; channel?: number | string; } /** * Defines an interface for a structure that was deserialized. */ export interface DeserializedStructure { /** * The data in the structure. */ data: any; /** * The list of values that were transferred and require extra processing to be fully transferred. */ transferred: Transferrable[]; } export interface StructureWithRefs { root: any; channel?: number | string; refs: { [key: string]: Ref; }; } interface MapRef { id: string; obj: Ref; } export interface Ref { root: any; type?: | 'ArrayBuffer' | 'Uint8Array' | 'Uint16Array' | 'Uint32Array' | 'Int8Array' | 'Int16Array' | 'Int32Array' | 'BigInt' | 'Date' | 'RegExp' | 'Map' | 'Set' | 'Error' | 'MessagePort'; }