Click here to Skip to main content
15,892,697 members
Articles / Programming Languages / C#

Advanced MIME Parser/Creator/Editor

Rate me:
Please Sign up or sign in to vote.
4.91/5 (66 votes)
5 Oct 2005 1M   7.7K   116  
An advanced MIME parser/creator/editor application.
using System;
using System.IO;
using System.Text;
using System.Collections;

namespace LumiSoft.Net.Mime
{
	/// <summary>
	/// Simple Mime constructor. 
	/// Use this class if you need to construct simple email messages, use Mime class insted if you need to create complex messages.
	/// </summary>
	/// <example>
	/// <code>
	///	MimeConstructor m = new MimeConstructor();
	///	m.From = new Mailbox("display name","sender@domain.com");
	///	m.To = new Mailbox[]{new Mailbox("display name","to@domain.com")};
	///	m.Subject = "test subject";
	///	m.Text = "Test text";
	///	
	///	// Get mime data
	///	// byte[] mimeData = m.ToByteData();
	///	// or m.ToStream(storeStream)
	///	// or m.ToFile(fileName)
	/// </code>
	/// </example>
	[Obsolete("This class is will be removed, use Mime class instead.")]
	public class MimeConstructor
	{
		private HeaderFieldCollection m_pHeader      = null;
		private string                m_Body         = "";
		private string                m_BodyHtml     = "";
		private string                m_CharSet      = "";
		private Attachments           m_pAttachments = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		public MimeConstructor()
		{
			m_pHeader = new HeaderFieldCollection();
			m_pHeader.Add("From:","");
			m_pHeader.Add("To:","");			
			m_pHeader.Add("Subject:","");
			m_pHeader.Add("Date:",DateTime.Now.ToUniversalTime().ToString("r",System.Globalization.DateTimeFormatInfo.InvariantInfo));
			m_pHeader.Add("Message-ID:","<" + Guid.NewGuid().ToString().Replace("-","") + "@" + System.Net.Dns.GetHostName() + ">");			
			m_pHeader.Add("MIME-Version:","1.0");

			m_pAttachments = new Attachments();

			m_CharSet = "utf-8";
		}


		// REMOVE ME: obsolete
		#region method ConstructBinaryMime

		/// <summary>
		/// Constructs mime message. [obsolete]
		/// </summary>
		/// <returns></returns>
		[Obsolete("Use ToStream(storeStream) instead ! Warning: stream position in ToStream isn't set to 0 any more",true)]
		public MemoryStream ConstructBinaryMime()
		{
			MemoryStream ms = new MemoryStream();
			ToStream(ms);
			ms.Position = 0;
			return ms;
		}

		#endregion

		// REMOVE ME: obsolete
		#region method ConstructMime

		/// <summary>
		/// Constructs mime message. [obsolete]
		/// </summary>
		/// <returns></returns>
		[Obsolete("Use ToStringData() instead",true)]
		public string ConstructMime()
		{
			return System.Text.Encoding.Default.GetString(ConstructBinaryMime().ToArray());
		}

		#endregion


		#region function ConstructBody

		private string ConstructBody(string boundaryID)
		{
			// TODO: encode text as quoted-printable

			StringBuilder str = new StringBuilder();

			str.Append("--" + boundaryID + "\r\n");
			str.Append("Content-Type: text/plain;\r\n");
				str.Append("\tcharset=\"" + m_CharSet + "\"\r\n");
			str.Append("Content-Transfer-Encoding: base64\r\n\r\n");

			str.Append(SplitString(Convert.ToBase64String(System.Text.Encoding.GetEncoding(m_CharSet).GetBytes(this.Body)) + "\r\n\r\n"));
			
			// We have html body, construct it.
			if(this.BodyHtml.Length > 0){
				str.Append("--" + boundaryID + "\r\n");
				str.Append("Content-Type: text/html;\r\n");
				str.Append("\tcharset=\"" + m_CharSet + "\"\r\n");
				str.Append("Content-Transfer-Encoding: base64\r\n\r\n");

				str.Append(SplitString(Convert.ToBase64String(System.Text.Encoding.GetEncoding(m_CharSet).GetBytes(this.BodyHtml))));
			}

			return str.ToString();
		}

