From 4916bb6282a8be31101fed14f640d55886f5f1a6 Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Fri, 25 Jul 2025 23:08:47 +0200 Subject: [PATCH 1/3] Add configurable max message size enforcement Introduces IMaxMessageSizeOptions, MaxMessageSizeOptions, and MaxMessageSizeHandling to allow configuration of maximum allowed message size and handling strategy. Updates relevant protocol and IO classes to enforce the limit, throwing MaxMessageSizeExceededException when exceeded in strict mode. Adjusts SmtpServerOptionsBuilder and ISmtpServerOptions to use the new options, and adds a test for strict enforcement. Based on this existing pull request of @boba2fett - https://github.com/cosullivan/SmtpServer/pull/213/files#diff-3ef8e28e7d91fa5d1ac55f908d172fc69242455c3a25d0a009c79cadc71916a3 --- Src/SmtpServer.Tests/PipeReaderTests.cs | 21 ++++++--- Src/SmtpServer.Tests/SmtpServerTests.cs | 16 +++++++ Src/SmtpServer/IMaxMessageSizeOptions.cs | 19 ++++++++ Src/SmtpServer/IO/PipeReaderExtensions.cs | 33 +++++++++----- Src/SmtpServer/ISmtpServerOptions.cs | 4 +- Src/SmtpServer/MaxMessageSizeHandling.cs | 18 ++++++++ Src/SmtpServer/MaxMessageSizeOptions.cs | 44 +++++++++++++++++++ Src/SmtpServer/Protocol/AuthCommand.cs | 10 ++--- Src/SmtpServer/Protocol/DataCommand.cs | 3 +- Src/SmtpServer/Protocol/EhloCommand.cs | 4 +- Src/SmtpServer/Protocol/MailCommand.cs | 2 +- .../MaxMessageSizeExceededException.cs | 12 +++++ Src/SmtpServer/SmtpServerOptionsBuilder.cs | 14 +++--- Src/SmtpServer/SmtpSession.cs | 1 + 14 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 Src/SmtpServer/IMaxMessageSizeOptions.cs create mode 100644 Src/SmtpServer/MaxMessageSizeHandling.cs create mode 100644 Src/SmtpServer/MaxMessageSizeOptions.cs create mode 100644 Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs diff --git a/Src/SmtpServer.Tests/PipeReaderTests.cs b/Src/SmtpServer.Tests/PipeReaderTests.cs index e6c4b76..b9a085a 100644 --- a/Src/SmtpServer.Tests/PipeReaderTests.cs +++ b/Src/SmtpServer.Tests/PipeReaderTests.cs @@ -24,8 +24,10 @@ public async Task CanReadLineAndRemoveTrailingCRLF() // arrange var reader = CreatePipeReader("abcde\r\n"); + var maxMessageSizeOptions = new MaxMessageSizeOptions(); + // act - var line = await reader.ReadLineAsync(Encoding.ASCII).ConfigureAwait(false); + var line = await reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions).ConfigureAwait(false); // assert Assert.Equal(5, line.Length); @@ -39,8 +41,10 @@ public async Task CanReadLinesWithInconsistentCRLF() // arrange var reader = CreatePipeReader("ab\rcd\ne\r\n"); + var maxMessageSizeOptions = new MaxMessageSizeOptions(); + // act - var line = await reader.ReadLineAsync(Encoding.ASCII).ConfigureAwait(false); + var line = await reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions).ConfigureAwait(false); // assert Assert.Equal(7, line.Length); @@ -54,10 +58,12 @@ public async Task CanReadMultipleLines() // arrange var reader = CreatePipeReader("abcde\r\nfghij\r\nklmno\r\n"); + var maxMessageSizeOptions = new MaxMessageSizeOptions(); + // act - var line1 = await reader.ReadLineAsync(Encoding.ASCII).ConfigureAwait(false); - var line2 = await reader.ReadLineAsync(Encoding.ASCII).ConfigureAwait(false); - var line3 = await reader.ReadLineAsync(Encoding.ASCII).ConfigureAwait(false); + var line1 = await reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions).ConfigureAwait(false); + var line2 = await reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions).ConfigureAwait(false); + var line3 = await reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions).ConfigureAwait(false); // assert Assert.Equal("abcde", line1); @@ -71,6 +77,8 @@ public async Task CanReadBlockWithDotStuffingRemoved() // arrange var reader = CreatePipeReader("abcd\r\n..1234\r\n.\r\n"); + var maxMessageSizeOptions = new MaxMessageSizeOptions(); + // act var text = ""; await reader.ReadDotBlockAsync( @@ -79,7 +87,8 @@ await reader.ReadDotBlockAsync( text = StringUtil.Create(buffer); return Task.CompletedTask; - }); + }, + maxMessageSizeOptions); // assert Assert.Equal("abcd\r\n.1234", text); diff --git a/Src/SmtpServer.Tests/SmtpServerTests.cs b/Src/SmtpServer.Tests/SmtpServerTests.cs index 300bf03..8b9fca2 100644 --- a/Src/SmtpServer.Tests/SmtpServerTests.cs +++ b/Src/SmtpServer.Tests/SmtpServerTests.cs @@ -1,4 +1,5 @@ using MailKit; +using MailKit.Net.Smtp; using SmtpServer.Authentication; using SmtpServer.ComponentModel; using SmtpServer.Mail; @@ -9,6 +10,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; @@ -162,6 +164,20 @@ public void WillTimeoutWaitingForCommand() } } + [Fact] + public void WillTerminateDueToTooMuchData() + { + var maxAcceptedMailMessageSize = 50; + + var largeMailContent = string.Concat(Enumerable.Repeat("Too long for 1024 bytes", 1000)); + using var mailMessage = MailClient.Message(from: "test1@test.com", to: "test2@test.com", text: largeMailContent); + + using (CreateServer(c => c.MaxMessageSize(maxAcceptedMailMessageSize, MaxMessageSizeHandling.Strict))) + { + Assert.Throws(() => MailClient.Send(mailMessage)); + } + } + [Fact] public async Task WillSessionTimeoutDuringMailDataTransmission() { diff --git a/Src/SmtpServer/IMaxMessageSizeOptions.cs b/Src/SmtpServer/IMaxMessageSizeOptions.cs new file mode 100644 index 0000000..a8630e9 --- /dev/null +++ b/Src/SmtpServer/IMaxMessageSizeOptions.cs @@ -0,0 +1,19 @@ +namespace SmtpServer +{ + /// + /// Defines configuration options for enforcing a maximum allowed message size according to the SMTP SIZE extension (RFC 1870). + /// Includes the size limit in bytes and the handling strategy for oversized messages. + /// + public interface IMaxMessageSizeOptions + { + /// + /// Gets or sets the maximum allowed message size in bytes. + /// + int Length { get; } + + /// + /// Gets the handling type an oversized message. + /// + MaxMessageSizeHandling Handling { get; } + } +} diff --git a/Src/SmtpServer/IO/PipeReaderExtensions.cs b/Src/SmtpServer/IO/PipeReaderExtensions.cs index d88427d..c34882e 100644 --- a/Src/SmtpServer/IO/PipeReaderExtensions.cs +++ b/Src/SmtpServer/IO/PipeReaderExtensions.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using SmtpServer.Protocol; using SmtpServer.Text; namespace SmtpServer.IO @@ -21,9 +22,10 @@ internal static class PipeReaderExtensions /// The reader to read from. /// The sequence to find to terminate the read operation. /// The callback to execute to process the buffer. + /// Handling of MaxMessageSize /// The cancellation token. /// The value that was read from the buffer. - static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func, Task> func, CancellationToken cancellationToken) + static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken) { if (reader == null) { @@ -35,6 +37,11 @@ static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func maxMessageSizeOptions.Length) + { + throw new MaxMessageSizeExceededException(); + } + if (read.Buffer.TryFind(sequence, ref head, out var tail)) { try @@ -60,32 +67,34 @@ static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func /// The reader to read from. /// The action to process the buffer. + /// Handling of MaxMessageSize /// The cancellation token. /// A task that can be used to wait on the operation on complete. - internal static ValueTask ReadLineAsync(this PipeReader reader, Func, Task> func, CancellationToken cancellationToken = default) + internal static ValueTask ReadLineAsync(this PipeReader reader, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } - return ReadUntilAsync(reader, CRLF, func, cancellationToken); + return ReadUntilAsync(reader, CRLF, func, maxMessageSizeOptions, cancellationToken); } /// /// Reads a line from the reader. /// /// The reader to read from. + /// Handling of MaxMessageSize /// The cancellation token. /// A task that can be used to wait on the operation on complete. - internal static ValueTask ReadLineAsync(this PipeReader reader, CancellationToken cancellationToken = default) + internal static ValueTask ReadLineAsync(this PipeReader reader, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } - return reader.ReadLineAsync(Encoding.ASCII, cancellationToken); + return reader.ReadLineAsync(Encoding.ASCII, maxMessageSizeOptions, cancellationToken); } /// @@ -93,9 +102,10 @@ internal static ValueTask ReadLineAsync(this PipeReader reader, Cancella /// /// The reader to read from. /// The encoding to use when converting the input. + /// Handling of MaxMessageSize /// The cancellation token. /// A task that can be used to wait on the operation on complete. - internal static async ValueTask ReadLineAsync(this PipeReader reader, Encoding encoding, CancellationToken cancellationToken = default) + internal static async ValueTask ReadLineAsync(this PipeReader reader, Encoding encoding, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) { if (reader == null) { @@ -111,6 +121,7 @@ await reader.ReadLineAsync( return Task.CompletedTask; }, + maxMessageSizeOptions, cancellationToken); return text; @@ -121,9 +132,10 @@ await reader.ReadLineAsync( /// /// The reader to read from. /// The action to process the buffer. + /// Handling of MaxMessageSize /// The cancellation token. /// The value that was read from the buffer. - internal static async ValueTask ReadDotBlockAsync(this PipeReader reader, Func, Task> func, CancellationToken cancellationToken = default) + internal static async ValueTask ReadDotBlockAsync(this PipeReader reader, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) { if (reader == null) { @@ -131,14 +143,15 @@ internal static async ValueTask ReadDotBlockAsync(this PipeReader reader, Func { buffer = Unstuff(buffer); return func(buffer); - }, + }, + maxMessageSizeOptions, cancellationToken); static ReadOnlySequence Unstuff(ReadOnlySequence buffer) diff --git a/Src/SmtpServer/ISmtpServerOptions.cs b/Src/SmtpServer/ISmtpServerOptions.cs index f253484..b0c1daa 100644 --- a/Src/SmtpServer/ISmtpServerOptions.cs +++ b/Src/SmtpServer/ISmtpServerOptions.cs @@ -9,9 +9,9 @@ namespace SmtpServer public interface ISmtpServerOptions { /// - /// Gets the maximum size of a message. + /// Gets the maximum message size option. /// - int MaxMessageSize { get; } + IMaxMessageSizeOptions MaxMessageSizeOptions { get; } /// /// The maximum number of retries before quitting the session. diff --git a/Src/SmtpServer/MaxMessageSizeHandling.cs b/Src/SmtpServer/MaxMessageSizeHandling.cs new file mode 100644 index 0000000..fdb6652 --- /dev/null +++ b/Src/SmtpServer/MaxMessageSizeHandling.cs @@ -0,0 +1,18 @@ +namespace SmtpServer +{ + /// + /// Choose how MaxMessageSize limit should be considered + /// + public enum MaxMessageSizeHandling + { + /// + /// Use the size limit for the SIZE extension of ESMTP + /// + Ignore = 0, + + /// + /// Close the session after too much data has been sent + /// + Strict = 1, + } +} diff --git a/Src/SmtpServer/MaxMessageSizeOptions.cs b/Src/SmtpServer/MaxMessageSizeOptions.cs new file mode 100644 index 0000000..5e8cbb8 --- /dev/null +++ b/Src/SmtpServer/MaxMessageSizeOptions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SmtpServer +{ + /// + /// Represents configuration settings for enforcing a maximum message size in SMTP, + /// including the size limit in bytes and the behavior when that limit is exceeded. + /// + public class MaxMessageSizeOptions : IMaxMessageSizeOptions + { + /// + /// Gets or sets the maximum allowed message size in bytes, + /// as specified by the SMTP SIZE extension (RFC 1870). + /// + public int Length { get; set; } + + /// + /// Gets or sets the handling strategy for messages that exceed the maximum allowed size. + /// + public MaxMessageSizeHandling Handling { get; set; } + + /// + /// Initializes a new instance of the class + /// with the specified handling strategy and message size limit. + /// + /// The strategy for handling messages that exceed the maximum allowed size. + /// The maximum allowed message size in bytes. + public MaxMessageSizeOptions(MaxMessageSizeHandling handling, int length) + { + Length = length; + Handling = handling; + } + + /// + /// Initializes a new instance of the class with default values. + /// + public MaxMessageSizeOptions() + { + + } + } +} diff --git a/Src/SmtpServer/Protocol/AuthCommand.cs b/Src/SmtpServer/Protocol/AuthCommand.cs index 6fb980f..0c84e31 100644 --- a/Src/SmtpServer/Protocol/AuthCommand.cs +++ b/Src/SmtpServer/Protocol/AuthCommand.cs @@ -106,7 +106,7 @@ async Task TryPlainAsync(ISessionContext context, CancellationToken cancel { await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, " "), cancellationToken).ConfigureAwait(false); - authentication = await context.Pipe.Input.ReadLineAsync(Encoding.ASCII, cancellationToken).ConfigureAwait(false); + authentication = await context.Pipe.Input.ReadLineAsync(Encoding.ASCII, context.ServerOptions.MaxMessageSizeOptions, cancellationToken).ConfigureAwait(false); } if (TryExtractFromBase64(authentication) == false) @@ -155,13 +155,13 @@ async Task TryLoginAsync(ISessionContext context, CancellationToken cancel //Username = VXNlcm5hbWU6 (base64) await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "VXNlcm5hbWU6"), cancellationToken).ConfigureAwait(false); - _user = await ReadBase64EncodedLineAsync(context.Pipe.Input, cancellationToken).ConfigureAwait(false); + _user = await ReadBase64EncodedLineAsync(context.Pipe.Input, context.ServerOptions.MaxMessageSizeOptions, cancellationToken).ConfigureAwait(false); } //Password = UGFzc3dvcmQ6 (base64) await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.ContinueWithAuth, "UGFzc3dvcmQ6"), cancellationToken).ConfigureAwait(false); - _password = await ReadBase64EncodedLineAsync(context.Pipe.Input, cancellationToken).ConfigureAwait(false); + _password = await ReadBase64EncodedLineAsync(context.Pipe.Input, context.ServerOptions.MaxMessageSizeOptions, cancellationToken).ConfigureAwait(false); return true; } @@ -172,9 +172,9 @@ async Task TryLoginAsync(ISessionContext context, CancellationToken cancel /// The pipe to read from. /// The cancellation token. /// The decoded Base64 string. - static async Task ReadBase64EncodedLineAsync(PipeReader reader, CancellationToken cancellationToken) + static async Task ReadBase64EncodedLineAsync(PipeReader reader, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken) { - var text = await reader.ReadLineAsync(cancellationToken); + var text = await reader.ReadLineAsync(maxMessageSizeOptions, cancellationToken); return text == null ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(text)); } diff --git a/Src/SmtpServer/Protocol/DataCommand.cs b/Src/SmtpServer/Protocol/DataCommand.cs index 3397b1c..0092370 100644 --- a/Src/SmtpServer/Protocol/DataCommand.cs +++ b/Src/SmtpServer/Protocol/DataCommand.cs @@ -52,7 +52,8 @@ await context.Pipe.Input.ReadDotBlockAsync( { // ReSharper disable once AccessToDisposedClosure response = await container.Instance.SaveAsync(context, context.Transaction, buffer, cancellationToken).ConfigureAwait(false); - }, + }, + context.ServerOptions.MaxMessageSizeOptions, cancellationToken).ConfigureAwait(false); await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false); diff --git a/Src/SmtpServer/Protocol/EhloCommand.cs b/Src/SmtpServer/Protocol/EhloCommand.cs index a9a483e..e797506 100644 --- a/Src/SmtpServer/Protocol/EhloCommand.cs +++ b/Src/SmtpServer/Protocol/EhloCommand.cs @@ -75,9 +75,9 @@ protected virtual IEnumerable GetExtensions(ISessionContext context) yield return "STARTTLS"; } - if (context.ServerOptions.MaxMessageSize > 0) + if (context.ServerOptions.MaxMessageSizeOptions.Length > 0) { - yield return $"SIZE {context.ServerOptions.MaxMessageSize}"; + yield return $"SIZE {context.ServerOptions.MaxMessageSizeOptions.Length}"; } if (IsPlainLoginAllowed(context)) diff --git a/Src/SmtpServer/Protocol/MailCommand.cs b/Src/SmtpServer/Protocol/MailCommand.cs index 9ec7dc2..45feadc 100644 --- a/Src/SmtpServer/Protocol/MailCommand.cs +++ b/Src/SmtpServer/Protocol/MailCommand.cs @@ -51,7 +51,7 @@ internal override async Task ExecuteAsync(SmtpSessionContext context, Canc var size = GetMessageSize(); // check against the server supplied maximum - if (context.ServerOptions.MaxMessageSize > 0 && size > context.ServerOptions.MaxMessageSize) + if (context.ServerOptions.MaxMessageSizeOptions.Length > 0 && size > context.ServerOptions.MaxMessageSizeOptions.Length) { await context.Pipe.Output.WriteReplyAsync(SmtpResponse.SizeLimitExceeded, cancellationToken).ConfigureAwait(false); return false; diff --git a/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs b/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs new file mode 100644 index 0000000..c0bec67 --- /dev/null +++ b/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs @@ -0,0 +1,12 @@ +using System; + +namespace SmtpServer.Protocol +{ + /// + /// Exception thrown when a message exceeds the maximum allowed size as defined by the SMTP SIZE extension (RFC 1870). + /// + public sealed class MaxMessageSizeExceededException : Exception + { + + } +} diff --git a/Src/SmtpServer/SmtpServerOptionsBuilder.cs b/Src/SmtpServer/SmtpServerOptionsBuilder.cs index 322a565..307ff8f 100644 --- a/Src/SmtpServer/SmtpServerOptionsBuilder.cs +++ b/Src/SmtpServer/SmtpServerOptionsBuilder.cs @@ -18,11 +18,12 @@ public ISmtpServerOptions Build() { var serverOptions = new SmtpServerOptions { + MaxMessageSizeOptions = new MaxMessageSizeOptions(), Endpoints = new List(), MaxRetryCount = 5, MaxAuthenticationAttempts = 3, NetworkBufferSize = 128, - CommandWaitTimeout = TimeSpan.FromMinutes(5) + CommandWaitTimeout = TimeSpan.FromMinutes(5), }; _setters.ForEach(setter => setter(serverOptions)); @@ -98,11 +99,12 @@ public SmtpServerOptionsBuilder Port(int port, bool isSecure) /// /// Sets the maximum message size. /// - /// The maximum message size to allow. + /// The maximum message size to allow in bytes. + /// The handling type. /// A OptionsBuilder to continue building on. - public SmtpServerOptionsBuilder MaxMessageSize(int value) + public SmtpServerOptionsBuilder MaxMessageSize(int length, MaxMessageSizeHandling handling = MaxMessageSizeHandling.Ignore) { - _setters.Add(options => options.MaxMessageSize = value); + _setters.Add(options => options.MaxMessageSizeOptions = new MaxMessageSizeOptions(handling, length)); return this; } @@ -160,9 +162,9 @@ public SmtpServerOptionsBuilder CommandWaitTimeout(TimeSpan value) class SmtpServerOptions : ISmtpServerOptions { /// - /// Gets or sets the maximum size of a message. + /// Gets or sets the maximum message size option. /// - public int MaxMessageSize { get; set; } + public IMaxMessageSizeOptions MaxMessageSizeOptions { get; set; } /// /// The maximum number of retries before quitting the session. diff --git a/Src/SmtpServer/SmtpSession.cs b/Src/SmtpServer/SmtpSession.cs index 9daaec0..292b7f5 100644 --- a/Src/SmtpServer/SmtpSession.cs +++ b/Src/SmtpServer/SmtpSession.cs @@ -131,6 +131,7 @@ await context.Pipe.Input.ReadLineAsync( return Task.CompletedTask; }, + context.ServerOptions.MaxMessageSizeOptions, cancellationTokenSource.Token).ConfigureAwait(false); return command; From 6b1e502b1ffd296d5f633f8d00e2c7f44c8f711e Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Fri, 25 Jul 2025 23:15:49 +0200 Subject: [PATCH 2/3] cleanup params --- Src/SmtpServer/IO/PipeReaderExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/SmtpServer/IO/PipeReaderExtensions.cs b/Src/SmtpServer/IO/PipeReaderExtensions.cs index c34882e..956dbf3 100644 --- a/Src/SmtpServer/IO/PipeReaderExtensions.cs +++ b/Src/SmtpServer/IO/PipeReaderExtensions.cs @@ -22,7 +22,7 @@ internal static class PipeReaderExtensions /// The reader to read from. /// The sequence to find to terminate the read operation. /// The callback to execute to process the buffer. - /// Handling of MaxMessageSize + /// Handling of MaxMessageSize. /// The cancellation token. /// The value that was read from the buffer. static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken) @@ -67,7 +67,7 @@ static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func /// The reader to read from. /// The action to process the buffer. - /// Handling of MaxMessageSize + /// Handling of MaxMessageSize. /// The cancellation token. /// A task that can be used to wait on the operation on complete. internal static ValueTask ReadLineAsync(this PipeReader reader, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) @@ -84,7 +84,7 @@ internal static ValueTask ReadLineAsync(this PipeReader reader, Func /// The reader to read from. - /// Handling of MaxMessageSize + /// Handling of MaxMessageSize. /// The cancellation token. /// A task that can be used to wait on the operation on complete. internal static ValueTask ReadLineAsync(this PipeReader reader, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) @@ -132,7 +132,7 @@ await reader.ReadLineAsync( /// /// The reader to read from. /// The action to process the buffer. - /// Handling of MaxMessageSize + /// Handling of MaxMessageSize. /// The cancellation token. /// The value that was read from the buffer. internal static async ValueTask ReadDotBlockAsync(this PipeReader reader, Func, Task> func, IMaxMessageSizeOptions maxMessageSizeOptions, CancellationToken cancellationToken = default) From 8d4ca03f56649fd9fb7689df6b57ff032775494f Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Sat, 2 Aug 2025 21:40:52 +0200 Subject: [PATCH 3/3] Change to SmtpResponseException --- Src/SmtpServer.Tests/SmtpServerTests.cs | 2 +- Src/SmtpServer/IO/PipeReaderExtensions.cs | 2 +- .../Protocol/MaxMessageSizeExceededException.cs | 12 ------------ Src/SmtpServer/Protocol/SmtpResponse.cs | 5 +++++ 4 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs diff --git a/Src/SmtpServer.Tests/SmtpServerTests.cs b/Src/SmtpServer.Tests/SmtpServerTests.cs index 8b9fca2..292cdaa 100644 --- a/Src/SmtpServer.Tests/SmtpServerTests.cs +++ b/Src/SmtpServer.Tests/SmtpServerTests.cs @@ -174,7 +174,7 @@ public void WillTerminateDueToTooMuchData() using (CreateServer(c => c.MaxMessageSize(maxAcceptedMailMessageSize, MaxMessageSizeHandling.Strict))) { - Assert.Throws(() => MailClient.Send(mailMessage)); + Assert.Throws(() => MailClient.Send(mailMessage)); } } diff --git a/Src/SmtpServer/IO/PipeReaderExtensions.cs b/Src/SmtpServer/IO/PipeReaderExtensions.cs index 956dbf3..c0c4f47 100644 --- a/Src/SmtpServer/IO/PipeReaderExtensions.cs +++ b/Src/SmtpServer/IO/PipeReaderExtensions.cs @@ -39,7 +39,7 @@ static async ValueTask ReadUntilAsync(PipeReader reader, byte[] sequence, Func maxMessageSizeOptions.Length) { - throw new MaxMessageSizeExceededException(); + throw new SmtpResponseException(SmtpResponse.MaxMessageSizeExceeded, true); } if (read.Buffer.TryFind(sequence, ref head, out var tail)) diff --git a/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs b/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs deleted file mode 100644 index c0bec67..0000000 --- a/Src/SmtpServer/Protocol/MaxMessageSizeExceededException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace SmtpServer.Protocol -{ - /// - /// Exception thrown when a message exceeds the maximum allowed size as defined by the SMTP SIZE extension (RFC 1870). - /// - public sealed class MaxMessageSizeExceededException : Exception - { - - } -} diff --git a/Src/SmtpServer/Protocol/SmtpResponse.cs b/Src/SmtpServer/Protocol/SmtpResponse.cs index d25867f..17487c9 100644 --- a/Src/SmtpServer/Protocol/SmtpResponse.cs +++ b/Src/SmtpServer/Protocol/SmtpResponse.cs @@ -70,6 +70,11 @@ public class SmtpResponse /// public static readonly SmtpResponse AuthenticationRequired = new SmtpResponse(SmtpReplyCode.AuthenticationRequired, "authentication required"); + /// + /// 552 MaxMessageSizeExceeded + /// + public static readonly SmtpResponse MaxMessageSizeExceeded = new SmtpResponse(SmtpReplyCode.SizeLimitExceeded, "message size exceeds fixed maximium message size"); + /// /// Constructor. ///