<PackageReference Include="SSH.NET" Version="2025.1.0" />

ShellStream

public sealed class ShellStream : Stream
Contains operation for working with SSH Shell.
using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Renci.SshNet { [NullableContext(1)] [Nullable(0)] public sealed class ShellStream : Stream { private const int DefaultBufferSize = 1024; private readonly ISession _session; private readonly Encoding _encoding; private readonly IChannelSession _channel; private readonly byte[] _carriageReturnBytes; private readonly byte[] _lineFeedBytes; private readonly bool _noTerminal; private readonly object _sync = new object(); private ArrayBuffer _readBuffer; private ArrayBuffer _writeBuffer; private bool _disposed; public bool DataAvailable { get { lock (_sync) { return _readBuffer.ActiveLength > 0; } } } public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => !_disposed; public override long Length { get { lock (_sync) { return _readBuffer.ActiveLength; } } } public override long Position { get { return 0; } set { throw new NotSupportedException(); } } [Nullable(new byte[] { 2, 1 })] [method: Nullable(new byte[] { 2, 1 })] [field: Nullable(new byte[] { 2, 1 })] public event EventHandler<ShellDataEventArgs> DataReceived; [Nullable(new byte[] { 2, 1 })] [method: Nullable(new byte[] { 2, 1 })] [field: Nullable(new byte[] { 2, 1 })] public event EventHandler<ExceptionEventArgs> ErrorOccurred; [Nullable(new byte[] { 2, 1 })] [method: Nullable(new byte[] { 2, 1 })] [field: Nullable(new byte[] { 2, 1 })] public event EventHandler<EventArgs> Closed; internal ShellStream(ISession session, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModeValues, int bufferSize) : this(session, bufferSize, false) { try { _channel.Open(); if (!_channel.SendPseudoTerminalRequest(terminalName, columns, rows, width, height, terminalModeValues)) throw new SshException("The pseudo-terminal request was not accepted by the server. Consult the server log for more information."); if (!_channel.SendShellRequest()) throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information."); } catch { Dispose(); throw; } } internal ShellStream(ISession session, int bufferSize) : this(session, bufferSize, true) { try { _channel.Open(); if (!_channel.SendShellRequest()) throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information."); } catch { Dispose(); throw; } } private ShellStream(ISession session, int bufferSize, bool noTerminal) { if (bufferSize == -1) bufferSize = 1024; ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bufferSize, "bufferSize"); _encoding = session.ConnectionInfo.Encoding; _session = session; _carriageReturnBytes = _encoding.GetBytes("\r"); _lineFeedBytes = _encoding.GetBytes("\n"); _channel = _session.CreateChannelSession(); _channel.DataReceived += Channel_DataReceived; _channel.Closed += Channel_Closed; _session.Disconnected += Session_Disconnected; _session.ErrorOccured += Session_ErrorOccurred; _readBuffer = new ArrayBuffer(bufferSize, false); _writeBuffer = new ArrayBuffer(bufferSize, false); _noTerminal = noTerminal; } public override void Flush() { ThrowHelper.ThrowObjectDisposedIf(_disposed, this); if (_writeBuffer.ActiveLength > 0) { _channel.SendData(_writeBuffer.DangerousGetUnderlyingBuffer(), _writeBuffer.ActiveStartOffset, _writeBuffer.ActiveLength); _writeBuffer.Discard(_writeBuffer.ActiveLength); } } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public void ChangeWindowSize(uint columns, uint rows, uint width, uint height) { ThrowHelper.ThrowObjectDisposedIf(_disposed, this); _channel.SendWindowChangeRequest(columns, rows, width, height); } public void Expect(params ExpectAction[] expectActions) { Expect(Timeout.InfiniteTimeSpan, expectActions); } public void Expect(TimeSpan timeout, params ExpectAction[] expectActions) { ExpectRegex(timeout, -1, expectActions); } public void Expect(TimeSpan timeout, int lookback, params ExpectAction[] expectActions) { ExpectRegex(timeout, lookback, expectActions); } [return: Nullable(2)] public string Expect(string text) { return Expect(text, Timeout.InfiniteTimeSpan, -1); } [return: Nullable(2)] public string Expect(string text, TimeSpan timeout, int lookback = -1) { ValidateTimeout(timeout); ValidateLookback(lookback); DateTime d = DateTime.Now.Add(timeout); byte[] bytes = _encoding.GetBytes(text); lock (_sync) { while (true) { int num = (lookback != -1) ? Math.Max(0, _readBuffer.ActiveLength - lookback) : 0; int num2 = _readBuffer.ActiveReadOnlySpan.Slice(num).IndexOf(bytes); if (num2 >= 0) { int num3 = num + num2 + bytes.Length; string string = GetString(num3); _readBuffer.Discard(num3); return string; } if (_disposed) return null; if (timeout == Timeout.InfiniteTimeSpan) Monitor.Wait(_sync); else { TimeSpan timeSpan = d - DateTime.Now; if (timeSpan < TimeSpan.Zero || !Monitor.Wait(_sync, timeSpan)) break; } } return null; } } [return: Nullable(2)] public string Expect(Regex regex) { return Expect(regex, Timeout.InfiniteTimeSpan, -1); } [return: Nullable(2)] public string Expect(Regex regex, TimeSpan timeout, int lookback = -1) { return ExpectRegex(timeout, lookback, new ExpectAction[1] { new ExpectAction(regex, delegate { }) }); } [return: Nullable(2)] private string ExpectRegex(TimeSpan timeout, int lookback, ExpectAction[] expectActions) { ValidateTimeout(timeout); ValidateLookback(lookback); DateTime d = DateTime.Now.Add(timeout); lock (_sync) { while (true) { string string = GetString(_readBuffer.ActiveLength); int num = (lookback != -1) ? Math.Max(string.Length - lookback, 0) : 0; foreach (ExpectAction expectAction in expectActions) { Regex.ValueMatchEnumerator valueMatchEnumerator = expectAction.Expect.EnumerateMatches(string.AsSpan(num)); if (valueMatchEnumerator.MoveNext()) { ValueMatch current = valueMatchEnumerator.Current; string text = string.Substring(0, num + current.Index + current.Length); _readBuffer.Discard(_encoding.GetByteCount(text)); expectAction.Action(text); return text; } } if (_disposed) return null; if (timeout == Timeout.InfiniteTimeSpan) Monitor.Wait(_sync); else { TimeSpan timeSpan = d - DateTime.Now; if (timeSpan < TimeSpan.Zero || !Monitor.Wait(_sync, timeSpan)) break; } } return null; } } public IAsyncResult BeginExpect(params ExpectAction[] expectActions) { return BeginExpect(Timeout.InfiniteTimeSpan, null, null, expectActions); } public IAsyncResult BeginExpect([Nullable(2)] AsyncCallback callback, params ExpectAction[] expectActions) { return BeginExpect(Timeout.InfiniteTimeSpan, callback, null, expectActions); } public IAsyncResult BeginExpect([Nullable(2)] AsyncCallback callback, [Nullable(2)] object state, params ExpectAction[] expectActions) { return BeginExpect(Timeout.InfiniteTimeSpan, callback, state, expectActions); } public IAsyncResult BeginExpect(TimeSpan timeout, [Nullable(2)] AsyncCallback callback, [Nullable(2)] object state, params ExpectAction[] expectActions) { return BeginExpect(timeout, -1, callback, state, expectActions); } public IAsyncResult BeginExpect(TimeSpan timeout, int lookback, [Nullable(2)] AsyncCallback callback, [Nullable(2)] object state, params ExpectAction[] expectActions) { return TaskToAsyncResult.Begin(Task.Run(() => ExpectRegex(timeout, lookback, expectActions)), callback, state); } [return: Nullable(2)] public string EndExpect(IAsyncResult asyncResult) { return TaskToAsyncResult.End<string>(asyncResult); } [NullableContext(2)] public string ReadLine() { return ReadLine(Timeout.InfiniteTimeSpan); } [NullableContext(2)] public string ReadLine(TimeSpan timeout) { ValidateTimeout(timeout); DateTime d = DateTime.Now.Add(timeout); lock (_sync) { while (true) { int num = _readBuffer.ActiveReadOnlySpan.IndexOf(_carriageReturnBytes); if (num >= 0) { ReadOnlySpan<byte> activeReadOnlySpan; int num2; if (num + _carriageReturnBytes.Length + _lineFeedBytes.Length > _readBuffer.ActiveLength) { activeReadOnlySpan = _readBuffer.ActiveReadOnlySpan; num2 = activeReadOnlySpan.Slice(0, num).IndexOf(_lineFeedBytes); } else { activeReadOnlySpan = _readBuffer.ActiveReadOnlySpan; num2 = activeReadOnlySpan.Slice(0, num + _carriageReturnBytes.Length + _lineFeedBytes.Length).IndexOf(_lineFeedBytes); } int num3 = num2; if (num3 >= 0 && num3 < num) { string string = GetString(num3); _readBuffer.Discard(num3 + _lineFeedBytes.Length); return string; } if (num3 != num + _carriageReturnBytes.Length) { string string2 = GetString(num); _readBuffer.Discard(num + _carriageReturnBytes.Length); return string2; } string string3 = GetString(num); _readBuffer.Discard(num + _carriageReturnBytes.Length + _lineFeedBytes.Length); return string3; } int num4 = _readBuffer.ActiveReadOnlySpan.IndexOf(_lineFeedBytes); if (num4 >= 0) { string string4 = GetString(num4); _readBuffer.Discard(num4 + _lineFeedBytes.Length); return string4; } if (_disposed) { string result = (_readBuffer.ActiveLength == 0) ? null : GetString(_readBuffer.ActiveLength); _readBuffer.Discard(_readBuffer.ActiveLength); return result; } if (timeout == Timeout.InfiniteTimeSpan) Monitor.Wait(_sync); else { TimeSpan timeSpan = d - DateTime.Now; if (timeSpan < TimeSpan.Zero || !Monitor.Wait(_sync, timeSpan)) break; } } return null; } } private static void ValidateTimeout(TimeSpan timeout) { if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException("timeout", "Value must be non-negative or equal to -1 millisecond (for infinite timeout)"); } private static void ValidateLookback(int lookback) { if (lookback <= 0 && lookback != -1) throw new ArgumentOutOfRangeException("lookback", "Value must be positive or equal to -1 (for no window)"); } public string Read() { lock (_sync) { string string = GetString(_readBuffer.ActiveLength); _readBuffer.Discard(_readBuffer.ActiveLength); return string; } } public override int Read(byte[] buffer, int offset, int count) { Stream.ValidateBufferArguments(buffer, offset, count); return Read(buffer.AsSpan(offset, count)); } [NullableContext(0)] public override int Read(Span<byte> buffer) { lock (_sync) { while (_readBuffer.ActiveLength == 0 && !_disposed) { Monitor.Wait(_sync); } int num = Math.Min(buffer.Length, _readBuffer.ActiveLength); ReadOnlySpan<byte> readOnlySpan = _readBuffer.ActiveReadOnlySpan; readOnlySpan = readOnlySpan.Slice(0, num); readOnlySpan.CopyTo(buffer); _readBuffer.Discard(num); return num; } } public override int ReadByte() { byte reference = 0; if (Read(new Span<byte>(ref reference)) != 0) return reference; return -1; } private string GetString(int length) { return _encoding.GetString(_readBuffer.DangerousGetUnderlyingBuffer(), _readBuffer.ActiveStartOffset, length); } [NullableContext(2)] public void Write(string text) { if (text != null) { Write(_encoding.GetBytes(text)); Flush(); } } public override void Write(byte[] buffer, int offset, int count) { Stream.ValidateBufferArguments(buffer, offset, count); Write(buffer.AsSpan(offset, count)); } [NullableContext(0)] public override void Write(ReadOnlySpan<byte> buffer) { ThrowHelper.ThrowObjectDisposedIf(_disposed, this); while (!buffer.IsEmpty) { if (_writeBuffer.AvailableLength == 0) Flush(); int num = Math.Min(buffer.Length, _writeBuffer.AvailableLength); buffer.Slice(0, num).CopyTo(_writeBuffer.AvailableSpan); _writeBuffer.Commit(num); buffer = buffer.Slice(num); } } public override void WriteByte(byte value) { byte reference = value; Write(new ReadOnlySpan<byte>(ref reference)); } public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { Stream.ValidateBufferArguments(buffer, offset, count); return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); } [NullableContext(0)] [AsyncStateMachine(typeof(<WriteAsync>d__68))] public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) { <WriteAsync>d__68 stateMachine = default(<WriteAsync>d__68); stateMachine.<>t__builder = AsyncValueTaskMethodBuilder.Create(); stateMachine.<>4__this = this; stateMachine.buffer = buffer; stateMachine.cancellationToken = cancellationToken; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, [Nullable(2)] AsyncCallback callback, [Nullable(2)] object state) { return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count), callback, state); } public override void EndWrite(IAsyncResult asyncResult) { TaskToAsyncResult.End(asyncResult); } public void WriteLine(string line) { Write(line + (_noTerminal ? "\n" : "\r")); } protected override void Dispose(bool disposing) { if (!disposing) base.Dispose(disposing); else { lock (_sync) { if (_disposed) return; _disposed = true; _session.Disconnected -= Session_Disconnected; _session.ErrorOccured -= Session_ErrorOccurred; _channel.DataReceived -= Channel_DataReceived; _channel.Closed -= Channel_Closed; _channel.Dispose(); Monitor.PulseAll(_sync); } base.Dispose(disposing); } } private void Session_ErrorOccurred([Nullable(2)] object sender, ExceptionEventArgs e) { this.ErrorOccurred?.Invoke(this, e); } private void Session_Disconnected([Nullable(2)] object sender, EventArgs e) { Dispose(); } private void Channel_Closed([Nullable(2)] object sender, ChannelEventArgs e) { Dispose(); if (this.Closed != null) ThreadAbstraction.ExecuteThread(delegate { this.Closed?.Invoke(this, EventArgs.Empty); }); } private void Channel_DataReceived([Nullable(2)] object sender, ChannelDataEventArgs e) { ArraySegment<byte> data; lock (_sync) { ref ArrayBuffer readBuffer = ref _readBuffer; data = e.Data; readBuffer.EnsureAvailableSpace(data.Count); e.Data.AsSpan().CopyTo(_readBuffer.AvailableSpan); ref ArrayBuffer readBuffer2 = ref _readBuffer; data = e.Data; readBuffer2.Commit(data.Count); Monitor.PulseAll(_sync); } EventHandler<ShellDataEventArgs> dataReceived = this.DataReceived; if (dataReceived != null) { data = e.Data; dataReceived(this, new ShellDataEventArgs(data.ToArray())); } } } }