Skip to content

Commit

Permalink
Implemented a way for subclasses to override behavior of failed RCPT …
Browse files Browse the repository at this point in the history
…TO commands

Fixes issue #309 and improved the solution for issue #256
  • Loading branch information
jstedfast committed Mar 12, 2016
1 parent 4fe27c7 commit ab1ca44
Showing 1 changed file with 87 additions and 20 deletions.
107 changes: 87 additions & 20 deletions MailKit/Net/Smtp/SmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,28 @@ void QueueCommand (SmtpCommand type, string command, CancellationToken cancellat
queued.Add (type);
}

void FlushCommandQueue (MailboxAddress sender, IList<MailboxAddress> recipients, CancellationToken cancellationToken)
/// <summary>
/// Invoked only when no recipients were accepted by the SMTP server.
/// </summary>
/// <remarks>
/// If <see cref="OnRecipientNotAccepted"/> is overridden to not throw
/// an exception, this method should be overridden to throw an appropriate
/// exception instead.
/// </remarks>
/// <param name="message">The message being sent.</param>
protected virtual void OnNoRecipientsAccepted (MimeMessage message)
{
}

void FlushCommandQueue (MimeMessage message, MailboxAddress sender, IList<MailboxAddress> recipients, CancellationToken cancellationToken)
{
if (queued.Count == 0)
return;

try {
var responses = new List<SmtpResponse> ();
Exception rex = null;
int count = 0;
int rcpt = 0;

// Note: queued commands are buffered by the stream
Expand All @@ -352,14 +366,18 @@ void FlushCommandQueue (MailboxAddress sender, IList<MailboxAddress> recipients,
for (int i = 0; i < responses.Count; i++) {
switch (queued[i]) {
case SmtpCommand.MailFrom:
ProcessMailFromResponse (responses[i], sender);
ProcessMailFromResponse (message, sender, responses[i]);
break;
case SmtpCommand.RcptTo:
ProcessRcptToResponse (responses[i], recipients[rcpt++]);
if (ProcessRcptToResponse (message, recipients[rcpt++], responses[i]))
count++;
break;
}
}

if (count == 0)
OnNoRecipientsAccepted (message);

if (rex != null)
throw new SmtpProtocolException ("Error reading a response from the SMTP server.", rex);
} finally {
Expand Down Expand Up @@ -1256,14 +1274,43 @@ protected override void VisitMimePart (MimePart entity)
}
}

static void ProcessMailFromResponse (SmtpResponse response, MailboxAddress mailbox)
/// <summary>
/// Invoked when the sender is accepted by the SMTP server.
/// </summary>
/// <remarks>
/// The default implementation does nothing.
/// </remarks>
/// <param name="message">The message being sent.</param>
/// <param name="mailbox">The mailbox used in the <c>MAIL FROM</c> command.</param>
/// <param name="response">The response to the <c>MAIL FROM</c> command.</param>
protected virtual void OnSenderAccepted (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
}

/// <summary>
/// Invoked when a recipient is not accepted by the SMTP server.
/// </summary>
/// <remarks>
/// The default implementation throws an appropriate <see cref="SmtpCommandException"/>.
/// </remarks>
/// <param name="message">The message being sent.</param>
/// <param name="mailbox">The mailbox used in the <c>MAIL FROM</c> command.</param>
/// <param name="response">The response to the <c>MAIL FROM</c> command.</param>
protected virtual void OnSenderNotAccepted (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
throw new SmtpCommandException (SmtpErrorCode.SenderNotAccepted, response.StatusCode, mailbox, response.Response);
}

void ProcessMailFromResponse (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
switch (response.StatusCode) {
case SmtpStatusCode.Ok:
OnSenderAccepted (message, mailbox, response);
break;
case SmtpStatusCode.MailboxNameNotAllowed:
case SmtpStatusCode.MailboxUnavailable:
throw new SmtpCommandException (SmtpErrorCode.SenderNotAccepted, response.StatusCode, mailbox, response.Response);
OnSenderNotAccepted (message, mailbox, response);
break;
case SmtpStatusCode.AuthenticationRequired:
throw new ServiceNotAuthenticatedException (response.Response);
default:
Expand Down Expand Up @@ -1316,33 +1363,51 @@ void MailFrom (MimeMessage message, MailboxAddress mailbox, SmtpExtension extens
return;
}

ProcessMailFromResponse (SendCommand (command, cancellationToken), mailbox);
var response = SendCommand (command, cancellationToken);

ProcessMailFromResponse (message, mailbox, response);
}

/// <summary>
/// Invoked when a recipient is accepted by the SMTP server.
/// </summary>
/// <remarks>
/// The default implementation does nothing.
/// </remarks>
/// <param name="message">The message being sent.</param>
/// <param name="mailbox">The mailbox used in the <c>RCPT TO</c> command.</param>
/// <param name="response">The response to the <c>RCPT TO</c> command.</param>
protected virtual void OnRecipientAccepted (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
}

/// <summary>
/// Process the response to a RCPT TO command.
/// Invoked when a recipient is not accepted by the SMTP server.
/// </summary>
/// <remarks>
/// <para>Processes the response to a RCPT TO command.</para>
/// <para>By default, this method no-op when the <paramref name="response"/>
/// <see cref="SmtpResponse.StatusCode"/> property has a value of
/// <see cref="SmtpStatusCode.Ok"/> or
/// <see cref="SmtpStatusCode.UserNotLocalWillForward"/> and will throw
/// an appropriate exception for all other status codes.</para>
/// The default implementation throws an appropriate <see cref="SmtpCommandException"/>.
/// </remarks>
/// <param name="response">The response to an RCPT TO command.</param>
/// <param name="mailbox">The mailbox used in the RCPT TO command.</param>
protected virtual void ProcessRcptToResponse (SmtpResponse response, MailboxAddress mailbox)
/// <param name="message">The message being sent.</param>
/// <param name="mailbox">The mailbox used in the <c>RCPT TO</c> command.</param>
/// <param name="response">The response to the <c>RCPT TO</c> command.</param>
protected virtual void OnRecipientNotAccepted (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
throw new SmtpCommandException (SmtpErrorCode.RecipientNotAccepted, response.StatusCode, mailbox, response.Response);
}

bool ProcessRcptToResponse (MimeMessage message, MailboxAddress mailbox, SmtpResponse response)
{
switch (response.StatusCode) {
case SmtpStatusCode.UserNotLocalWillForward:
case SmtpStatusCode.Ok:
break;
OnRecipientAccepted (message, mailbox, response);
return true;
case SmtpStatusCode.UserNotLocalTryAlternatePath:
case SmtpStatusCode.MailboxNameNotAllowed:
case SmtpStatusCode.MailboxUnavailable:
case SmtpStatusCode.MailboxBusy:
throw new SmtpCommandException (SmtpErrorCode.RecipientNotAccepted, response.StatusCode, mailbox, response.Response);
OnRecipientNotAccepted (message, mailbox, response);
return false;
case SmtpStatusCode.AuthenticationRequired:
throw new ServiceNotAuthenticatedException (response.Response);
default:
Expand Down Expand Up @@ -1402,7 +1467,9 @@ void RcptTo (MimeMessage message, MailboxAddress mailbox, CancellationToken canc
return;
}

ProcessRcptToResponse (SendCommand (command, cancellationToken), mailbox);
var response = SendCommand (command, cancellationToken);

ProcessRcptToResponse (message, mailbox, response);
}

class SendContext
Expand Down Expand Up @@ -1570,7 +1637,7 @@ void Send (FormatOptions options, MimeMessage message, MailboxAddress sender, IL
// Note: if PIPELINING is supported, this will flush all outstanding
// MAIL FROM and RCPT TO commands to the server and then process all
// of their responses.
FlushCommandQueue (sender, recipients, cancellationToken);
FlushCommandQueue (message, sender, recipients, cancellationToken);

if ((extensions & SmtpExtension.BinaryMime) != 0)
Bdat (format, message, cancellationToken, progress);
Expand Down

0 comments on commit ab1ca44

Please sign in to comment.