ShellStream
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()));
}
}
}
}