diff --git a/src/connect.ts b/src/connect.ts index b1d6876..c645b4c 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -1,6 +1,7 @@ import { readFile } from 'fs'; import { Socket } from 'net'; -import { Client, ConnectConfig } from 'ssh2'; +import { Client, ConnectConfig, SFTPWrapper as SFTPWrapperReal } from 'ssh2'; +import { SFTPStream } from 'ssh2-streams'; import * as vscode from 'vscode'; import { loadConfigs, openConfigurationEditor } from './config'; import { FileSystemConfig } from './manager'; @@ -8,6 +9,10 @@ import * as proxy from './proxy'; import { getSession as getPuttySession } from './putty'; import { toPromise } from './toPromise'; +// tslint:disable-next-line:variable-name +const SFTPWrapper = require('ssh2/lib/SFTPWrapper') as (new (stream: SFTPStream) => SFTPWrapperReal); +type SFTPWrapper = SFTPWrapperReal; + function replaceVariables(string?: string) { if (typeof string !== 'string') return string; return string.replace(/\$\w+/g, key => process.env[key.substr(1)] || ''); @@ -171,3 +176,33 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable } }); } + +export function getSFTP(client: Client, config: FileSystemConfig): Promise { + return new Promise((resolve, reject) => { + if (!config.sftpCommand) { + return client.sftp((err, sftp) => { + if (err) { + client.end(); + reject(err); + } + resolve(sftp); + }); + } + client.exec(config.sftpCommand, (err, channel) => { + if (err) { + client.end(); + return reject(err); + } + channel.once('close', () => (client.end(), reject())); + channel.once('error', () => (client.end(), reject())); + try { + const sftps = new SFTPStream(); + channel.pipe(sftps).pipe(channel); + const sftp = new SFTPWrapper(sftps); + resolve(sftp); + } catch (e) { + reject(e); + } + }); + }); +} diff --git a/src/manager.ts b/src/manager.ts index f2b77a8..98f5784 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -3,9 +3,10 @@ import { readFile } from 'fs'; import { parse as parseJsonc, ParseError } from 'jsonc-parser'; import * as path from 'path'; import { Client, ClientChannel, ConnectConfig } from 'ssh2'; +import { SFTPStream } from 'ssh2-streams'; import * as vscode from 'vscode'; import { getConfig, loadConfigs, openConfigurationEditor, updateConfig } from './config'; -import { createSocket, createSSH } from './connect'; +import { createSocket, createSSH, getSFTP } from './connect'; import SSHFileSystem, { EMPTY_FILE_SYSTEM } from './sshFileSystem'; import { MemoryDuplex } from './streams'; import { catchingPromise, toPromise } from './toPromise'; @@ -31,6 +32,7 @@ export interface FileSystemConfig extends ConnectConfig { proxy?: ProxyConfig; privateKeyPath?: string; hop?: string; + sftpCommand?: string; } export enum ConfigStatus { @@ -194,35 +196,29 @@ export class Manager implements vscode.FileSystemProvider, vscode.TreeDataProvid } root = root.replace(/^~/, home.replace(/\/$/, '')); } - client.sftp(async (err, sftp) => { - if (err) { - client.end(); - return reject(err); + const sftp = await getSFTP(client, config); + const fs = new SSHFileSystem(name, sftp, root, config!); + try { + const rootUri = vscode.Uri.parse(`ssh://${name}/`); + const stat = await fs.stat(rootUri); + // tslint:disable-next-line:no-bitwise + if (!(stat.type & vscode.FileType.Directory)) { + throw vscode.FileSystemError.FileNotADirectory(rootUri); } - sftp.once('end', () => client.end()); - const fs = new SSHFileSystem(name, sftp, root, config!); - try { - const rootUri = vscode.Uri.parse(`ssh://${name}/`); - const stat = await fs.stat(rootUri); - // tslint:disable-next-line:no-bitwise - if (!(stat.type & vscode.FileType.Directory)) { - throw vscode.FileSystemError.FileNotADirectory(rootUri); - } - } catch (e) { - let message = `Couldn't read the root directory '${fs.root}' on the server for SSH FS '${name}'`; - if (e instanceof vscode.FileSystemError) { - message = `Path '${fs.root}' in SSH FS '${name}' is not a directory`; - } - await vscode.window.showErrorMessage(message, 'Okay'); - return reject(); + } catch (e) { + let message = `Couldn't read the root directory '${fs.root}' on the server for SSH FS '${name}'`; + if (e instanceof vscode.FileSystemError) { + message = `Path '${fs.root}' in SSH FS '${name}' is not a directory`; } - this.fileSystems.push(fs); - delete this.creatingFileSystems[name]; - vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer'); - this.onDidChangeTreeDataEmitter.fire(); - client.once('close', hadError => hadError ? this.commandReconnect(name) : (!fs.closing && this.promptReconnect(name))); - return resolve(fs); - }); + await vscode.window.showErrorMessage(message, 'Okay'); + return reject(); + } + this.fileSystems.push(fs); + delete this.creatingFileSystems[name]; + vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer'); + this.onDidChangeTreeDataEmitter.fire(); + client.once('close', hadError => hadError ? this.commandReconnect(name) : (!fs.closing && this.promptReconnect(name))); + return resolve(fs); }).catch((e) => { this.onDidChangeTreeDataEmitter.fire(); if (!e) {