Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#

SIP Stack with SIP Proxy - (VOIP)

Rate me:
Please Sign up or sign in to vote.
4.86/5 (45 votes)
11 Jun 2007CPOL2 min read 1.6M   28.1K   162  
C# implementation of SIP
using System;
using System.IO;
using System.Collections;
using System.Text;

namespace LumiSoft.Net.Mime
{
	/// <summary>
	/// Rfc 2822 Mime Entity.
	/// </summary>
	public class MimeEntity
	{
		private HeaderFieldCollection m_pHeader           = null;
		private MimeEntity            m_pParentEntity     = null;
		private MimeEntityCollection  m_pChildEntities    = null;
		private byte[]                m_EncodedData       = null;
		private Hashtable             m_pHeaderFieldCache = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		public MimeEntity()
		{
			m_pHeader = new HeaderFieldCollection();
			m_pChildEntities = new MimeEntityCollection(this);
			m_pHeaderFieldCache = new Hashtable();
		}


		#region method Parse

		/// <summary>
		/// Parses mime entity from stream.
		/// </summary>
		/// <param name="stream">Data stream from where to read data.</param>
		/// <param name="toBoundary">Entity data is readed to specified boundary.</param>
		/// <returns>Returns false if last entity. Returns true for mulipart entity, if there are more entities.</returns>
		internal bool Parse(Stream stream,String toBoundary)
		{
			// Clear header fields
			m_pHeader.Clear();
			m_pHeaderFieldCache.Clear();

			// Parse header
			m_pHeader.Parse(stream);

			// Parse entity and child entities if any (Conent-Type: multipart/xxx...)

			// Multipart entity
			if((this.ContentType & MediaType_enum.Multipart) != 0){
				// There must be be boundary ID (rfc 1341 7.2.1  The Content-Type field for multipart entities requires one parameter, "boundary", which is used to specify the encapsulation boundary.)
				string boundaryID = this.ContentType_Boundary;
				if(boundaryID == null){
					// This is invalid message, just skip this mime entity
				}
				else{
					// There is one or more mime entities
				
					// Find first boundary start position
					StreamLineReader reader = new StreamLineReader(stream);
                    reader.CRLF_LinesOnly = false;
					byte[] lineData = reader.ReadLine();					
					while(lineData != null){
						string line = System.Text.Encoding.Default.GetString(lineData);
						if(line.StartsWith("--" + boundaryID)){
							break;
						}
						
						lineData = reader.ReadLine();
					}
					// This is invalid entity, boundary start not found. Skip that entity.
					if(lineData == null){
						return false;
					}
					
					// Start parsing child entities ot this entity
					while(true){					
						// Parse and add child entity
						MimeEntity childEntity = new MimeEntity();					
						this.ChildEntities.Add(childEntity);
						
						// This is last entity, stop parsing
						if(childEntity.Parse(stream,boundaryID) == false){
							break;
						}
						// else{
						// There are more entities, parse them
					}
					
					// This entity is child of mulipart entity.
					// All this entity child entities are parsed,
					// we need to move stream position to next entity start.
					if(toBoundary != null && toBoundary.Length > 0){
						lineData = reader.ReadLine();					
						while(lineData != null){
							String line = System.Text.Encoding.Default.GetString(lineData);
							if(line.StartsWith("--" + toBoundary)){
								break;
							}
							
							lineData = reader.ReadLine();
						}
						
						// Invalid boundary end, there can't be more entities 
						if(lineData == null){
							return false;
						}
						
						// See if last boundary or there is more. Last boundary ends with --
						if(System.Text.Encoding.Default.GetString(lineData).EndsWith(toBoundary + "--")){
							return false; 
						}
						// else{
						// There are more entities					
						return true;
					}
				}
			}
			// Singlepart entity
			else{
				// Boundary is specified, read data to specified boundary.
				if(toBoundary != null && toBoundary.Length > 0){
					MemoryStream entityData = new MemoryStream();
					StreamLineReader reader = new StreamLineReader(stream);
                    reader.CRLF_LinesOnly = false;
					byte[] lineData = reader.ReadLine();
					bool firstLine = true;
					while(lineData != null){
						string line = System.Text.Encoding.Default.GetString(lineData);
						if(line.StartsWith("--" + toBoundary)){
							break;
						}

						// Add previous line CRLF. 
						// We must do so because we need to skip last CRLF before boundary end, this is part of boundary.
						// For example:
						//	--boundary
						//  header
						//	
						//  data<CRLF> ----> this CRLF is part of boundary and must not added to boundary data !
						//	--boundary--
						if(!firstLine){
							entityData.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);
						}
						else{
							firstLine = false;
						}
						
						// Write line to buffer
						entityData.Write(lineData,0,lineData.Length);
										
						lineData = reader.ReadLine();
					}
				
					// This is invalid entity, unexpected end of entity, no boundary closing tag.
					if(lineData == null){
                        // Just return data what was readed.
                        m_EncodedData = entityData.ToArray();
                        return false;

                        /*
						if(this.ParentEntity != null){
							this.ParentEntity.ChildEntities.Remove(this);
						}*/
					}
					
					m_EncodedData = entityData.ToArray();
					
					// See if last boundary or there is more. Last boundary ends with --
					if(System.Text.Encoding.Default.GetString(lineData).EndsWith(toBoundary + "--")){
						return false; 
					}
					
					return true;
				}
				// Boundary isn't specified, read data to the stream end. 
				else{
                    m_EncodedData = new byte[stream.Length - stream.Position];
					stream.Read(m_EncodedData,0,m_EncodedData.Length);
				}
			}
			
