import * as net from 'net' ; import * as xml2js from 'xml2js' ; import { Buffer } from 'buffer' ; import { EventEmitter2 } from 'eventemitter2' ; import { Event } from './Event' ; export enum HeaderNames { ContentLength = 'Content-Length' , ContentType = 'Content-Type' , ReplyText = 'Reply-Text' , EventName = 'Event-Name' , EventSubclass = 'Event-Subclass' , JobUuid = 'Job-UUID' } export enum ParserEvent { Error = 'error' , Event = 'esl::event' } export type IHeadersMap = Partial < { [ key ] : string ; } > ; export class Parser extends EventEmitter2 { buffer : Buffer = Buffer . alloc ( 0 ) ; private _bodyLen = 0 ; private _encoding : BufferEncoding = 'utf8' ; private _headers : IHeadersMap = { } ; constructor ( public socket , encoding = 'utf8' ) { super ( { wildcard : true , delimiter : '::' , maxListeners : 25 , } ) ; this . _encoding = encoding ; socket . on ( 'data' , this . _onData . bind ( this ) ) ; socket . on ( 'end' , this . _onEnd . bind ( this ) ) ; } static parseHeaderText ( text ) { const lines = text . split ( '\n' ) ; const headers = { } ; for ( let i = 0 ; i < lines . length ; ++ i ) { const line = lines [ i ] ; if ( ! line ) continue ; const data = lines [ i ] . split ( ': ' ) ; const key = data . shift ( ) ; const value = decodeURIComponent ( data . join ( ': ' ) ) ; if ( ! key ) continue ; if ( key === HeaderNames . ContentLength ) headers [ key ] = parseInt ( value , 10 ) ; else headers [ key ] = value ; } return headers ; } static parsePlainBody ( text ) { const headerEnd = text . indexOf ( '\n\n' ) ; const headers = Parser . parseHeaderText ( text . substring ( 0 , headerEnd ) ) ; const contentLengthHeader = headers [ HeaderNames . ContentLength ] ; let error = null ; if ( contentLengthHeader ) { const len = parseInt ( contentLengthHeader , 10 ) ; const start = headerEnd + 2 ; const end = start + len ; if ( end > text . length ) error = new Error ( 'Invalid content length for plain body.' ) ; else headers . _body = text . substring ( start , end ) ; } return { error , headers } ; } static parseXmlBody ( xmlText ) { const parser = new xml2js . Parser ( { explicitArray : false , explicitRoot : false } ) ; let error = null ; let headers = { } ; parser . parseString ( xmlText , ( err , data ) => { error = err ; headers = data . headers ; if ( data . headers [ HeaderNames . ContentLength ] ) headers . _body = data . body ; } ) ; return { error , headers } ; } private _onData ( data ) { this . buffer = Buffer . concat ( [ this . buffer , data ] , this . buffer . length + data . length ) ; if ( this . _bodyLen > 0 ) this . _parseBody ( ) ; else this . _parseHeaders ( ) ; } private _onEnd ( ) { } private _indexOfHeaderEnd ( ) { for ( let i = 0 , len = this . buffer . length - 1 ; i < len ; ++ i ) { if ( this . buffer [ i ] === 0x0a && this . buffer [ i + 1 ] === 0x0a ) { return i ; } } return - 1 ; } private _parseHeaders ( ) { const headEnd = this . _indexOfHeaderEnd ( ) ; if ( headEnd === - 1 ) return ; const headText = this . buffer . toString ( this . _encoding , 0 , headEnd ) ; this . buffer = this . buffer . slice ( headEnd + 2 ) ; this . _headers = Parser . parseHeaderText ( headText ) ; const contentLengthHeader = this . _headers [ HeaderNames . ContentLength ] ; if ( contentLengthHeader ) { this . _bodyLen = parseInt ( contentLengthHeader , 10 ) ; if ( this . buffer . length ) this . _parseBody ( ) ; } else { this . _parseEvent ( '' ) ; if ( this . buffer . length ) this . _parseHeaders ( ) ; } } private _parseBody ( ) { if ( this . buffer . length < this . _bodyLen ) return ; const body = this . buffer . toString ( this . _encoding , 0 , this . _bodyLen ) ; this . buffer = this . buffer . slice ( this . _bodyLen ) ; this . _bodyLen = 0 ; this . _parseEvent ( body ) ; this . _parseHeaders ( ) ; } private _parseEvent ( body ) { let data = null ; switch ( this . _headers [ HeaderNames . ContentType ] ) { case 'text/event-json' : { try { data = JSON . parse ( body ) ; } catch ( e ) { this . emit ( ParserEvent . Error , e ) ; return ; } break ; } case 'text/event-plain' : { const { error , headers } = Parser . parsePlainBody ( body ) ; if ( error ) this . emit ( ParserEvent . Error , error ) ; data = headers ; break ; } case 'text/event-xml' : { const { error , headers } = Parser . parseXmlBody ( body ) ; if ( error ) this . emit ( ParserEvent . Error , error ) ; data = headers ; break ; } } let event ; if ( data ) event = new Event ( data ) ; else event = new Event ( this . _headers , body ) ; const reply = event . getHeader ( HeaderNames . ReplyText ) ; if ( reply ) { if ( reply . indexOf ( '-ERR' ) === 0 ) event . addHeader ( 'Modesl-Reply-ERR' , reply . substring ( 5 ) ) ; else if ( reply . indexOf ( '+OK' ) === 0 ) event . addHeader ( 'Modesl-Reply-OK' , reply . substring ( 4 ) ) ; } this . emit ( ParserEvent . Event , event , this . _headers , body ) ; } }