		#endregion

//** Make some global method
		#region method AddressesToString

		/// <summary>
		/// Converts address array to RFC 2822 address field fomat.
		/// </summary>
		/// <param name="addresses"></param>
		/// <returns></returns>
		private string AddressesToString(MailboxAddress[] addresses)
		{
			string retVal = "";
			for(int i=0;i<addresses.Length;i++){
				// For last address don't add , and <TAB>
				if(i == (addresses.Length - 1)){
					retVal += addresses[i].MailboxString;
				}
				else{
					retVal += addresses[i].MailboxString + ",\t";
				}
			}

			return retVal;
		}

		#endregion
//** Make some global method
		#region method AddressesToArray

		/// <summary>
		///  Converts RFC 2822 address filed value to address array.
		/// </summary>
		/// <param name="addressFieldValue"></param>
		/// <returns></returns>
		private MailboxAddress[] AddressesToArray(string addressFieldValue)
		{			
			// We need to parse right !!! 
			// Can't use standard String.Split() because commas in quoted strings must be skiped.
			// Example: "ivar, test" <ivar@lumisoft.ee>,"xxx" <ivar2@lumisoft.ee>

			string[] retVal = TextUtils.SplitQuotedString(addressFieldValue,',');

			MailboxAddress[] xxx = new MailboxAddress[retVal.Length];			
			for(int i=0;i<retVal.Length;i++){				
				xxx[i] = MailboxAddress.Parse(retVal[i].Trim()); // Trim <TAB>s
			}
			
            return xxx;
		}

		#endregion


		#region method ToStringData

		/// <summary>
		/// Stores mime message to string.
		/// </summary>
		/// <returns></returns>
		public string ToStringData()
		{
			return System.Text.Encoding.Default.GetString(ToByteData());
		}

		#endregion

		#region method ToByteData

		/// <summary>
		/// Stores mime message to byte[].
		/// </summary>
		/// <returns></returns>
		public byte[] ToByteData()
		{
			MemoryStream ms = new MemoryStream();
			ToStream(ms);
			return ms.ToArray();
		}

		#endregion

		#region method ToFile

		/// <summary>
		/// Stores mime message to specified file.
		/// </summary>
		/// <param name="fileName">File name.</param>
		public void ToFile(string fileName)
		{
			using(FileStream fs = File.Create(fileName)){
				ToStream(fs);
			}
		}

		#endregion

		#region method ToStream

