File: System\Net\Mail\SmtpCommands.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;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Net.Mime;
using System.Runtime.ExceptionServices;
using System.Text;
 
namespace System.Net.Mail
{
    internal static class CheckCommand
    {
        private static readonly AsyncCallback s_onReadLine = new AsyncCallback(OnReadLine);
        private static readonly AsyncCallback s_onWrite = new AsyncCallback(OnWrite);
 
        internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state)
        {
            MultiAsyncResult multiResult = new MultiAsyncResult(conn, callback, state);
            multiResult.Enter();
            IAsyncResult writeResult = conn.BeginFlush(s_onWrite, multiResult);
            if (writeResult.CompletedSynchronously)
            {
                conn.EndFlush(writeResult);
                multiResult.Leave();
            }
            SmtpReplyReader reader = conn.Reader!.GetNextReplyReader();
            multiResult.Enter();
 
            //this actually does a read on the stream.
            IAsyncResult result = reader.BeginReadLine(s_onReadLine, multiResult);
            if (result.CompletedSynchronously)
            {
                LineInfo info = SmtpReplyReader.EndReadLine(result);
                if (!(multiResult.Result is Exception))
                    multiResult.Result = info;
                multiResult.Leave();
            }
            multiResult.CompleteSequence();
            return multiResult;
        }
 
 
        internal static object EndSend(IAsyncResult result, out string response)
        {
            object commandResult = MultiAsyncResult.End(result)!;
            if (commandResult is Exception e)
            {
                ExceptionDispatchInfo.Throw(e);
            }
 
            LineInfo info = (LineInfo)commandResult;
            response = info.Line;
            return info.StatusCode;
        }
 
