Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Added: Configuration option to define the maximum allowed message size.
- Added: Support for custom SMTP greeting messages.
- Fixed: NetworkBufferSize now controls the stream read buffer used by the SMTP connection pipe.
- Improved: Optimized protection against excessively long text segments to enhance stability and performance.

```cs
Expand Down
84 changes: 84 additions & 0 deletions src/SmtpServer.Tests/SecurableDuplexPipeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SmtpServer.IO;
using Xunit;

namespace SmtpServer.Tests
{
public class SecurableDuplexPipeTests
{
[Fact]
public async Task CanUseConfiguredNetworkBufferSize()
{
// arrange
var stream = new RecordingReadStream();
using var pipe = new SecurableDuplexPipe(stream, 4096, () => { });

// act
var result = await pipe.Input.ReadAsync();
pipe.Input.AdvanceTo(result.Buffer.End);

// assert
Assert.Equal(4096, stream.LastReadBufferSize);
}

sealed class RecordingReadStream : Stream
{
public int LastReadBufferSize { get; private set; }

public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => 0;

public override long Position
{
get => 0;
set => throw new NotSupportedException();
}

public override void Flush()
{
}

public override int Read(byte[] buffer, int offset, int count)
{
LastReadBufferSize = count;
return 0;
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
LastReadBufferSize = count;
return Task.FromResult(0);
}

public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
LastReadBufferSize = buffer.Length;
return new ValueTask<int>(0);
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotSupportedException();
}

public override void Write(byte[] buffer, int offset, int count)
{
}

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return default;
}
}
}
}
8 changes: 8 additions & 0 deletions src/SmtpServer.Tests/SmtpServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,14 @@ public void EndpointListenerWillRaiseEndPointEvents()
Assert.True(stopped);
}

[Theory]
[InlineData(0)]
[InlineData(-1)]
public void CanNotConfigureInvalidNetworkBufferSize(int value)
{
Assert.Throws<ArgumentOutOfRangeException>(() => new SmtpServerOptionsBuilder().NetworkBufferSize(value));
}

public static X509Certificate2 CreateSelfSignedCertificate(string subjectName)
{
var validityPeriodInYears = 1;
Expand Down
9 changes: 6 additions & 3 deletions src/SmtpServer/IO/SecurableDuplexPipe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ namespace SmtpServer.IO
internal sealed class SecurableDuplexPipe : ISecurableDuplexPipe
{
readonly Action _disposeAction;
readonly int _networkBufferSize;
Stream _stream;
bool _disposed;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="stream">The stream that the pipe is reading and writing to.</param>
/// <param name="networkBufferSize">The size of the buffer to use when reading from the stream.</param>
/// <param name="disposeAction">The action to execute when the stream has been disposed.</param>
internal SecurableDuplexPipe(Stream stream, Action disposeAction)
internal SecurableDuplexPipe(Stream stream, int networkBufferSize, Action disposeAction)
{
_stream = stream;
_networkBufferSize = networkBufferSize;
_disposeAction = disposeAction;

Input = PipeReader.Create(_stream);
Input = PipeReader.Create(_stream, new StreamPipeReaderOptions(bufferSize: networkBufferSize));
Output = PipeWriter.Create(_stream);
}

Expand Down Expand Up @@ -54,7 +57,7 @@ await sslStream.AuthenticateAsServerAsync(

_stream = sslStream;

Input = PipeReader.Create(_stream);
Input = PipeReader.Create(_stream, new StreamPipeReaderOptions(bufferSize: _networkBufferSize));
Output = PipeWriter.Create(_stream);
}

Expand Down
2 changes: 1 addition & 1 deletion src/SmtpServer/Net/EndpointListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public async Task<ISecurableDuplexPipe> GetPipeAsync(ISessionContext context, Ca

var stream = tcpClient.GetStream();

return new SecurableDuplexPipe(stream, () =>
return new SecurableDuplexPipe(stream, context.ServerOptions.NetworkBufferSize, () =>
{
try
{
Expand Down
5 changes: 5 additions & 0 deletions src/SmtpServer/SmtpServerOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ public SmtpServerOptionsBuilder MaxAuthenticationAttempts(int value)
/// <returns>An OptionsBuilder to continue building on.</returns>
public SmtpServerOptionsBuilder NetworkBufferSize(int value)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "The network buffer size must be greater than zero.");
}

_setters.Add(options => options.NetworkBufferSize = value);

return this;
Expand Down