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, "@");
return result;
}
}
}