'use strict' const { kMaxLength } = require('buffer') const { createInflate, constants: { DEFLATE, INFLATE, Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, Z_DEFAULT_MEMLEVEL, Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_PARTIAL_FLUSH } } = require('bare-node-zlib') /** * Node's `zlib` exposes `createInflate()._handle.constructor` for the sync * binding. Bare's `bare-node-zlib` / `bare-zlib` often has no `_handle`, so * reading `.constructor` at module load throws. Resolve lazily; callers that * need zlib compression get a clear error if unavailable. */ let ZlibHandleCtor function getZlibHandleCtor() { if (ZlibHandleCtor !== undefined) return ZlibHandleCtor try { const inf = createInflate() ZlibHandleCtor = inf && inf._handle && typeof inf._handle.constructor === 'function' ? inf._handle.constructor : null } catch { ZlibHandleCtor = null } return ZlibHandleCtor } function processCallback() { throw new Error('Should not get here') } function zlibOnError(message, errno, code) { const self = this._owner // There is no way to cleanly recover. // Continuing only obscures problems. const error = new Error(message) error.errno = errno error.code = code self._err = error } function _close(engine) { // Caller may invoke .close after a zlib error (which will null _handle). if (!engine._handle) return engine._handle.close() engine._handle = null } class Zlib { constructor(mode) { const windowBits = Z_DEFAULT_WINDOWBITS const level = Z_DEFAULT_COMPRESSION const memLevel = Z_DEFAULT_MEMLEVEL const strategy = Z_DEFAULT_STRATEGY const dictionary = undefined this._err = undefined this._writeState = new Uint32Array(2) this._chunkSize = Z_DEFAULT_CHUNK this._maxOutputLength = kMaxLength this._outBuffer = Buffer.allocUnsafe(this._chunkSize) this._outOffset = 0 const Handle = getZlibHandleCtor() if (!Handle) { throw new Error( 'bare-ssh2 zlib: native zlib _handle unavailable (use compression "none" only on Bare/Pear)' ) } this._handle = new Handle(mode) this._handle._owner = this this._handle.onerror = zlibOnError this._handle.init( windowBits, level, memLevel, strategy, this._writeState, processCallback, dictionary ) } writeSync(chunk, retChunks) { const handle = this._handle if (!handle) throw new Error('Invalid Zlib instance') let availInBefore = chunk.length let availOutBefore = this._chunkSize - this._outOffset let inOff = 0 let availOutAfter let availInAfter let buffers let nread = 0 const state = this._writeState let buffer = this._outBuffer let offset = this._outOffset const chunkSize = this._chunkSize while (true) { handle.writeSync( Z_PARTIAL_FLUSH, chunk, // in inOff, // in_off availInBefore, // in_len buffer, // out offset, // out_off availOutBefore ) // out_len if (this._err) throw this._err availOutAfter = state[0] availInAfter = state[1] const inDelta = availInBefore - availInAfter const have = availOutBefore - availOutAfter if (have > 0) { const out = offset === 0 && have === buffer.length ? buffer : buffer.slice(offset, offset + have) offset += have if (!buffers) buffers = out else if (buffers.push === undefined) buffers = [buffers, out] else buffers.push(out) nread += out.byteLength if (nread > this._maxOutputLength) { _close(this) throw new Error(`Output length exceeded maximum of ${this._maxOutputLength}`) } } else if (have !== 0) { throw new Error('have should not go down') } // Exhausted the output buffer, or used all the input create a new one. if (availOutAfter === 0 || offset >= chunkSize) { availOutBefore = chunkSize offset = 0 buffer = Buffer.allocUnsafe(chunkSize) } if (availOutAfter === 0) { // Not actually done. Need to reprocess. // Also, update the availInBefore to the availInAfter value, // so that if we have to hit it a third (fourth, etc.) time, // it'll have the correct byte counts. inOff += inDelta availInBefore = availInAfter } else { break } } this._outBuffer = buffer this._outOffset = offset if (nread === 0) buffers = Buffer.alloc(0) if (retChunks) { buffers.totalLen = nread return buffers } if (buffers.push === undefined) return buffers const output = Buffer.allocUnsafe(nread) for (let i = 0, p = 0; i < buffers.length; ++i) { const buf = buffers[i] output.set(buf, p) p += buf.length } return output } } class ZlibPacketWriter { constructor(protocol) { this.allocStart = 0 this.allocStartKEX = 0 this._protocol = protocol this._zlib = new Zlib(DEFLATE) } cleanup() { if (this._zlib) _close(this._zlib) } alloc(payloadSize, force) { return Buffer.allocUnsafe(payloadSize) } finalize(payload, force) { if (this._protocol._kexinit === undefined || force) { const output = this._zlib.writeSync(payload, true) const packet = this._protocol._cipher.allocPacket(output.totalLen) if (output.push === undefined) { packet.set(output, 5) } else { for (let i = 0, p = 5; i < output.length; ++i) { const chunk = output[i] packet.set(chunk, p) p += chunk.length } } return packet } return payload } } class PacketWriter { constructor(protocol) { this.allocStart = 5 this.allocStartKEX = 5 this._protocol = protocol } cleanup() {} alloc(payloadSize, force) { if (this._protocol._kexinit === undefined || force) return this._protocol._cipher.allocPacket(payloadSize) return Buffer.allocUnsafe(payloadSize) } finalize(packet, force) { return packet } } class ZlibPacketReader { constructor() { this._zlib = new Zlib(INFLATE) } cleanup() { if (this._zlib) _close(this._zlib) } read(data) { return this._zlib.writeSync(data, false) } } class PacketReader { cleanup() {} read(data) { return data } } module.exports = { PacketReader, PacketWriter, ZlibPacketReader, ZlibPacketWriter }