/**
 * This file is part of the vscode-remote-workspace distribution.
 * Copyright (c) Marcel Joachim Kloubert.
 *
 * vscode-remote-workspace is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, version 3.
 *
 * vscode-remote-workspace is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */ import * as _ from 'lodash' ; import * as FSExtra from 'fs-extra' ; import * as OS from 'os' ; import * as Path from 'path' ; const SFTP = require ( 'ssh2-sftp-client' ) ; import * as vscode from 'vscode' ; import * as vscode_helpers from 'vscode-helpers' ; import * as vscrw from '../extension' ; import * as vscrw_fs from '../fs' ; type FileModeMapper = { [ mode ] : string | string [ ] ; } ; interface SFTPConnection { cache : SFTPConnectionCache ; changeMode : ( ft : vscode . FileType , u : vscode . Uri , m ? : number ) => PromiseLike < boolean > ; client : any ; followSymLinks : boolean ; keepMode : boolean ; noop : string ; noQueue : boolean ; queue : any ; } interface SFTPConnectionCache { stats : any ; } interface SFTPFileRights { user : string ; group : string ; other : string ; } interface SFTPFileStat extends vscode . FileStat { __vscrw_fileinfo : any ; } type STPModeValueOrPath = string | number | false ; /**
 * SFTP file system.
 */ export class SFTPFileSystem extends vscrw_fs . FileSystemBase { private readonly _CONN_CACHE : any = { } ; private readonly _EXECUTE_REMOTE_COMMAND_LISTENER : vscrw . EventListener ; /**
     * Initializes a new instance of that class.
     */ public constructor ( ) { super ( ) ; this . _EXECUTE_REMOTE_COMMAND_LISTENER = ( execArgs ) => { execArgs . increaseExecutionCounter ( ) ; ( async ( ) => { try { if ( SFTPFileSystem . scheme === vscode_helpers . normalizeString ( execArgs . uri . scheme ) ) { const RESPONSE = await this . executeRemoteCommand ( execArgs ) ; if ( execArgs . callback ) { execArgs . callback ( null , { stdOut : RESPONSE , } ) ; } } } catch ( e ) { if ( execArgs . callback ) { execArgs . callback ( e ) ; } else { throw e ; } } } ) ( ) . then ( ( ) => { } , ( err ) => { vscrw . showError ( err ) ; } ) ; } ; vscode_helpers . EVENTS . on ( vscrw . EVENT_EXECUTE_REMOTE_COMMAND , this . _EXECUTE_REMOTE_COMMAND_LISTENER ) ; } inheritdoc public async createDirectory ( uri ) { await this . forConnection ( uri , async ( conn ) => { const STAT = await this . tryGetStat ( uri , conn ) ; if ( false !== STAT ) { if ( vscode . FileType . Directory === STAT . type ) { this . throwWithoutAuthority ( uri , u => vscode . FileSystemError . FileExists ( u ) ) ; } else { this . throwWithoutAuthority ( uri , u => vscode . FileSystemError . NoPermissions ( u ) ) ; } } await conn . client . mkdir ( vscrw . normalizePath ( uri . path ) , true ) ; await conn . changeMode ( vscode . FileType . Directory , uri ) ; } ) ; } inheritdoc public async delete ( uri , options ) { await this . forConnection ( uri , async ( conn ) => { const STAT = await this . statInner ( uri , conn ) ; if ( vscode . FileType . Directory === STAT . type ) { await conn . client . rmdir ( vscrw . normalizePath ( uri . path ) , options . recursive ) ; } else { await conn . client . delete ( vscrw . normalizePath ( uri . path ) ) ; } } ) ; } private async executeRemoteCommand ( execArgs ) { const CONN = await this . openConnection ( execArgs . uri , true ) ; try { return await this . forConnection ( execArgs . uri , ( conn ) => { return execServerCommand ( conn . client , `cd "${ execArgs . uri . path }" && ${ execArgs . command }` ) ; } , CONN ) ; } finally { await tryCloseConnection ( CONN ) ; } } private async forConnection < TResult = any > ( uri , action , existingConn ? ) { const DO_IT = async ( connectionToUse ) => { if ( action ) { return await Promise . resolve ( action ( connectionToUse ) ) ; } } ; const USE_EXISTING_CONN = ! _ . isNil ( existingConn ) ; const CONNECTION_TO_USE = USE_EXISTING_CONN ? existingConn : await this . openConnection ( uri ) ; try { if ( CONNECTION_TO_USE . noQueue || USE_EXISTING_CONN ) { return await DO_IT ( CONNECTION_TO_USE ) ; } return await CONNECTION_TO_USE . queue . add ( async ( ) => { return DO_IT ( CONNECTION_TO_USE ) ; } ) ; } catch ( e ) { this . logger . trace ( e , 'fs.sftp.SFTPFileSystem.forConnection()' ) ; throw e ; } } inheritdoc protected onDispose ( ) { for ( const CACHE_KEY of Object . keys ( this . _CONN_CACHE ) ) { this . tryCloseAndDeleteConnectionSync ( CACHE_KEY ) ; } vscode_helpers . tryRemoveListener ( vscode_helpers . EVENTS , vscrw . EVENT_EXECUTE_REMOTE_COMMAND , this . _EXECUTE_REMOTE_COMMAND_LISTENER ) ; } private async openConnection ( uri , noCache ? ) { noCache = vscode_helpers . toBooleanSafe ( noCache ) ; const CACHE_KEY = vscrw . getConnectionCacheKey ( uri ) ; const PARAMS = vscrw . getUriParams ( uri ) ; let conn = false ; if ( ! noCache ) { conn = await this . testConnection ( CACHE_KEY ) ; } if ( false === conn ) { const HOST_AND_CRED = await vscrw . extractHostAndCredentials ( uri , 22 ) ; const MODE = vscode_helpers . toStringSafe ( PARAMS [ 'mode' ] ) ; let dirMode = vscode_helpers . toStringSafe ( PARAMS [ 'dirmode' ] ) ; if ( vscode_helpers . isEmptyString ( dirMode ) ) { dirMode = MODE ; } let fileModeValueOrPath = false ; if ( ! vscode_helpers . isEmptyString ( MODE ) ) { fileModeValueOrPath = parseInt ( MODE . trim ( ) ) ; if ( isNaN ( fileModeValueOrPath ) ) { fileModeValueOrPath = MODE ; } } let dirModeValueOrPath = false ; if ( ! vscode_helpers . isEmptyString ( dirMode ) ) { dirModeValueOrPath = parseInt ( dirMode . trim ( ) ) ; if ( isNaN ( dirModeValueOrPath ) ) { dirModeValueOrPath = dirMode ; } } let noop = vscode_helpers . toStringSafe ( PARAMS [ 'noop' ] ) ; if ( vscode_helpers . isEmptyString ( noop ) ) { noop = undefined ; } let queueSize = parseInt ( vscode_helpers . toStringSafe ( PARAMS [ 'queuesize' ] ) . trim ( ) ) ; if ( isNaN ( queueSize ) ) { queueSize = 1 ; } conn = { cache : { stats : { } } , changeMode : ( ft , u , m ? ) => { const LOG_TAG = `fs.sftp.openConnection.changeMode(${ u })` ; m = parseInt ( vscode_helpers . toStringSafe ( m ) . trim ( ) ) ; return new Promise < boolean > ( async ( resolve , reject ) => { let completedInvoked = false ; const COMPLETED = ( err ) => { if ( completedInvoked ) { return ; } completedInvoked = true ; if ( err ) { this . logger . trace ( err , LOG_TAG ) ; resolve ( false ) ; } else { resolve ( true ) ; } } ; try { const SFTP_CONN = < SFTPConnection > conn ; let action = ( ) => { COMPLETED ( null ) ; } ; let modeValueOrPathToUse = false ; if ( isNaN ( m ) ) { if ( vscode . FileType . Directory === ft ) { modeValueOrPathToUse = dirModeValueOrPath ; } else { modeValueOrPathToUse = fileModeValueOrPath ; } } else { modeValueOrPathToUse = m ; } if ( false !== modeValueOrPathToUse ) { let mapper ; if ( _ . isNumber ( modeValueOrPathToUse ) ) { mapper = { } ; mapper [ modeValueOrPathToUse ] = '**/*' ; } else if ( _ . isString ( modeValueOrPathToUse ) ) { const MODE_FILE = vscrw . mapToUsersHome ( modeValueOrPathToUse ) ; if ( await vscode_helpers . isFile ( MODE_FILE ) ) { mapper = JSON . parse ( await FSExtra . readFile ( MODE_FILE , 'utf8' ) ) ; } else { this . logger . warn ( `Mode file '${ modeValueOrPathToUse }' not found!` , LOG_TAG ) ; } } if ( mapper ) { const FILE_OR_FOLDER = vscrw . normalizePath ( u . path ) ; let modeToSet = false ; for ( const M in mapper ) { const MODE_VALUE = parseInt ( vscode_helpers . toStringSafe ( M ) . trim ( ) , 8 ) ; if ( isNaN ( MODE_VALUE ) ) { this . logger . warn ( `'${ M }' is not valid mode value!` , LOG_TAG ) ; continue ; } const PATTERNS = vscode_helpers . asArray ( mapper [ M ] ) . map ( x => { return vscode_helpers . toStringSafe ( x ) ; } ) . filter ( x => ! vscode_helpers . isEmptyString ( x ) ) . map ( x => { if ( ! x . trim ( ) . startsWith ( '/' ) ) { x = '/' + x ; } return x ; } ) ; if ( vscode_helpers . doesMatch ( FILE_OR_FOLDER , PATTERNS ) ) { modeToSet = MODE_VALUE ; } } if ( false !== modeToSet ) { this . logger . info ( `Setting mode of '${ FILE_OR_FOLDER }' to ${ modeToSet . toString ( 8 ) }` , LOG_TAG ) ; action = ( ) => { SFTP_CONN . client [ 'sftp' ] . chmod ( FILE_OR_FOLDER , < number > modeToSet , ( err ) => { COMPLETED ( err ) ; } ) ; } ; } } } action ( ) ; } catch ( e ) { COMPLETED ( e ) ; } } ) ; } , client : new SFTP ( ) , followSymLinks : vscrw . isTrue ( PARAMS [ 'follow' ] , true ) , keepMode : vscrw . isTrue ( PARAMS [ 'keepmode' ] , true ) , noop : noop , noQueue : ! vscrw . isTrue ( PARAMS [ 'queue' ] , true ) , queue : vscode_helpers . createQueue ( { concurrency : queueSize , } ) , } ; let agent = vscode_helpers . toStringSafe ( PARAMS [ 'agent' ] ) ; let agentForward = vscode_helpers . normalizeString ( PARAMS [ 'agentforward' ] ) ; let debug = vscrw . isTrue ( PARAMS [ 'debug' ] ) ; let hashes = vscode_helpers . normalizeString ( PARAMS [ 'allowedhashes' ] ) . split ( ',' ) . map ( h => { return h . trim ( ) ; } ) . filter ( h => { return '' !== h ; } ) ; let hostHash = vscode_helpers . normalizeString ( PARAMS [ 'hash' ] ) ; let keepAlive = parseFloat ( vscode_helpers . toStringSafe ( PARAMS [ 'keepalive' ] ) . trim ( ) ) ; const NO_PHRASE_FILE = vscrw . isTrue ( PARAMS [ 'nophrasefile' ] ) ; let passphrase = vscode_helpers . toStringSafe ( PARAMS [ 'phrase' ] ) ; let readyTimeout = parseInt ( vscode_helpers . normalizeString ( PARAMS [ 'timeout' ] ) ) ; let tryKeyboard = vscode_helpers . normalizeString ( PARAMS [ 'trykeyboard' ] ) ; if ( '' === passphrase ) { passphrase = undefined ; } try { if ( ! vscode_helpers . isEmptyString ( passphrase ) ) { if ( ! NO_PHRASE_FILE ) { const PHRASE_FILE = vscrw . mapToUsersHome ( passphrase ) ; if ( await vscode_helpers . isFile ( PHRASE_FILE ) ) { passphrase = await FSExtra . readFile ( PHRASE_FILE , 'utf8' ) ; } } } } catch { } let privateKey ; let key = vscode_helpers . toStringSafe ( PARAMS [ 'key' ] ) ; if ( ! vscode_helpers . isEmptyString ( key ) ) { try { let keyFile = key ; if ( ! Path . isAbsolute ( keyFile ) ) { keyFile = Path . join ( OS . homedir ( ) , '.ssh' , keyFile ) ; } keyFile = Path . resolve ( keyFile ) ; if ( await vscode_helpers . isFile ( keyFile ) ) { privateKey = await FSExtra . readFile ( keyFile ) ; } } catch { } if ( ! privateKey ) { privateKey = new Buffer ( key , 'base64' ) ; } } const OPTS = { agent : vscode_helpers . isEmptyString ( agent ) ? ( process . platform === 'win32' ? 'pageant' : process . env . SSH_AUTH_SOCK ) : agent , agentForward : vscrw . isTrue ( agentForward ) , host : HOST_AND_CRED . host , hostHash : < any > ( '' === hostHash ? 'md5' : hostHash ) , hostVerifier : ( keyHash ) => { if ( hashes . length < 1 ) { return true ; } return hashes . indexOf ( vscode_helpers . normalizeString ( keyHash ) ) > - 1 ; } , keepaliveInterval : isNaN ( keepAlive ) ? undefined : Math . floor ( keepAlive * 1000.0 ) , passphrase : '' === passphrase ? undefined : passphrase , password : HOST_AND_CRED . password , privateKey : privateKey , port : HOST_AND_CRED . port , readyTimeout : isNaN ( readyTimeout ) ? 20000 : readyTimeout , tryKeyboard : '' === tryKeyboard ? undefined : vscrw . isTrue ( tryKeyboard ) , username : HOST_AND_CRED . user , } ; if ( debug ) { OPTS . debug = ( information ) => { try { this . logger . info ( information , `sftp://${ HOST_AND_CRED . host }:${ HOST_AND_CRED . port }` ) ; } catch { } } ; } if ( ! noCache ) { await this . tryCloseAndDeleteConnection ( CACHE_KEY ) ; } if ( tryKeyboard ) { const PWD = vscode_helpers . toStringSafe ( HOST_AND_CRED . password ) ; conn . client [ 'client' ] . on ( 'keyboard-interactive' , ( name , instructions , instructionsLang , prompts , finish ) => { try { finish ( [ PWD ] ) ; } catch ( e ) { this . logger . trace ( e , 'fs.sftp.SFTPFileSystem.openConnection(keyboard-interactive)' ) ; } } ) ; } await conn . client . connect ( OPTS ) ; if ( ! noCache ) { this . _CONN_CACHE [ CACHE_KEY ] = conn ; } } return conn ; } inheritdoc public async readDirectory ( uri ) { return this . forConnection ( uri , async ( conn ) => { const ITEMS = [ ] ; try { const LIST = await conn . client . list ( vscrw . normalizePath ( uri . path ) ) ; for ( const ITEM of LIST ) { const S = await toFileStat ( ITEM , uri , conn ) ; ITEMS . push ( [ S [ 0 ] , S [ 1 ] . type ] ) ; } } catch { this . throwWithoutAuthority ( uri , u => vscode . FileSystemError . FileNotFound ( u ) ) ; } return vscode_helpers . from ( ITEMS ) . orderBy ( i => { return i [ 1 ] === vscode . FileType . Directory ? 0 : 1 ; } ) . thenBy ( i => { return vscode_helpers . normalizeString ( i [ 0 ] ) ; } ) . toArray ( ) ; } ) ; } inheritdoc public readFile ( uri ) { return this . forConnection ( uri , async ( conn ) => { return vscrw . asBuffer ( await conn . client . get ( vscrw . normalizePath ( uri . path ) ) ) ; } ) ; } param { vscode . ExtensionContext } context return { SFTPFileSystem } public static register ( context ) { const NEW_FS = new SFTPFileSystem ( ) ; try { context . subscriptions . push ( vscode . workspace . registerFileSystemProvider ( SFTPFileSystem . scheme , NEW_FS , { isCaseSensitive : true } ) ) ; } catch ( e ) { vscode_helpers . tryDispose ( NEW_FS ) ; throw e ; } return NEW_FS ; } inheritdoc public async rename ( oldUri , newUri , options ) { await this . forConnection ( oldUri , async ( conn ) => { const OLD_STAT = await this . statInner ( oldUri , conn ) ; const NEW_STAT = await this . tryGetStat ( newUri , conn ) ; if ( false !== NEW_STAT ) { if ( ! options . overwrite ) { this . throwWithoutAuthority ( newUri , u => vscode . FileSystemError . FileExists ( u ) ) ; } } await conn . client . rename ( vscrw . normalizePath ( oldUri . path ) , vscrw . normalizePath ( newUri . path ) ) ; await conn . changeMode ( OLD_STAT . type , newUri ) ; } ) ; } /**
     * Stores the name of the scheme.
     */ public static readonly scheme = 'sftp' ; inheritdoc public async stat ( uri ) { return this . statInner ( uri ) ; } private async statInner ( uri , existingConn ? ) { if ( '/' === vscrw . normalizePath ( uri . path ) ) { return { '__vscrw_fileinfo' : undefined , type : vscode . FileType . Directory , ctime : 0 , mtime : 0 , size : 0 , } ; } return await this . forConnection ( uri , async ( conn ) => { let stat = false ; try { const URI_PATH = vscrw . normalizePath ( uri . path ) ; const NAME = Path . basename ( URI_PATH ) ; const DIR = vscrw . normalizePath ( Path . dirname ( URI_PATH ) ) ; const LIST = await conn . client . list ( DIR ) ; for ( const ITEM of LIST ) { if ( ITEM . name === NAME ) { const S = await toFileStat ( ITEM , uriWithNewPath ( uri , DIR ) , conn ) ; stat = S [ 1 ] ; break ; } } } catch { } if ( false === stat ) { this . throwWithoutAuthority ( uri , u => vscode . FileSystemError . FileNotFound ( u ) ) ; } return < SFTPFileStat > stat ; } , existingConn ) ; } private async testConnection ( cacheKey ) { let result = false ; const CONN = this . _CONN_CACHE [ cacheKey ] ; if ( ! _ . isNil ( CONN ) ) { try { if ( _ . isNil ( CONN . noop ) ) { await CONN . client . list ( '/' ) ; } else { await execServerCommand ( CONN . client , CONN . noop ) ; } result = CONN ; } catch { result = false ; } } if ( false === result ) { await this . tryCloseAndDeleteConnection ( cacheKey ) ; } return result ; } private async tryCloseAndDeleteConnection ( cacheKey ) { await tryCloseConnection ( this . _CONN_CACHE [ cacheKey ] ) ; delete this . _CONN_CACHE [ cacheKey ] ; } private tryCloseAndDeleteConnectionSync ( cacheKey ) { this . tryCloseAndDeleteConnection ( cacheKey ) . then ( ( ) => { } , ( err ) => { } ) ; } private async tryGetMod ( uri , stat ? , existingConn ? ) { if ( arguments . length < 2 ) { stat = await this . tryGetStat ( uri , existingConn ) ; } let mod ; if ( false !== stat ) { if ( stat . __vscrw_fileinfo ) { mod = chmodRightsToNumber ( stat . __vscrw_fileinfo . rights ) ; } } return mod ; } private async tryGetStat ( uri , existingConn ? ) { let stat ; try { stat = await this . statInner ( uri , existingConn ) ; } catch { stat = false ; } return stat ; } inheritdoc public watch ( uri , options ) { return { dispose : ( ) => { } } ; } inheritdoc public async writeFile ( uri , content , options ) { await this . forConnection ( uri , async ( conn ) => { const STAT = await this . tryGetStat ( uri , conn ) ; this . throwIfWriteFileIsNotAllowed ( STAT , options , uri ) ; let oldMod ; if ( conn . keepMode ) { oldMod = await this . tryGetMod ( uri , STAT , conn ) ; } await conn . client . put ( vscrw . asBuffer ( content ) , vscrw . normalizePath ( uri . path ) ) ; await conn . changeMode ( vscode . FileType . File , uri , oldMod ) ; } ) ; } } function chmodRightsToNumber ( rights ) { if ( _ . isNil ( rights ) ) { return < any > rights ; } const USER = vscode_helpers . normalizeString ( rights . user ) ; const GROUP = vscode_helpers . normalizeString ( rights . group ) ; const OTHER = vscode_helpers . normalizeString ( rights . other ) ; let u = 0 ; for ( let i = 0 ; i < USER . length ; i ++ ) { switch ( USER [ i ] ) { case 'r' : u = u | 4 ; break ; case 'w' : u = u | 2 ; break ; case 'x' : u = u | 1 ; break ; } } let g = 0 ; for ( let i = 0 ; i < GROUP . length ; i ++ ) { switch ( GROUP [ i ] ) { case 'r' : g = g | 4 ; break ; case 'w' : g = g | 2 ; break ; case 'x' : g = g | 1 ; break ; } } let o = 0 ; for ( let i = 0 ; i < OTHER . length ; i ++ ) { switch ( OTHER [ i ] ) { case 'r' : o = o | 4 ; break ; case 'w' : o = o | 2 ; break ; case 'x' : o = o | 1 ; break ; } } return parseInt ( `${ u }${ g }${ o }` ) ; } function execServerCommand ( conn , cmd ) { cmd = vscode_helpers . toStringSafe ( cmd ) ; return new Promise < Buffer > ( ( resolve , reject ) => { let output ; let completedInvoked = false ; const COMPLETED = ( err ) => { if ( completedInvoked ) { return ; } completedInvoked = true ; if ( err ) { reject ( err ) ; } else { resolve ( output ) ; } } ; try { output = Buffer . alloc ( 0 ) ; conn [ 'client' ] . exec ( cmd , ( err , stream ) => { if ( err ) { COMPLETED ( err ) ; return ; } try { let dataListener ; let endListener ; let errorListener ; const CLOSE_STREAM = ( err ) => { vscode_helpers . tryRemoveListener ( stream , 'end' , endListener ) ; vscode_helpers . tryRemoveListener ( stream , 'error' , errorListener ) ; vscode_helpers . tryRemoveListener ( stream , 'data' , dataListener ) ; COMPLETED ( err ) ; } ; errorListener = ( streamErr ) => { CLOSE_STREAM ( streamErr ) ; } ; endListener = ( ) => { CLOSE_STREAM ( null ) ; } ; dataListener = ( chunk ) => { if ( _ . isNil ( chunk ) ) { return ; } try { if ( ! Buffer . isBuffer ( chunk ) ) { chunk = new Buffer ( vscode_helpers . toStringSafe ( chunk ) , 'binary' ) ; } output = Buffer . concat ( [ output , chunk ] ) ; } catch ( e ) { CLOSE_STREAM ( e ) ; } } ; try { stream . once ( 'error' , errorListener ) ; stream . once ( 'end' , endListener ) ; stream . on ( 'data' , dataListener ) ; } catch ( e ) { CLOSE_STREAM ( e ) ; } } catch ( e ) { COMPLETED ( e ) ; } } ) ; } catch ( e ) { COMPLETED ( e ) ; } } ) ; } async function toFileStat ( fi , uri , conn ) { if ( fi ) { const STAT = { '__vscrw_fileinfo' : fi , type : vscode . FileType . Unknown , ctime : 0 , mtime : 0 , size : 0 , } ; if ( 'd' === fi . type ) { STAT . type = vscode . FileType . Directory ; } else if ( 'l' === fi . type ) { STAT . type = vscode . FileType . SymbolicLink ; if ( conn . followSymLinks ) { try { const FILE_OR_FOLDER = vscrw . normalizePath ( Path . join ( uri . path , fi . name ) ) ; const CACHED_VALUE = conn . cache . stats [ FILE_OR_FOLDER ] ; if ( _ . isNil ( CACHED_VALUE ) ) { let type = false ; try { const STREAM = await conn . client . get ( FILE_OR_FOLDER ) ; try { if ( _ . isFunction ( STREAM . close ) ) { STREAM . close ( ) ; } } catch { } type = vscode . FileType . File ; } catch { try { await conn . client . list ( FILE_OR_FOLDER ) ; type = vscode . FileType . Directory ; } catch { } } if ( false !== type ) { STAT . type = type ; } } else { STAT . type = CACHED_VALUE ; } } catch { STAT . type = vscode . FileType . SymbolicLink ; } } } else if ( '-' === fi . type ) { STAT . type = vscode . FileType . File ; } if ( vscode . FileType . File === STAT . type ) { STAT . size = fi . size ; STAT . ctime = fi . modifyTime ; STAT . mtime = fi . modifyTime ; } if ( isNaN ( STAT . ctime ) ) { STAT . ctime = 0 ; } if ( isNaN ( STAT . mtime ) ) { STAT . mtime = 0 ; } if ( isNaN ( STAT . size ) ) { STAT . size = 0 ; } return [ fi . name , STAT ] ; } } async function tryCloseConnection ( conn ) { try { if ( conn ) { await conn . client . end ( ) ; } return true ; } catch { return false ; } } function uriWithNewPath ( uri , newPath ) { if ( uri ) { return vscode . Uri . parse ( `sftp://${ uri . authority }${ vscode_helpers . toStringSafe ( newPath ) }${ vscode_helpers . isEmptyString ( uri . query ) ? '' : ( '?' + uri . query ) }` ) ; } }