/**
 * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
 * Copyright (c) 2016, Daniel Imms (MIT License).
 * Copyright (c) 2018, Microsoft Corporation (MIT License).
 */ import * as fs from 'fs' ; import * as os from 'os' ; import * as path from 'path' ; import { Socket } from 'net' ; import { ArgvOrCommandLine } from './types' ; import { fork } from 'child_process' ; import { ConoutConnection } from './windowsConoutConnection' ; let conptyNative ; let winptyNative ; /**
 * The amount of time to wait for additional data after the conpty shell process has exited before
 * shutting down the socket. The timer will be reset if a new data event comes in after the timer
 * has started.
 */ const FLUSH_DATA_INTERVAL = 1000 ; /**
 * This agent sits between the WindowsTerminal class and provides a common interface for both conpty
 * and winpty.
 */ export class WindowsPtyAgent { private _inSocket : Socket ; private _outSocket : Socket ; private _pid : number ; private _innerPid : number ; private _innerPidHandle : number ; private _closeTimeout : NodeJS . Timer ; private _exitCode : number | undefined ; private _conoutSocketWorker : ConoutConnection ; private _fd : any ; private _pty : number ; private _ptyNative : IConptyNative | IWinptyNative ; public get inSocket ( ) : Socket { return this . _inSocket ; } public get outSocket ( ) : Socket { return this . _outSocket ; } public get fd ( ) : any { return this . _fd ; } public get innerPid ( ) : number { return this . _innerPid ; } public get pty ( ) : number { return this . _pty ; } constructor ( file , args , env , cwd , cols , rows , debug , private _useConpty , conptyInheritCursor = false ) { if ( this . _useConpty === undefined || this . _useConpty === true ) { this . _useConpty = this . _getWindowsBuildNumber ( ) >= 18309 ; } if ( this . _useConpty ) { if ( ! conptyNative ) { try { conptyNative = require ( '../build/Release/conpty.node' ) ; } catch ( outerError ) { try { conptyNative = require ( '../build/Debug/conpty.node' ) ; } catch ( innerError ) { console . error ( 'innerError' , innerError ) ; throw outerError ; } } } } else { if ( ! winptyNative ) { try { winptyNative = require ( '../build/Release/pty.node' ) ; } catch ( outerError ) { try { winptyNative = require ( '../build/Debug/pty.node' ) ; } catch ( innerError ) { console . error ( 'innerError' , innerError ) ; throw outerError ; } } } } this . _ptyNative = this . _useConpty ? conptyNative : winptyNative ; cwd = path . resolve ( cwd ) ; const commandLine = argsToCommandLine ( file , args ) ; let term ; if ( this . _useConpty ) { term = ( this . _ptyNative as IConptyNative ) . startProcess ( file , cols , rows , debug , this . _generatePipeName ( ) , conptyInheritCursor ) ; } else { term = ( this . _ptyNative as IWinptyNative ) . startProcess ( file , commandLine , env , cwd , cols , rows , debug ) ; this . _pid = ( term as IWinptyProcess ) . pid ; this . _innerPid = ( term as IWinptyProcess ) . innerPid ; this . _innerPidHandle = ( term as IWinptyProcess ) . innerPidHandle ; } this . _fd = term . fd ; this . _pty = term . pty ; this . _outSocket = new Socket ( ) ; this . _outSocket . setEncoding ( 'utf8' ) ; this . _conoutSocketWorker = new ConoutConnection ( term . conout ) ; this . _conoutSocketWorker . onReady ( ( ) => { this . _conoutSocketWorker . connectSocket ( this . _outSocket ) ; } ) ; this . _outSocket . on ( 'connect' , ( ) => { this . _outSocket . emit ( 'ready_datapipe' ) ; } ) ; const inSocketFD = fs . openSync ( term . conin , 'w' ) ; this . _inSocket = new Socket ( { fd : inSocketFD , readable : false , writable : true } ) ; this . _inSocket . setEncoding ( 'utf8' ) ; if ( this . _useConpty ) { const connect = ( this . _ptyNative as IConptyNative ) . connect ( this . _pty , commandLine , cwd , env , c => this . _$onProcessExit ( c ) ) ; this . _innerPid = connect . pid ; } } public resize ( cols , rows ) { if ( this . _useConpty ) { if ( this . _exitCode !== undefined ) { throw new Error ( 'Cannot resize a pty that has already exited' ) ; } this . _ptyNative . resize ( this . _pty , cols , rows ) ; return ; } this . _ptyNative . resize ( this . _pid , cols , rows ) ; } public kill ( ) { this . _inSocket . readable = false ; this . _outSocket . readable = false ; if ( this . _useConpty ) { this . _getConsoleProcessList ( ) . then ( consoleProcessList => { consoleProcessList . forEach ( ( pid ) => { try { process . kill ( pid ) ; } catch ( e ) { } } ) ; ( this . _ptyNative as IConptyNative ) . kill ( this . _pty ) ; } ) ; } else { ( this . _ptyNative as IWinptyNative ) . kill ( this . _pid , this . _innerPidHandle ) ; const processList = ( this . _ptyNative as IWinptyNative ) . getProcessList ( this . _pid ) ; processList . forEach ( pid => { try { process . kill ( pid ) ; } catch ( e ) { } } ) ; } this . _conoutSocketWorker . dispose ( ) ; } private _getConsoleProcessList ( ) { return new Promise < number [ ] > ( resolve => { const agent = fork ( path . join ( __dirname , 'conpty_console_list_agent' ) , [ this . _innerPid . toString ( ) ] ) ; agent . on ( 'message' , message => { clearTimeout ( timeout ) ; resolve ( message . consoleProcessList ) ; } ) ; const timeout = setTimeout ( ( ) => { agent . kill ( ) ; resolve ( [ this . _innerPid ] ) ; } , 5000 ) ; } ) ; } public get exitCode ( ) : number { if ( this . _useConpty ) { return this . _exitCode ; } return ( this . _ptyNative as IWinptyNative ) . getExitCode ( this . _innerPidHandle ) ; } private _getWindowsBuildNumber ( ) { const osVersion = ( /(\d+)\.(\d+)\.(\d+)/g ) . exec ( os . release ( ) ) ; let buildNumber = 0 ; if ( osVersion && osVersion . length === 4 ) { buildNumber = parseInt ( osVersion [ 3 ] ) ; } return buildNumber ; } private _generatePipeName ( ) { return `conpty-${ Math . random ( ) * 10000000 }` ; } /**
     * Triggered from the native side when a contpy process exits.
     */ private _$onProcessExit ( exitCode ) { this . _exitCode = exitCode ; this . _flushDataAndCleanUp ( ) ; this . _outSocket . on ( 'data' , ( ) => this . _flushDataAndCleanUp ( ) ) ; } private _flushDataAndCleanUp ( ) { if ( this . _closeTimeout ) { clearTimeout ( this . _closeTimeout ) ; } this . _closeTimeout = setTimeout ( ( ) => this . _cleanUpProcess ( ) , FLUSH_DATA_INTERVAL ) ; } private _cleanUpProcess ( ) { this . _inSocket . readable = false ; this . _outSocket . readable = false ; this . _outSocket . destroy ( ) ; } } export function argsToCommandLine ( file , args ) { if ( isCommandLine ( args ) ) { if ( args . length === 0 ) { return file ; } return `${ argsToCommandLine ( file , [ ] ) } ${ args }` ; } const argv = [ file ] ; Array . prototype . push . apply ( argv , args ) ; let result = '' ; for ( let argIndex = 0 ; argIndex < argv . length ; argIndex ++ ) { if ( argIndex > 0 ) { result += ' ' ; } const arg = argv [ argIndex ] ; const hasLopsidedEnclosingQuote = xOr ( ( arg [ 0 ] !== '"' ) , ( arg [ arg . length - 1 ] !== '"' ) ) ; const hasNoEnclosingQuotes = ( ( arg [ 0 ] !== '"' ) && ( arg [ arg . length - 1 ] !== '"' ) ) ; const quote = arg === '' || ( arg . indexOf ( ' ' ) !== - 1 || arg . indexOf ( '\t' ) !== - 1 ) && ( ( arg . length > 1 ) && ( hasLopsidedEnclosingQuote || hasNoEnclosingQuotes ) ) ; if ( quote ) { result += '\"' ; } let bsCount = 0 ; for ( let i = 0 ; i < arg . length ; i ++ ) { const p = arg [ i ] ; if ( p === '\\' ) { bsCount ++ ; } else if ( p === '"' ) { result += repeatText ( '\\' , bsCount * 2 + 1 ) ; result += '"' ; bsCount = 0 ; } else { result += repeatText ( '\\' , bsCount ) ; bsCount = 0 ; result += p ; } } if ( quote ) { result += repeatText ( '\\' , bsCount * 2 ) ; result += '\"' ; } else { result += repeatText ( '\\' , bsCount ) ; } } return result ; } function isCommandLine ( args ) { return typeof args === 'string' ; } function repeatText ( text , count ) { let result = '' ; for ( let i = 0 ; i < count ; i ++ ) { result += text ; } return result ; } function xOr ( arg1 , arg2 ) { return ( ( arg1 && ! arg2 ) || ( ! arg1 && arg2 ) ) ; }