From c7490f8768e98f6deb6f8e8ac683ea0e6fb67d4a Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 29 Nov 2024 07:45:39 -0800 Subject: [PATCH 1/7] fix #1459 --- src/Listener/PodeHttpRequest.cs | 51 +++-- src/Listener/PodeRequest.cs | 316 ++++++++++++++---------------- src/Listener/PodeSignalRequest.cs | 34 +++- src/Listener/PodeSmtpRequest.cs | 43 +++- src/Listener/PodeTcpRequest.cs | 26 ++- 5 files changed, 271 insertions(+), 199 deletions(-) diff --git a/src/Listener/PodeHttpRequest.cs b/src/Listener/PodeHttpRequest.cs index 1742f0b17..dd5d6b091 100644 --- a/src/Listener/PodeHttpRequest.cs +++ b/src/Listener/PodeHttpRequest.cs @@ -412,22 +412,51 @@ public override void PartialDispose() base.PartialDispose(); } - public override void Dispose() + /* public override void Dispose() + { + RawBody = default; + _body = string.Empty; + + if (BodyStream != default(MemoryStream)) + { + BodyStream.Dispose(); + } + + if (Form != default(PodeForm)) + { + Form.Dispose(); + } + + base.Dispose(); + }*/ + + /// + /// Dispose managed and unmanaged resources. + /// + /// Indicates whether the method is called explicitly or by garbage collection. + protected override void Dispose(bool disposing) { - RawBody = default; - _body = string.Empty; - - if (BodyStream != default(MemoryStream)) + if (disposing) { - BodyStream.Dispose(); - } + // Custom cleanup logic for PodeHttpRequest + RawBody = default; + _body = string.Empty; - if (Form != default(PodeForm)) - { - Form.Dispose(); + if (BodyStream != null) + { + BodyStream.Dispose(); + BodyStream = null; + } + + if (Form != null) + { + Form.Dispose(); + Form = null; + } } - base.Dispose(); + // Call the base Dispose to clean up shared resources + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index f36a1d938..fe840e0b7 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -1,14 +1,13 @@ using System; using System.IO; using System.Net; -using System.Net.Http; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; namespace Pode { @@ -27,9 +26,8 @@ public class PodeRequest : PodeProtocol, IDisposable public bool IsKeepAlive { get; protected set; } // Flags indicating request characteristics and handling status - public virtual bool CloseImmediately { get => false; } - public virtual bool IsProcessable { get => true; } - + public virtual bool CloseImmediately => false; + public virtual bool IsProcessable => true; // Input stream for incoming request data public Stream InputStream { get; private set; } public PodeStreamState State { get; private set; } @@ -39,19 +37,17 @@ public class PodeRequest : PodeProtocol, IDisposable public X509Certificate Certificate { get; private set; } public bool AllowClientCertificate { get; private set; } public PodeTlsMode TlsMode { get; private set; } - public X509Certificate2 ClientCertificate { get; set; } - public SslPolicyErrors ClientCertificateErrors { get; set; } + public X509Certificate2 ClientCertificate { get; private set; } + public SslPolicyErrors ClientCertificateErrors { get; private set; } public SslProtocols Protocols { get; private set; } - - // Error handling for request processing public PodeRequestException Error { get; set; } public bool IsAborted => Error != default(PodeRequestException); public bool IsDisposed { get; private set; } // Address and Scheme properties for the request public virtual string Address => Context.PodeSocket.HasHostnames - ? $"{Context.PodeSocket.Hostname}:{((IPEndPoint)LocalEndPoint).Port}" - : $"{((IPEndPoint)LocalEndPoint).Address}:{((IPEndPoint)LocalEndPoint).Port}"; + ? $"{Context.PodeSocket.Hostname}:{((IPEndPoint)LocalEndPoint).Port}" + : $"{((IPEndPoint)LocalEndPoint).Address}:{((IPEndPoint)LocalEndPoint).Port}"; public virtual string Scheme => SslUpgraded ? $"{Context.PodeSocket.Type}s" : $"{Context.PodeSocket.Type}"; @@ -60,10 +56,15 @@ public class PodeRequest : PodeProtocol, IDisposable protected PodeContext Context; // Encoding and buffer for handling incoming data - protected static UTF8Encoding Encoding = new UTF8Encoding(); - private byte[] Buffer; + protected static readonly UTF8Encoding Encoding = new UTF8Encoding(); + + // A fixed buffer used to temporarily store data read from the input stream. + // This buffer is readonly to prevent reassignment and reduce memory allocations. + private readonly byte[] Buffer; + private MemoryStream BufferStream; private const int BufferSize = 16384; + private readonly SemaphoreSlim StreamLock = new SemaphoreSlim(1, 1); /// /// Initializes a new instance of the PodeRequest class. @@ -83,6 +84,7 @@ public PodeRequest(Socket socket, PodeSocket podeSocket, PodeContext context) Protocols = podeSocket.Protocols; Context = context; State = PodeStreamState.New; + Buffer = new byte[BufferSize]; // Allocate buffer once } /// @@ -145,6 +147,7 @@ public async Task Open(CancellationToken cancellationToken) } } + /// /// Upgrades the current connection to SSL/TLS. /// @@ -152,49 +155,62 @@ public async Task Open(CancellationToken cancellationToken) /// A Task representing the async operation. public async Task UpgradeToSSL(CancellationToken cancellationToken) { - if (SslUpgraded) + if (SslUpgraded || IsDisposed) { State = PodeStreamState.Open; return; // Already upgraded } // Create an SSL stream for secure communication - var ssl = new SslStream(InputStream, false, new RemoteCertificateValidationCallback(ValidateCertificateCallback)); + var ssl = new SslStream(InputStream, false, ValidateCertificateCallback); // Authenticate the SSL stream, handling cancellation and exceptions - using (cancellationToken.Register(() => ssl.Dispose())) + try { - try + using (cancellationToken.Register(() => { - // Authenticate the SSL stream - await ssl.AuthenticateAsServerAsync(Certificate, AllowClientCertificate, Protocols, false).ConfigureAwait(false); - - // Set InputStream to the upgraded SSL stream - InputStream = ssl; - SslUpgraded = true; - State = PodeStreamState.Open; - } - catch (Exception ex) when (ex is OperationCanceledException || ex is IOException || ex is ObjectDisposedException) - { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); - State = PodeStreamState.Error; - Error = new PodeRequestException(ex, 500); - } - catch (AuthenticationException ex) - { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug); - State = PodeStreamState.Error; - Error = new PodeRequestException(ex, 400); - } - catch (Exception ex) + if (ssl != null && !IsDisposed) + { + ssl.Dispose(); + } + })) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); - State = PodeStreamState.Error; - Error = new PodeRequestException(ex, 502); + + // Authenticate the SSL stream + await ssl.AuthenticateAsServerAsync(Certificate, AllowClientCertificate, Protocols, false) + .ConfigureAwait(false); } + + // Set InputStream to the upgraded SSL stream + InputStream = ssl; + SslUpgraded = true; + State = PodeStreamState.Open; + } + catch (Exception ex) when (ex is OperationCanceledException || ex is IOException || ex is ObjectDisposedException) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); + ssl?.Dispose(); + State = PodeStreamState.Error; + Error = new PodeRequestException(ex, 500); + } + + catch (AuthenticationException ex) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug); + ssl?.Dispose(); + State = PodeStreamState.Error; + Error = new PodeRequestException(ex, 400); + } + catch (Exception ex) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + ssl?.Dispose(); + State = PodeStreamState.Error; + Error = new PodeRequestException(ex, 502); } } + /// /// Callback to validate client certificates during the SSL handshake. /// @@ -221,42 +237,50 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific /// A Task representing the async operation, with a boolean indicating whether the connection should be closed. public async Task Receive(CancellationToken cancellationToken) { - // Check if the stream is open - if (State != PodeStreamState.Open) - { - return false; - } - + await StreamLock.WaitAsync(cancellationToken); try { - Error = default; + if (State != PodeStreamState.Open || InputStream == null) + { + return false; + } + + Error = null; - Buffer = new byte[BufferSize]; using (BufferStream = new MemoryStream()) { var close = true; while (true) { -#if NETCOREAPP2_1_OR_GREATER - // Read data from the input stream - var read = await InputStream.ReadAsync(Buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false); - if (read <= 0) + if (InputStream == null || cancellationToken.IsCancellationRequested || IsDisposed) { break; } - // Write the data to the buffer stream - await BufferStream.WriteAsync(Buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); + int read = 0; + try + { + // Read data from the input stream +#if NETCOREAPP2_1_OR_GREATER + read = await InputStream.ReadAsync(Buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false); #else - // Read data from the input stream - var read = await InputStream.ReadAsync(Buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false); + read = await InputStream.ReadAsync(Buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false); +#endif + } + catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug); + break; + } if (read <= 0) { break; } - // Write the data to the buffer stream +#if NETCOREAPP2_1_OR_GREATER + await BufferStream.WriteAsync(Buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); +#else await BufferStream.WriteAsync(Buffer, 0, read, cancellationToken).ConfigureAwait(false); #endif @@ -292,6 +316,10 @@ public async Task Receive(CancellationToken cancellationToken) PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); Error = ex; } + catch (ObjectDisposedException ex) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + } catch (Exception ex) { PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); @@ -299,101 +327,12 @@ public async Task Receive(CancellationToken cancellationToken) } finally { + StreamLock.Release(); PartialDispose(); } - return false; } - /// - /// Reads data from the input stream until the specified bytes are found. - /// - /// The bytes to check for in the input stream. - /// Token to monitor for cancellation requests. - /// A Task representing the async operation, with a string containing the data read. - public async Task Read(byte[] checkBytes, CancellationToken cancellationToken) - { - // Check if the stream is open - if (State != PodeStreamState.Open) - { - return string.Empty; - } - - // Read data from the input stream until the check bytes are found - var buffer = new byte[BufferSize]; - using (var bufferStream = new MemoryStream()) - { - while (true) - { -#if NETCOREAPP2_1_OR_GREATER - // Read data from the input stream - var read = await InputStream.ReadAsync(buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false); - if (read <= 0) - { - break; - } - - // Write the data to the buffer stream - await bufferStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); -#else - // Read data from the input stream - var read = await InputStream.ReadAsync(buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false); - if (read <= 0) - { - break; - } - - // Write the data to the buffer stream - await bufferStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); -#endif - // Validate the input data - if (Socket.Available > 0 || !ValidateInputInternal(bufferStream.ToArray(), checkBytes)) - { - continue; - } - - break; - } - - return Encoding.GetString(bufferStream.ToArray()).Trim(); - } - } - - /// - /// Validates the input bytes against the specified check bytes. - /// - /// The bytes to validate. - /// The bytes to check against. - /// True if validation is successful, otherwise false. - private static bool ValidateInputInternal(byte[] bytes, byte[] checkBytes) - { - if (bytes.Length == 0) - { - return false; // Need more bytes - } - - if (checkBytes == default(byte[]) || checkBytes.Length == 0) - { - return true; // No specific bytes to check - } - - if (bytes.Length < checkBytes.Length) - { - return false; // Not enough bytes - } - - // Check if the input ends with checkBytes - for (var i = 0; i < checkBytes.Length; i++) - { - if (bytes[bytes.Length - (checkBytes.Length - i)] != checkBytes[i]) - { - return false; - } - } - - return true; - } - /// /// Parses the received bytes. This method should be implemented in derived classes. /// @@ -421,41 +360,78 @@ protected virtual bool ValidateInput(byte[] bytes) /// public virtual void PartialDispose() { - if (BufferStream != default(MemoryStream)) + try { - BufferStream.Dispose(); - BufferStream = default; - } + if (BufferStream != default(MemoryStream)) + { + BufferStream.Dispose(); + BufferStream = default; + } - Buffer = default; + // Clear the contents of the Buffer array + if (Buffer != null) + { + Array.Clear(Buffer, 0, Buffer.Length); + } + + } + catch (Exception ex) + { + PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + } } /// - /// Disposes of the request and its associated resources. + /// Dispose managed and unmanaged resources. /// - public virtual void Dispose() + /// Indicates if disposing is called manually or by garbage collection. + protected virtual void Dispose(bool disposing) { - if (IsDisposed) + if (!IsDisposed) { - return; - } + if (disposing) + { + // Dispose managed resources + if (InputStream != default(Stream)) + { + State = PodeStreamState.Closed; + InputStream.Dispose(); + InputStream = default; + } - IsDisposed = true; + if (Socket != default(Socket)) + { + PodeSocket.CloseSocket(Socket); + Socket = default; + } - if (Socket != default(Socket)) - { - PodeSocket.CloseSocket(Socket); - } + PartialDispose(); - if (InputStream != default(Stream)) - { - State = PodeStreamState.Closed; - InputStream.Dispose(); - InputStream = default; + // Dispose SemaphoreSlim + StreamLock.Dispose(); + } + + // Dispose unmanaged resources if any + IsDisposed = true; } + } + + /// + /// Disposes of the request and its associated resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - PartialDispose(); - PodeHelpers.WriteErrorMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); + /// + /// Finalizer to release unmanaged resources. + /// + ~PodeRequest() + { + Dispose(false); } + } } diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index f7acc49cb..dd593d471 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -127,18 +127,40 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel return true; } - public override void Dispose() + /* public override void Dispose() + { + // send close frame + if (!IsDisposed) + { + PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); + Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait(); + } + + // remove client, and dispose + Context.Listener.Signals.Remove(Signal.ClientId); + base.Dispose(); + }*/ + + /// + /// Dispose managed and unmanaged resources. + /// + /// Indicates if the method is called explicitly or by garbage collection. + protected override void Dispose(bool disposing) { - // send close frame - if (!IsDisposed) + if (disposing && !IsDisposed) { + // Send close frame PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); + + // Wait for the close frame to be sent Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait(); + + // Remove the client signal + Context.Listener.Signals.Remove(Signal.ClientId); } - // remove client, and dispose - Context.Listener.Signals.Remove(Signal.ClientId); - base.Dispose(); + // Call the base Dispose to clean up other resources + base.Dispose(disposing); } } diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index 219a88640..cb9c8b425 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -516,20 +516,47 @@ private string ConvertBodyType(byte[] bytes, string contentType) } } - public override void Dispose() - { - RawBody = default; - Body = string.Empty; + /* public override void Dispose() + { + RawBody = default; + Body = string.Empty; - if (Attachments != default(List)) + if (Attachments != default(List)) + { + foreach (var attachment in Attachments) + { + attachment.Dispose(); + } + } + + base.Dispose(); + }*/ + + /// + /// Dispose managed and unmanaged resources. + /// + /// Indicates if the method is called explicitly or by garbage collection. + protected override void Dispose(bool disposing) + { + if (disposing) { - foreach (var attachment in Attachments) + // Custom cleanup logic for PodeSmtpRequest + RawBody = default; + Body = string.Empty; + + if (Attachments != null) { - attachment.Dispose(); + foreach (var attachment in Attachments) + { + attachment.Dispose(); + } + + Attachments = null; } } - base.Dispose(); + // Call the base Dispose to clean up other resources + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs index c48982538..df18de9bd 100644 --- a/src/Listener/PodeTcpRequest.cs +++ b/src/Listener/PodeTcpRequest.cs @@ -76,11 +76,29 @@ public void Close() Context.Dispose(true); } - public override void Dispose() + /* public override void Dispose() + { + RawBody = default; + _body = string.Empty; + base.Dispose(); + }*/ + + /// + /// Dispose managed and unmanaged resources. + /// + /// Indicates if the method is called explicitly or by garbage collection. + protected override void Dispose(bool disposing) { - RawBody = default; - _body = string.Empty; - base.Dispose(); + if (disposing) + { + // Custom cleanup logic for PodeTcpRequest + RawBody = default; + _body = string.Empty; + } + + // Call the base Dispose to clean up other resources + base.Dispose(disposing); } + } } \ No newline at end of file From 0e4cc8b11f077ccc966ea96af5fe6166ec485dca Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 29 Nov 2024 16:58:21 -0800 Subject: [PATCH 2/7] Code cleanup --- examples/Web-SelfSigned.ps1 | 39 +++++++++++++++++++++++++++++++ src/Listener/PodeHttpRequest.cs | 18 -------------- src/Listener/PodeSignalRequest.cs | 14 ----------- src/Listener/PodeSmtpRequest.cs | 28 +++++----------------- src/Listener/PodeTcpRequest.cs | 7 ------ 5 files changed, 45 insertions(+), 61 deletions(-) create mode 100644 examples/Web-SelfSigned.ps1 diff --git a/examples/Web-SelfSigned.ps1 b/examples/Web-SelfSigned.ps1 new file mode 100644 index 000000000..94a2e6356 --- /dev/null +++ b/examples/Web-SelfSigned.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + A sample PowerShell script to set up a HTTPS Pode server with a self-sign certificate + +.DESCRIPTION + This script sets up a Pode server listening on port 8081 in HTTPS + +.EXAMPLE + To run the sample: ./Web-SelfSigned.ps1 + +.LINK + https://github.com/Badgerati/Pode/blob/develop/examples/Web-SelfSigned.ps1 + +.NOTES + Author: Pode Team + License: MIT License +#> +try { + $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) + $podePath = Split-Path -Parent -Path $ScriptPath + if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) { + Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop + } + else { + Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop + } +} +catch { throw } + +Start-PodeServer -Threads 6 { + Add-PodeEndpoint -Address localhost -Port '8081' -Protocol 'Https' -SelfSigned + + New-PodeLoggingMethod -File -Name 'requests' | Enable-PodeRequestLogging + New-PodeLoggingMethod -File -Name 'errors' | Enable-PodeErrorLogging + + Add-PodeRoute -Method Get -Path / -ScriptBlock { + Write-PodeTextResponse -Value 'Test' + } +} \ No newline at end of file diff --git a/src/Listener/PodeHttpRequest.cs b/src/Listener/PodeHttpRequest.cs index dd5d6b091..916f34b79 100644 --- a/src/Listener/PodeHttpRequest.cs +++ b/src/Listener/PodeHttpRequest.cs @@ -412,24 +412,6 @@ public override void PartialDispose() base.PartialDispose(); } - /* public override void Dispose() - { - RawBody = default; - _body = string.Empty; - - if (BodyStream != default(MemoryStream)) - { - BodyStream.Dispose(); - } - - if (Form != default(PodeForm)) - { - Form.Dispose(); - } - - base.Dispose(); - }*/ - /// /// Dispose managed and unmanaged resources. /// diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index dd593d471..7e030dfd9 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -127,20 +127,6 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel return true; } - /* public override void Dispose() - { - // send close frame - if (!IsDisposed) - { - PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); - Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait(); - } - - // remove client, and dispose - Context.Listener.Signals.Remove(Signal.ClientId); - base.Dispose(); - }*/ - /// /// Dispose managed and unmanaged resources. /// diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index cb9c8b425..5259bcf21 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -50,7 +50,7 @@ public PodeSmtpRequest(Socket socket, PodeSocket podeSocket, PodeContext context Type = PodeProtocolType.Smtp; } - private bool IsCommand(string content, string command) + private static bool IsCommand(string content, string command) { if (string.IsNullOrWhiteSpace(content)) { @@ -291,7 +291,7 @@ public void Reset() Attachments = new List(); } - private string ParseEmail(string value) + private static string ParseEmail(string value) { var parts = value.Split(':'); if (parts.Length > 1) @@ -364,7 +364,7 @@ private Hashtable ParseHeaders(string value) return headers; } - private bool IsBodyValid(string value) + private static bool IsBodyValid(string value) { var lines = value.Split(new string[] { PodeHelpers.NEW_LINE }, StringSplitOptions.None); return Array.LastIndexOf(lines, ".") > -1; @@ -423,7 +423,7 @@ private void ParseBoundary() } } - private string ParseBody(string value, string boundary = null) + private static string ParseBody(string value, string boundary = null) { // split the message up var lines = value.Split(new string[] { PodeHelpers.NEW_LINE }, StringSplitOptions.None); @@ -455,7 +455,7 @@ private string ParseBody(string value, string boundary = null) return body; } - private byte[] ConvertBodyEncoding(string body, string contentEncoding) + private static byte[] ConvertBodyEncoding(string body, string contentEncoding) { switch (contentEncoding.ToLowerInvariant()) { @@ -476,7 +476,7 @@ private byte[] ConvertBodyEncoding(string body, string contentEncoding) } } - private string ConvertBodyType(byte[] bytes, string contentType) + private static string ConvertBodyType(byte[] bytes, string contentType) { if (bytes == default(byte[]) || bytes.Length == 0) { @@ -516,22 +516,6 @@ private string ConvertBodyType(byte[] bytes, string contentType) } } - /* public override void Dispose() - { - RawBody = default; - Body = string.Empty; - - if (Attachments != default(List)) - { - foreach (var attachment in Attachments) - { - attachment.Dispose(); - } - } - - base.Dispose(); - }*/ - /// /// Dispose managed and unmanaged resources. /// diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs index df18de9bd..847805e5a 100644 --- a/src/Listener/PodeTcpRequest.cs +++ b/src/Listener/PodeTcpRequest.cs @@ -76,13 +76,6 @@ public void Close() Context.Dispose(true); } - /* public override void Dispose() - { - RawBody = default; - _body = string.Empty; - base.Dispose(); - }*/ - /// /// Dispose managed and unmanaged resources. /// From acf7a90b0a3162fe46f2afc48aebf1c9a42eddc3 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 2 Dec 2024 09:26:26 -0800 Subject: [PATCH 3/7] applied suggested changes --- src/Listener/PodeHttpRequest.cs | 10 ++- src/Listener/PodeRequest.cs | 142 ++++++++++++++++++++++-------- src/Listener/PodeSignalRequest.cs | 4 +- src/Listener/PodeSmtpRequest.cs | 2 + src/Listener/PodeTcpRequest.cs | 1 + 5 files changed, 118 insertions(+), 41 deletions(-) diff --git a/src/Listener/PodeHttpRequest.cs b/src/Listener/PodeHttpRequest.cs index 916f34b79..6c3eee6b5 100644 --- a/src/Listener/PodeHttpRequest.cs +++ b/src/Listener/PodeHttpRequest.cs @@ -418,22 +418,24 @@ public override void PartialDispose() /// Indicates whether the method is called explicitly or by garbage collection. protected override void Dispose(bool disposing) { + if (IsDisposed) return; + if (disposing) { // Custom cleanup logic for PodeHttpRequest RawBody = default; _body = string.Empty; - if (BodyStream != null) + if (BodyStream != default(MemoryStream)) { BodyStream.Dispose(); - BodyStream = null; + BodyStream = default; } - if (Form != null) + if (Form != default(PodeForm)) { Form.Dispose(); - Form = null; + Form = default; } } diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index fe840e0b7..c99cc3c14 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -236,8 +236,7 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific /// Token to monitor for cancellation requests. /// A Task representing the async operation, with a boolean indicating whether the connection should be closed. public async Task Receive(CancellationToken cancellationToken) - { - await StreamLock.WaitAsync(cancellationToken); + { try { if (State != PodeStreamState.Open || InputStream == null) @@ -316,10 +315,6 @@ public async Task Receive(CancellationToken cancellationToken) PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); Error = ex; } - catch (ObjectDisposedException ex) - { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); - } catch (Exception ex) { PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); @@ -333,6 +328,96 @@ public async Task Receive(CancellationToken cancellationToken) return false; } + + /// + /// Reads data from the input stream until the specified bytes are found. + /// + /// The bytes to check for in the input stream. + /// Token to monitor for cancellation requests. + /// A Task representing the async operation, with a string containing the data read. + public async Task Read(byte[] checkBytes, CancellationToken cancellationToken) + { + // Check if the stream is open + if (State != PodeStreamState.Open) + { + return string.Empty; + } + + // Read data from the input stream until the check bytes are found + var buffer = new byte[BufferSize]; + using (var bufferStream = new MemoryStream()) + { + while (true) + { +#if NETCOREAPP2_1_OR_GREATER + // Read data from the input stream + var read = await InputStream.ReadAsync(buffer.AsMemory(0, BufferSize), cancellationToken).ConfigureAwait(false); + if (read <= 0) + { + break; + } + + // Write the data to the buffer stream + await bufferStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); +#else + // Read data from the input stream + var read = await InputStream.ReadAsync(buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false); + if (read <= 0) + { + break; + } + + // Write the data to the buffer stream + await bufferStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false); +#endif + // Validate the input data + if (Socket.Available > 0 || !ValidateInputInternal(bufferStream.ToArray(), checkBytes)) + { + continue; + } + + break; + } + + return Encoding.GetString(bufferStream.ToArray()).Trim(); + } + } + + /// + /// Validates the input bytes against the specified check bytes. + /// + /// The bytes to validate. + /// The bytes to check against. + /// True if validation is successful, otherwise false. + private static bool ValidateInputInternal(byte[] bytes, byte[] checkBytes) + { + if (bytes.Length == 0) + { + return false; // Need more bytes + } + + if (checkBytes == default(byte[]) || checkBytes.Length == 0) + { + return true; // No specific bytes to check + } + + if (bytes.Length < checkBytes.Length) + { + return false; // Not enough bytes + } + + // Check if the input ends with checkBytes + for (var i = 0; i < checkBytes.Length; i++) + { + if (bytes[bytes.Length - (checkBytes.Length - i)] != checkBytes[i]) + { + return false; + } + } + + return true; + } + /// /// Parses the received bytes. This method should be implemented in derived classes. /// @@ -387,32 +472,26 @@ public virtual void PartialDispose() /// Indicates if disposing is called manually or by garbage collection. protected virtual void Dispose(bool disposing) { - if (!IsDisposed) - { - if (disposing) - { - // Dispose managed resources - if (InputStream != default(Stream)) - { - State = PodeStreamState.Closed; - InputStream.Dispose(); - InputStream = default; - } + if (IsDisposed) return; - if (Socket != default(Socket)) - { - PodeSocket.CloseSocket(Socket); - Socket = default; - } + IsDisposed = true; - PartialDispose(); + if (disposing) + { + if (InputStream != default(Stream)) + { + InputStream.Dispose(); + InputStream = default; + } - // Dispose SemaphoreSlim - StreamLock.Dispose(); + if (Socket != default(Socket)) + { + PodeSocket.CloseSocket(Socket); + Socket = default; } - // Dispose unmanaged resources if any - IsDisposed = true; + PartialDispose(); + StreamLock.Dispose(); } } @@ -424,14 +503,5 @@ public void Dispose() Dispose(true); GC.SuppressFinalize(this); } - - /// - /// Finalizer to release unmanaged resources. - /// - ~PodeRequest() - { - Dispose(false); - } - } } diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index 7e030dfd9..13e2fa420 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -133,7 +133,9 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel /// Indicates if the method is called explicitly or by garbage collection. protected override void Dispose(bool disposing) { - if (disposing && !IsDisposed) + if (IsDisposed) return; + + if (disposing) { // Send close frame PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index 5259bcf21..73ccee9b2 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -522,6 +522,8 @@ private static string ConvertBodyType(byte[] bytes, string contentType) /// Indicates if the method is called explicitly or by garbage collection. protected override void Dispose(bool disposing) { + if (IsDisposed) return; + if (disposing) { // Custom cleanup logic for PodeSmtpRequest diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs index 847805e5a..debd03b94 100644 --- a/src/Listener/PodeTcpRequest.cs +++ b/src/Listener/PodeTcpRequest.cs @@ -82,6 +82,7 @@ public void Close() /// Indicates if the method is called explicitly or by garbage collection. protected override void Dispose(bool disposing) { + if (IsDisposed) return; if (disposing) { // Custom cleanup logic for PodeTcpRequest From f2cc6bd1c2816194aeabc3b367b24228247ab2b6 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 3 Dec 2024 07:49:41 -0800 Subject: [PATCH 4/7] reinstate await StreamLock.WaitAsync(cancellationToken); --- src/Listener/PodeRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index c99cc3c14..5bf3dddfc 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -236,7 +236,8 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific /// Token to monitor for cancellation requests. /// A Task representing the async operation, with a boolean indicating whether the connection should be closed. public async Task Receive(CancellationToken cancellationToken) - { + { + await StreamLock.WaitAsync(cancellationToken); try { if (State != PodeStreamState.Open || InputStream == null) From 609c2fff84cebf9dd6395a20706f5ad48c13722b Mon Sep 17 00:00:00 2001 From: mdaneri Date: Thu, 16 Jan 2025 17:20:04 -0800 Subject: [PATCH 5/7] Update PodeRequest.cs Removed the StreamLock semaphore --- src/Listener/PodeRequest.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index 5bf3dddfc..7fd7e4461 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -64,7 +64,6 @@ public class PodeRequest : PodeProtocol, IDisposable private MemoryStream BufferStream; private const int BufferSize = 16384; - private readonly SemaphoreSlim StreamLock = new SemaphoreSlim(1, 1); /// /// Initializes a new instance of the PodeRequest class. @@ -237,7 +236,6 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific /// A Task representing the async operation, with a boolean indicating whether the connection should be closed. public async Task Receive(CancellationToken cancellationToken) { - await StreamLock.WaitAsync(cancellationToken); try { if (State != PodeStreamState.Open || InputStream == null) @@ -323,14 +321,13 @@ public async Task Receive(CancellationToken cancellationToken) } finally { - StreamLock.Release(); PartialDispose(); } return false; } - /// + /// /// Reads data from the input stream until the specified bytes are found. /// /// The bytes to check for in the input stream. @@ -492,7 +489,6 @@ protected virtual void Dispose(bool disposing) } PartialDispose(); - StreamLock.Dispose(); } } From c9f47005d3daf9ba6d266360e5eed4c52a2e9817 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 22 Feb 2025 07:47:50 -0800 Subject: [PATCH 6/7] Update PodeRequest.cs --- src/Listener/PodeRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index 7fd7e4461..6f9c39289 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -37,8 +37,8 @@ public class PodeRequest : PodeProtocol, IDisposable public X509Certificate Certificate { get; private set; } public bool AllowClientCertificate { get; private set; } public PodeTlsMode TlsMode { get; private set; } - public X509Certificate2 ClientCertificate { get; private set; } - public SslPolicyErrors ClientCertificateErrors { get; private set; } + public X509Certificate2 ClientCertificate { get; set; } + public SslPolicyErrors ClientCertificateErrors { get; set; } public SslProtocols Protocols { get; private set; } public PodeRequestException Error { get; set; } public bool IsAborted => Error != default(PodeRequestException); @@ -168,9 +168,9 @@ public async Task UpgradeToSSL(CancellationToken cancellationToken) { using (cancellationToken.Register(() => { - if (ssl != null && !IsDisposed) + if (!IsDisposed) { - ssl.Dispose(); + ssl?.Dispose(); } })) { From b153f1bb13d8effdfa44fa9a41da457d47e77bc8 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 22 Feb 2025 08:08:00 -0800 Subject: [PATCH 7/7] Update PodeRequest.cs --- src/Listener/PodeRequest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index 6f9c39289..021d7ea1a 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -478,6 +478,7 @@ protected virtual void Dispose(bool disposing) { if (InputStream != default(Stream)) { + State = PodeStreamState.Closed; InputStream.Dispose(); InputStream = default; }