		/// <summary>
		/// Stores mime message to specified stream. Stream position stays where mime writing ends.
		/// </summary>
		/// <param name="storeStream">Stream where to store mime message.</param>
		public void ToStream(Stream storeStream)
		{
			byte[] buf = null; 

			string mainBoundaryID = "----=_NextPart_" + Guid.NewGuid().ToString().Replace("-","_");

			// Write headers
			buf = System.Text.Encoding.Default.GetBytes(m_pHeader.ToHeaderString("utf-8"));
			storeStream.Write(buf,0,buf.Length);


			// Content-Type:
			buf = System.Text.Encoding.Default.GetBytes("Content-Type: " + "multipart/mixed;\r\n\tboundary=\"" + mainBoundaryID + "\"\r\n");
			storeStream.Write(buf,0,buf.Length);

			// 
			buf = System.Text.Encoding.Default.GetBytes("\r\nThis is a multi-part message in MIME format.\r\n\r\n");
			storeStream.Write(buf,0,buf.Length);

			
			string bodyBoundaryID = "----=_NextPart_" + Guid.NewGuid().ToString().Replace("-","_");

			buf = System.Text.Encoding.Default.GetBytes("--" + mainBoundaryID + "\r\n");
			storeStream.Write(buf,0,buf.Length);
			buf = System.Text.Encoding.Default.GetBytes("Content-Type: multipart/alternative;\r\n\tboundary=\"" + bodyBoundaryID + "\"\r\n\r\n");
			storeStream.Write(buf,0,buf.Length);

			buf = System.Text.Encoding.Default.GetBytes(ConstructBody(bodyBoundaryID));
			storeStream.Write(buf,0,buf.Length);

			buf = System.Text.Encoding.Default.GetBytes("--" + bodyBoundaryID + "--\r\n");
			storeStream.Write(buf,0,buf.Length);

			//-- Construct attachments
			foreach(Attachment att in m_pAttachments){
				buf = System.Text.Encoding.Default.GetBytes("\r\n--" + mainBoundaryID + "\r\n");
				storeStream.Write(buf,0,buf.Length);

				buf = System.Text.Encoding.Default.GetBytes("Content-Type: application/octet;\r\n\tname=\"" + Core.CanonicalEncode(att.FileName,m_CharSet) + "\"\r\n");
				storeStream.Write(buf,0,buf.Length);

				buf = System.Text.Encoding.Default.GetBytes("Content-Transfer-Encoding: base64\r\n");
				storeStream.Write(buf,0,buf.Length);

				buf = System.Text.Encoding.Default.GetBytes("Content-Disposition: attachment;\r\n\tfilename=\"" + Core.CanonicalEncode(att.FileName,m_CharSet) + "\"\r\n\r\n");
				storeStream.Write(buf,0,buf.Length);
				
				buf = System.Text.Encoding.Default.GetBytes(SplitString(Convert.ToBase64String(att.FileData)));
				storeStream.Write(buf,0,buf.Length);
			}

			buf = System.Text.Encoding.Default.GetBytes("\r\n");
			storeStream.Write(buf,0,buf.Length);
			
			buf = System.Text.Encoding.Default.GetBytes("--" + mainBoundaryID + "--\r\n");
			storeStream.Write(buf,0,buf.Length);
		}

		#endregion
        

		#region method SplitString

		private string SplitString(string sIn)
		{
			StringBuilder str = new StringBuilder();

			int len = sIn.Length;
			int pos = 0;
			while(pos < len){
				if((len - pos) > 76){
					str.Append(sIn.Substring(pos,76) + "\r\n");
				}
				else{
					str.Append(sIn.Substring(pos,sIn.Length - pos) + "\r\n");
				}
				pos += 76;
			}

			return str.ToString();
		}

		#endregion

		
		#region Properties Implementation

		/// <summary>
		/// Gets message header. You can do message header customization here.
		/// </summary>
		public HeaderFieldCollection Header
		{
			get{ return m_pHeader; }
		}

		/// <summary>
		/// Gets header as RFC 2822 message headers.
		/// </summary>
		public string HeaderString
		{
			get{ return m_pHeader.ToHeaderString("utf-8"); }
		}

