Click here to Skip to main content
15,894,017 members
Articles / Web Development / ASP.NET

POP3 Client Over Firewall

Rate me:
Please Sign up or sign in to vote.
3.45/5 (8 votes)
21 Jul 20072 min read 40.4K   736   35  
A webservice implementation to connect to POP3 servers
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

using SiProd.BaseElements.Network.Emails;
using SiProd.BaseElements.Text.Encoding;

namespace SiProd.BaseElements.Network.Emails
{
    public class EMailParser
    {
        private const string NEWLINE = "\r\n";
        
        public EMailParser()
        {
        }

        public EMailMessage CreateEmailFromRawText(string RawEmail)
        {
            QuotedTextDecoder decoder = new QuotedTextDecoder();
            string decoded = decoder.DecodeFromQuotedPrintable(RawEmail, false);

            EMailMessage result = new EMailMessage();
            ParseEMailMessage(ref result, decoded);
            return result;
        }

        public void ParseEMailMessageHeader(ref EMailMessage OriginalEmail, string RawEMail)
        {
            ParseEMailMessageHeader(ref OriginalEmail, RawEMail, true);
        }
        public void ParseEMailMessageHeader(ref EMailMessage OriginalEmail, string RawEMail, bool Overwrite)
        {
            string[] messageLines = ExtractMessageLines(RawEMail);
            int endHeaderPos = FindHeaderEnd(messageLines);

            string MessageBoundary = string.Empty;

            ParseMessageHeaders(ref OriginalEmail, GetHeaderLines(messageLines, endHeaderPos), out MessageBoundary);
        }
        
        public void ParseEMailMessage(ref EMailMessage OriginalEmail, string RawEMail)
        {
            ParseEMailMessage(ref OriginalEmail, RawEMail, true);
        }
        public void ParseEMailMessage(ref EMailMessage OriginalEmail, string RawEMail, bool Overwrite)
        {
            QuotedTextDecoder decoder = new QuotedTextDecoder();
            string decoded = decoder.DecodeFromQuotedPrintable(RawEMail, false);
            decoded = RemoveInvalidChars(decoded); 

            string[] messageLines = ExtractMessageLines(decoded);
            int endHeaderPos = FindHeaderEnd(messageLines);

            string messageBoundary = string.Empty;

            ParseMessageHeaders(ref OriginalEmail, GetHeaderLines(messageLines, endHeaderPos), out messageBoundary);

            int[] boundariesPos;

            boundariesPos = FindBoundariesPositions(messageLines, messageBoundary, endHeaderPos);
            
            ParseMessageContent(ref OriginalEmail, GetBodyLines(messageLines, endHeaderPos), boundariesPos);
        }

        private string[] ExtractMessageLines(string RawEMail)
        {
            Regex rxLines = new Regex(@"(.*)\r\n");
            MatchCollection mLines = rxLines.Matches(RawEMail);

            string[] result = new string[mLines.Count];

            for (int i = 0; i < mLines.Count; ++i)
            {
                result[i] = mLines[i].Value.Remove(mLines[i].Value.Length - 2, 2);
            }

            return result;
        }

