Skip to content

Commit

Permalink
Refactored SmtpClient to share disconnect-on-error logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jstedfast committed Jan 15, 2024
1 parent 4310535 commit 0a5029d
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 91 deletions.
77 changes: 30 additions & 47 deletions MailKit/Net/Smtp/AsyncSmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ async Task<QueueResults> FlushCommandQueueAsync (MimeMessage message, MailboxAdd
return ParseCommandQueueResponses (message, sender, recipients, responses, rex);
}

async Task<SmtpResponse> SendCommandInternalAsync (string command, CancellationToken cancellationToken)
{
try {
return await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

/// <summary>
/// Asynchronously send a custom command to the SMTP server.
/// </summary>
Expand Down Expand Up @@ -109,7 +119,7 @@ async Task<QueueResults> FlushCommandQueueAsync (MimeMessage message, MailboxAdd
/// <exception cref="SmtpProtocolException">
/// An SMTP protocol exception occurred.
/// </exception>
protected async Task<SmtpResponse> SendCommandAsync (string command, CancellationToken cancellationToken = default)
protected Task<SmtpResponse> SendCommandAsync (string command, CancellationToken cancellationToken = default)
{
if (command == null)
throw new ArgumentNullException (nameof (command));
Expand All @@ -122,25 +132,17 @@ protected async Task<SmtpResponse> SendCommandAsync (string command, Cancellatio
if (!command.EndsWith ("\r\n", StringComparison.Ordinal))
command += "\r\n";

try {
return await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
return SendCommandInternalAsync (command, cancellationToken);
}

async Task<SmtpResponse> SendEhloAsync (bool connecting, string helo, CancellationToken cancellationToken)
Task<SmtpResponse> SendEhloAsync (bool connecting, string helo, CancellationToken cancellationToken)
{
var command = CreateEhloCommand (helo);

try {
return await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
} catch {
if (!connecting)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
if (connecting)
return Stream.SendCommandAsync (command, cancellationToken);

return SendCommandInternalAsync (command, cancellationToken);
}

async Task EhloAsync (bool connecting, CancellationToken cancellationToken)
Expand Down Expand Up @@ -225,26 +227,23 @@ public override async Task AuthenticateAsync (SaslMechanism mechanism, Cancellat
detector.IsAuthenticating = true;

try {
response = await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync (command, cancellationToken).ConfigureAwait (false);

if (response.StatusCode == SmtpStatusCode.AuthenticationMechanismTooWeak)
throw new AuthenticationException (response.Response);

try {
while (response.StatusCode == SmtpStatusCode.AuthenticationChallenge) {
challenge = await mechanism.ChallengeAsync (response.Response, cancellationToken).ConfigureAwait (false);
response = await Stream.SendCommandAsync (challenge + "\r\n", cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync (challenge + "\r\n", cancellationToken).ConfigureAwait (false);
}

saslException = null;
} catch (SaslException ex) {
// reset the authentication state
response = await Stream.SendCommandAsync ("\r\n", cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync ("\r\n", cancellationToken).ConfigureAwait (false);
saslException = ex;
}
} catch (Exception ex) when (ex is not AuthenticationException) {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
} finally {
detector.IsAuthenticating = false;
}
Expand Down Expand Up @@ -358,7 +357,7 @@ public override async Task AuthenticateAsync (Encoding encoding, ICredentials cr
saslException = null;

try {
response = await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync (command, cancellationToken).ConfigureAwait (false);

if (response.StatusCode == SmtpStatusCode.AuthenticationMechanismTooWeak)
continue;
Expand All @@ -369,18 +368,15 @@ public override async Task AuthenticateAsync (Encoding encoding, ICredentials cr
break;

challenge = await sasl.ChallengeAsync (response.Response, cancellationToken).ConfigureAwait (false);
response = await Stream.SendCommandAsync (challenge + "\r\n", cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync (challenge + "\r\n", cancellationToken).ConfigureAwait (false);
}

saslException = null;
} catch (SaslException ex) {
// reset the authentication state
response = await Stream.SendCommandAsync ("\r\n", cancellationToken).ConfigureAwait (false);
response = await SendCommandInternalAsync ("\r\n", cancellationToken).ConfigureAwait (false);
saslException = ex;
}
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
} finally {
detector.IsAuthenticating = false;
}
Expand Down Expand Up @@ -818,14 +814,7 @@ public override async Task NoOpAsync (CancellationToken cancellationToken = defa
if (!IsConnected)
throw new ServiceNotConnectedException ("The SmtpClient is not connected.");

SmtpResponse response;

try {
response = await Stream.SendCommandAsync ("NOOP\r\n", cancellationToken).ConfigureAwait (false);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
var response = await SendCommandInternalAsync ("NOOP\r\n", cancellationToken).ConfigureAwait (false);

if (response.StatusCode != SmtpStatusCode.Ok)
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);
Expand Down Expand Up @@ -940,23 +929,17 @@ async Task<string> MessageDataAsync (FormatOptions options, MimeMessage message,

async Task ResetAsync (CancellationToken cancellationToken)
{
try {
var response = await Stream.SendCommandAsync ("RSET\r\n", cancellationToken).ConfigureAwait (false);
if (response.StatusCode != SmtpStatusCode.Ok)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
} catch (SmtpCommandException) {
// do not disconnect
} catch {
var response = await SendCommandInternalAsync ("RSET\r\n", cancellationToken).ConfigureAwait (false);

if (response.StatusCode != SmtpStatusCode.Ok)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
}
}

async Task<string> SendAsync (FormatOptions options, MimeMessage message, MailboxAddress sender, IList<MailboxAddress> recipients, CancellationToken cancellationToken, ITransferProgress progress)
{
var format = Prepare (options, message, sender, recipients, out var extensions);
var pipeline = (capabilities & SmtpCapabilities.Pipelining) != 0;
var bdat = UseBdatCommand (extensions);
SmtpResponse dataResponse = null;
long size;

if (bdat || (Capabilities & SmtpCapabilities.Size) != 0 || progress != null) {
Expand Down Expand Up @@ -996,7 +979,7 @@ async Task<string> SendAsync (FormatOptions options, MimeMessage message, Mailbo
if (bdat)
return await BdatAsync (format, message, size, cancellationToken, progress).ConfigureAwait (false);

dataResponse = await Stream.SendCommandAsync ("DATA\r\n", cancellationToken).ConfigureAwait (false);
var dataResponse = await Stream.SendCommandAsync ("DATA\r\n", cancellationToken).ConfigureAwait (false);

ParseDataResponse (dataResponse);
dataResponse = null;
Expand Down Expand Up @@ -1175,7 +1158,7 @@ public override Task<string> SendAsync (FormatOptions options, MimeMessage messa
/// </exception>
public async Task<InternetAddressList> ExpandAsync (string alias, CancellationToken cancellationToken = default)
{
var response = await Stream.SendCommandAsync (CreateExpandCommand (alias), cancellationToken).ConfigureAwait (false);
var response = await SendCommandInternalAsync (CreateExpandCommand (alias), cancellationToken).ConfigureAwait (false);

return ParseExpandResponse (response);
}
Expand Down Expand Up @@ -1222,7 +1205,7 @@ public async Task<InternetAddressList> ExpandAsync (string alias, CancellationTo
/// </exception>
public async Task<MailboxAddress> VerifyAsync (string address, CancellationToken cancellationToken = default)
{
var response = await Stream.SendCommandAsync (CreateVerifyCommand (address), cancellationToken).ConfigureAwait (false);
var response = await SendCommandInternalAsync (CreateVerifyCommand (address), cancellationToken).ConfigureAwait (false);

return ParseVerifyResponse (response);
}
Expand Down
71 changes: 27 additions & 44 deletions MailKit/Net/Smtp/SmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,16 @@ QueueResults FlushCommandQueue (MimeMessage message, MailboxAddress sender, ILis
return ParseCommandQueueResponses (message, sender, recipients, responses, rex);
}

SmtpResponse SendCommandInternal (string command, CancellationToken cancellationToken)
{
try {
return Stream.SendCommand (command, cancellationToken);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

/// <summary>
/// Send a custom command to the SMTP server.
/// </summary>
Expand Down Expand Up @@ -696,12 +706,7 @@ protected SmtpResponse SendCommand (string command, CancellationToken cancellati
if (!command.EndsWith ("\r\n", StringComparison.Ordinal))
command += "\r\n";

try {
return Stream.SendCommand (command, cancellationToken);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
return SendCommandInternal (command, cancellationToken);
}

static bool ReadNextLine (string text, ref int index, out int lineStartIndex, out int lineEndIndex)
Expand Down Expand Up @@ -871,13 +876,10 @@ SmtpResponse SendEhlo (bool connecting, string helo, CancellationToken cancellat
{
var command = CreateEhloCommand (helo);

try {
if (connecting)
return Stream.SendCommand (command, cancellationToken);
} catch {
if (!connecting)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}

return SendCommandInternal (command, cancellationToken);
}

void Ehlo (bool connecting, CancellationToken cancellationToken)
Expand Down Expand Up @@ -981,26 +983,23 @@ public override void Authenticate (SaslMechanism mechanism, CancellationToken ca
detector.IsAuthenticating = true;

try {
response = Stream.SendCommand (command, cancellationToken);
response = SendCommandInternal (command, cancellationToken);

if (response.StatusCode == SmtpStatusCode.AuthenticationMechanismTooWeak)
throw new AuthenticationException (response.Response);

try {
while (response.StatusCode == SmtpStatusCode.AuthenticationChallenge) {
challenge = mechanism.Challenge (response.Response, cancellationToken);
response = Stream.SendCommand (challenge + "\r\n", cancellationToken);
response = SendCommandInternal (challenge + "\r\n", cancellationToken);
}

saslException = null;
} catch (SaslException ex) {
// reset the authentication state
response = Stream.SendCommand ("\r\n", cancellationToken);
response = SendCommandInternal ("\r\n", cancellationToken);
saslException = ex;
}
} catch (Exception ex) when (ex is not AuthenticationException) {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
} finally {
detector.IsAuthenticating = false;
}
Expand Down Expand Up @@ -1133,7 +1132,7 @@ public override void Authenticate (Encoding encoding, ICredentials credentials,
saslException = null;

try {
response = Stream.SendCommand (command, cancellationToken);
response = SendCommandInternal (command, cancellationToken);

if (response.StatusCode == SmtpStatusCode.AuthenticationMechanismTooWeak)
continue;
Expand All @@ -1144,18 +1143,15 @@ public override void Authenticate (Encoding encoding, ICredentials credentials,
break;

challenge = sasl.Challenge (response.Response, cancellationToken);
response = Stream.SendCommand (challenge + "\r\n", cancellationToken);
response = SendCommandInternal (challenge + "\r\n", cancellationToken);
}

saslException = null;
} catch (SaslException ex) {
// reset the authentication state
response = Stream.SendCommand ("\r\n", cancellationToken);
response = SendCommandInternal ("\r\n", cancellationToken);
saslException = ex;
}
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
} finally {
detector.IsAuthenticating = false;
}
Expand Down Expand Up @@ -1667,14 +1663,7 @@ public override void NoOp (CancellationToken cancellationToken = default)
if (!IsConnected)
throw new ServiceNotConnectedException ("The SmtpClient is not connected.");

SmtpResponse response;

try {
response = Stream.SendCommand ("NOOP\r\n", cancellationToken);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
var response = SendCommandInternal ("NOOP\r\n", cancellationToken);

if (response.StatusCode != SmtpStatusCode.Ok)
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);
Expand Down Expand Up @@ -2214,15 +2203,10 @@ string MessageData (FormatOptions options, MimeMessage message, long size, Cance

void Reset (CancellationToken cancellationToken)
{
try {
var response = Stream.SendCommand ("RSET\r\n", cancellationToken);
if (response.StatusCode != SmtpStatusCode.Ok)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
} catch (SmtpCommandException) {
// do not disconnect
} catch {
var response = SendCommandInternal ("RSET\r\n", cancellationToken);

if (response.StatusCode != SmtpStatusCode.Ok)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
}
}

/// <summary>
Expand Down Expand Up @@ -2328,7 +2312,6 @@ string Send (FormatOptions options, MimeMessage message, MailboxAddress sender,
var format = Prepare (options, message, sender, recipients, out var extensions);
var pipeline = (capabilities & SmtpCapabilities.Pipelining) != 0;
var bdat = UseBdatCommand (extensions);
SmtpResponse dataResponse = null;
long size;

if (bdat || (Capabilities & SmtpCapabilities.Size) != 0 || progress != null) {
Expand Down Expand Up @@ -2368,7 +2351,7 @@ string Send (FormatOptions options, MimeMessage message, MailboxAddress sender,
if (bdat)
return Bdat (format, message, size, cancellationToken, progress);

dataResponse = Stream.SendCommand ("DATA\r\n", cancellationToken);
var dataResponse = Stream.SendCommand ("DATA\r\n", cancellationToken);

ParseDataResponse (dataResponse);
dataResponse = null;
Expand Down Expand Up @@ -2627,7 +2610,7 @@ static InternetAddressList ParseExpandResponse (SmtpResponse response)
/// </exception>
public InternetAddressList Expand (string alias, CancellationToken cancellationToken = default)
{
var response = Stream.SendCommand (CreateExpandCommand (alias), cancellationToken);
var response = SendCommandInternal (CreateExpandCommand (alias), cancellationToken);

return ParseExpandResponse (response);
}
Expand Down Expand Up @@ -2701,7 +2684,7 @@ static MailboxAddress ParseVerifyResponse (SmtpResponse response)
/// </exception>
public MailboxAddress Verify (string address, CancellationToken cancellationToken = default)
{
var response = Stream.SendCommand (CreateVerifyCommand (address), cancellationToken);
var response = SendCommandInternal (CreateVerifyCommand (address), cancellationToken);

return ParseVerifyResponse (response);
}
Expand Down

0 comments on commit 0a5029d

Please sign in to comment.