File: net\System\Net\mail\SmtpReplyReaderFactory.cs
Project: ndp\fx\src\System.csproj (System)
namespace System.Net.Mail
{
    using System;
    using System.IO;
    using System.Text;
    using System.Collections;
 
    //Streams created are read only and return 0 once a full server reply has been read
    //To get the next server reply, call GetNextReplyReader
    class SmtpReplyReaderFactory
    {
        enum ReadState
        {
            Status0,
            Status1,
            Status2,
            ContinueFlag,
            ContinueCR,
            ContinueLF,
            LastCR,
            LastLF,
            Done
        }
 
 
        BufferedReadStream bufferedStream;
        byte[] byteBuffer;
        SmtpReplyReader currentReader;
        const int DefaultBufferSize = 256;
        ReadState readState = ReadState.Status0;
        SmtpStatusCode statusCode;
 
        internal SmtpReplyReaderFactory(Stream stream)
        {
            bufferedStream = new BufferedReadStream(stream);
        }
 
        internal SmtpReplyReader CurrentReader
        {
            get
            {
                return currentReader;
            }
        }
 
        internal SmtpStatusCode StatusCode
        {
            get
            {
                return statusCode;
            }
        }
 
        internal IAsyncResult BeginReadLines(SmtpReplyReader caller, AsyncCallback callback, object state)
        {
            ReadLinesAsyncResult result =  new ReadLinesAsyncResult(this, callback, state);
            result.Read(caller);
            return result;
        }
 
        internal IAsyncResult BeginReadLine(SmtpReplyReader caller, AsyncCallback callback, object state)
        {
            ReadLinesAsyncResult result =  new ReadLinesAsyncResult(this, callback, state, true);
            result.Read(caller);
            return result;
        }
        
        
        internal void Close(SmtpReplyReader caller)
        {
            if (currentReader == caller)
            {
                if (readState != ReadState.Done)
                {
                    if (byteBuffer == null)
                    {
                        byteBuffer = new byte[SmtpReplyReaderFactory.DefaultBufferSize];
                    }
 
                    while (0 != Read(caller, byteBuffer, 0, byteBuffer.Length));
                }
 
                currentReader = null;
            }
        }
 
        internal LineInfo[] EndReadLines(IAsyncResult result)
        {
            return ReadLinesAsyncResult.End(result);
        }
 
        internal LineInfo EndReadLine(IAsyncResult result)
        {
            LineInfo[] info = ReadLinesAsyncResult.End(result);
            if(info != null && info.Length >0){
                return info[0];
            }
            return new LineInfo();
        }
 
        internal SmtpReplyReader GetNextReplyReader()
        {
            if (currentReader != null)
            {
                currentReader.Close();
            }
 
            readState = ReadState.Status0;
            currentReader = new SmtpReplyReader(this);
            return currentReader;
        }
 
