Click here to Skip to main content
15,885,914 members
Articles / Programming Languages / C#

SMTP and POP3 Mail Server

Rate me:
Please Sign up or sign in to vote.
4.88/5 (96 votes)
29 Sep 20031 min read 1M   18.9K   315  
An SMTP and POP3 mail server written using the .NET Framework and C#.
using System;
using System.IO;
using LumiSoft.Net.Mime;

namespace LumiSoft.Net.NNTP.Server
{
	/// <summary>
	/// Parses headers for a news article.
	/// </summary>
	public class HeaderParser
	{
		private string    m_Headers     = "";
		private string    m_From        = "";
		private string[]  m_Newsgroups  = null;
		private string    m_Subject     = "";
		private string    m_MsgID       = "";
		private string    m_References  = "";
		private string    m_Xref        = "";
		private string    m_Lines       = "";	
		private string    m_Body        = "";
		private DateTime  m_MsgDate;

		public HeaderParser(byte[] msg)
		{				
			MemoryStream msgStrm = new MemoryStream(msg);

			m_Headers = ParseHeaders(msgStrm);

			m_From        = ParseFrom(m_Headers);
			m_Newsgroups  = ParseNewsgroups(m_Headers);
			m_Subject     = ParseSubject(m_Headers);
			m_MsgDate     = ParseDate(m_Headers);
			m_MsgID       = ParseMessageID(m_Headers);
			m_References  = ParseReferences(m_Headers);
			m_Xref		  = ParseXRef(m_Headers);
			m_Lines       = ParseLines(m_Headers);	
								
		}

		#region function ParseHeaders

		/// <summary>
		/// Parses mime headers from message.
		/// </summary>
		/// <param name="msgStrm"></param>
		/// <returns></returns>
		private string ParseHeaders(MemoryStream msgStrm)
		{
			/*3.1.  GENERAL DESCRIPTION
			A message consists of header fields and, optionally, a body.
			The  body  is simply a sequence of lines containing ASCII charac-
			ters.  It is separated from the headers by a null line  (i.e.,  a
			line with nothing preceding the CRLF).
			*/

			string headers = "";

			TextReader r = new StreamReader(msgStrm);
			string line = r.ReadLine();

			while(line != null && line.Length != 0)
			{
				headers += line + "\r\n";

				line = r.ReadLine();
			}

			headers += "\r\n";

			return headers;
		}

		#endregion

		#region function ParseHeaders

		/// <summary>
		/// Parses body from message.
		/// </summary>
		/// <param name="msgStrm"></param>
		/// <returns></returns>
		public static string ParseBody(MemoryStream msgStrm)
		{
			string body    = "";
			TextReader r = new StreamReader(msgStrm);
			string line = r.ReadLine();

			while(line != null && line.Length != 0)
			{				
				line = r.ReadLine();
			}
			line = r.ReadLine();
			while(line != null && line.Length != 0)
			{
				body += line + "\r\n";
				line = r.ReadLine();
			}

			body += "\r\n";
			return body;
		}

		#endregion

		#region function ParseFrom

		/// <summary>
		/// Parse sender from message.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseFrom(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
				  {
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("FROM:"))
					{						
						return CDecode(line.Substring(5).Trim());
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function ParseNewsgroups

		private string[] ParseNewsgroups(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
				  {
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("TO:"))
					{
						line = line.Substring(3);

						string to = line;
						if(line.EndsWith(","))
						{
							to = "";
							while(line.EndsWith(","))
							{
								to += line;
								line = r.ReadLine();
							}
						}

						string[] tox = to.Split(new char[]{','});
						for(int i=0;i<tox.Length;i++)
						{
							tox[i] = CDecode(tox[i]);
						}

						return tox;
					}

					line = r.ReadLine();
				}
			}

			return null;
		}

		#endregion

		#region function ParseSubject

