using System; using System.IO; using System.Collections.Generic; using System.Text; using MailServer.Misc.IO; namespace MailServer.Misc.MIME { /// /// Represents a MIME entity. Defined in RFC 2045 2.4. /// public class MIME_Entity : IDisposable { private bool m_IsDisposed = false; private MIME_Entity m_pParent = null; private MIME_h_Collection m_pHeader = null; private MIME_b m_pBody = null; private MIME_b_Provider m_pBodyProvider = null; /// /// Default constructor. /// public MIME_Entity() { m_pHeader = new MIME_h_Collection(new MIME_h_Provider()); m_pBodyProvider = new MIME_b_Provider(); } #region method Dispose /// /// Cleans up any resources being used. This method is thread-safe. /// public void Dispose() { lock(this){ if(m_IsDisposed){ return; } m_IsDisposed = true; m_pHeader = null; m_pParent = null; } } #endregion #region method ToFile /// /// Stores MIME entity to the specified file. /// /// File name with path where to store MIME entity. /// Header 8-bit words ecnoder. Value null means that words are not encoded. /// Charset to use to encode 8-bit header parameters. Value null means parameters not encoded. /// Is raised when file is null. /// Is raised when any of the arguments has invalid value. public void ToFile(string file,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset) { if(file == null){ throw new ArgumentNullException("file"); } if(file == ""){ throw new ArgumentException("Argument 'file' value must be specified."); } using(FileStream fs = File.Create(file)){ ToStream(fs,headerWordEncoder,headerParmetersCharset); } } #endregion #region method ToStream /// /// Store MIME enity to the specified stream. /// /// Stream where to store MIME entity. Storing starts form stream current position. /// Header 8-bit words ecnoder. Value null means that words are not encoded. /// Charset to use to encode 8-bit header parameters. Value null means parameters not encoded. /// Is raised when stream is null. public void ToStream(Stream stream,MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset) { if(stream == null){ throw new ArgumentNullException("stream"); } m_pHeader.ToStream(stream,headerWordEncoder,headerParmetersCharset); stream.Write(new byte[]{(int)'\r',(int)'\n'},0,2); m_pBody.ToStream(stream,headerWordEncoder,headerParmetersCharset); } #endregion #region method ToString /// /// Returns MIME entity as string. /// /// Returns MIME entity as string. public override string ToString() { return ToString(null,null); } /// /// Returns MIME entity as string. /// /// Header 8-bit words ecnoder. Value null means that words are not encoded. /// Charset to use to encode 8-bit header parameters. Value null means parameters not encoded. /// Returns MIME entity as string. public string ToString(MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset) { using(MemoryStream ms = new MemoryStream()){ ToStream(ms,headerWordEncoder,headerParmetersCharset); return Encoding.UTF8.GetString(ms.ToArray()); } } #endregion #region method ToByte /// /// Returns MIME entity as byte[]. /// /// Header 8-bit words ecnoder. Value null means that words are not encoded. /// Charset to use to encode 8-bit header parameters. Value null means parameters not encoded. /// Returns MIME entity as byte[]. public byte[] ToByte(MIME_Encoding_EncodedWord headerWordEncoder,Encoding headerParmetersCharset) { using(MemoryStream ms = new MemoryStream()){ ToStream(ms,headerWordEncoder,headerParmetersCharset); return ms.ToArray(); } } #endregion #region method Parse /// /// Parses MIME entiry from the specified stream. /// /// Source stream. /// Default content type. /// Is raised when stream or defaultContentType is null reference. internal void Parse(SmartStream stream,MIME_h_ContentType defaultContentType) { if(stream == null){ throw new ArgumentNullException("stream"); } if(defaultContentType == null){ throw new ArgumentNullException("defaultContentType"); } m_pHeader.Parse(stream); m_pBody = m_pBodyProvider.Parse(this,stream,defaultContentType); m_pBody.SetParent(this,false); } #endregion #region method SetParent /// /// Sets MIME entity parent entity. /// /// Parent entity. internal void SetParent(MIME_Entity parent) { m_pParent = parent; } #endregion #region Properties Implementation // Permanent headerds list: http://www.rfc-editor.org/rfc/rfc4021.txt /// /// Gets if this object is disposed. /// public bool IsDisposed { get{ return m_IsDisposed; } } /// /// Gets if this entity is modified since it has loaded. /// /// Is riased when this class is disposed and this property is accessed. public bool IsModified { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } return m_pHeader.IsModified || m_pBody.IsModified; } } /// /// Gets the parent entity of this entity, returns null if this is the root entity. /// /// Is raised when this object is disposed and this property is accessed. public MIME_Entity Parent { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } return m_pParent; } } /// /// Gets MIME entity header field collection. /// /// Is raised when this object is disposed and this property is accessed. public MIME_h_Collection Header { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } return m_pHeader; } } /// /// Gets or sets MIME version number. Value null means that header field does not exist. Normally this value is 1.0. Defined in RFC 2045 section 4. /// /// Is raised when this object is disposed and this property is accessed. /// An indicator that this message is formatted according to the MIME /// standard, and an indication of which version of MIME is used. public string MimeVersion { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("MIME-Version"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("MIME-Version"); } else{ MIME_h h = m_pHeader.GetFirst("MIME-Version"); if(h == null){ h = new MIME_h_Unstructured("MIME-Version",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets content body part ID. Value null means that header field does not exist. Defined in RFC 2045 7. /// /// Is raised when this object is disposed and this property is accessed. /// Specifies a Unique ID for one MIME body part of the content of a message. public string ContentID { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-ID"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-ID"); } else{ MIME_h h = m_pHeader.GetFirst("Content-ID"); if(h == null){ h = new MIME_h_Unstructured("Content-ID",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets description of message body part. Value null means that header field does not exist. Defined in RFC 2045 8. /// /// Is raised when this object is disposed and this property is accessed. /// Description of a particular body part of a message; for example, a caption for an image body part. public string ContentDescription { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Description"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h == null){ h = new MIME_h_Unstructured("Content-Description",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets content transfer encoding. Value null means that header field does not exist. /// RFC defined values are in MIME_TransferEncodings. Defined in RFC 2045 6. /// /// Is raised when this object is disposed and this property is accessed. /// Coding method used in a MIME message body part. public string ContentTransferEncoding { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Transfer-Encoding"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Transfer-Encoding"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Transfer-Encoding"); if(h == null){ h = new MIME_h_Unstructured("Content-Transfer-Encoding",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets MIME content type. Value null means that header field does not exist. Defined in RFC 2045 5. /// /// Is raised when this object is disposed and this property is accessed. /// Is raised when header field parsing errors. public MIME_h_ContentType ContentType { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Type"); if(h != null){ if(!(h is MIME_h_ContentType)){ throw new ParseException("Header field 'ContentType' parsing failed."); } return (MIME_h_ContentType)h; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Type"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Type"); if(h == null){ m_pHeader.Add(value); } else{ m_pHeader.ReplaceFirst(value); } } } } /// /// Gets or sets base to be used for resolving relative URIs within this content part. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Base to be used for resolving relative URIs within this content part. See also Content-Location. public string ContentBase { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Base"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Base"); if(h == null){ h = new MIME_h_Unstructured("Content-Base",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets URI for retrieving a body part. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// URI using which the content of this body-part part was retrieved, /// might be retrievable, or which otherwise gives a globally unique identification of the content. public string ContentLocation { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Location"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Location"); if(h == null){ h = new MIME_h_Unstructured("Content-Location",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets content features of a MIME body part. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// The 'Content-features:' header can be used to annotate a MIME body part with a media feature expression, /// to indicate features of the body part content. See also RFC 2533, RFC 2506, and RFC 2045. public string Contentfeatures { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-features"); } else{ MIME_h h = m_pHeader.GetFirst("Content-features"); if(h == null){ h = new MIME_h_Unstructured("Content-features",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets content disposition. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Indicates whether a MIME body part is to be shown inline or is an attachment; can also indicate a /// suggested filename for use when saving an attachment to a file. /// Is raised when header field parsing errors. public MIME_h_ContentDisposition ContentDisposition { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Disposition"); if(h != null){ if(!(h is MIME_h_ContentDisposition)){ throw new ParseException("Header field 'ContentDisposition' parsing failed."); } return (MIME_h_ContentDisposition)h; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Disposition"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Disposition"); if(h == null){ m_pHeader.Add(value); } else{ m_pHeader.ReplaceFirst(value); } } } } /// /// Gets or sets language of message content. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Can include a code for the natural language used in a message; e.g., 'en' for English. /// Can also contain a list of languages for a message containing more than one language. public string ContentLanguage { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Language"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Language"); if(h == null){ h = new MIME_h_Unstructured("Content-Language",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets message alternative content. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Information about the media features of alternative content formats available for the current message. public string ContentAlternative { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Alternative"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Alternative"); if(h == null){ h = new MIME_h_Unstructured("Content-Alternative",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets content MD5 checksum. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Checksum of content to ensure that it has not been modified. public string ContentMD5 { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-MD5"); } else{ MIME_h h = m_pHeader.GetFirst("Content-MD5"); if(h == null){ h = new MIME_h_Unstructured("Content-MD5",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets time duration of content. Value null means that header field does not exist. /// /// Is raised when this object is disposed and this property is accessed. /// Time duration of body part content, in seconds (e.g., for audio message). public string ContentDuration { get{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } MIME_h h = m_pHeader.GetFirst("Content-Description"); if(h != null){ return ((MIME_h_Unstructured)h).Value; } else{ return null; } } set{ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(value == null){ m_pHeader.RemoveAll("Content-Duration"); } else{ MIME_h h = m_pHeader.GetFirst("Content-Duration"); if(h == null){ h = new MIME_h_Unstructured("Content-Duration",value); m_pHeader.Add(h); } else{ ((MIME_h_Unstructured)h).Value = value; } } } } /// /// Gets or sets MIME entity body. /// /// Is raised when null reference passed. public MIME_b Body { get{ return m_pBody; } set{ if(value == null){ throw new ArgumentNullException("Body"); } m_pBody = value; m_pBody.SetParent(this,true); } } #endregion } }