        private void ParseMessageHeaders(ref EMailMessage OriginalEmail, string[] MessageHeaderLines, out string MessageBoundary)
        {
            Regex regEx = new Regex(@"^[\t]*(.*?)[:=](.*)$");
            StringBuilder rawHeader = new StringBuilder();
            MessageBoundary = string.Empty;

            foreach (string line in MessageHeaderLines)
            {
                Match match = regEx.Match(line);

                rawHeader.AppendFormat(@"{0}{1}", line, NEWLINE);

                if (match.Groups.Count >= 3)
                {
                    switch (match.Groups[1].Value.ToLower())
                    {
                        case "subject":
                            OriginalEmail.Subject = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "from":
                            OriginalEmail.From = ParseEmailAddress(match.Groups[2].Value.Trim());
                            break;
                        case "to":
                            OriginalEmail.To = ParseEmailAddressTag(match.Groups[2].Value.Trim());
                            break;
                        case "date":
                            OriginalEmail.Date = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "priority":
                        case "importance":
                            OriginalEmail.Priority = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "reply-to":
                            OriginalEmail.ReplyTo = ParseEmailAddress(match.Groups[2].Value.Trim());
                            break;
                        case "content-class":
                            OriginalEmail.ContentClasses = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "mime-version":
                            OriginalEmail.MIMEVersion = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "content-type":
                            int semiColonIndx = match.Groups[2].Value.IndexOf(";");
                            if (semiColonIndx >= 0)
                            {
                                OriginalEmail.ContentType = GetCleanValue(match.Groups[2].Value.Substring(0, semiColonIndx));
                                string junkBoundary = string.Empty;
                                ParseMessageHeaders(ref OriginalEmail, new string[] { GetCleanValue(match.Groups[2].Value.Substring(semiColonIndx + 1)) }, out junkBoundary);
                            }
                            else
                            {
                                OriginalEmail.ContentType = GetCleanValue(match.Groups[2].Value.Trim());
                            }
                            break;
                        case "cc":
                            OriginalEmail.CC = ParseEmailAddressTag(match.Groups[2].Value.Trim());
                            break;
                        case "subject-encoding":
                            OriginalEmail.SubjectEncoding = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "body-encoding":
                            OriginalEmail.BodyEncoding = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "boundary":
                            string cleanBoundary = GetCleanValue(match.Groups[2].Value.Trim());
                            MessageBoundary = string.Format("--{0}", cleanBoundary);
                            break;
                        default:
                            OriginalEmail.OtherHeaders.Add(match.Groups[0].Value.ToString());
                            break;
                    }
                }
            }

            OriginalEmail.Header = rawHeader.ToString();
            
            //OriginalEmail.BodyEncoding = ExtractMessageField("", RawEMail);
        }
        private void ParseMessageContent(ref EMailMessage OriginalEmail, string[] MessageContentLines, int[] BoundariesPos)
        {
            int bodyAttachsCount = 0;
            EMailBodyAlternateView refBody;
            if (OriginalEmail.ContentType == null)
            {
                OriginalEmail.ContentType = "text/plain";
            }
            switch (OriginalEmail.ContentType.ToLower())
            { 
                case "text/plain":
                case "text/html":
                case "text/htm":
                case "":
                    OriginalEmail.Body = new EMailBodyAlternateView();
                    refBody = OriginalEmail.Body;
                    ParseAlternateView(ref refBody, GetStringArrayPart(MessageContentLines, 0, MessageContentLines.Length - 1));
                    break;
                case "multipart/alternative":
                    bodyAttachsCount = BoundariesPos.Length - 2;
                    OriginalEmail.Body = new EMailBodyAlternateView();
                    refBody = OriginalEmail.Body;
                    ParseAlternateView(ref refBody, GetStringArrayPart(MessageContentLines, BoundariesPos[0] + 1, BoundariesPos[1] - 1));

                    OriginalEmail.Views = new EMailBodyAlternateView[BoundariesPos.Length - 2];
                    for (int i = 1; i <= OriginalEmail.Views.Length; ++i)
                    {
                        string[] viewLines = GetStringArrayPart(MessageContentLines, BoundariesPos[i] + 1, BoundariesPos[i + 1] - 1);
                        OriginalEmail.Views[i - 1] = new EMailBodyAlternateView();
                        ParseAlternateView(ref OriginalEmail.Views[i - 1], viewLines);
                    }
                    break;
                case "multipart/mixed":
                case "multipart/related":
                    bodyAttachsCount = BoundariesPos.Length - 2;
                    string[] bodyLines = GetStringArrayPart(MessageContentLines, BoundariesPos[0] + 1, BoundariesPos[1] - 1);
                    ParseMixedContent(ref OriginalEmail, bodyLines);
                    break;
            }

            if (bodyAttachsCount > 0)
            { 
                OriginalEmail.Attachments = new EMailAttachment[bodyAttachsCount];
                for(int i = 0; i<bodyAttachsCount; ++i)
                {
                    OriginalEmail.Attachments[i] = new EMailAttachment();
                    //int startLine = BoundariesPos[BoundariesPos.Length - (BoundariesPos.Length - bodyAttachsCount) + i];
                    //int endLine = BoundariesPos[BoundariesPos.Length - (BoundariesPos.Length - bodyAttachsCount) + i + 1];

                    int startLine = BoundariesPos[1+i];
                    int endLine = BoundariesPos[2+i];
                    
                    ParseAttachment(ref OriginalEmail.Attachments[i], GetStringArrayPart(MessageContentLines, startLine+1, endLine-1));
                }
            }
        }

        private void ParseAlternateView(ref EMailBodyAlternateView AlternateView, string[] ViewLines)
        {
            int headerEndPos = FindHeaderEnd(ViewLines);
            ParseAlternateViewHeaders(ref AlternateView, GetHeaderLines(ViewLines, headerEndPos));
            ParseAlternateViewContent(ref AlternateView, GetBodyLines(ViewLines, headerEndPos));
        }
        private void ParseAlternateViewHeaders(ref EMailBodyAlternateView AlternateView, string[] ViewHeaderLines)
        {
            Regex regEx = new Regex(@"^[\t]*(.*?)[:=](.*)$");
            ArrayList linkedRes = new ArrayList();

            foreach (string line in ViewHeaderLines)
            {
                Match match = regEx.Match(line);

                if (match.Groups.Count >= 3)
                {
                    switch (match.Groups[1].Value.ToLower())
                    {
                        case "content-type":
                            int semiColonIndx = match.Groups[2].Value.IndexOf(";");
                            if (semiColonIndx >= 0)
                            {
                                AlternateView.ContentType = GetCleanValue(match.Groups[2].Value.Substring(0, semiColonIndx));
                                ParseAlternateViewHeaders(ref AlternateView, new string[] { GetCleanValue(match.Groups[2].Value.Substring(semiColonIndx + 1)) });
                            }
                            else
                            {
                                AlternateView.ContentType = GetCleanValue(match.Groups[2].Value.Trim());
                            }
                            break;
                        case "content-transfer-encoding":
                            AlternateView.ContentTransferEncoding = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "charset":
                            AlternateView.Charset = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "base-uri":
                            AlternateView.BaseUri = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "id":
                            AlternateView.Id = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "linked-resources":
                            linkedRes.Add(GetCleanValue(match.Groups[2].Value.Trim()));
                            break;
                    }
                }
            }
            if(linkedRes.Count >0)
            {
                AlternateView.LinkedResources = new string[linkedRes.Count];
                linkedRes.CopyTo(AlternateView.LinkedResources);
            }

        }
        private void ParseAlternateViewContent(ref EMailBodyAlternateView AlternateView, string[] ViewContentLines)
        {
            AlternateView.ContentStream = ConcatenateArray(ViewContentLines);
        }

        private void ParseAttachment(ref EMailAttachment Attachment, string[] AttachmentLines)
        {
            int headerEndLine = FindHeaderEnd(AttachmentLines);
            ParseAttachmentHeaders(ref Attachment, GetHeaderLines(AttachmentLines, headerEndLine));
            ParseAttachmentContent(ref Attachment, GetBodyLines(AttachmentLines, headerEndLine));
        }
        private void ParseAttachmentHeaders(ref EMailAttachment Attachment, string[] AttachmentHeaderLines)
        {
            Regex regEx = new Regex(@"^[\t]*(.*?)[:=](.*)$");
            ArrayList linkedRes = new ArrayList();

            foreach (string line in AttachmentHeaderLines)
            {
                Match match = regEx.Match(line);

                if (match.Groups.Count >= 3)
                {
                    switch (match.Groups[1].Value.ToLower())
                    {
                        case "content-type":
                            Attachment.ContentType = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "content-transfer-encoding":
                            Attachment.ContentTransferEncoding = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "content-disposition":
                            Attachment.ContentDisposition = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "name-encoding":
                            Attachment.NameEncoding= GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "ID":
                            Attachment.Id = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "content-description":
                            Attachment.ContentDescription= GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "filename":
                            Attachment.FileName= GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                        case "name":
                            Attachment.Name = GetCleanValue(match.Groups[2].Value.Trim());
                            break;
                    }
                }
            }
        }
        private void ParseAttachmentContent(ref EMailAttachment Attachment, string[] AttachmentContentLines)
        {
            Attachment.ContentStream = ConcatenateArray(AttachmentContentLines);
        }

        private void ParseMixedContent(ref EMailMessage OriginalEmail, string[] ContentLines)
        {
            int endHeaderPos = FindHeaderEnd(ContentLines);
            string MessageBoundary = string.Empty;

            ParseMixedContentHeaders(ref OriginalEmail, GetHeaderLines(ContentLines, endHeaderPos), out MessageBoundary);

            int[] BoundariesPos;

            if (MessageBoundary != string.Empty)
            {
                BoundariesPos = FindBoundariesPositions(ContentLines, MessageBoundary, endHeaderPos);
            }
            else
            {
                BoundariesPos = new int[] { 0, ContentLines.Length-endHeaderPos-1 };
            }

            OriginalEmail.Body = new EMailBodyAlternateView();
            EMailBodyAlternateView refBody = OriginalEmail.Body;
            ParseAlternateView(ref refBody, GetStringArrayPart(GetBodyLines(ContentLines, endHeaderPos), BoundariesPos[0] + 1, BoundariesPos[1] - 1));

            OriginalEmail.Views = new EMailBodyAlternateView[BoundariesPos.Length - 2];
            for (int i = 1; i <= OriginalEmail.Views.Length; ++i)
            {
                string[] viewLines = GetStringArrayPart(GetBodyLines(ContentLines, endHeaderPos), BoundariesPos[i] + 1, BoundariesPos[i + 1] - 1);
                OriginalEmail.Views[i - 1] = new EMailBodyAlternateView();
                ParseAlternateView(ref OriginalEmail.Views[i - 1], viewLines);
            }
            
        }
        private void ParseMixedContentHeaders(ref EMailMessage OriginalEmail, string[] MixedContentHeaderLines, out string MessageBoundary)
        { 
            Regex regEx = new Regex(@"^[\t]*(.*?)[:=](.*)$");
            ArrayList linkedRes = new ArrayList();
            MessageBoundary = string.Empty;

            foreach (string line in MixedContentHeaderLines)
            {
                Match match = regEx.Match(line);

                if (match.Groups.Count >= 3)
                {
                    switch (match.Groups[1].Value.ToLower())
                    {
                        case "boundary":
                            string cleanBoundary = GetCleanValue(match.Groups[2].Value.Trim());
                            MessageBoundary = string.Format("--{0}", cleanBoundary);
                            break;
                    }
                }
            }
        }

