Files
bare-operating-system/packages/bare-os-openssh/vendor/bare-ssh2/lib/utils.js
T

305 lines
9.0 KiB
JavaScript

'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) return
if (channel.incoming && channel.incoming.state === 'closed') return
self._chanMgr.remove(recipient)
if (channel.server && channel.constructor.name === 'Session') 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'
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 '[email protected]':
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
)
}
}