		/// <summary>
		/// Gets or sets header field "<b>Message-ID:</b>" value. Returns null if value isn't set.
		/// </summary>
		public string MessageID
		{
			get{
				if(m_pHeader.Contains("Message-ID:")){
					return m_pHeader.GetFirst("Message-ID:").Value;
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("Message-ID:")){
					m_pHeader.GetFirst("Message-ID:").Value = value;
				}
				else{
					m_pHeader.Add("Message-ID:",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>To:</b>" value. Returns null if value isn't set.
		/// </summary>
		public MailboxAddress[] To
		{
			get{ 
				if(m_pHeader.Contains("To:")){
					return AddressesToArray(m_pHeader.GetFirst("To:").Value);
				}
				else{
					return null;
				}
			}

			set{
				if(m_pHeader.Contains("To:")){
					m_pHeader.GetFirst("To:").Value = AddressesToString(value);
				}
				else{
					m_pHeader.Add("To:",AddressesToString(value));
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Cc:</b>" value. Returns null if value isn't set.
		/// </summary>
		public MailboxAddress[] Cc
		{
			get{ 
				if(m_pHeader.Contains("Cc:")){
					return AddressesToArray(m_pHeader.GetFirst("Cc:").Value);
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("Cc:")){
					m_pHeader.GetFirst("Cc:").Value = AddressesToString(value);
				}
				else{
					m_pHeader.Add("Cc:",AddressesToString(value));
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Bcc:</b>" value. Returns null if value isn't set.
		/// </summary>
		public MailboxAddress[] Bcc
		{
			get{ 
				if(m_pHeader.Contains("Bcc:")){
					return AddressesToArray(m_pHeader.GetFirst("Bcc:").Value);
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("Bcc:")){
					m_pHeader.GetFirst("Bcc:").Value = AddressesToString(value);
				}
				else{
					m_pHeader.Add("Bcc:",AddressesToString(value));
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>From:</b>" value. Returns null if value isn't set.
		/// </summary>
		public MailboxAddress From
		{
			get{  
				if(m_pHeader.Contains("From:")){
					return MailboxAddress.Parse(m_pHeader.GetFirst("From:").Value);
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("From:")){
					m_pHeader.GetFirst("From:").Value = value.MailboxString;
				}
				else{
					m_pHeader.Add("From:",value.MailboxString);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Disposition-Notification-To:</b>" value. Returns null if value isn't set.
		/// </summary>
		public string DSN
		{
			get{ 
				if(m_pHeader.Contains("Disposition-Notification-To:")){
					return m_pHeader.GetFirst("Disposition-Notification-To:").Value;
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("Disposition-Notification-To:")){
					m_pHeader.GetFirst("Disposition-Notification-To:").Value = value;
				}
				else{
					m_pHeader.Add("Disposition-Notification-To:",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Subject:</b>" value. Returns null if value isn't set.
		/// </summary>
		public string Subject
		{
			get{ 
				if(m_pHeader.Contains("Subject:")){
					return m_pHeader.GetFirst("Subject:").Value;
				}
				else{
					return null;
				}
			}

			set{ 
				if(m_pHeader.Contains("Subject:")){
					m_pHeader.GetFirst("Subject:").Value = value;
				}
				else{
					m_pHeader.Add("Subject:",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Date:</b>" value. Returns null if value isn't set.
		/// </summary>
		public DateTime Date
		{
			get{ 
				if(m_pHeader.Contains("Date:")){
					return DateTime.ParseExact(m_pHeader.GetFirst("Date:").Value,"r",System.Globalization.DateTimeFormatInfo.InvariantInfo);
				}
				else{
					return DateTime.MinValue;
				}
			}

			set{				
				if(m_pHeader.Contains("Date:")){
					m_pHeader.GetFirst("Date:").Value = value.ToUniversalTime().ToString("r",System.Globalization.DateTimeFormatInfo.InvariantInfo);
				}
				else{
					m_pHeader.Add("Date:",value.ToUniversalTime().ToString("r",System.Globalization.DateTimeFormatInfo.InvariantInfo));
				}
			}
		}

		/// <summary>
		/// Gets or sets body text.
		/// </summary>
		public string Body
		{
			get{ return m_Body; }

			set{
				if(value == null){
					m_Body = "";
				}
				else{
					m_Body = value; 
				}
			}
		}

		/// <summary>
		/// Gets or sets html body.
		/// </summary>
		public string BodyHtml
		{
			get{ return m_BodyHtml; }

			set{ m_BodyHtml = value; }
		}

		/// <summary>
		/// Gets or sets message charset. Default is 'utf-8'.
		/// </summary>
		public string CharSet
		{
			get{ return m_CharSet; }

			set{ m_CharSet = value; }
		}

		/// <summary>
		/// Gets referance to attachments collection.
		/// </summary>
		public Attachments Attachments
		{
			get{ return m_pAttachments; }
		}

		#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