        private static void OnReadLine(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!;
                try
                {
                    SmtpConnection conn = (SmtpConnection)multiResult.Context;
                    LineInfo info = SmtpReplyReader.EndReadLine(result);
                    if (!(multiResult.Result is Exception))
                        multiResult.Result = info;
                    multiResult.Leave();
                }
                catch (Exception e)
                {
                    multiResult.Leave(e);
                }
            }
        }
 
        private static void OnWrite(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!;
                try
                {
                    SmtpConnection conn = (SmtpConnection)multiResult.Context;
                    conn.EndFlush(result);
                    multiResult.Leave();
                }
                catch (Exception e)
                {
                    multiResult.Leave(e);
                }
            }
        }
 
        internal static SmtpStatusCode Send(SmtpConnection conn, out string response)
        {
            conn.Flush();
            SmtpReplyReader reader = conn.Reader!.GetNextReplyReader();
            LineInfo info = reader.ReadLine();
            response = info.Line;
            reader.Close();
            return info.StatusCode;
        }
    }
 
    internal static class ReadLinesCommand
    {
        private static readonly AsyncCallback s_onReadLines = new AsyncCallback(OnReadLines);
        private static readonly AsyncCallback s_onWrite = new AsyncCallback(OnWrite);
 
        internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state)
        {
            MultiAsyncResult multiResult = new MultiAsyncResult(conn, callback, state);
            multiResult.Enter();
            IAsyncResult writeResult = conn.BeginFlush(s_onWrite, multiResult);
            if (writeResult.CompletedSynchronously)
            {
                conn.EndFlush(writeResult);
                multiResult.Leave();
            }
            SmtpReplyReader reader = conn.Reader!.GetNextReplyReader();
            multiResult.Enter();
            IAsyncResult readLinesResult = reader.BeginReadLines(s_onReadLines, multiResult);
            if (readLinesResult.CompletedSynchronously)
            {
                LineInfo[] lines = SmtpReplyReader.EndReadLines(readLinesResult);
                if (!(multiResult.Result is Exception))
                    multiResult.Result = lines;
                multiResult.Leave();
            }
            multiResult.CompleteSequence();
            return multiResult;
        }
 
        internal static LineInfo[] EndSend(IAsyncResult result)
        {
            object commandResult = MultiAsyncResult.End(result)!;
            if (commandResult is Exception e)
            {
                ExceptionDispatchInfo.Throw(e);
            }
            return (LineInfo[])commandResult;
        }
 
        private static void OnReadLines(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!;
                try
                {
                    SmtpConnection conn = (SmtpConnection)multiResult.Context;
                    LineInfo[] lines = SmtpReplyReader.EndReadLines(result);
                    if (!(multiResult.Result is Exception))
                        multiResult.Result = lines;
                    multiResult.Leave();
                }
                catch (Exception e)
                {
                    multiResult.Leave(e);
                }
            }
        }
 
        private static void OnWrite(IAsyncResult result)
        {
            if (!result.CompletedSynchronously)
            {
                MultiAsyncResult multiResult = (MultiAsyncResult)result.AsyncState!;
                try
                {
                    SmtpConnection conn = (SmtpConnection)multiResult.Context;
                    conn.EndFlush(result);
                    multiResult.Leave();
                }
                catch (Exception e)
                {
                    multiResult.Leave(e);
                }
            }
        }
        internal static LineInfo[] Send(SmtpConnection conn)
        {
            conn.Flush();
            return conn.Reader!.GetNextReplyReader().ReadLines();
        }
    }
 
    internal static class AuthCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, string type, string message, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, type, message);
            return ReadLinesCommand.BeginSend(conn, callback, state);
        }
 
        internal static IAsyncResult BeginSend(SmtpConnection conn, string? message, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, message);
            return ReadLinesCommand.BeginSend(conn, callback, state);
        }
 
        private static LineInfo CheckResponse(LineInfo[] lines)
        {
            if (lines == null || lines.Length == 0)
            {
                throw new SmtpException(SR.SmtpAuthResponseInvalid);
            }
            System.Diagnostics.Debug.Assert(lines.Length == 1, "Did not expect more than one line response for auth command");
            return lines[0];
        }
 
        internal static LineInfo EndSend(IAsyncResult result)
        {
            return CheckResponse(ReadLinesCommand.EndSend(result));
        }
        private static void PrepareCommand(SmtpConnection conn, string type, string message)
        {
            conn.BufferBuilder.Append(SmtpCommands.Auth);
            conn.BufferBuilder.Append(type);
            conn.BufferBuilder.Append((byte)' ');
            conn.BufferBuilder.Append(message);
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        private static void PrepareCommand(SmtpConnection conn, string? message)
        {
            conn.BufferBuilder.Append(message);
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        internal static LineInfo Send(SmtpConnection conn, string type, string message)
        {
            PrepareCommand(conn, type, message);
            return CheckResponse(ReadLinesCommand.Send(conn));
        }
 
        internal static LineInfo Send(SmtpConnection conn, string? message)
        {
            PrepareCommand(conn, message);
            return CheckResponse(ReadLinesCommand.Send(conn));
        }
    }
 
    internal static class DataCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn);
            return CheckCommand.BeginSend(conn, callback, state);
        }
 
        private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.StartMailInput:
                    {
                        return;
                    }
                case SmtpStatusCode.LocalErrorInProcessing:
                case SmtpStatusCode.TransactionFailed:
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, serverResponse);
                        }
 
                        throw new SmtpException(statusCode, serverResponse, true);
                    }
            }
        }
 
        internal static void EndSend(IAsyncResult result)
        {
            string response;
            SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response);
            CheckResponse(statusCode, response);
        }
 
        private static void PrepareCommand(SmtpConnection conn)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.Data);
        }
 
        internal static void Send(SmtpConnection conn)
        {
            PrepareCommand(conn);
            string response;
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            CheckResponse(statusCode, response);
        }
    }
 
    internal static class DataStopCommand
    {
        private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.Ok:
                    {
                        return;
                    }
                case SmtpStatusCode.ExceededStorageAllocation:
                case SmtpStatusCode.TransactionFailed:
                case SmtpStatusCode.LocalErrorInProcessing:
                case SmtpStatusCode.InsufficientStorage:
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, serverResponse);
                        }
 
                        throw new SmtpException(statusCode, serverResponse, true);
                    }
            }
        }
 
        private static void PrepareCommand(SmtpConnection conn)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.DataStop);
        }
        internal static void Send(SmtpConnection conn)
        {
            PrepareCommand(conn);
            string response;
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            CheckResponse(statusCode, response);
        }
    }
 
    internal static class EHelloCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, domain);
            return ReadLinesCommand.BeginSend(conn, callback, state);
        }
 
        private static string[] CheckResponse(LineInfo[] lines)
        {
            if (lines == null || lines.Length == 0)
            {
                throw new SmtpException(SR.SmtpEhloResponseInvalid);
            }
            if (lines[0].StatusCode != SmtpStatusCode.Ok)
            {
                if ((int)lines[0].StatusCode < 400)
                {
                    throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, lines[0].Line);
                }
 
                throw new SmtpException(lines[0].StatusCode, lines[0].Line, true);
            }
            string[] extensions = new string[lines.Length - 1];
            for (int i = 1; i < lines.Length; i++)
            {
                extensions[i - 1] = lines[i].Line;
            }
            return extensions;
        }
 
        internal static string[] EndSend(IAsyncResult result)
        {
            return CheckResponse(ReadLinesCommand.EndSend(result));
        }
        private static void PrepareCommand(SmtpConnection conn, string domain)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.EHello);
            conn.BufferBuilder.Append(domain);
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        internal static string[] Send(SmtpConnection conn, string domain)
        {
            PrepareCommand(conn, domain);
            return CheckResponse(ReadLinesCommand.Send(conn));
        }
    }
 
    internal static class HelloCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, string domain, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, domain);
            return CheckCommand.BeginSend(conn, callback, state);
        }
 
        private static void CheckResponse(SmtpStatusCode statusCode, string serverResponse)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.Ok:
                    {
                        return;
                    }
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, serverResponse);
                        }
 
                        throw new SmtpException(statusCode, serverResponse, true);
                    }
            }
        }
 
        internal static void EndSend(IAsyncResult result)
        {
            string response;
            SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response);
            CheckResponse(statusCode, response);
        }
 
        private static void PrepareCommand(SmtpConnection conn, string domain)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.Hello);
            conn.BufferBuilder.Append(domain);
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        internal static void Send(SmtpConnection conn, string domain)
        {
            PrepareCommand(conn, domain);
            string response;
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            CheckResponse(statusCode, response);
        }
    }
 
    internal static class StartTlsCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn);
            return CheckCommand.BeginSend(conn, callback, state);
        }
 
        private static void CheckResponse(SmtpStatusCode statusCode, string response)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.ServiceReady:
                    {
                        return;
                    }
 
                case SmtpStatusCode.ClientNotPermitted:
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, response);
                        }
 
                        throw new SmtpException(statusCode, response, true);
                    }
            }
        }
 
        internal static void EndSend(IAsyncResult result)
        {
            string response;
            SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response);
            CheckResponse(statusCode, response);
        }
 
        private static void PrepareCommand(SmtpConnection conn)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.StartTls);
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        internal static void Send(SmtpConnection conn)
        {
            PrepareCommand(conn);
            string response;
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            CheckResponse(statusCode, response);
        }
    }
 
    internal static class MailCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, ReadOnlySpan<byte> command, MailAddress from,
            bool allowUnicode, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, command, from, allowUnicode);
            return CheckCommand.BeginSend(conn, callback, state);
        }
 
        private static void CheckResponse(SmtpStatusCode statusCode, string response)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.Ok:
                    {
                        return;
                    }
                case SmtpStatusCode.ExceededStorageAllocation:
                case SmtpStatusCode.LocalErrorInProcessing:
                case SmtpStatusCode.InsufficientStorage:
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, response);
                        }
 
                        throw new SmtpException(statusCode, response, true);
                    }
            }
        }
 
        internal static void EndSend(IAsyncResult result)
        {
            string response;
            SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response);
            CheckResponse(statusCode, response);
        }
 
        private static void PrepareCommand(SmtpConnection conn, ReadOnlySpan<byte> command, MailAddress from, bool allowUnicode)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
            conn.BufferBuilder.Append(command);
            string fromString = from.GetSmtpAddress(allowUnicode);
            conn.BufferBuilder.Append(fromString, allowUnicode);
            if (allowUnicode)
            {
                conn.BufferBuilder.Append(" BODY=8BITMIME SMTPUTF8");
            }
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
        internal static void Send(SmtpConnection conn, ReadOnlySpan<byte> command, MailAddress from, bool allowUnicode)
        {
            PrepareCommand(conn, command, from, allowUnicode);
            string response;
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            CheckResponse(statusCode, response);
        }
    }
 
    internal static class RecipientCommand
    {
        internal static IAsyncResult BeginSend(SmtpConnection conn, string to, AsyncCallback? callback, object? state)
        {
            PrepareCommand(conn, to);
            return CheckCommand.BeginSend(conn, callback, state);
        }
 
        private static bool CheckResponse(SmtpStatusCode statusCode, string response)
        {
            switch (statusCode)
            {
                case SmtpStatusCode.Ok:
                case SmtpStatusCode.UserNotLocalWillForward:
                    {
                        return true;
                    }
                case SmtpStatusCode.MailboxUnavailable:
                case SmtpStatusCode.UserNotLocalTryAlternatePath:
                case SmtpStatusCode.ExceededStorageAllocation:
                case SmtpStatusCode.MailboxNameNotAllowed:
                case SmtpStatusCode.MailboxBusy:
                case SmtpStatusCode.InsufficientStorage:
                    {
                        return false;
                    }
                default:
                    {
                        if ((int)statusCode < 400)
                        {
                            throw new SmtpException(SR.net_webstatus_ServerProtocolViolation, response);
                        }
 
                        throw new SmtpException(statusCode, response, true);
                    }
            }
        }
 
        internal static bool EndSend(IAsyncResult result, out string response)
        {
            SmtpStatusCode statusCode = (SmtpStatusCode)CheckCommand.EndSend(result, out response);
            return CheckResponse(statusCode, response);
        }
 
        private static void PrepareCommand(SmtpConnection conn, string to)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.Recipient);
            conn.BufferBuilder.Append(to, true); // Unicode validation was done prior
            conn.BufferBuilder.Append(SmtpCommands.CRLF);
        }
 
 
        internal static bool Send(SmtpConnection conn, string to, out string response)
        {
            PrepareCommand(conn, to);
            SmtpStatusCode statusCode = CheckCommand.Send(conn, out response);
            return CheckResponse(statusCode, response);
        }
    }
 
    internal static class QuitCommand
    {
        private static void PrepareCommand(SmtpConnection conn)
        {
            if (conn.IsStreamOpen)
            {
                throw new InvalidOperationException(SR.SmtpDataStreamOpen);
            }
 
            conn.BufferBuilder.Append(SmtpCommands.Quit);
        }
 
        internal static void Send(SmtpConnection conn)
        {
            PrepareCommand(conn);
 
            // We simply flush and don't read the response
            // to avoid blocking call that will impact users
            // that are using async api, since this code
            // will run on Dispose()
            conn.Flush();
        }
    }
 
    internal static class SmtpCommands
    {
        internal static ReadOnlySpan<byte> Auth => "AUTH "u8;
        internal static ReadOnlySpan<byte> CRLF => "\r\n"u8;
        internal static ReadOnlySpan<byte> Data => "DATA\r\n"u8;
        internal static ReadOnlySpan<byte> DataStop => "\r\n.\r\n"u8;
        internal static ReadOnlySpan<byte> EHello => "EHLO "u8;
        internal static ReadOnlySpan<byte> Expand => "EXPN "u8;
        internal static ReadOnlySpan<byte> Hello => "HELO "u8;
        internal static ReadOnlySpan<byte> Help => "HELP"u8;
        internal static ReadOnlySpan<byte> Mail => "MAIL FROM:"u8;
        internal static ReadOnlySpan<byte> Noop => "NOOP\r\n"u8;
        internal static ReadOnlySpan<byte> Quit => "QUIT\r\n"u8;
        internal static ReadOnlySpan<byte> Recipient => "RCPT TO:"u8;
        internal static ReadOnlySpan<byte> Reset => "RSET\r\n"u8;
        internal static ReadOnlySpan<byte> Send => "SEND FROM:"u8;
        internal static ReadOnlySpan<byte> SendAndMail => "SAML FROM:"u8;
        internal static ReadOnlySpan<byte> SendOrMail => "SOML FROM:"u8;
        internal static ReadOnlySpan<byte> Turn => "TURN\r\n"u8;
        internal static ReadOnlySpan<byte> Verify => "VRFY "u8;
        internal static ReadOnlySpan<byte> StartTls => "STARTTLS"u8;
    }
 
    internal readonly struct LineInfo
    {
        internal LineInfo(SmtpStatusCode statusCode, string line)
        {
            StatusCode = statusCode;
            Line = line;
        }
        internal string Line { get; }
        internal SmtpStatusCode StatusCode { get; }
    }
}