        int ProcessRead(byte[] buffer, int offset, int read, bool readLine)
        {
            // if 0 bytes were read,there was a failure
            if (read == 0)
            {
                throw new IOException(SR.GetString(SR.net_io_readfailure, SR.net_io_connectionclosed));
            }
 
            unsafe
            {
                fixed (byte* pBuffer = buffer)
                {
                    byte* start = pBuffer + offset;
                    byte* ptr = start;
                    byte* end = ptr + read;
 
                    switch (readState)
                    {
                        case ReadState.Status0:
                        {
                            if (ptr < end)
                            {
                                byte b = *ptr++;
                                if (b < '0' && b > '9')
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
 
                                statusCode = (SmtpStatusCode)(100*(b - '0'));
 
                                goto case ReadState.Status1;
                            }
                            readState = ReadState.Status0;
                            break;
                        }
                        case ReadState.Status1:
                        {
                            if (ptr < end)
                            {
                                byte b = *ptr++;
                                if (b < '0' && b > '9')
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
 
                                statusCode += 10*(b - '0');
 
                                goto case ReadState.Status2;
                            }
                            readState = ReadState.Status1;
                            break;
                        }
                        case ReadState.Status2:
                        {
                            if (ptr < end)
                            {
                                byte b = *ptr++;
                                if (b < '0' && b > '9')
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
 
                                statusCode += b - '0';
 
                                goto case ReadState.ContinueFlag;
                            }
                            readState = ReadState.Status2;
                            break;
                        }
                        case ReadState.ContinueFlag:
                        {
                            if (ptr < end)
                            {
                                byte b = *ptr++;
                                if (b == ' ')       // last line
                                {
                                    goto case ReadState.LastCR;
                                }
                                else if (b == '-')  // more lines coming
                                {
                                    goto case ReadState.ContinueCR;
                                }
                                else                // error
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
                            }
                            readState = ReadState.ContinueFlag;
                            break;
                        }
                        case ReadState.ContinueCR:
                        {
                            while (ptr < end)
                            {
                                if (*ptr++ == '\r')
                                {
                                    goto case ReadState.ContinueLF;
                                }
                            }
                            readState = ReadState.ContinueCR;
                            break;
                        }
                        case ReadState.ContinueLF:
                        {
                            if (ptr < end)
                            {
                                if (*ptr++ != '\n')
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
                                if (readLine)
                                {
                                    readState = ReadState.Status0;
                                    return (int)(ptr - start);
                                }
                                goto case ReadState.Status0;
                            }
                            readState = ReadState.ContinueLF;
                            break;
                        }
                        case ReadState.LastCR:
                        {
                            while (ptr < end)
                            {
                                if (*ptr++ == '\r')
                                {
                                    goto case ReadState.LastLF;
                                }
                            }
                            readState = ReadState.LastCR;
                            break;
                        }
                        case ReadState.LastLF:
                        {
                            if (ptr < end)
                            {
                                if (*ptr++ != '\n')
                                {
                                    throw new FormatException(SR.GetString(SR.SmtpInvalidResponse));
                                }
                                goto case ReadState.Done;
                            }
                            readState = ReadState.LastLF;
                            break;
                        }
                        case ReadState.Done:
                        {
                            int actual = (int)(ptr - start);
                            readState = ReadState.Done;
                            return actual;
                        }
                    }
                    return (int)(ptr - start);
                }
            }
        }
               
        internal int Read(SmtpReplyReader caller, byte[] buffer, int offset, int count)
        {
            // if we've already found the delimitter, then return 0 indicating
            // end of stream.
            if (count == 0 || currentReader != caller || readState == ReadState.Done)
            {
                return 0;
            }
 
            int read = bufferedStream.Read(buffer, offset, count);
            int actual = ProcessRead(buffer, offset, read, false);
            if (actual < read)
            {
                bufferedStream.Push(buffer, offset + actual, read - actual);
            }
 
            return actual;
        }
              
 
        internal LineInfo ReadLine(SmtpReplyReader caller)
        {
           LineInfo[] info = ReadLines(caller,true);
           if(info != null && info.Length >0){
               return info[0];
           }
           return new LineInfo();
        }
 
