diff --git a/README.md b/README.md index d0e6f7fc..e08c929e 100644 --- a/README.md +++ b/README.md @@ -1028,7 +1028,7 @@ You can find more examples in the `examples` directory of this repository. * **setNoDelay**([< _boolean_ >noDelay]) - _Client_ - Calls [`setNoDelay()`](https://nodejs.org/docs/latest/api/net.html#socketsetnodelaynodelay) on the underlying socket. Disabling Nagle's algorithm improves latency at the expense of lower throughput. -* **sftp**(< _function_ >callback) - _(void)_ - Starts an SFTP session. `callback` has 2 parameters: < _Error_ >err, < _SFTP_ >sftp. For methods available on `sftp`, see the [`SFTP` client documentation](https://github.com/mscdex/ssh2/blob/master/SFTP.md). +* **sftp**([< _object_ >env, ]< _function_ >callback) - _(void)_ - Starts an SFTP session. `env` is an environment to use when executing `sftp` methods. `callback` has 2 parameters: < _Error_ >err, < _SFTP_ >sftp. For methods available on `sftp`, see the [`SFTP` client documentation](https://github.com/mscdex/ssh2/blob/master/SFTP.md). * **shell**([[< _mixed_ >window,] < _object_ >options]< _function_ >callback) - _(void)_ - Starts an interactive shell session on the server, with an optional `window` object containing pseudo-tty settings (see 'Pseudo-TTY settings'). If `window === false`, then no pseudo-tty is allocated. `options` supports the `x11` and `env` options as described in `exec()`. `callback` has 2 parameters: < _Error_ >err, < _Channel_ >stream. diff --git a/lib/client.js b/lib/client.js index aa94ace0..7291c2ce 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1552,17 +1552,22 @@ class Client extends EventEmitter { return this; } - sftp(cb) { + sftp(env, cb) { if (!this._sock || !isWritable(this._sock)) throw new Error('Not connected'); + if (typeof env === 'function') { + cb = env; + env = undefined; + } + openChannel(this, 'sftp', (err, sftp) => { if (err) { cb(err); return; } - reqSubsystem(sftp, 'sftp', (err, sftp_) => { + const reqSubsystemCb = (err, sftp_) => { if (err) { cb(err); return; @@ -1608,7 +1613,20 @@ class Client extends EventEmitter { .on('close', onExit); sftp._init(); - }); + }; + + if (typeof env === 'object' && env !== null) { + reqEnv(sftp, env, (err) => { + if (err) { + cb(err); + return; + } + + reqSubsystem(sftp, 'sftp', reqSubsystemCb); + }); + } else { + reqSubsystem(sftp, 'sftp', reqSubsystemCb); + } }); return this; @@ -1842,16 +1860,33 @@ function reqExec(chan, cmd, opts, cb) { chan._client._protocol.exec(chan.outgoing.id, cmd, true); } -function reqEnv(chan, env) { - if (chan.outgoing.state !== 'open') +function reqEnv(chan, env, cb) { + const wantReply = (typeof cb === 'function'); + + if (chan.outgoing.state !== 'open') { + if (wantReply) + cb(new Error('Channel is not open')); return; + } + + if (wantReply) { + chan._callbacks.push((had_err) => { + if (had_err) { + cb(had_err !== true + ? had_err + : new Error('Unable to set environment')); + return; + } + cb(); + }); + } const keys = Object.keys(env || {}); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const val = env[key]; - chan._client._protocol.env(chan.outgoing.id, key, val, false); + chan._client._protocol.env(chan.outgoing.id, key, val, wantReply); } } diff --git a/test/test-sftp.js b/test/test-sftp.js index 1bda7668..595e5091 100644 --- a/test/test-sftp.js +++ b/test/test-sftp.js @@ -755,6 +755,42 @@ setup('WriteStream', mustCall((client, server) => { })); } +{ + const { client, server } = setup_( + 'SFTP client sets environment', + { + client: { username: 'foo', password: 'bar' }, + server: { hostKeys: [ fixture('ssh_host_rsa_key') ] }, + }, + ); + + const env = { SSH2NODETEST: 'foo' }; + + server.on('connection', mustCall((conn) => { + conn.on('authentication', mustCall((ctx) => { + ctx.accept(); + })).on('ready', mustCall(() => { + conn.on('session', mustCall((accept, reject) => { + accept().on('env', mustCall((accept, reject, info) => { + accept && accept(); + assert(info.key === Object.keys(env)[0], 'Wrong env key'); + assert(info.val === Object.values(env)[0], 'Wrong env value'); + })).on('sftp', mustCall((accept, reject) => { + accept(); + })); + })); + })); + })); + + client.on('ready', mustCall(() => { + const timeout = setTimeout(mustNotCall(), 1000); + client.sftp(env, mustCall((err, sftp) => { + clearTimeout(timeout); + assert(!err, `Unexpected exec error: ${err}`); + client.end(); + })); + })); +} // ============================================================================= function setup(title, cb) {