Add sftpCommand config option to allow different ways of launching sftp-server

pull/64/head
Kelvin Schoofs 6 years ago
parent 0e62c9731f
commit 288f4ad845

@ -1,6 +1,7 @@
import { readFile } from 'fs'; import { readFile } from 'fs';
import { Socket } from 'net'; 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 * as vscode from 'vscode';
import { loadConfigs, openConfigurationEditor } from './config'; import { loadConfigs, openConfigurationEditor } from './config';
import { FileSystemConfig } from './manager'; import { FileSystemConfig } from './manager';
@ -8,6 +9,10 @@ import * as proxy from './proxy';
import { getSession as getPuttySession } from './putty'; import { getSession as getPuttySession } from './putty';
import { toPromise } from './toPromise'; 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) { function replaceVariables(string?: string) {
if (typeof string !== 'string') return string; if (typeof string !== 'string') return string;
return string.replace(/\$\w+/g, key => process.env[key.substr(1)] || ''); 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<SFTPWrapper> {
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);
}
});
});
}

@ -3,9 +3,10 @@ import { readFile } from 'fs';
import { parse as parseJsonc, ParseError } from 'jsonc-parser'; import { parse as parseJsonc, ParseError } from 'jsonc-parser';
import * as path from 'path'; import * as path from 'path';
import { Client, ClientChannel, ConnectConfig } from 'ssh2'; import { Client, ClientChannel, ConnectConfig } from 'ssh2';
import { SFTPStream } from 'ssh2-streams';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getConfig, loadConfigs, openConfigurationEditor, updateConfig } from './config'; 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 SSHFileSystem, { EMPTY_FILE_SYSTEM } from './sshFileSystem';
import { MemoryDuplex } from './streams'; import { MemoryDuplex } from './streams';
import { catchingPromise, toPromise } from './toPromise'; import { catchingPromise, toPromise } from './toPromise';
@ -31,6 +32,7 @@ export interface FileSystemConfig extends ConnectConfig {
proxy?: ProxyConfig; proxy?: ProxyConfig;
privateKeyPath?: string; privateKeyPath?: string;
hop?: string; hop?: string;
sftpCommand?: string;
} }
export enum ConfigStatus { export enum ConfigStatus {
@ -194,35 +196,29 @@ export class Manager implements vscode.FileSystemProvider, vscode.TreeDataProvid
} }
root = root.replace(/^~/, home.replace(/\/$/, '')); root = root.replace(/^~/, home.replace(/\/$/, ''));
} }
client.sftp(async (err, sftp) => { const sftp = await getSFTP(client, config);
if (err) { const fs = new SSHFileSystem(name, sftp, root, config!);
client.end(); try {
return reject(err); 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()); } catch (e) {
const fs = new SSHFileSystem(name, sftp, root, config!); let message = `Couldn't read the root directory '${fs.root}' on the server for SSH FS '${name}'`;
try { if (e instanceof vscode.FileSystemError) {
const rootUri = vscode.Uri.parse(`ssh://${name}/`); message = `Path '${fs.root}' in SSH FS '${name}' is not a directory`;
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();
} }
this.fileSystems.push(fs); await vscode.window.showErrorMessage(message, 'Okay');
delete this.creatingFileSystems[name]; return reject();
vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer'); }
this.onDidChangeTreeDataEmitter.fire(); this.fileSystems.push(fs);
client.once('close', hadError => hadError ? this.commandReconnect(name) : (!fs.closing && this.promptReconnect(name))); delete this.creatingFileSystems[name];
return resolve(fs); 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) => { }).catch((e) => {
this.onDidChangeTreeDataEmitter.fire(); this.onDidChangeTreeDataEmitter.fire();
if (!e) { if (!e) {

Loading…
Cancel
Save