        internal LineInfo[] ReadLines(SmtpReplyReader caller)
        {
            return ReadLines(caller,false);
        }
 
 
        internal LineInfo[] ReadLines(SmtpReplyReader caller, bool oneLine)
        {
            if (caller != currentReader || readState == ReadState.Done)
            {
                return new LineInfo[0];
            }
 
            if (byteBuffer == null)
            {
                byteBuffer = new byte[SmtpReplyReaderFactory.DefaultBufferSize];
            }
 
            System.Diagnostics.Debug.Assert(readState == ReadState.Status0);
 
            StringBuilder builder = new StringBuilder();
            ArrayList lines = new ArrayList();
            int statusRead = 0;
 
            for(int start = 0, read = 0; ; )
            {
                if (start == read)
                {
                    read = bufferedStream.Read(byteBuffer, 0, byteBuffer.Length);
                    start = 0;
                }
 
                int actual = ProcessRead(byteBuffer, start, read - start, true);
 
                if (statusRead < 4)
                {
                    int left = Math.Min(4-statusRead, actual);
                    statusRead += left;
                    start += left;
                    actual -= left;
                    if (actual == 0)
                    {
                        continue;
                    }
                }
 
                builder.Append(Encoding.UTF8.GetString(byteBuffer, start, actual));
                start += actual;
 
                if (readState == ReadState.Status0)
                {
                    statusRead = 0;
                    lines.Add(new LineInfo(statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF
                    
                    if(oneLine){
                        bufferedStream.Push(byteBuffer, start, read - start);
                        return (LineInfo[])lines.ToArray(typeof(LineInfo));
                    }
                    builder = new StringBuilder();
                }
                else if (readState == ReadState.Done)
                {
                    lines.Add(new LineInfo(statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF
                    bufferedStream.Push(byteBuffer, start, read - start);
                    return (LineInfo[])lines.ToArray(typeof(LineInfo));
                }
            }
        }
 
        class ReadLinesAsyncResult : LazyAsyncResult
        {
            StringBuilder builder;
            ArrayList lines;
            SmtpReplyReaderFactory parent;
            static AsyncCallback readCallback = new AsyncCallback(ReadCallback);
            int read;
            int statusRead;
            bool oneLine;
 
            internal ReadLinesAsyncResult(SmtpReplyReaderFactory parent, AsyncCallback callback, object state) : base(null, state, callback)
            {
                this.parent = parent;
            }
 
            internal ReadLinesAsyncResult(SmtpReplyReaderFactory parent, AsyncCallback callback, object state, bool oneLine) : base(null, state, callback)
            {
                this.oneLine = oneLine;
                this.parent = parent;
            }
 
            internal void Read(SmtpReplyReader caller){
 
                // if we've already found the delimitter, then return 0 indicating
                // end of stream.
                if (parent.currentReader != caller || parent.readState == ReadState.Done)
                {
                    InvokeCallback();
                    return;
                }
 
                if (parent.byteBuffer == null)
                {
                    parent.byteBuffer = new byte[SmtpReplyReaderFactory.DefaultBufferSize];
                }
 
                System.Diagnostics.Debug.Assert(parent.readState == ReadState.Status0);
 
                builder = new StringBuilder();
                lines = new ArrayList();
 
                Read();
            }
 
            internal static LineInfo[] End(IAsyncResult result)
            {
                ReadLinesAsyncResult thisPtr = (ReadLinesAsyncResult)result;
                thisPtr.InternalWaitForCompletion();
                return (LineInfo[])thisPtr.lines.ToArray(typeof(LineInfo));
            }
 
            void Read()
            {
                do
                {
                    IAsyncResult result = parent.bufferedStream.BeginRead(parent.byteBuffer, 0, parent.byteBuffer.Length, readCallback, this);
                    if (!result.CompletedSynchronously)
                    {
                        return;
                    }
                    read = parent.bufferedStream.EndRead(result);
                } while(ProcessRead());
            }
 
            static void ReadCallback(IAsyncResult result)
            {
                if (!result.CompletedSynchronously)
                {
                    Exception exception = null;
                    ReadLinesAsyncResult thisPtr = (ReadLinesAsyncResult)result.AsyncState;
                    try
                    {
                        thisPtr.read = thisPtr.parent.bufferedStream.EndRead(result);
                        if (thisPtr.ProcessRead())
                        {
                            thisPtr.Read();
                        }
                    }
                    catch (Exception e)
                    {   exception = e;
                    }
 
                    if(exception != null){
                        thisPtr.InvokeCallback(exception);
                    }
                }
            }
 
            bool ProcessRead()
            {
                if (read == 0)
                {
                    throw new IOException(SR.GetString(SR.net_io_readfailure, SR.net_io_connectionclosed));
                }
 
                for(int start = 0; start != read; )
                {
                    int actual = parent.ProcessRead(parent.byteBuffer, start, read - start, true);
 
                    if (statusRead < 4)
                    {
                        int left = Math.Min(4-statusRead, actual);
                        statusRead += left;
                        start += left;
                        actual -= left;
                        if (actual == 0)
                        {
                            continue;
                        }
                    }
 
                    builder.Append(Encoding.UTF8.GetString(parent.byteBuffer, start, actual));
                    start += actual;
 
                    if (parent.readState == ReadState.Status0)
                    {
                        lines.Add(new LineInfo(parent.statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF
                        builder = new StringBuilder();
                        statusRead = 0;
 
                        if (oneLine) {
                            parent.bufferedStream.Push(parent.byteBuffer, start, read - start);
                            InvokeCallback();
                            return false;
                        }
                    }
                    else if (parent.readState == ReadState.Done)
                    {
                        lines.Add(new LineInfo(parent.statusCode, builder.ToString(0, builder.Length - 2))); // return everything except CRLF
                        parent.bufferedStream.Push(parent.byteBuffer, start, read - start);
                        InvokeCallback();
                        return false;
                    }
                }
                return true;
            }
 
        }
    }
}