		/// <summary>
		/// Parses subject from message.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseSubject(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
				  {
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("SUBJECT:"))
					{						
						return CDecode(line.Substring(8).Trim());
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function ParseDate

		/// <summary>
		/// Parse message date.
		/// </summary>
		/// <param name="headers"></param>
		private DateTime ParseDate(string headers)
		{
			DateTime date = DateTime.Today;
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
				  {
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("DATE:"))
					{						
						date = ParseDateS(line.Substring(5).Trim()); 
						break;
					}
					line = r.ReadLine();					
				}
			}
			return date;
		}

		#endregion

		#region function ParseMessageID

		/// <summary>
		/// Parse message ID.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseMessageID(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
				  {
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("MESSAGE-ID:"))
					{						
						return line.Substring(11).Trim();
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function ParseReferences

		/// <summary>
		/// Parse References.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseReferences(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
			{
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("REFERENCES:"))
					{						
						return line.Substring(11).Trim();
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function ParseXRef

		/// <summary>
		/// Parse XRef.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseXRef(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
			{
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("XREF:"))
					{						
						return line.Substring(5).Trim();
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function ParseLines

		/// <summary>
		/// Parse Lines.
		/// </summary>
		/// <param name="headers"></param>
		/// <returns></returns>
		private string ParseLines(string headers)
		{
			using(TextReader r = new StreamReader(new MemoryStream(System.Text.Encoding.ASCII.GetBytes(headers))))
			{
				string line = r.ReadLine();

				while(line != null)
				{
					if(line.ToUpper().StartsWith("LINES:"))
					{						
						return line.Substring(6).Trim();
					}

					line = r.ReadLine();
				}
			}

			return "";
		}

		#endregion

		#region function GetHeader

		public static byte[] GetHeader(byte[] data)
		{
			using(MemoryStream strm = new MemoryStream(data))
				  {
				TextReader reader = (TextReader)new StreamReader(strm);				
				string headerStr = "";

				string line = reader.ReadLine();
				while(line != null)
				{				
					// End of header reached
					if(line.Length == 0)
					{
						break;
					}

					headerStr += line + "\r\n";

					line = reader.ReadLine();
				}
		
				return System.Text.Encoding.ASCII.GetBytes(headerStr);
			}
		}

		#endregion
        
		#region function ParseHeaderFields

		/// <summary>
		/// Returns requested header fields lines.
		/// </summary>
		/// <param name="fieldsStr">Header fields to get.</param>
		/// <param name="data">Message data.</param>
		/// <returns></returns>
		public static string ParseHeaderFields(string fieldsStr,byte[] data)
		{
			string retVal = "";

			string[] fields = fieldsStr.Split(' ');
			using(MemoryStream mStrm = new MemoryStream(data))
				  {
				TextReader r = new StreamReader(mStrm);
				string line = r.ReadLine();
				
				bool fieldFound = false;
				// Loop all header lines
				while(line != null)
				{ 
					// End of header
					if(line.Length == 0)
					{
						break;
					}

					// Filed continues
					if(fieldFound && line.StartsWith("\t"))
					{
						retVal += line + "\r\n";
					}
					else
					{
						fieldFound = false;

						// Check if wanted field
						foreach(string field in fields)
						{
							if(line.Trim().ToLower().StartsWith(field.Trim().ToLower()))
							{
								retVal += line + "\r\n";
								fieldFound = true;
							}
						}
					}

					line = r.ReadLine();
				}
			}

			return retVal;
		}

		#endregion

		#region function ParseHeaderFieldsNot

		/// <summary>
		/// Returns header fields lines except requested.
		/// </summary>
		/// <param name="fieldsStr">Header fields to skip.</param>
		/// <param name="data">Message data.</param>
		/// <returns></returns>
		public static string ParseHeaderFieldsNot(string fieldsStr,byte[] data)
		{
			string retVal = "";

			string[] fields = fieldsStr.Split(' ');
			using(MemoryStream mStrm = new MemoryStream(data))
				  {
				TextReader r = new StreamReader(mStrm);
				string line = r.ReadLine();
				
				bool fieldFound = false;
				// Loop all header lines
				while(line != null)
				{ 
					// End of header
					if(line.Length == 0)
					{
						break;
					}

					// Filed continues
					if(fieldFound && line.StartsWith("\t"))
					{
						retVal += line + "\r\n";
					}
					else
					{
						fieldFound = false;

						// Check if wanted field
						foreach(string field in fields)
						{
							if(line.Trim().ToLower().StartsWith(field.Trim().ToLower()))
							{								
								fieldFound = true;
							}
						}

						if(!fieldFound)
						{
							retVal += line + "\r\n";
						}
					}

					line = r.ReadLine();
				}
			}

			return retVal;
		}

		#endregion

		#region function ParseText

		/// <summary>
		/// Parses body text from message
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public static string ParseText(byte[] data)
		{
			MimeParser p = new MimeParser(data);

			return p.BodyText;
		}

		#endregion

		#region static function ParseDate

		/// <summary>
		/// Parses rfc2822 datetime.
		/// </summary>
		/// <param name="date">Date string</param>
		/// <returns></returns>
		public static DateTime ParseDateS(string date)
		{
			/*
			GMT  -0000
			EDT  -0400
			EST  -0500
			CDT  -0500
			CST  -0600
			MDT  -0600
			MST  -0700
			PDT  -0700
			PST  -0800
			*/

			try
			{

				date = date.Replace("GMT","-0000");
				date = date.Replace("EDT","-0400");
				date = date.Replace("EST","-0500");
				date = date.Replace("CDT","-0500");
				date = date.Replace("CST","-0600");
				date = date.Replace("MDT","-0600");
				date = date.Replace("MST","-0700");
				date = date.Replace("PDT","-0700");
				date = date.Replace("PST","-0800");
				date = date.Replace("UTC","");
				date = date.Replace("(UTC)","").Trim();
				date = date.Replace("(C-0500)","").Trim();
				date = date.Replace("(-0000)","").Trim();
				date = date.Replace("(-0400)","").Trim();
				date = date.Replace("(-0500)","").Trim();
				date = date.Replace("(-0600)","").Trim();
				date = date.Replace("(-0700)","").Trim();
				date = date.Replace("(-0800)","").Trim();
				date = date.Replace("()","").Trim();


				string[] formats = new string[]{
												   "r",
												   "ddd, d MMM yyyy HH':'mm':'ss zzz",
												   "ddd, dd MMM yyyy HH':'mm':'ss zzz",
												   "ddd, dd MMM yy HH':'mm':'ss zzz",
												   "dd MMM yyyy HH':'mm':'ss zzz",
												   "dd MMM yyyy HH':'mm':'ss",
												   "dd MMM yy HH':'mm':'ss zzz",
												   "ddd, dd MMM yyyy H':'mm':'ss zzz",
												   "dd'-'MMM'-'yyyy HH':'mm':'ss zzz"   
											   };

				return DateTime.ParseExact(date,formats,System.Globalization.DateTimeFormatInfo.InvariantInfo,System.Globalization.DateTimeStyles.None); 
			}
			catch(Exception x)
			{

				return DateTime.Today;
				

			}
		}

		#endregion

		#region function AddHeader

		/// <summary>
		/// Adds a header to a message.
		/// </summary>
		/// <param name="msgdata">Message data.</param>
		/// <param name="header">Name of header</param>
		/// <param name="data">value</param>		
		/// <returns></returns>

		public static MemoryStream AddHeader(MemoryStream msgdata,string header,string data)
		{			
			using(MemoryStream mStrm = new MemoryStream(msgdata.ToArray()))
			{
				string headers = "";
				bool   exists  = false;
				TextReader r = new StreamReader(mStrm);
				string line = r.ReadLine();
								
				// Loop all header lines
				while(line != null)
				{ 					
					// End of header
					if(line.Length == 0)
					{
						break;
					}
					
					headers += line + "\r\n";

					if(line.ToUpper().IndexOf(header.ToUpper()) > -1) //check if header not exists
					{
						exists = true;
					}
					line = r.ReadLine();
				}
				
				if(!exists)
				{
					headers += header + " " + data; //Add header
				}
				headers += "\r\n";				
				headers += "\r\n" +"\0" + "\r\n"+ r.ReadToEnd(); //Add null line,then rest of msg
				
				MemoryStream ms = new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(headers));
				return ms;
			}

			
		}

		#endregion

		#region function CDecode

		private string CDecode(string data)
		{			
			if(data.IndexOf("=?") > -1)
			{
				int index = data.IndexOf("=?");

				string[] parts = data.Substring(index+2).Split(new char[]{'?'});
				
				string encoding = parts[0];
				string type     = parts[1];
				string datax    = parts[2];

				System.Text.Encoding enc = System.Text.Encoding.GetEncoding(encoding);
				if(type.ToUpper() == "Q")
				{
					return Core.QDecode(enc,datax);
				}

				if(type.ToUpper() == "B")
				{
					return enc.GetString(Convert.FromBase64String(datax));
				}				
			}

			return data;
		}

		#endregion

		#region Properties Implementation

		/// <summary>
		/// Gets message headers.
		/// </summary>
		public string Headers
		{
			get{ return m_Headers; }
		}

		/// <summary>
		/// Gets sender.
		/// </summary>
		public string From
		{
			get{ return m_From; }
		}

		/// <summary>
		/// Gets newsgroups.
		/// </summary>
		public string[] Newsgroups
		{
			get{ return m_Newsgroups; }
		}
		/// <summary>
		/// Gets msg body.
		/// </summary>
		public string Body
		{
			get{ return m_Body; }
		}

		/// <summary>
		/// Gets subject.
		/// </summary>
		public string Subject
		{
			get{ return m_Subject; }
		}
		/// <summary>
		/// Gets messageID.
		/// </summary>
		public string MessageID
		{
			get{ return m_MsgID; }
		}
		/// <summary>
		/// Gets References.
		/// </summary>
		public string References
		{
			get{ return m_References; }
		}
		/// <summary>
		/// Gets XReferences.
		/// </summary>
		public string XRef
		{
			get{ return m_Xref; }
		}

		/// <summary>
		/// Gets Lines.
		/// </summary>
		public string Lines
		{
			get{ return m_Lines; }
		}

		/// <summary>
		/// Gets message date.
		/// </summary>
		public DateTime MessageDate
		{
			get{ return m_MsgDate; }
		}

		#endregion
	}
}

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
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions