123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- 'use strict';
- const { SFTP } = require('./protocol/SFTP.js');
- const MAX_CHANNEL = 2 ** 32 - 1;
- function onChannelOpenFailure(self, recipient, info, cb) {
- self._chanMgr.remove(recipient);
- if (typeof cb !== 'function')
- return;
- let err;
- if (info instanceof Error) {
- err = info;
- } else if (typeof info === 'object' && info !== null) {
- err = new Error(`(SSH) Channel open failure: ${info.description}`);
- err.reason = info.reason;
- } else {
- err = new Error(
- '(SSH) Channel open failure: server closed channel unexpectedly'
- );
- err.reason = '';
- }
- cb(err);
- }
- function onCHANNEL_CLOSE(self, recipient, channel, err, dead) {
- if (typeof channel === 'function') {
- // We got CHANNEL_CLOSE instead of CHANNEL_OPEN_FAILURE when
- // requesting to open a channel
- onChannelOpenFailure(self, recipient, err, channel);
- return;
- }
- if (typeof channel !== 'object'
- || channel === null
- || channel.incoming.state === 'closed') {
- return;
- }
- channel.incoming.state = 'closed';
- if (channel.readable)
- channel.push(null);
- if (channel.server) {
- if (channel.stderr.writable)
- channel.stderr.end();
- } else if (channel.stderr.readable) {
- channel.stderr.push(null);
- }
- if (channel.constructor !== SFTP
- && (channel.outgoing.state === 'open'
- || channel.outgoing.state === 'eof')
- && !dead) {
- channel.close();
- }
- if (channel.outgoing.state === 'closing')
- channel.outgoing.state = 'closed';
- self._chanMgr.remove(recipient);
- const readState = channel._readableState;
- const writeState = channel._writableState;
- if (writeState && !writeState.ending && !writeState.finished && !dead)
- channel.end();
- // Take care of any outstanding channel requests
- const chanCallbacks = channel._callbacks;
- channel._callbacks = [];
- for (let i = 0; i < chanCallbacks.length; ++i)
- chanCallbacks[i](true);
- if (channel.server) {
- if (!channel.readable
- || channel.destroyed
- || (readState && readState.endEmitted)) {
- channel.emit('close');
- } else {
- channel.once('end', () => channel.emit('close'));
- }
- } else {
- let doClose;
- switch (channel.type) {
- case 'direct-streamlocal@openssh.com':
- case 'direct-tcpip':
- doClose = () => channel.emit('close');
- break;
- default: {
- // Align more with node child processes, where the close event gets
- // the same arguments as the exit event
- const exit = channel._exit;
- doClose = () => {
- if (exit.code === null)
- channel.emit('close', exit.code, exit.signal, exit.dump, exit.desc);
- else
- channel.emit('close', exit.code);
- };
- }
- }
- if (!channel.readable
- || channel.destroyed
- || (readState && readState.endEmitted)) {
- doClose();
- } else {
- channel.once('end', doClose);
- }
- const errReadState = channel.stderr._readableState;
- if (!channel.stderr.readable
- || channel.stderr.destroyed
- || (errReadState && errReadState.endEmitted)) {
- channel.stderr.emit('close');
- } else {
- channel.stderr.once('end', () => channel.stderr.emit('close'));
- }
- }
- }
- class ChannelManager {
- constructor(client) {
- this._client = client;
- this._channels = {};
- this._cur = -1;
- this._count = 0;
- }
- add(val) {
- // Attempt to reserve an id
- let id;
- // Optimized paths
- if (this._cur < MAX_CHANNEL) {
- id = ++this._cur;
- } else if (this._count === 0) {
- // Revert and reset back to fast path once we no longer have any channels
- // open
- this._cur = 0;
- id = 0;
- } else {
- // Slower lookup path
- // This path is triggered we have opened at least MAX_CHANNEL channels
- // while having at least one channel open at any given time, so we have
- // to search for a free id.
- const channels = this._channels;
- for (let i = 0; i < MAX_CHANNEL; ++i) {
- if (channels[i] === undefined) {
- id = i;
- break;
- }
- }
- }
- if (id === undefined)
- return -1;
- this._channels[id] = (val || true);
- ++this._count;
- return id;
- }
- update(id, val) {
- if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
- throw new Error(`Invalid channel id: ${id}`);
- if (val && this._channels[id])
- this._channels[id] = val;
- }
- get(id) {
- if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
- throw new Error(`Invalid channel id: ${id}`);
- return this._channels[id];
- }
- remove(id) {
- if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
- throw new Error(`Invalid channel id: ${id}`);
- if (this._channels[id]) {
- delete this._channels[id];
- if (this._count)
- --this._count;
- }
- }
- cleanup(err) {
- const channels = this._channels;
- this._channels = {};
- this._cur = -1;
- this._count = 0;
- const chanIDs = Object.keys(channels);
- const client = this._client;
- for (let i = 0; i < chanIDs.length; ++i) {
- const id = +chanIDs[i];
- const channel = channels[id];
- onCHANNEL_CLOSE(client, id, channel._channel || channel, err, true);
- }
- }
- }
- const isRegExp = (() => {
- const toString = Object.prototype.toString;
- return (val) => toString.call(val) === '[object RegExp]';
- })();
- function generateAlgorithmList(algoList, defaultList, supportedList) {
- if (Array.isArray(algoList) && algoList.length > 0) {
- // Exact list
- for (let i = 0; i < algoList.length; ++i) {
- if (supportedList.indexOf(algoList[i]) === -1)
- throw new Error(`Unsupported algorithm: ${algoList[i]}`);
- }
- return algoList;
- }
- if (typeof algoList === 'object' && algoList !== null) {
- // Operations based on the default list
- const keys = Object.keys(algoList);
- let list = defaultList;
- for (let i = 0; i < keys.length; ++i) {
- const key = keys[i];
- let val = algoList[key];
- switch (key) {
- case 'append':
- if (!Array.isArray(val))
- val = [val];
- if (Array.isArray(val)) {
- for (let j = 0; j < val.length; ++j) {
- const append = val[j];
- if (typeof append === 'string') {
- if (!append || list.indexOf(append) !== -1)
- continue;
- if (supportedList.indexOf(append) === -1)
- throw new Error(`Unsupported algorithm: ${append}`);
- if (list === defaultList)
- list = list.slice();
- list.push(append);
- } else if (isRegExp(append)) {
- for (let k = 0; k < supportedList.length; ++k) {
- const algo = supportedList[k];
- if (append.test(algo)) {
- if (list.indexOf(algo) !== -1)
- continue;
- if (list === defaultList)
- list = list.slice();
- list.push(algo);
- }
- }
- }
- }
- }
- break;
- case 'prepend':
- if (!Array.isArray(val))
- val = [val];
- if (Array.isArray(val)) {
- for (let j = val.length; j >= 0; --j) {
- const prepend = val[j];
- if (typeof prepend === 'string') {
- if (!prepend || list.indexOf(prepend) !== -1)
- continue;
- if (supportedList.indexOf(prepend) === -1)
- throw new Error(`Unsupported algorithm: ${prepend}`);
- if (list === defaultList)
- list = list.slice();
- list.unshift(prepend);
- } else if (isRegExp(prepend)) {
- for (let k = supportedList.length; k >= 0; --k) {
- const algo = supportedList[k];
- if (prepend.test(algo)) {
- if (list.indexOf(algo) !== -1)
- continue;
- if (list === defaultList)
- list = list.slice();
- list.unshift(algo);
- }
- }
- }
- }
- }
- break;
- case 'remove':
- if (!Array.isArray(val))
- val = [val];
- if (Array.isArray(val)) {
- for (let j = 0; j < val.length; ++j) {
- const search = val[j];
- if (typeof search === 'string') {
- if (!search)
- continue;
- const idx = list.indexOf(search);
- if (idx === -1)
- continue;
- if (list === defaultList)
- list = list.slice();
- list.splice(idx, 1);
- } else if (isRegExp(search)) {
- for (let k = 0; k < list.length; ++k) {
- if (search.test(list[k])) {
- if (list === defaultList)
- list = list.slice();
- list.splice(k, 1);
- --k;
- }
- }
- }
- }
- }
- break;
- }
- }
- return list;
- }
- return defaultList;
- }
- module.exports = {
- ChannelManager,
- generateAlgorithmList,
- onChannelOpenFailure,
- onCHANNEL_CLOSE,
- isWritable: (stream) => {
- // XXX: hack to workaround regression in node
- // See: https://github.com/nodejs/node/issues/36029
- return (stream
- && stream.writable
- && stream._readableState
- && stream._readableState.ended === false);
- },
- };
|