			return false;
		}

		#endregion


		#region method ToStream

		/// <summary>
		/// Stores mime entity and it's child entities to specified stream.
		/// </summary>
		/// <param name="storeStream">Stream where to store mime entity.</param>
		public void ToStream(Stream storeStream)
		{			
			// Write headers
			byte[] data = System.Text.Encoding.Default.GetBytes(FoldHeader(this.HeaderString));
			storeStream.Write(data,0,data.Length);

			// If multipart entity, write child entities.(multipart entity don't contain data, it contains nested entities )
			if((this.ContentType & MediaType_enum.Multipart) != 0){
				string boundary = this.ContentType_Boundary;			
				foreach(MimeEntity entity in this.ChildEntities){
					// Write boundary start. Syntax: <CRLF>--BoundaryID<CRLF>
					data = System.Text.Encoding.Default.GetBytes("\r\n--" + boundary + "\r\n");
					storeStream.Write(data,0,data.Length);

					// Force child entity to store itself
                    entity.ToStream(storeStream);
				}

				// Write boundaries end Syntax: <CRLF>--BoundaryID--<CRLF>
				data = System.Text.Encoding.Default.GetBytes("\r\n--" + boundary + "--\r\n");
				storeStream.Write(data,0,data.Length);
			}
			// If singlepart (text,image,audio,video,message, ...), write entity data.
			else{				
				// Write blank line between headers and content
				storeStream.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);

				if(this.DataEncoded != null){
					storeStream.Write(this.DataEncoded,0,this.DataEncoded.Length);
				}				
			}
		}

		#endregion


		#region method DataToFile

		/// <summary>
		/// Saves this.Data property value to specified file.
		/// </summary>
		/// <param name="fileName">File name where to store data.</param>
		public void DataToFile(string fileName)
		{
			using(FileStream fs = File.Create(fileName)){
				DataToStream(fs);
			}
		}

		#endregion

		#region method DataToStream

		/// <summary>
		/// Saves this.Data property value to specified stream.
		/// </summary>
		/// <param name="stream">Stream where to store data.</param>
		public void DataToStream(Stream stream)
		{
			byte[] data = this.Data;
			stream.Write(data,0,data.Length);
		}

		#endregion

		#region method DataFromFile

		/// <summary>
		/// Loads MimeEntity.Data property from file.
		/// </summary>
		/// <param name="fileName">File name.</param>
		public void DataFromFile(string fileName)
		{
			using(FileStream fs = File.OpenRead(fileName)){
				DataFromStream(fs);
			}
		}

		#endregion

		#region method DataFromStream

		/// <summary>
		/// Loads MimeEntity.Data property from specified stream. Note: reading starts from current position and stream isn't closed.
		/// </summary>
		/// <param name="stream">Data stream.</param>
		public void DataFromStream(Stream stream)
		{
			byte[] data = new byte[stream.Length];
			stream.Read(data,0,(int)stream.Length);

			this.Data = data;
		}

		#endregion


		#region method EncodeData

		/// <summary>
		/// Encodes data with specified content transfer encoding.
		/// </summary>
		/// <param name="data">Data to encode.</param>
		/// <param name="encoding">Encoding with what to encode data.</param>
		private byte[] EncodeData(byte[] data,ContentTransferEncoding_enum encoding)
		{
			// Allow only known Content-Transfer-Encoding (ContentTransferEncoding_enum value),
            // otherwise we don't know how to encode data.
			if(encoding == ContentTransferEncoding_enum.NotSpecified){
				throw new Exception("Please specify Content-Transfer-Encoding first !");
			}
			if(encoding == ContentTransferEncoding_enum.Unknown){
				throw new Exception("Not supported Content-Transfer-Encoding. If it's your custom encoding, encode data yourself and set it with DataEncoded property !");
			}
				
			if(encoding == ContentTransferEncoding_enum.Base64){
				return Core.Base64Encode(data);
			}
			else if(encoding == ContentTransferEncoding_enum.QuotedPrintable){
				return Core.QuotedPrintableEncode(data);
			}
			else{
				return data;
			}
		}

		#endregion

		#region method FoldHeader

		/// <summary>
		/// Folds header.
		/// </summary>
		/// <param name="header">Header string.</param>
		/// <returns></returns>
		private string FoldHeader(string header)
		{			
			/* Rfc 2822 2.2.3 Long Header Fields
				Each header field is logically a single line of characters comprising
				the field name, the colon, and the field body.  For convenience
				however, and to deal with the 998/78 character limitations per line,
				the field body portion of a header field can be split into a multiple
				line representation; this is called "folding".  The general rule is
				imply WSP characters), a CRLF may be inserted before any WSP.  For
				example, the header field:

					Subject: This is a test

					can be represented as:

							Subject: This
								is a test
			*/

			// Just fold header fields what contain <TAB>

			StringBuilder retVal = new StringBuilder();
			
			header = header.Replace("\r\n","\n");
			string[] headerLines = header.Split('\n');		
			foreach(string headerLine in headerLines){
				// Folding is needed
				if(headerLine.IndexOf('\t') > -1){
					retVal.Append(headerLine.Replace("\t","\r\n\t") + "\r\n");
				}
				// No folding needed, just write back header line
				else{
					retVal.Append(headerLine + "\r\n");
				}
			}
			// Split splits last line <CRLF> to element, but we don't need it 
			if(retVal.Length > 1){
				return retVal.ToString(0,retVal.Length - 2);
			}
			else{
				return retVal.ToString();
			}
		}

		#endregion
		

		#region Properties Implementation

		/// <summary>
		/// Gets message header.
		/// </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 parent entity of this entity. If this entity is top level, then this property returns null.
		/// </summary>
		public MimeEntity ParentEntity
		{
			get{ return m_pParentEntity; }
		}

		/// <summary>
		/// Gets child entities. This property is available only if ContentType = multipart/... .
		/// </summary>
		public MimeEntityCollection ChildEntities
		{
			get{ return m_pChildEntities; }
		}


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

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

		/// <summary>
		/// Gets or sets header field "<b>Content-Type:</b>" value. This property specifies what entity data is.
		/// NOTE: ContentType can't be changed while there is data specified(Exception is thrown) in this mime entity, because it isn't
		/// possible todo data conversion between different types. For example text/xx has charset parameter and other types don't,
		/// changing loses it and text data becomes useless.
		/// </summary>
		public MediaType_enum ContentType
		{
			get{ 
				if(m_pHeader.Contains("Content-Type:")){
					string contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:")).Value;
					return MimeUtils.ParseMediaType(contentType);
				}
				else{
					return MediaType_enum.NotSpecified;
				}				
			}

			set{
				if(this.DataEncoded != null){
					throw new Exception("ContentType can't be changed while there is data specified, set data to null before !");
				}
				if(value == MediaType_enum.Unknown){
					throw new Exception("MediaType_enum.Unkown isn't allowed to set !");
				}
				if(value == MediaType_enum.NotSpecified){
					throw new Exception("MediaType_enum.NotSpecified isn't allowed to set !");
				}
				
				string contentType = "";
				//--- Text/xxx --------------------------------//
				if(value == MediaType_enum.Text_plain){
					contentType = "text/plain; charset=\"utf-8\"";
				}
				else if(value == MediaType_enum.Text_html){
					contentType = "text/html; charset=\"utf-8\"";
				}
				else if(value == MediaType_enum.Text_xml){
					contentType = "text/xml; charset=\"utf-8\"";
				}
				else if(value == MediaType_enum.Text_rtf){
					contentType = "text/rtf; charset=\"utf-8\"";
				}
				else if(value == MediaType_enum.Text){
					contentType = "text; charset=\"utf-8\"";
				}
				//---------------------------------------------//

				//--- Image/xxx -------------------------------//
				else if(value == MediaType_enum.Image_gif){
					contentType = "image/gif";
				}
				else if(value == MediaType_enum.Image_tiff){
					contentType = "image/tiff";
				}
				else if(value == MediaType_enum.Image_jpeg){
					contentType = "image/jpeg";
				}
				else if(value == MediaType_enum.Image){
					contentType = "image";
				}
				//---------------------------------------------//

				//--- Audio/xxx -------------------------------//
				else if(value == MediaType_enum.Audio){
					contentType = "audio";
				}
				//---------------------------------------------//

				//--- Video/xxx -------------------------------//
				else if(value == MediaType_enum.Video){
					contentType = "video";
				}
				//---------------------------------------------//

				//--- Application/xxx -------------------------//
				else if(value == MediaType_enum.Application_octet_stream){
					contentType = "application/octet-stream";
				}
				else if(value == MediaType_enum.Application){
					contentType = "application";
				}
				//---------------------------------------------//

				//--- Multipart/xxx ---------------------------//
				else if(value == MediaType_enum.Multipart_mixed){
					contentType = "multipart/mixed;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				else if(value == MediaType_enum.Multipart_alternative){
					contentType = "multipart/alternative;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				else if(value == MediaType_enum.Multipart_parallel){
					contentType = "multipart/parallel;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				else if(value == MediaType_enum.Multipart_related){
					contentType = "multipart/related;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				else if(value == MediaType_enum.Multipart_signed){
					contentType = "multipart/signed;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				else if(value == MediaType_enum.Multipart){
					contentType = "multipart;	boundary=\"part_" + Guid.NewGuid().ToString().Replace("-","_") + "\"";
				}
				//---------------------------------------------//

				//--- Message/xxx -----------------------------//
				else if(value == MediaType_enum.Message_rfc822){
					contentType = "message/rfc822";
				}
				else if(value == MediaType_enum.Message){
					contentType = "message";
				}
				//---------------------------------------------//

				else{
					throw new Exception("Invalid flags combination of MediaType_enum was specified !");
				}

				if(m_pHeader.Contains("Content-Type:")){
					m_pHeader.GetFirst("Content-Type:").Value = contentType;
				}
				else{
					m_pHeader.Add("Content-Type:",contentType);
				}
			}
		}

		
		/// <summary>
		/// Gets or sets header field "<b>Content-Type:</b>" value. Returns null if value isn't set. This property specifies what entity data is.
		/// This property is meant for advanced users, who needs other values what defined MediaType_enum provides.
		/// Example value: text/plain; charset="utf-8". 
		/// NOTE: ContentType can't be changed while there is data specified(Exception is thrown) in this mime entity, because it isn't
		/// possible todo data conversion between different types. For example text/xx has charset parameter and other types don't,
		/// changing loses it and text data becomes useless.
		/// </summary>
		public string ContentTypeString
		{
			get{
				if(m_pHeader.Contains("Content-Type:")){
					return m_pHeader.GetFirst("Content-Type:").Value;
				}
				else{
					return null;
				}
			}

			set{ 
				if(this.DataEncoded != null){
					throw new Exception("ContentType can't be changed while there is data specified, set data to null before !");
				}
				if(m_pHeader.Contains("Content-Type:")){
					m_pHeader.GetFirst("Content-Type:").Value = value;
				}
				else{
					m_pHeader.Add("Content-Type:",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Content-Transfer-Encoding:</b>" value. This property specifies how data is encoded/decoded.
		/// If you set this value, it's recommended that you use QuotedPrintable for text and Base64 for binary data.
		/// 7bit,_8bit,Binary are today obsolete (used for parsing). 
		/// </summary>
		public ContentTransferEncoding_enum ContentTransferEncoding
		{
			get{ 
				if(m_pHeader.Contains("Content-Transfer-Encoding:")){
					return MimeUtils.ParseContentTransferEncoding(m_pHeader.GetFirst("Content-Transfer-Encoding:").Value);
				}
				else{
					return ContentTransferEncoding_enum.NotSpecified;
				}
			}

			set{
				if(value == ContentTransferEncoding_enum.Unknown){
					throw new Exception("ContentTransferEncoding_enum.Unknown isn't allowed to set !");
				}
				if(value == ContentTransferEncoding_enum.NotSpecified){
					throw new Exception("ContentTransferEncoding_enum.NotSpecified isn't allowed to set !");
				}

				string encoding = MimeUtils.ContentTransferEncodingToString(value);

				// There is entity data specified and encoding changed, we need to convert existing data
				if(this.DataEncoded != null){
					ContentTransferEncoding_enum oldEncoding = this.ContentTransferEncoding;
					if(oldEncoding == ContentTransferEncoding_enum.Unknown || oldEncoding == ContentTransferEncoding_enum.NotSpecified){
						throw new Exception("Data can't be converted because old encoding '" + MimeUtils.ContentTransferEncodingToString(oldEncoding) + "' is unknown !");
					}

					this.DataEncoded = EncodeData(this.Data,value);
				}

				if(m_pHeader.Contains("Content-Transfer-Encoding:")){
					m_pHeader.GetFirst("Content-Transfer-Encoding:").Value = encoding;
				}
				else{
					m_pHeader.Add("Content-Transfer-Encoding:",encoding);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Content-Disposition:</b>" value.
		/// </summary>
		public ContentDisposition_enum ContentDisposition
		{
			get{ 
				if(m_pHeader.Contains("Content-Disposition:")){
					return MimeUtils.ParseContentDisposition(m_pHeader.GetFirst("Content-Disposition:").Value);
				}
				else{
					return ContentDisposition_enum.NotSpecified;
				}
			}

			set{
				if(value == ContentDisposition_enum.Unknown){
					throw new Exception("ContentDisposition_enum.Unknown isn't allowed to set !");
				}

				// Just remove Content-Disposition: header field if exists
				if(value == ContentDisposition_enum.NotSpecified){
					HeaderField disposition = m_pHeader.GetFirst("Content-Disposition:");
					if(disposition != null){
						m_pHeader.Remove(disposition);
					}
				}
				else{
					string disposition = MimeUtils.ContentDispositionToString(value);
					if(m_pHeader.Contains("Content-Disposition:")){
						m_pHeader.GetFirst("Content-Disposition:").Value = disposition;
					}
					else{
						m_pHeader.Add("Content-Disposition:",disposition);
					}
				}
			}
		}

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

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

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

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

		/// <summary>
		/// Gets or sets "<b>Content-Type:</b>" header field "<b>name</b>" parameter.
		/// Returns null if Content-Type: header field value isn't set or Content-Type: header field "<b>name</b>" parameter isn't set.
		/// <p/>
		/// Note: Content-Type must be application/xxx or exception is thrown.
		/// This property is obsolete today, it's replaced with <b>Content-Disposition: filename</b> parameter.
		/// If possible always set FileName property instead of it. 
		/// </summary>
		public string ContentType_Name
		{
			get{ 
				if(m_pHeader.Contains("Content-Type:")){
					ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
					if(contentType.Parameters.Contains("name")){
						return contentType.Parameters["name"];
					}
					else{
						return null;
					}
				}
				else{
					return null;
				}
			}

			set{
				if(!m_pHeader.Contains("Content-Type:")){
					throw new Exception("Please specify Content-Type first !");
				}
				if((this.ContentType & MediaType_enum.Application) == 0){
					throw new Exception("Parameter name is available only for ContentType application/xxx !");
				}

				ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
				if(contentType.Parameters.Contains("name")){
					contentType.Parameters["name"] = value;
				}
				else{
					contentType.Parameters.Add("name",value);
				}
			}
		}
        
		/// <summary>
		/// Gets or sets "<b>Content-Type:</b>" header field "<b>charset</b>" parameter.
		/// Returns null if Content-Type: header field value isn't set or Content-Type: header field "<b>charset</b>" parameter isn't set.
		/// If you don't know what charset to use then <b>utf-8</b> is recommended, most of times this is sufficient.
		/// Note: Content-Type must be text/xxx or exception is thrown.
		/// </summary>
		public string ContentType_CharSet
		{
			get{ 
				if(m_pHeader.Contains("Content-Type:")){
					ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
					if(contentType.Parameters.Contains("charset")){
						return contentType.Parameters["charset"];
					}
					else{
						return null;
					}					
				}
				else{
					return null;
				}
			}

			set{
				if(!m_pHeader.Contains("Content-Type:")){
					throw new Exception("Please specify Content-Type first !");
				}
				if((this.ContentType & MediaType_enum.Text) == 0){
					throw new Exception("Parameter boundary is available only for ContentType text/xxx !");
				}

				// There is data specified, we need to convert it because charset changed
				if(this.DataEncoded != null){
					string currentCharSet = this.ContentType_CharSet;
                    if(currentCharSet == null){
                        currentCharSet = "ascii";
                    }
					try{
						System.Text.Encoding.GetEncoding(currentCharSet);
					}
					catch{
						throw new Exception("Data can't be converted because current charset '" + currentCharSet + "' isn't supported !");
					}
					try{
						System.Text.Encoding encoding = System.Text.Encoding.GetEncoding(value);
						this.Data = encoding.GetBytes(this.DataText);
					}
					catch{
						throw new Exception("Data can't be converted because new charset '" + value + "' isn't supported !");
					}
				}

				ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
				if(contentType.Parameters.Contains("charset")){
					contentType.Parameters["charset"] = value;
				}
				else{
					contentType.Parameters.Add("charset",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets "<b>Content-Type:</b>" header field "<b>boundary</b>" parameter.
		/// Returns null if Content-Type: header field value isn't set or Content-Type: header field "<b>boundary</b>" parameter isn't set.
		/// Note: Content-Type must be multipart/xxx or exception is thrown.
		/// </summary>
		public string ContentType_Boundary
		{
			get{ 
				if(m_pHeader.Contains("Content-Type:")){
					ParametizedHeaderField contentDisposition = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
					if(contentDisposition.Parameters.Contains("boundary")){
						return contentDisposition.Parameters["boundary"];
					}
					else{
						return null;
					}					
				}
				else{
					return null;
				}
			}

			set{
				if(!m_pHeader.Contains("Content-Type:")){
					throw new Exception("Please specify Content-Type first !");
				}
				if((this.ContentType & MediaType_enum.Multipart) == 0){
					throw new Exception("Parameter boundary is available only for ContentType multipart/xxx !");
				}

				ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Type:"));
				if(contentType.Parameters.Contains("boundary")){
					contentType.Parameters["boundary"] = value;
				}
				else{
					contentType.Parameters.Add("boundary",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets "<b>Content-Disposition:</b>" header field "<b>filename</b>" parameter.
		/// Returns null if Content-Disposition: header field value isn't set or Content-Disposition: header field "<b>filename</b>" parameter isn't set.
		/// Note: Content-Disposition must be attachment or inline.
		/// </summary>
		public string ContentDisposition_FileName
		{
			get{ 
				if(m_pHeader.Contains("Content-Disposition:")){
					ParametizedHeaderField contentDisposition = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Disposition:"));
					if(contentDisposition.Parameters.Contains("filename")){
						return contentDisposition.Parameters["filename"];
					}
					else{
						return null;
					}					
				}
				else{
					return null;
				}
			}

			set{
				if(!m_pHeader.Contains("Content-Disposition:")){
					throw new Exception("Please specify Content-Disposition first !");
				}

				ParametizedHeaderField contentType = new ParametizedHeaderField(m_pHeader.GetFirst("Content-Disposition:"));
				if(contentType.Parameters.Contains("filename")){
					contentType.Parameters["filename"] = value;
				}
				else{
					contentType.Parameters.Add("filename",value);
				}
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Date:</b>" value.
		/// </summary>
		public DateTime Date
		{
			get{ 
				if(m_pHeader.Contains("Date:")){
					try{
						return MimeUtils.ParseDate(m_pHeader.GetFirst("Date:").Value);
					}
					catch{
						return DateTime.MinValue;
					}
				}
				else{
					return DateTime.MinValue;
				}
			}

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

		/// <summary>
		/// Gets or sets header field "<b>Message-ID:</b>" value. Returns null if value isn't set.
		/// Syntax: '&lt;'id-left@id-right'&gt;'. Example: &lt;621bs724bfs8@jnfsjaas4263&gt;
		/// </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 AddressList To
		{
			get{ 
				if(m_pHeader.Contains("To:")){
					// There is already cached version, return it
					if(m_pHeaderFieldCache.Contains("To:")){
						return (AddressList)m_pHeaderFieldCache["To:"];
					}
					// These isn't cached version, we need to create it
					else{
						// Create and bound address-list to existing header field
						HeaderField field = m_pHeader.GetFirst("To:");
						AddressList list = new AddressList();
						list.Parse(field.Value);
						list.BoundedHeaderField = field;

						// Cache header field
						m_pHeaderFieldCache["To:"] = list;

						return list;
					}					
				}
				else{
					return null;
				}
			}

			set{
				// Just remove header field
				if(value == null){
					m_pHeader.Remove(m_pHeader.GetFirst("To:"));
					return;
				}

				// Release old address collection
				if(m_pHeaderFieldCache["To:"] != null){
					((AddressList)m_pHeaderFieldCache["To:"]).BoundedHeaderField = null;
				}

				// Bound address-list to To: header field. If header field doesn't exist, add it.
				HeaderField to = m_pHeader.GetFirst("To:");
				if(to == null){
					to = new HeaderField("To:",value.ToAddressListString());
					m_pHeader.Add(to);
				}
                else{
                    to.Value = value.ToAddressListString();
                }
				value.BoundedHeaderField = to;

                m_pHeaderFieldCache["To:"] = value;
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Cc:</b>" value. Returns null if value isn't set.
		/// </summary>
		public AddressList Cc
		{
			get{
				if(m_pHeader.Contains("Cc:")){
					// There is already cached version, return it
					if(m_pHeaderFieldCache.Contains("Cc:")){
						return (AddressList)m_pHeaderFieldCache["Cc:"];
					}
					// These isn't cached version, we need to create it
					else{
						// Create and bound address-list to existing header field
						HeaderField field = m_pHeader.GetFirst("Cc:");
						AddressList list = new AddressList();
						list.Parse(field.Value);
						list.BoundedHeaderField = field;

						// Cache header field
						m_pHeaderFieldCache["Cc:"] = list;

						return list;
					}					
				}
				else{
					return null;
				}
			}

			set{
				// Just remove header field
				if(value == null){
					m_pHeader.Remove(m_pHeader.GetFirst("Cc:"));
					return;
				}

				// Release old address collection
				if(m_pHeaderFieldCache["Cc:"] != null){
					((AddressList)m_pHeaderFieldCache["Cc:"]).BoundedHeaderField = null;
				}

				// Bound address-list to To: header field. If header field doesn't exist, add it.
				HeaderField cc = m_pHeader.GetFirst("Cc:");
				if(cc == null){
					cc = new HeaderField("Cc:",value.ToAddressListString());
					m_pHeader.Add(cc);
				}
                else{
                    cc.Value = value.ToAddressListString();
                }
				value.BoundedHeaderField = cc;

                m_pHeaderFieldCache["Cc:"] = value;
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>Bcc:</b>" value. Returns null if value isn't set.
		/// </summary>
		public AddressList Bcc
		{
			get{ 
				if(m_pHeader.Contains("Bcc:")){
					// There is already cached version, return it
					if(m_pHeaderFieldCache.Contains("Bcc:")){
						return (AddressList)m_pHeaderFieldCache["Bcc:"];
					}
					// These isn't cached version, we need to create it
					else{
						// Create and bound address-list to existing header field
						HeaderField field = m_pHeader.GetFirst("Bcc:");
						AddressList list = new AddressList();
						list.Parse(field.Value);
						list.BoundedHeaderField = field;

						// Cache header field
						m_pHeaderFieldCache["Bcc:"] = list;

						return list;
					}					
				}
				else{
					return null;
				}
			}

			set{
				// Just remove header field
				if(value == null){
					m_pHeader.Remove(m_pHeader.GetFirst("Bcc:"));
					return;
				}

				// Release old address collection
				if(m_pHeaderFieldCache["Bcc:"] != null){
					((AddressList)m_pHeaderFieldCache["Bcc:"]).BoundedHeaderField = null;
				}

				// Bound address-list to To: header field. If header field doesn't exist, add it.
				HeaderField bcc = m_pHeader.GetFirst("Bcc:");
				if(bcc == null){
					bcc = new HeaderField("Bcc:",value.ToAddressListString());
					m_pHeader.Add(bcc);
				}
                else{
                    bcc.Value = value.ToAddressListString();
                }
				value.BoundedHeaderField = bcc;

                m_pHeaderFieldCache["Bcc:"] = value;
			}
		}

		/// <summary>
		/// Gets or sets header field "<b>From:</b>" value. Returns null if value isn't set.
		/// </summary>
		public AddressList From
		{
			get{  
				if(m_pHeader.Contains("From:")){
					// There is already cached version, return it
					if(m_pHeaderFieldCache.Contains("From:")){
						return (AddressList)m_pHeaderFieldCache["From:"];
					}
					// These isn't cached version, we need to create it
					else{
						// Create and bound address-list to existing header field
						HeaderField field = m_pHeader.GetFirst("From:");
						AddressList list = new AddressList();
						list.Parse(field.Value);
						list.BoundedHeaderField = field;

						// Cache header field
						m_pHeaderFieldCache["From:"] = list;

						return list;
					}					
				}
				else{
					return null;
				}
			}

			set{
				// Just remove header field
				if(value == null && m_pHeader.Contains("From:")){
					m_pHeader.Remove(m_pHeader.GetFirst("From:"));
					return;
				}

				// Release old address collection
				if(m_pHeaderFieldCache["From:"] != null){
					((AddressList)m_pHeaderFieldCache["From:"]).BoundedHeaderField = null;
				}

				// Bound address-list to To: header field. If header field doesn't exist, add it.
				HeaderField from = m_pHeader.GetFirst("From:");
				if(from == null){
					from = new HeaderField("From:",value.ToAddressListString());
					m_pHeader.Add(from);
				}
                else{
                    from.Value = value.ToAddressListString();
                }
				value.BoundedHeaderField = from;

                m_pHeaderFieldCache["From:"] = value;
			}
		}

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

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

		/// <summary>
		/// Gets or sets header field "<b>Reply-To:</b>" value. Returns null if value isn't set.
		/// </summary>
		public AddressList ReplyTo
		{
			get{ 
				if(m_pHeader.Contains("Reply-To:")){
					// There is already cached version, return it
					if(m_pHeaderFieldCache.Contains("Reply-To:")){
						return (AddressList)m_pHeaderFieldCache["Reply-To:"];
					}
					// These isn't cached version, we need to create it
					else{
						// Create and bound address-list to existing header field
						HeaderField field = m_pHeader.GetFirst("Reply-To:");
						AddressList list = new AddressList();
						list.Parse(field.Value);
						list.BoundedHeaderField = field;

						// Cache header field
						m_pHeaderFieldCache["Reply-To:"] = list;

						return list;
					}					
				}
				else{
					return null;
				}
			}

			set{
				// Just remove header field
				if(value == null && m_pHeader.Contains("Reply-To:")){
					m_pHeader.Remove(m_pHeader.GetFirst("Reply-To:"));
					return;
				}

				// Release old address collection
				if(m_pHeaderFieldCache["Reply-To:"] != null){
					((AddressList)m_pHeaderFieldCache["Reply-To:"]).BoundedHeaderField = null;
				}

				// Bound address-list to To: header field. If header field doesn't exist, add it.
				HeaderField replyTo = m_pHeader.GetFirst("Reply-To:");
				if(replyTo == null){
					replyTo = new HeaderField("Reply-To:",value.ToAddressListString());
					m_pHeader.Add(replyTo);
				}
                else{
                    replyTo.Value = value.ToAddressListString();
                }
				value.BoundedHeaderField = replyTo;

                m_pHeaderFieldCache["Reply-To:"] = value;
			}
		}

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

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

		/// <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 entity data. Data is encoded/decoded with "<b>Content-Transfer-Encoding:</b>" header field value.
		/// Note: This property can be set only if Content-Type: isn't multipart.
		/// </summary>
		public byte[] Data
		{
			get{ 
				// Decode Data
				ContentTransferEncoding_enum encoding = this.ContentTransferEncoding;
				if(encoding == ContentTransferEncoding_enum.Base64){
					return Core.Base64Decode(this.DataEncoded);				
				}
				else if(encoding == ContentTransferEncoding_enum.QuotedPrintable){
					return Core.QuotedPrintableDecode(this.DataEncoded);
				}
				else{
					return this.DataEncoded;
				}
			}

			set{
				if(value == null){
					this.DataEncoded = null;
					return;
				}
				
				ContentTransferEncoding_enum encoding = this.ContentTransferEncoding;
				this.DataEncoded = EncodeData(value,encoding);
			}
		}

		/// <summary>
		/// Gets or sets entity text data. Data is encoded/decoded with "<b>Content-Transfer-Encoding:</b>" header field value with this.Charset charset.
		/// Note: This property is available only if ContentType is Text/xxx... or no content type specified, othwerwise Excpetion is thrown.
		/// </summary>
		public string DataText
		{			
			get{ 
				if((this.ContentType & MediaType_enum.Text) == 0 && (this.ContentType & MediaType_enum.NotSpecified) == 0){
					throw new Exception("This property is available only if ContentType is Text/xxx... !");
				}

				try{
					string charSet = this.ContentType_CharSet;
					// Charset isn't specified, use system default
					if(charSet == null){
						return System.Text.Encoding.Default.GetString(this.Data);
					}
					else{
						return System.Text.Encoding.GetEncoding(charSet).GetString(this.Data);
					}				
				}
				// Not supported charset, use default
				catch{
					return System.Text.Encoding.Default.GetString(this.Data);
				}
			}

			set{
				if(value == null){
					this.DataEncoded = null;
					return;
				}

				// Check charset
				string charSet = this.ContentType_CharSet;
				if(charSet == null){
					throw new Exception("Please specify CharSet property first !");
				}
                               
                Encoding encoding = null;
				try{
                    encoding = Encoding.GetEncoding(charSet);					
				}
				catch{
					throw new Exception("Not supported charset '" + charSet + "' ! If you need to use this charset, then set data through Data or DataEncoded property.");
				}
                this.Data = encoding.GetBytes(value);
			}
		}

		/// <summary>
		/// Gets or sets entity encoded data. If you set this value, be sure that you encode this value as specified by Content-Transfer-Encoding: header field.
		/// Set this value only if you need custom Content-Transfer-Encoding: what current Mime class won't support, other wise set data through this.Data property. 
		/// Note: This property can be set only if Content-Type: isn't multipart.
		/// </summary>
		public byte[] DataEncoded
		{
			get{ return m_EncodedData; }

			set{ m_EncodedData = value; }
		}
				
		#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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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