File: System\Net\Mail\SmtpTransport.cs
Web Access
Project: src\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Net.Mail
{
    internal sealed class SmtpTransport
    {
        internal const int DefaultPort = 25;
 
        private readonly ISmtpAuthenticationModule[] _authenticationModules;
        private SmtpConnection? _connection;
        private readonly SmtpClient _client;
        private ICredentialsByHost? _credentials;
        private bool _shouldAbort;
 
        private bool _enableSsl;
        private X509CertificateCollection? _clientCertificates;
 
        internal SmtpTransport(SmtpClient client) : this(client, SmtpAuthenticationManager.GetModules())
        {
        }
 
        internal SmtpTransport(SmtpClient client, ISmtpAuthenticationModule[] authenticationModules)
        {
            ArgumentNullException.ThrowIfNull(authenticationModules);
 
            _client = client;
            _authenticationModules = authenticationModules;
        }
 
        internal ICredentialsByHost? Credentials
        {
            get
            {
                return _credentials;
            }
            set
            {
                _credentials = value;
            }
        }
 
        internal bool IsConnected
        {
            get
            {
                return _connection != null && _connection.IsConnected;
            }
        }
 
        internal bool EnableSsl
        {
            get
            {
                return _enableSsl;
            }
            set
            {
                _enableSsl = value;
            }
        }
 
        internal X509CertificateCollection ClientCertificates => _clientCertificates ??= new X509CertificateCollection();
 
        internal bool ServerSupportsEai
        {
            get { return _connection != null && _connection.ServerSupportsEai; }
        }
 
        internal void GetConnection(string host, int port)
        {
            GetConnectionAsync<SyncReadWriteAdapter>(host, port).GetAwaiter().GetResult();
        }
 
        internal async Task GetConnectionAsync(string host, int port, CancellationToken cancellationToken = default)
        {
            await GetConnectionAsync<AsyncReadWriteAdapter>(host, port, cancellationToken).ConfigureAwait(false);
        }
 
        internal Task GetConnectionAsync<TIOAdapter>(string host, int port, CancellationToken cancellationToken = default)
            where TIOAdapter : IReadWriteAdapter
        {
            lock (this)
            {
                _connection = new SmtpConnection(this, _client, _credentials, _authenticationModules);
                if (_shouldAbort)
                {
                    _connection.Abort();
                }
                _shouldAbort = false;
            }
 
            if (NetEventSource.Log.IsEnabled()) NetEventSource.Associate(this, _connection);
 
            if (EnableSsl)
            {
                _connection.EnableSsl = true;
                _connection.ClientCertificates = ClientCertificates;
            }
 
            return _connection.GetConnectionAsync<TIOAdapter>(host, port, cancellationToken);
        }
 
        internal async Task<(MailWriter, List<SmtpFailedRecipientException>?)> SendMailAsync<TIOAdapter>(MailAddress sender, MailAddressCollection recipients, string deliveryNotify, bool allowUnicode, CancellationToken cancellationToken = default)
            where TIOAdapter : IReadWriteAdapter
        {
            ArgumentNullException.ThrowIfNull(sender);
            ArgumentNullException.ThrowIfNull(recipients);
 
            await MailCommand.SendAsync<TIOAdapter>(_connection!, SmtpCommands.Mail, sender, allowUnicode, cancellationToken).ConfigureAwait(false);
            List<SmtpFailedRecipientException>? failedRecipientExceptions = null;
 
            foreach (MailAddress address in recipients)
            {
                string smtpAddress = address.GetSmtpAddress(allowUnicode);
                string to = smtpAddress + (_connection!.DSNEnabled ? deliveryNotify : string.Empty);
                (bool success, string? response) = await RecipientCommand.SendAsync<TIOAdapter>(_connection, to, cancellationToken).ConfigureAwait(false);
                if (!success)
                {
                    (failedRecipientExceptions ??= new()).Add(
                        new SmtpFailedRecipientException(_connection.Reader!.StatusCode, smtpAddress, response));
                }
            }
 
            if (failedRecipientExceptions?.Count > 0 && failedRecipientExceptions.Count == recipients.Count)
            {
                var exception = failedRecipientExceptions.Count == 1
                    ? failedRecipientExceptions[0]
                    : new SmtpFailedRecipientsException(failedRecipientExceptions, true);
                exception.fatal = true;
                throw exception;
            }
 
            await DataCommand.SendAsync<TIOAdapter>(_connection!, cancellationToken).ConfigureAwait(false);
            return (new MailWriter(_connection!.GetClosableStream(), encodeForTransport: true), failedRecipientExceptions);
        }
 
        internal void ReleaseConnection()
        {
            _connection?.ReleaseConnection();
        }
 
        internal void Abort()
        {
            lock (this)
            {
                if (_connection != null)
                {
                    _connection.Abort();
                }
                else
                {
                    _shouldAbort = true;
                }
            }
        }
    }
}