        private int FindHeaderEnd(string[] FullContent)
        {
            int result = -1;
            int currentLine = 0;
            while (FullContent[currentLine].Trim() != string.Empty)
            {
                ++currentLine;
            }

            result = currentLine;

            return result;
        }

        private int[] FindBoundariesPositions(string[] MessageLines, string Boundary, int HeaderEndPosition)
        {
            int[] result = null;

            if (Boundary != null && Boundary != string.Empty)
            {
                ArrayList linesNumbers = new ArrayList();

                for (int i = HeaderEndPosition; i < MessageLines.Length; ++i)
                {
                    if (MessageLines[i].Trim().Equals(Boundary) || MessageLines[i].Trim().Equals(string.Format("{0}--", Boundary)))
                    {
                        linesNumbers.Add(i - HeaderEndPosition -1);
                    }
                }
                result = new int[linesNumbers.Count];
                linesNumbers.CopyTo(result);
            }

            return result;
        }

        private string[] GetHeaderLines(string[] FullContent, int HeaderEndLineNumber)
        {
            string[] result = new string[HeaderEndLineNumber];
            Array.Copy(FullContent, result, HeaderEndLineNumber);
            return result;
        }

        private string[] GetBodyLines(string[] FullContent, int HeaderEndLineNumber)
        {
            string[] result = new string[FullContent.Length - HeaderEndLineNumber];
            Array.Copy(FullContent, HeaderEndLineNumber, result, 0, FullContent.Length - HeaderEndLineNumber);
            return result;
        }

        private string[] GetStringArrayPart(string[] StringArray, int StartIndex, int EndIndex)
        {
            string[] result = null;
            if (StringArray == null || StartIndex < 0 || EndIndex > StringArray.Length - 1 || StringArray.Length < 1)
            {
                return result;
            }

            result = new string[EndIndex - StartIndex +1];
            for (int i = StartIndex, j = 0; i <= EndIndex; ++i, ++j)
            {
                result[j] = StringArray[i];
            }

            return result;
        }

        private string GetCleanValue(string DirtyValue)
        {
            string result;

            result = DirtyValue.Replace("\"", string.Empty);
            result = result.Replace("\\", string.Empty);
            result = result.Replace(";", string.Empty);
            result = result.Trim();

            return result;
        }

        private string ConcatenateArray(string[] BodyPartLines)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string line in BodyPartLines)
            {
                sb.AppendFormat("{0}{1}", line, NEWLINE);
            }
            return sb.ToString();
        }

        private EMailAddress[] ParseEmailAddressTag(string emailHeaderTag)
        {
            EMailAddress[] result;
            char[] sepatators = ",;".ToCharArray();
            string[] emails = emailHeaderTag.Split(sepatators);
            ArrayList list = new ArrayList();
            foreach (string emailTag in emails)
            { 
                EMailAddress address = ParseEmailAddress(emailTag);
                if (address.DisplayName != null && address.Host != null)
                { list.Add(address); }
            }
            result = new EMailAddress[list.Count];
            list.CopyTo(result);
            return result;
        }

        private EMailAddress ParseEmailAddress(string emailAddressTag)
        {
            EMailAddress result = null;
            string[] parts = new string[2];
            if (EMailAddress.ValidateAddress(emailAddressTag))
            {
                parts[0] = emailAddressTag;
                parts[1] = string.Empty;
            }
            else
            {
                Regex regEx = new Regex(@"<.*>$");
                Match match = regEx.Match(emailAddressTag);
                if (match.Success)
                {
                    parts[0] = match.Value;
                    parts[0] = parts[0].Substring(1, parts[0].Length - 2);
                    parts[1] = emailAddressTag.Remove(match.Index).Trim();
                }
                else
                {
                    parts[0] = parts[1] = emailAddressTag;
                }
            }
            result = new EMailAddress(parts[0], parts[1]);
            return result;
        }

        private string RemoveInvalidChars(string Input)
        {
            string result = string.Empty;

            Regex rg = new Regex("&#x([[0-9][A-F]]{3});", RegexOptions.Multiline);
            MatchCollection founds = rg.Matches(Input);

            result = rg.Replace(Input, "&#x040;");
            return result;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Carlos Salvatore
Argentina Argentina
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.
This is a Organisation (No members)


Comments and Discussions