/** * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ import { IDisposable } from 'common/Types'; import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType } from 'common/parser/Types'; import { utf32ToString } from 'common/input/TextDecoder'; import { Params } from 'common/parser/Params'; import { PAYLOAD_LIMIT } from 'common/parser/Constants'; const EMPTY_HANDLERS: IDcsHandler[] = []; export class DcsParser implements IDcsParser { private _handlers: IHandlerCollection = Object.create(null); private _active: IDcsHandler[] = EMPTY_HANDLERS; private _ident: number = 0; private _handlerFb: DcsFallbackHandlerType = () => {}; public dispose(): void { this._handlers = Object.create(null); this._handlerFb = () => {}; this._active = EMPTY_HANDLERS; } public registerHandler(ident: number, handler: IDcsHandler): IDisposable { if (this._handlers[ident] === undefined) { this._handlers[ident] = []; } const handlerList = this._handlers[ident]; handlerList.push(handler); return { dispose: () => { const handlerIndex = handlerList.indexOf(handler); if (handlerIndex !== -1) { handlerList.splice(handlerIndex, 1); } } }; } public clearHandler(ident: number): void { if (this._handlers[ident]) delete this._handlers[ident]; } public setHandlerFallback(handler: DcsFallbackHandlerType): void { this._handlerFb = handler; } public reset(): void { if (this._active.length) { this.unhook(false); } this._active = EMPTY_HANDLERS; this._ident = 0; } public hook(ident: number, params: IParams): void { // always reset leftover handlers this.reset(); this._ident = ident; this._active = this._handlers[ident] || EMPTY_HANDLERS; if (!this._active.length) { this._handlerFb(this._ident, 'HOOK', params); } else { for (let j = this._active.length - 1; j >= 0; j--) { this._active[j].hook(params); } } } public put(data: Uint32Array, start: number, end: number): void { if (!this._active.length) { this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end)); } else { for (let j = this._active.length - 1; j >= 0; j--) { this._active[j].put(data, start, end); } } } public unhook(success: boolean): void { if (!this._active.length) { this._handlerFb(this._ident, 'UNHOOK', success); } else { let j = this._active.length - 1; for (; j >= 0; j--) { if (this._active[j].unhook(success)) { break; } } j--; // cleanup left over handlers for (; j >= 0; j--) { this._active[j].unhook(false); } } this._active = EMPTY_HANDLERS; this._ident = 0; } } // predefine empty params as [0] (ZDM) const EMPTY_PARAMS = new Params(); EMPTY_PARAMS.addParam(0); /** * Convenient class to create a DCS handler from a single callback function. * Note: The payload is currently limited to 50 MB (hardcoded). */ export class DcsHandler implements IDcsHandler { private _data = ''; private _params: IParams = EMPTY_PARAMS; private _hitLimit: boolean = false; constructor(private _handler: (data: string, params: IParams) => boolean) {} public hook(params: IParams): void { // since we need to preserve params until `unhook`, we have to clone it // (only borrowed from parser and spans multiple parser states) // perf optimization: // clone only, if we have non empty params, otherwise stick with default this._params = (params.length > 1 || params.params[0]) ? params.clone() : EMPTY_PARAMS; this._data = ''; this._hitLimit = false; } public put(data: Uint32Array, start: number, end: number): void { if (this._hitLimit) { return; } this._data += utf32ToString(data, start, end); if (this._data.length > PAYLOAD_LIMIT) { this._data = ''; this._hitLimit = true; } } public unhook(success: boolean): boolean { let ret = false; if (this._hitLimit) { ret = false; } else if (success) { ret = this._handler(this._data, this._params); } this._params = EMPTY_PARAMS; this._data = ''; this._hitLimit = false; return ret; } }