Codingstyle nullable

This commit is contained in:
2019-12-08 21:23:54 +01:00
parent aa9fcd4a36
commit d0b26111dd
50 changed files with 8669 additions and 9749 deletions
+154 -158
View File
@@ -1,166 +1,162 @@
// ReSharper disable InconsistentNaming
namespace Swan.Net.Smtp
{
namespace Swan.Net.Smtp {
/// <summary>
/// Enumerates all of the well-known SMTP command names.
/// </summary>
public enum SmtpCommandNames {
/// <summary>
/// Enumerates all of the well-known SMTP command names.
/// An unknown command
/// </summary>
public enum SmtpCommandNames
{
/// <summary>
/// An unknown command
/// </summary>
Unknown,
/// <summary>
/// The helo command
/// </summary>
HELO,
/// <summary>
/// The ehlo command
/// </summary>
EHLO,
/// <summary>
/// The quit command
/// </summary>
QUIT,
/// <summary>
/// The help command
/// </summary>
HELP,
/// <summary>
/// The noop command
/// </summary>
NOOP,
/// <summary>
/// The rset command
/// </summary>
RSET,
/// <summary>
/// The mail command
/// </summary>
MAIL,
/// <summary>
/// The data command
/// </summary>
DATA,
/// <summary>
/// The send command
/// </summary>
SEND,
/// <summary>
/// The soml command
/// </summary>
SOML,
/// <summary>
/// The saml command
/// </summary>
SAML,
/// <summary>
/// The RCPT command
/// </summary>
RCPT,
/// <summary>
/// The vrfy command
/// </summary>
VRFY,
/// <summary>
/// The expn command
/// </summary>
EXPN,
/// <summary>
/// The starttls command
/// </summary>
STARTTLS,
/// <summary>
/// The authentication command
/// </summary>
AUTH,
}
Unknown,
/// <summary>
/// Enumerates the reply code severities.
/// The helo command
/// </summary>
public enum SmtpReplyCodeSeverities
{
/// <summary>
/// The unknown severity
/// </summary>
Unknown = 0,
/// <summary>
/// The positive completion severity
/// </summary>
PositiveCompletion = 200,
/// <summary>
/// The positive intermediate severity
/// </summary>
PositiveIntermediate = 300,
/// <summary>
/// The transient negative severity
/// </summary>
TransientNegative = 400,
/// <summary>
/// The permanent negative severity
/// </summary>
PermanentNegative = 500,
}
HELO,
/// <summary>
/// Enumerates the reply code categories.
/// The ehlo command
/// </summary>
public enum SmtpReplyCodeCategories
{
/// <summary>
/// The unknown category
/// </summary>
Unknown = -1,
/// <summary>
/// The syntax category
/// </summary>
Syntax = 0,
/// <summary>
/// The information category
/// </summary>
Information = 1,
/// <summary>
/// The connections category
/// </summary>
Connections = 2,
/// <summary>
/// The unspecified a category
/// </summary>
UnspecifiedA = 3,
/// <summary>
/// The unspecified b category
/// </summary>
UnspecifiedB = 4,
/// <summary>
/// The system category
/// </summary>
System = 5,
}
EHLO,
/// <summary>
/// The quit command
/// </summary>
QUIT,
/// <summary>
/// The help command
/// </summary>
HELP,
/// <summary>
/// The noop command
/// </summary>
NOOP,
/// <summary>
/// The rset command
/// </summary>
RSET,
/// <summary>
/// The mail command
/// </summary>
MAIL,
/// <summary>
/// The data command
/// </summary>
DATA,
/// <summary>
/// The send command
/// </summary>
SEND,
/// <summary>
/// The soml command
/// </summary>
SOML,
/// <summary>
/// The saml command
/// </summary>
SAML,
/// <summary>
/// The RCPT command
/// </summary>
RCPT,
/// <summary>
/// The vrfy command
/// </summary>
VRFY,
/// <summary>
/// The expn command
/// </summary>
EXPN,
/// <summary>
/// The starttls command
/// </summary>
STARTTLS,
/// <summary>
/// The authentication command
/// </summary>
AUTH,
}
/// <summary>
/// Enumerates the reply code severities.
/// </summary>
public enum SmtpReplyCodeSeverities {
/// <summary>
/// The unknown severity
/// </summary>
Unknown = 0,
/// <summary>
/// The positive completion severity
/// </summary>
PositiveCompletion = 200,
/// <summary>
/// The positive intermediate severity
/// </summary>
PositiveIntermediate = 300,
/// <summary>
/// The transient negative severity
/// </summary>
TransientNegative = 400,
/// <summary>
/// The permanent negative severity
/// </summary>
PermanentNegative = 500,
}
/// <summary>
/// Enumerates the reply code categories.
/// </summary>
public enum SmtpReplyCodeCategories {
/// <summary>
/// The unknown category
/// </summary>
Unknown = -1,
/// <summary>
/// The syntax category
/// </summary>
Syntax = 0,
/// <summary>
/// The information category
/// </summary>
Information = 1,
/// <summary>
/// The connections category
/// </summary>
Connections = 2,
/// <summary>
/// The unspecified a category
/// </summary>
UnspecifiedA = 3,
/// <summary>
/// The unspecified b category
/// </summary>
UnspecifiedB = 4,
/// <summary>
/// The system category
/// </summary>
System = 5,
}
}
+367 -385
View File
@@ -1,388 +1,370 @@
namespace Swan.Net.Smtp
{
using System.Threading;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Net.Security;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Mail;
#nullable enable
using System.Threading;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Net.Security;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Mail;
namespace Swan.Net.Smtp {
/// <summary>
/// Represents a basic SMTP client that is capable of submitting messages to an SMTP server.
/// </summary>
/// <example>
/// The following code explains how to send a simple e-mail.
/// <code>
/// using System.Net.Mail;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new Swan.Net.Smtp.SmtpClient("smtp.gmail.com", 587);
///
/// // send an email
/// client.SendMailAsync(
/// new MailMessage("sender@test.com", "recipient@test.cm", "Subject", "Body"));
/// }
/// }
/// </code>
///
/// The following code demonstrates how to sent an e-mail using a SmtpSessionState:
/// <code>
/// using Swan.Net.Smtp;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
///
/// The following code shows how to send an e-mail with an attachment using MimeKit:
/// <code>
/// using MimeKit;
/// using Swan.Net.Smtp;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // load a file as an attachment
/// var attachment = new MimePart("image", "gif")
/// {
/// Content = new
/// MimeContent(File.OpenRead("meme.gif"), ContentEncoding.Default),
/// ContentDisposition =
/// new ContentDisposition(ContentDisposition.Attachment),
/// ContentTransferEncoding = ContentEncoding.Base64,
/// FileName = Path.GetFileName("meme.gif")
/// };
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
/// </example>
public class SmtpClient {
/// <summary>
/// Represents a basic SMTP client that is capable of submitting messages to an SMTP server.
/// Initializes a new instance of the <see cref="SmtpClient" /> class.
/// </summary>
/// <example>
/// The following code explains how to send a simple e-mail.
/// <code>
/// using System.Net.Mail;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new Swan.Net.Smtp.SmtpClient("smtp.gmail.com", 587);
///
/// // send an email
/// client.SendMailAsync(
/// new MailMessage("sender@test.com", "recipient@test.cm", "Subject", "Body"));
/// }
/// }
/// </code>
///
/// The following code demonstrates how to sent an e-mail using a SmtpSessionState:
/// <code>
/// using Swan.Net.Smtp;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
///
/// The following code shows how to send an e-mail with an attachment using MimeKit:
/// <code>
/// using MimeKit;
/// using Swan.Net.Smtp;
///
/// class Example
/// {
/// static void Main()
/// {
/// // create a new smtp client using google's smtp server
/// var client = new SmtpClient("smtp.gmail.com", 587);
///
/// // create a new session state with a sender address
/// var session = new SmtpSessionState { SenderAddress = "sender@test.com" };
///
/// // add a recipient
/// session.Recipients.Add("recipient@test.cm");
///
/// // load a file as an attachment
/// var attachment = new MimePart("image", "gif")
/// {
/// Content = new
/// MimeContent(File.OpenRead("meme.gif"), ContentEncoding.Default),
/// ContentDisposition =
/// new ContentDisposition(ContentDisposition.Attachment),
/// ContentTransferEncoding = ContentEncoding.Base64,
/// FileName = Path.GetFileName("meme.gif")
/// };
///
/// // send
/// client.SendMailAsync(session);
/// }
/// }
/// </code>
/// </example>
public class SmtpClient
{
/// <summary>
/// Initializes a new instance of the <see cref="SmtpClient" /> class.
/// </summary>
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
/// <exception cref="ArgumentNullException">host.</exception>
public SmtpClient(string host, int port)
{
Host = host ?? throw new ArgumentNullException(nameof(host));
Port = port;
ClientHostname = Network.HostName;
}
/// <summary>
/// Gets or sets the credentials. No credentials will be used if set to null.
/// </summary>
/// <value>
/// The credentials.
/// </value>
public NetworkCredential Credentials { get; set; }
/// <summary>
/// Gets the host.
/// </summary>
/// <value>
/// The host.
/// </value>
public string Host { get; }
/// <summary>
/// Gets the port.
/// </summary>
/// <value>
/// The port.
/// </value>
public int Port { get; }
/// <summary>
/// Gets or sets a value indicating whether the SSL is enabled.
/// If set to false, communication between client and server will not be secured.
/// </summary>
/// <value>
/// <c>true</c> if [enable SSL]; otherwise, <c>false</c>.
/// </value>
public bool EnableSsl { get; set; }
/// <summary>
/// Gets or sets the name of the client that gets announced to the server.
/// </summary>
/// <value>
/// The client hostname.
/// </value>
public string ClientHostname { get; set; }
/// <summary>
/// Sends an email message asynchronously.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">message.</exception>
public Task SendMailAsync(
MailMessage message,
string? sessionId = null,
RemoteCertificateValidationCallback? callback = null,
CancellationToken cancellationToken = default)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
var state = new SmtpSessionState
{
AuthMode = Credentials == null ? string.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
ClientHostname = ClientHostname,
IsChannelSecure = EnableSsl,
SenderAddress = message.From.Address,
};
if (Credentials != null)
{
state.Username = Credentials.UserName;
state.Password = Credentials.Password;
}
foreach (var recipient in message.To)
{
state.Recipients.Add(recipient.Address);
}
state.DataBuffer.AddRange(message.ToMimeMessage().ToArray());
return SendMailAsync(state, sessionId, callback, cancellationToken);
}
/// <summary>
/// Sends an email message using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class.
/// </summary>
/// <param name="sessionState">The state.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">sessionState.</exception>
public Task SendMailAsync(
SmtpSessionState sessionState,
string? sessionId = null,
RemoteCertificateValidationCallback? callback = null,
CancellationToken cancellationToken = default)
{
if (sessionState == null)
throw new ArgumentNullException(nameof(sessionState));
return SendMailAsync(new[] { sessionState }, sessionId, callback, cancellationToken);
}
/// <summary>
/// Sends an array of email messages using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class.
/// </summary>
/// <param name="sessionStates">The session states.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">sessionStates.</exception>
/// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception>
/// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception>
public async Task SendMailAsync(
IEnumerable<SmtpSessionState> sessionStates,
string? sessionId = null,
RemoteCertificateValidationCallback? callback = null,
CancellationToken cancellationToken = default)
{
if (sessionStates == null)
throw new ArgumentNullException(nameof(sessionStates));
using var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(Host, Port).ConfigureAwait(false);
using var connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000);
var sender = new SmtpSender(sessionId);
try
{
// Read the greeting message
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
// EHLO 1
await SendEhlo(sender, connection, cancellationToken).ConfigureAwait(false);
// STARTTLS
if (EnableSsl)
{
sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
if (await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false)
throw new SecurityException("Could not upgrade the channel to SSL.");
}
// EHLO 2
await SendEhlo(sender, connection, cancellationToken).ConfigureAwait(false);
// AUTH
if (Credentials != null)
{
var auth = new ConnectionAuth(connection, sender, Credentials);
await auth.AuthenticateAsync(cancellationToken).ConfigureAwait(false);
}
foreach (var sessionState in sessionStates)
{
{
// MAIL FROM
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
// RCPT TO
foreach (var recipient in sessionState.Recipients)
{
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
{
// DATA
sender.RequestText = $"{SmtpCommandNames.DATA}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
{
// CONTENT
var dataTerminator = sessionState.DataBuffer
.Skip(sessionState.DataBuffer.Count - 5)
.ToText();
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, cancellationToken).ConfigureAwait(false);
if (!dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator))
await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
}
{
// QUIT
sender.RequestText = $"{SmtpCommandNames.QUIT}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
}
catch (Exception ex)
{
throw new SmtpException($"Could not send email - Session ID {sessionId}. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}");
}
}
private async Task SendEhlo(SmtpSender sender, Connection connection, CancellationToken cancellationToken)
{
sender.RequestText = $"{SmtpCommandNames.EHLO} {ClientHostname}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
do
{
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
}
while (!sender.IsReplyOk);
sender.ValidateReply();
}
private class ConnectionAuth
{
private readonly SmtpSender _sender;
private readonly Connection _connection;
private readonly NetworkCredential _credentials;
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials)
{
_connection = connection;
_sender = sender;
_credentials = credentials;
}
public async Task AuthenticateAsync(CancellationToken ct)
{
_sender.RequestText =
$"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.UserName))}";
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply();
_sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.Password));
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply();
}
}
}
/// <param name="host">The host.</param>
/// <param name="port">The port.</param>
/// <exception cref="ArgumentNullException">host.</exception>
public SmtpClient(String host, Int32 port) {
this.Host = host ?? throw new ArgumentNullException(nameof(host));
this.Port = port;
this.ClientHostname = Network.HostName;
}
/// <summary>
/// Gets or sets the credentials. No credentials will be used if set to null.
/// </summary>
/// <value>
/// The credentials.
/// </value>
public NetworkCredential? Credentials {
get; set;
}
/// <summary>
/// Gets the host.
/// </summary>
/// <value>
/// The host.
/// </value>
public String Host {
get;
}
/// <summary>
/// Gets the port.
/// </summary>
/// <value>
/// The port.
/// </value>
public Int32 Port {
get;
}
/// <summary>
/// Gets or sets a value indicating whether the SSL is enabled.
/// If set to false, communication between client and server will not be secured.
/// </summary>
/// <value>
/// <c>true</c> if [enable SSL]; otherwise, <c>false</c>.
/// </value>
public Boolean EnableSsl {
get; set;
}
/// <summary>
/// Gets or sets the name of the client that gets announced to the server.
/// </summary>
/// <value>
/// The client hostname.
/// </value>
public String ClientHostname {
get; set;
}
/// <summary>
/// Sends an email message asynchronously.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">message.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public Task SendMailAsync(MailMessage message, String? sessionId = null, RemoteCertificateValidationCallback? callback = null, CancellationToken cancellationToken = default) {
if(message == null) {
throw new ArgumentNullException(nameof(message));
}
SmtpSessionState state = new SmtpSessionState {
AuthMode = this.Credentials == null ? String.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
ClientHostname = ClientHostname,
IsChannelSecure = EnableSsl,
SenderAddress = message.From.Address,
};
if(this.Credentials != null) {
state.Username = this.Credentials.UserName;
state.Password = this.Credentials.Password;
}
foreach(MailAddress recipient in message.To) {
state.Recipients.Add(recipient.Address);
}
state.DataBuffer.AddRange(message.ToMimeMessage().ToArray());
return this.SendMailAsync(state, sessionId, callback, cancellationToken);
}
/// <summary>
/// Sends an email message using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class.
/// </summary>
/// <param name="sessionState">The state.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">sessionState.</exception>
public Task SendMailAsync(SmtpSessionState sessionState, String? sessionId = null, RemoteCertificateValidationCallback? callback = null, CancellationToken cancellationToken = default) {
if(sessionState == null) {
throw new ArgumentNullException(nameof(sessionState));
}
return this.SendMailAsync(new[] { sessionState }, sessionId, callback, cancellationToken);
}
/// <summary>
/// Sends an array of email messages using a session state object.
/// Credentials, Enable SSL and Client Hostname are NOT taken from the state object but
/// rather from the properties of this class.
/// </summary>
/// <param name="sessionStates">The session states.</param>
/// <param name="sessionId">The session identifier.</param>
/// <param name="callback">The callback.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A task that represents the asynchronous of send email operation.
/// </returns>
/// <exception cref="ArgumentNullException">sessionStates.</exception>
/// <exception cref="SecurityException">Could not upgrade the channel to SSL.</exception>
/// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception>
public async Task SendMailAsync(IEnumerable<SmtpSessionState> sessionStates, String? sessionId = null, RemoteCertificateValidationCallback? callback = null, CancellationToken cancellationToken = default) {
if(sessionStates == null) {
throw new ArgumentNullException(nameof(sessionStates));
}
using TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync(this.Host, this.Port).ConfigureAwait(false);
using Connection connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000);
SmtpSender sender = new SmtpSender(sessionId);
try {
// Read the greeting message
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
// EHLO 1
await this.SendEhlo(sender, connection, cancellationToken).ConfigureAwait(false);
// STARTTLS
if(this.EnableSsl) {
sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
if(await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false) {
throw new SecurityException("Could not upgrade the channel to SSL.");
}
}
// EHLO 2
await this.SendEhlo(sender, connection, cancellationToken).ConfigureAwait(false);
// AUTH
if(this.Credentials != null) {
ConnectionAuth auth = new ConnectionAuth(connection, sender, this.Credentials);
await auth.AuthenticateAsync(cancellationToken).ConfigureAwait(false);
}
foreach(SmtpSessionState sessionState in sessionStates) {
{
// MAIL FROM
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
// RCPT TO
foreach(String recipient in sessionState.Recipients) {
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
{
// DATA
sender.RequestText = $"{SmtpCommandNames.DATA}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
{
// CONTENT
String dataTerminator = sessionState.DataBuffer.Skip(sessionState.DataBuffer.Count - 5).ToText();
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, cancellationToken).ConfigureAwait(false);
if(!dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator)) {
await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, cancellationToken).ConfigureAwait(false);
}
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
}
{
// QUIT
sender.RequestText = $"{SmtpCommandNames.QUIT}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
sender.ValidateReply();
}
} catch(Exception ex) {
throw new SmtpException($"Could not send email - Session ID {sessionId}. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}");
}
}
private async Task SendEhlo(SmtpSender sender, Connection connection, CancellationToken cancellationToken) {
sender.RequestText = $"{SmtpCommandNames.EHLO} {this.ClientHostname}";
await connection.WriteLineAsync(sender.RequestText, cancellationToken).ConfigureAwait(false);
do {
sender.ReplyText = await connection.ReadLineAsync(cancellationToken).ConfigureAwait(false);
}
while(!sender.IsReplyOk);
sender.ValidateReply();
}
private class ConnectionAuth {
private readonly SmtpSender _sender;
private readonly Connection _connection;
private readonly NetworkCredential _credentials;
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) {
this._connection = connection;
this._sender = sender;
this._credentials = credentials;
}
public async Task AuthenticateAsync(CancellationToken ct) {
this._sender.RequestText = $"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.UserName))}";
await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
this._sender.ValidateReply();
this._sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.Password));
await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
this._sender.ValidateReply();
}
}
}
}
+25 -26
View File
@@ -1,29 +1,28 @@
namespace Swan.Net.Smtp
{
using System;
namespace Swan.Net.Smtp {
/// <summary>
/// Contains useful constants and definitions.
/// </summary>
public static class SmtpDefinitions {
/// <summary>
/// Contains useful constants and definitions.
/// The string sequence that delimits the end of the DATA command.
/// </summary>
public static class SmtpDefinitions
{
/// <summary>
/// The string sequence that delimits the end of the DATA command.
/// </summary>
public const string SmtpDataCommandTerminator = "\r\n.\r\n";
/// <summary>
/// Lists the AUTH methods supported by default.
/// </summary>
public static class SmtpAuthMethods
{
/// <summary>
/// The plain method.
/// </summary>
public const string Plain = "PLAIN";
/// <summary>
/// The login method.
/// </summary>
public const string Login = "LOGIN";
}
}
public const String SmtpDataCommandTerminator = "\r\n.\r\n";
/// <summary>
/// Lists the AUTH methods supported by default.
/// </summary>
public static class SmtpAuthMethods {
/// <summary>
/// The plain method.
/// </summary>
public const String Plain = "PLAIN";
/// <summary>
/// The login method.
/// </summary>
public const String Login = "LOGIN";
}
}
}
+52 -59
View File
@@ -1,60 +1,53 @@
namespace Swan.Net.Smtp
{
using Logging;
using System;
using System.Linq;
using System.Net.Mail;
/// <summary>
/// Use this class to store the sender session data.
/// </summary>
internal class SmtpSender
{
private readonly string _sessionId;
private string _requestText;
public SmtpSender(string sessionId)
{
_sessionId = sessionId;
}
public string RequestText
{
get => _requestText;
set
{
_requestText = value;
$" TX {_requestText}".Trace(typeof(SmtpClient), _sessionId);
}
}
public string ReplyText { get; set; }
public bool IsReplyOk => ReplyText.StartsWith("250 ", StringComparison.OrdinalIgnoreCase);
public void ValidateReply()
{
if (ReplyText == null)
throw new SmtpException("There was no response from the server");
try
{
var response = SmtpServerReply.Parse(ReplyText);
$" RX {ReplyText} - {response.IsPositive}".Trace(typeof(SmtpClient), _sessionId);
if (response.IsPositive) return;
var responseContent = response.Content.Any()
? string.Join(";", response.Content.ToArray())
: string.Empty;
throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent);
}
catch (Exception ex)
{
if (!(ex is SmtpException))
throw new SmtpException($"Could not parse server response: {ReplyText}");
}
}
}
using Swan.Logging;
using System;
using System.Linq;
using System.Net.Mail;
namespace Swan.Net.Smtp {
/// <summary>
/// Use this class to store the sender session data.
/// </summary>
internal class SmtpSender {
private readonly String _sessionId;
private String _requestText;
public SmtpSender(String sessionId) => this._sessionId = sessionId;
public String RequestText {
get => this._requestText;
set {
this._requestText = value;
$" TX {this._requestText}".Trace(typeof(SmtpClient), this._sessionId);
}
}
public String ReplyText {
get; set;
}
public Boolean IsReplyOk => this.ReplyText.StartsWith("250 ", StringComparison.OrdinalIgnoreCase);
public void ValidateReply() {
if(this.ReplyText == null) {
throw new SmtpException("There was no response from the server");
}
try {
SmtpServerReply response = SmtpServerReply.Parse(this.ReplyText);
$" RX {this.ReplyText} - {response.IsPositive}".Trace(typeof(SmtpClient), this._sessionId);
if(response.IsPositive) {
return;
}
String responseContent = response.Content.Any() ? String.Join(";", response.Content.ToArray()) : String.Empty;
throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent);
} catch(Exception ex) {
if(!(ex is SmtpException)) {
throw new SmtpException($"Could not parse server response: {this.ReplyText}");
}
}
}
}
}
+253 -240
View File
@@ -1,243 +1,256 @@
namespace Swan.Net.Smtp
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Swan.Net.Smtp {
/// <summary>
/// Represents an SMTP server response object.
/// </summary>
public class SmtpServerReply {
#region Constructors
/// <summary>
/// Represents an SMTP server response object.
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
public class SmtpServerReply
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string statusCode, params string[] content)
{
Content = new List<string>();
ReplyCode = responseCode;
EnhancedStatusCode = statusCode;
Content.AddRange(content);
IsValid = responseCode >= 200 && responseCode < 600;
ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
if (!IsValid) return;
if (responseCode >= 200) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
if (responseCode >= 300) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
if (responseCode >= 400) ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
if (responseCode >= 500) ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
if (responseCode >= 600) ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
if (int.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out var middleDigit))
{
if (middleDigit >= 0 && middleDigit <= 5)
ReplyCodeCategory = (SmtpReplyCodeCategories) middleDigit;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
public SmtpServerReply()
: this(0, string.Empty, string.Empty)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string statusCode, string content)
: this(responseCode, statusCode, new[] {content})
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <param name="responseCode">The response code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string content)
: this(responseCode, string.Empty, content)
{
}
#endregion
#region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2)
/// <summary>
/// Gets the command unrecognized reply.
/// </summary>
public static SmtpServerReply CommandUnrecognized =>
new SmtpServerReply(500, "Syntax error, command unrecognized");
/// <summary>
/// Gets the syntax error arguments reply.
/// </summary>
public static SmtpServerReply SyntaxErrorArguments =>
new SmtpServerReply(501, "Syntax error in parameters or arguments");
/// <summary>
/// Gets the command not implemented reply.
/// </summary>
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented");
/// <summary>
/// Gets the bad sequence of commands reply.
/// </summary>
public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands");
/// <summary>
/// Gets the protocol violation reply.
/// </summary>=
public static SmtpServerReply ProtocolViolation =>
new SmtpServerReply(451, "Requested action aborted: error in processing");
/// <summary>
/// Gets the system status bye reply.
/// </summary>
public static SmtpServerReply SystemStatusBye =>
new SmtpServerReply(221, "Service closing transmission channel");
/// <summary>
/// Gets the system status help reply.
/// </summary>=
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321");
/// <summary>
/// Gets the bad syntax command empty reply.
/// </summary>
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax");
/// <summary>
/// Gets the OK reply.
/// </summary>
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK");
/// <summary>
/// Gets the authorization required reply.
/// </summary>
public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required");
#endregion
#region Properties
/// <summary>
/// Gets the response severity.
/// </summary>
public SmtpReplyCodeSeverities ReplyCodeSeverity { get; }
/// <summary>
/// Gets the response category.
/// </summary>
public SmtpReplyCodeCategories ReplyCodeCategory { get; }
/// <summary>
/// Gets the numeric response code.
/// </summary>
public int ReplyCode { get; }
/// <summary>
/// Gets the enhanced status code.
/// </summary>
public string EnhancedStatusCode { get; }
/// <summary>
/// Gets the content.
/// </summary>
public List<string> Content { get; }
/// <summary>
/// Returns true if the response code is between 200 and 599.
/// </summary>
public bool IsValid { get; }
/// <summary>
/// Gets a value indicating whether this instance is positive.
/// </summary>
public bool IsPositive => ReplyCode >= 200 && ReplyCode <= 399;
#endregion
#region Methods
/// <summary>
/// Parses the specified text into a Server Reply for thorough analysis.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>A new instance of SMTP server response object.</returns>
public static SmtpServerReply Parse(string text)
{
var lines = text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length == 0) return new SmtpServerReply();
var lastLineParts = lines.Last().Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
var enhancedStatusCode = string.Empty;
int.TryParse(lastLineParts[0], out var responseCode);
if (lastLineParts.Length > 1)
{
if (lastLineParts[1].Split('.').Length == 3)
enhancedStatusCode = lastLineParts[1];
}
var content = new List<string>();
for (var i = 0; i < lines.Length; i++)
{
var splitChar = i == lines.Length - 1 ? " " : "-";
var lineParts = lines[i].Split(new[] {splitChar}, 2, StringSplitOptions.None);
var lineContent = lineParts.Last();
if (string.IsNullOrWhiteSpace(enhancedStatusCode) == false)
lineContent = lineContent.Replace(enhancedStatusCode, string.Empty).Trim();
content.Add(lineContent);
}
return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray());
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var responseCodeText = ReplyCode.ToString(CultureInfo.InvariantCulture);
var statusCodeText = string.IsNullOrWhiteSpace(EnhancedStatusCode)
? string.Empty
: $" {EnhancedStatusCode.Trim()}";
if (Content.Count == 0) return $"{responseCodeText}{statusCodeText}";
var builder = new StringBuilder();
for (var i = 0; i < Content.Count; i++)
{
var isLastLine = i == Content.Count - 1;
builder.Append(isLastLine
? $"{responseCodeText}{statusCodeText} {Content[i]}"
: $"{responseCodeText}-{Content[i]}\r\n");
}
return builder.ToString();
}
#endregion
}
/// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(Int32 responseCode, String statusCode, params String[] content) {
this.Content = new List<String>();
this.ReplyCode = responseCode;
this.EnhancedStatusCode = statusCode;
this.Content.AddRange(content);
this.IsValid = responseCode >= 200 && responseCode < 600;
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
this.ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
if(!this.IsValid) {
return;
}
if(responseCode >= 200) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
}
if(responseCode >= 300) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
}
if(responseCode >= 400) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
}
if(responseCode >= 500) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
}
if(responseCode >= 600) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
}
if(Int32.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out Int32 middleDigit)) {
if(middleDigit >= 0 && middleDigit <= 5) {
this.ReplyCodeCategory = (SmtpReplyCodeCategories)middleDigit;
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
public SmtpServerReply() : this(0, String.Empty, String.Empty) {
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(Int32 responseCode, String statusCode, String content) : this(responseCode, statusCode, new[] { content }) {
}
/// <summary>
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary>
/// <param name="responseCode">The response code.</param>
/// <param name="content">The content.</param>
public SmtpServerReply(Int32 responseCode, String content) : this(responseCode, String.Empty, content) {
}
#endregion
#region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2)
/// <summary>
/// Gets the command unrecognized reply.
/// </summary>
public static SmtpServerReply CommandUnrecognized => new SmtpServerReply(500, "Syntax error, command unrecognized");
/// <summary>
/// Gets the syntax error arguments reply.
/// </summary>
public static SmtpServerReply SyntaxErrorArguments => new SmtpServerReply(501, "Syntax error in parameters or arguments");
/// <summary>
/// Gets the command not implemented reply.
/// </summary>
public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented");
/// <summary>
/// Gets the bad sequence of commands reply.
/// </summary>
public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands");
/// <summary>
/// Gets the protocol violation reply.
/// </summary>=
public static SmtpServerReply ProtocolViolation => new SmtpServerReply(451, "Requested action aborted: error in processing");
/// <summary>
/// Gets the system status bye reply.
/// </summary>
public static SmtpServerReply SystemStatusBye => new SmtpServerReply(221, "Service closing transmission channel");
/// <summary>
/// Gets the system status help reply.
/// </summary>=
public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321");
/// <summary>
/// Gets the bad syntax command empty reply.
/// </summary>
public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax");
/// <summary>
/// Gets the OK reply.
/// </summary>
public static SmtpServerReply Ok => new SmtpServerReply(250, "OK");
/// <summary>
/// Gets the authorization required reply.
/// </summary>
public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required");
#endregion
#region Properties
/// <summary>
/// Gets the response severity.
/// </summary>
public SmtpReplyCodeSeverities ReplyCodeSeverity {
get;
}
/// <summary>
/// Gets the response category.
/// </summary>
public SmtpReplyCodeCategories ReplyCodeCategory {
get;
}
/// <summary>
/// Gets the numeric response code.
/// </summary>
public Int32 ReplyCode {
get;
}
/// <summary>
/// Gets the enhanced status code.
/// </summary>
public String EnhancedStatusCode {
get;
}
/// <summary>
/// Gets the content.
/// </summary>
public List<String> Content {
get;
}
/// <summary>
/// Returns true if the response code is between 200 and 599.
/// </summary>
public Boolean IsValid {
get;
}
/// <summary>
/// Gets a value indicating whether this instance is positive.
/// </summary>
public Boolean IsPositive => this.ReplyCode >= 200 && this.ReplyCode <= 399;
#endregion
#region Methods
/// <summary>
/// Parses the specified text into a Server Reply for thorough analysis.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>A new instance of SMTP server response object.</returns>
public static SmtpServerReply Parse(String text) {
String[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
if(lines.Length == 0) {
return new SmtpServerReply();
}
String[] lastLineParts = lines.Last().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
String enhancedStatusCode = String.Empty;
_ = Int32.TryParse(lastLineParts[0], out Int32 responseCode);
if(lastLineParts.Length > 1) {
if(lastLineParts[1].Split('.').Length == 3) {
enhancedStatusCode = lastLineParts[1];
}
}
List<String> content = new List<String>();
for(Int32 i = 0; i < lines.Length; i++) {
String splitChar = i == lines.Length - 1 ? " " : "-";
String[] lineParts = lines[i].Split(new[] { splitChar }, 2, StringSplitOptions.None);
String lineContent = lineParts.Last();
if(String.IsNullOrWhiteSpace(enhancedStatusCode) == false) {
lineContent = lineContent.Replace(enhancedStatusCode, String.Empty).Trim();
}
content.Add(lineContent);
}
return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray());
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override String ToString() {
String responseCodeText = this.ReplyCode.ToString(CultureInfo.InvariantCulture);
String statusCodeText = String.IsNullOrWhiteSpace(this.EnhancedStatusCode) ? String.Empty : $" {this.EnhancedStatusCode.Trim()}";
if(this.Content.Count == 0) {
return $"{responseCodeText}{statusCodeText}";
}
StringBuilder builder = new StringBuilder();
for(Int32 i = 0; i < this.Content.Count; i++) {
Boolean isLastLine = i == this.Content.Count - 1;
_ = builder.Append(isLastLine ? $"{responseCodeText}{statusCodeText} {this.Content[i]}" : $"{responseCodeText}-{this.Content[i]}\r\n");
}
return builder.ToString();
}
#endregion
}
}
+176 -155
View File
@@ -1,158 +1,179 @@
namespace Swan.Net.Smtp
{
using System.Collections.Generic;
using System.Collections.Generic;
using System;
namespace Swan.Net.Smtp {
/// <summary>
/// Represents the state of an SMTP session associated with a client.
/// </summary>
public class SmtpSessionState {
/// <summary>
/// Represents the state of an SMTP session associated with a client.
/// Initializes a new instance of the <see cref="SmtpSessionState"/> class.
/// </summary>
public class SmtpSessionState
{
/// <summary>
/// Initializes a new instance of the <see cref="SmtpSessionState"/> class.
/// </summary>
public SmtpSessionState()
{
DataBuffer = new List<byte>();
Reset(true);
ResetAuthentication();
}
#region Properties
/// <summary>
/// Gets the contents of the data buffer.
/// </summary>
public List<byte> DataBuffer { get; protected set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has initiated.
/// </summary>
public bool HasInitiated { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the current session supports extensions.
/// </summary>
public bool SupportsExtensions { get; set; }
/// <summary>
/// Gets or sets the client hostname.
/// </summary>
public string ClientHostname { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the session is currently receiving DATA.
/// </summary>
public bool IsInDataMode { get; set; }
/// <summary>
/// Gets or sets the sender address.
/// </summary>
public string SenderAddress { get; set; }
/// <summary>
/// Gets the recipients.
/// </summary>
public List<string> Recipients { get; } = new List<string>();
/// <summary>
/// Gets or sets the extended data supporting any additional field for storage by a responder implementation.
/// </summary>
public object ExtendedData { get; set; }
#endregion
#region AUTH State
/// <summary>
/// Gets or sets a value indicating whether this instance is in authentication mode.
/// </summary>
public bool IsInAuthMode { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Gets a value indicating whether this instance has provided username.
/// </summary>
public bool HasProvidedUsername => string.IsNullOrWhiteSpace(Username) == false;
/// <summary>
/// Gets or sets a value indicating whether this instance is authenticated.
/// </summary>
public bool IsAuthenticated { get; set; }
/// <summary>
/// Gets or sets the authentication mode.
/// </summary>
public string AuthMode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is channel secure.
/// </summary>
public bool IsChannelSecure { get; set; }
/// <summary>
/// Resets the authentication state.
/// </summary>
public void ResetAuthentication()
{
Username = string.Empty;
Password = string.Empty;
AuthMode = string.Empty;
IsInAuthMode = false;
IsAuthenticated = false;
}
#endregion
#region Methods
/// <summary>
/// Resets the data mode to false, clears the recipients, the sender address and the data buffer.
/// </summary>
public void ResetEmail()
{
IsInDataMode = false;
Recipients.Clear();
SenderAddress = string.Empty;
DataBuffer.Clear();
}
/// <summary>
/// Resets the state table entirely.
/// </summary>
/// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param>
public void Reset(bool clearExtensionData)
{
HasInitiated = false;
SupportsExtensions = false;
ClientHostname = string.Empty;
ResetEmail();
if (clearExtensionData)
ExtendedData = null;
}
/// <summary>
/// Creates a new object that is a copy of the current instance.
/// </summary>
/// <returns>A clone.</returns>
public virtual SmtpSessionState Clone()
{
var clonedState = this.CopyPropertiesToNew<SmtpSessionState>(new[] {nameof(DataBuffer)});
clonedState.DataBuffer.AddRange(DataBuffer);
clonedState.Recipients.AddRange(Recipients);
return clonedState;
}
#endregion
}
public SmtpSessionState() {
this.DataBuffer = new List<Byte>();
this.Reset(true);
this.ResetAuthentication();
}
#region Properties
/// <summary>
/// Gets the contents of the data buffer.
/// </summary>
public List<Byte> DataBuffer {
get; protected set;
}
/// <summary>
/// Gets or sets a value indicating whether this instance has initiated.
/// </summary>
public Boolean HasInitiated {
get; set;
}
/// <summary>
/// Gets or sets a value indicating whether the current session supports extensions.
/// </summary>
public Boolean SupportsExtensions {
get; set;
}
/// <summary>
/// Gets or sets the client hostname.
/// </summary>
public String ClientHostname {
get; set;
}
/// <summary>
/// Gets or sets a value indicating whether the session is currently receiving DATA.
/// </summary>
public Boolean IsInDataMode {
get; set;
}
/// <summary>
/// Gets or sets the sender address.
/// </summary>
public String SenderAddress {
get; set;
}
/// <summary>
/// Gets the recipients.
/// </summary>
public List<String> Recipients { get; } = new List<String>();
/// <summary>
/// Gets or sets the extended data supporting any additional field for storage by a responder implementation.
/// </summary>
public Object ExtendedData {
get; set;
}
#endregion
#region AUTH State
/// <summary>
/// Gets or sets a value indicating whether this instance is in authentication mode.
/// </summary>
public Boolean IsInAuthMode {
get; set;
}
/// <summary>
/// Gets or sets the username.
/// </summary>
public String Username {
get; set;
}
/// <summary>
/// Gets or sets the password.
/// </summary>
public String Password {
get; set;
}
/// <summary>
/// Gets a value indicating whether this instance has provided username.
/// </summary>
public Boolean HasProvidedUsername => String.IsNullOrWhiteSpace(this.Username) == false;
/// <summary>
/// Gets or sets a value indicating whether this instance is authenticated.
/// </summary>
public Boolean IsAuthenticated {
get; set;
}
/// <summary>
/// Gets or sets the authentication mode.
/// </summary>
public String AuthMode {
get; set;
}
/// <summary>
/// Gets or sets a value indicating whether this instance is channel secure.
/// </summary>
public Boolean IsChannelSecure {
get; set;
}
/// <summary>
/// Resets the authentication state.
/// </summary>
public void ResetAuthentication() {
this.Username = String.Empty;
this.Password = String.Empty;
this.AuthMode = String.Empty;
this.IsInAuthMode = false;
this.IsAuthenticated = false;
}
#endregion
#region Methods
/// <summary>
/// Resets the data mode to false, clears the recipients, the sender address and the data buffer.
/// </summary>
public void ResetEmail() {
this.IsInDataMode = false;
this.Recipients.Clear();
this.SenderAddress = String.Empty;
this.DataBuffer.Clear();
}
/// <summary>
/// Resets the state table entirely.
/// </summary>
/// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param>
public void Reset(Boolean clearExtensionData) {
this.HasInitiated = false;
this.SupportsExtensions = false;
this.ClientHostname = String.Empty;
this.ResetEmail();
if(clearExtensionData) {
this.ExtendedData = null;
}
}
/// <summary>
/// Creates a new object that is a copy of the current instance.
/// </summary>
/// <returns>A clone.</returns>
public virtual SmtpSessionState Clone() {
SmtpSessionState clonedState = this.CopyPropertiesToNew<SmtpSessionState>(new[] { nameof(this.DataBuffer) });
clonedState.DataBuffer.AddRange(this.DataBuffer);
clonedState.Recipients.AddRange(this.Recipients);
return clonedState;
}
#endregion
}
}