// ----------------------------------------------------------------------- // // Copyright (C) 2003-2006 Angel Marin // // This file is part of SharpMimeTools // // SharpMimeTools is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // SharpMimeTools is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with SharpMimeTools; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // // ----------------------------------------------------------------------- using System; namespace anmar.SharpMimeTools { /// /// rfc 2045 entity /// public class SharpMimeMessage : System.Collections.IEnumerable { private struct MessageInfo { internal long start; internal long start_body; internal long end; internal anmar.SharpMimeTools.SharpMimeHeader header; internal anmar.SharpMimeTools.SharpMimeMessageCollection parts; internal MessageInfo ( anmar.SharpMimeTools.SharpMimeMessageStream m, long start ) { this.start = start; this.header = new anmar.SharpMimeTools.SharpMimeHeader ( m, this.start ); this.start_body = this.header.BodyPosition; this.end = -1; parts = new anmar.SharpMimeTools.SharpMimeMessageCollection(); } } #if LOG private static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endif private anmar.SharpMimeTools.SharpMimeMessageStream message; private MessageInfo mi; /// /// Initializes a new instance of the class from a /// /// to read the message from public SharpMimeMessage( System.IO.Stream message ) { this.message = new anmar.SharpMimeTools.SharpMimeMessageStream (message); this.mi = new MessageInfo ( this.message, this.message.Stream.Position ); } private SharpMimeMessage( anmar.SharpMimeTools.SharpMimeMessageStream message, long startpoint ) { this.message = message; this.mi = new MessageInfo ( this.message, startpoint ); } private SharpMimeMessage( anmar.SharpMimeTools.SharpMimeMessageStream message, long startpoint, long endpoint ) { this.message = message; this.mi = new MessageInfo ( this.message, startpoint ); this.mi.end = endpoint; } /// /// Clears the parts references contained in this instance and calls the Close method in those parts. /// /// This method does not close the underling used to create this instance. public void Close() { foreach ( anmar.SharpMimeTools.SharpMimeMessage part in this.mi.parts ) part.Close(); this.mi.parts.Clear(); } /// /// Dumps the body of this entity into a /// /// where we want to write the body /// true OK;false if write operation fails public bool DumpBody ( System.IO.Stream stream ) { if ( stream==null ) return false; bool error = false; if ( stream.CanWrite ) { System.Byte[] buffer = null; switch (this.Header.ContentTransferEncoding) { case "quoted-printable": buffer = this.mi.header.Encoding.GetBytes(this.BodyDecoded); break; case "base64": try { buffer = System.Convert.FromBase64String(this.Body); #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error("Error Converting base64 string", e); #else } catch ( System.Exception ) { #endif error = true; } break; case "7bit": case "8bit": case "binary": case null: buffer = System.Text.Encoding.ASCII.GetBytes(this.Body); break; default: #if LOG if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Unsuported Content-Transfer-Encoding [", this.Header.ContentTransferEncoding, "]")); #endif error=true; break; } try { if ( !error && buffer!=null ) stream.Write ( buffer, 0, buffer.Length ); #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error("Error dumping body", e); #else } catch ( System.Exception ) { #endif error = true; } buffer = null; } else { #if LOG if ( log.IsErrorEnabled ) log.Error("Can't write to stream"); #endif error = true; } return !error; } /// /// Dumps the body of this entity into a file /// /// path of the destination folder /// that represents the file where the body has been saved public System.IO.FileInfo DumpBody ( System.String path ) { return this.DumpBody ( path, this.Name ); } /// /// Dumps the body of this entity into a file /// /// path of the destination folder /// true if the filename must be generated incase we can't find a valid one in the headers /// that represents the file where the body has been saved public System.IO.FileInfo DumpBody ( System.String path, bool generatename ) { System.String name = this.Name; if ( name==null && generatename ) name = System.String.Format ( "generated_{0}.{1}", this.GetHashCode(), this.Header.SubType ); return this.DumpBody ( path, name ); } /// /// Dumps the body of this entity into a file /// /// path of the destination folder /// name of the file /// that represents the file where the body has been saved public System.IO.FileInfo DumpBody ( System.String path, System.String name ) { System.IO.FileInfo file = null; if ( name!=null ) { #if LOG if ( log.IsDebugEnabled ) log.Debug ("Found attachment: " + name); #endif name = System.IO.Path.GetFileName(name); // Dump file contents try { System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo ( path ); dir.Create(); try { file = new System.IO.FileInfo (System.IO.Path.Combine (path, name) ); } catch ( System.ArgumentException ) { file = null; #if LOG if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Filename [", System.IO.Path.Combine (path, name), "] is not allowed by the filesystem")); #endif } if ( file!=null && dir.Exists ) { if ( dir.FullName.Equals (new System.IO.DirectoryInfo (file.Directory.FullName).FullName) ) { if ( !file.Exists ) { #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("Saving attachment [", file.FullName, "] ...")); #endif System.IO.Stream stream = null; try { stream = file.Create(); #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Error creating file [", file.FullName, "]"), e); #else } catch ( System.Exception ) { #endif } bool error = !this.DumpBody (stream); if ( stream!=null ) stream.Close(); stream = null; if ( error ) { #if LOG if ( log.IsErrorEnabled ) log.Error (System.String.Concat("Error writting file [", file.FullName, "] to disk")); #endif if ( stream!=null ) file.Delete(); } else { #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("Attachment saved [", file.FullName, "]")); #endif // The file should be there file.Refresh(); // Set file dates if ( this.Header.ContentDispositionParameters.ContainsKey("creation-date") ) file.CreationTime = anmar.SharpMimeTools.SharpMimeTools.parseDate ( this.Header.ContentDispositionParameters["creation-date"] ); if ( this.Header.ContentDispositionParameters.ContainsKey("modification-date") ) file.LastWriteTime = anmar.SharpMimeTools.SharpMimeTools.parseDate ( this.Header.ContentDispositionParameters["modification-date"] ); if ( this.Header.ContentDispositionParameters.ContainsKey("read-date") ) file.LastAccessTime = anmar.SharpMimeTools.SharpMimeTools.parseDate ( this.Header.ContentDispositionParameters["read-date"] ); } #if LOG } else if ( log.IsDebugEnabled ) { log.Debug("File already exists, skipping."); #endif } #if LOG } else if ( log.IsDebugEnabled ) { log.Debug(System.String.Concat ("Folder name mistmatch. [", dir.FullName, "]<>[", new System.IO.DirectoryInfo (file.Directory.FullName).FullName, "]")); #endif } #if LOG } else if ( file!=null && log.IsErrorEnabled ) { log.Error ("Destination folder does not exists."); #endif } dir = null; #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error ("Error writting to disk: " + name, e); #else } catch ( System.Exception ) { #endif try { if ( file!=null ) { file.Refresh(); if ( file.Exists ) file.Delete (); } } catch ( System.Exception ) {} file = null; } } return file; } /// /// Returns an enumerator that can iterate through the parts of a multipart entity /// /// A for the parts of a multipart entity public System.Collections.IEnumerator GetEnumerator() { this.parse(); return this.mi.parts.GetEnumerator(); } /// /// Returns the requested part of a multipart entity /// /// index of the requested part /// A for the requested part public anmar.SharpMimeTools.SharpMimeMessage GetPart ( int index ) { return this.Parts.Get ( index ); } private bool parse () { bool error = false; #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("Parsing requested, type: ", this.mi.header.TopLevelMediaType.ToString(), ", subtype: ", this.mi.header.SubType) ); #endif if ( !this.IsMultipart || this.Equals(this.mi.parts.Parent) ) { #if LOG if ( log.IsDebugEnabled ) log.Debug ("Parsing requested and this is not a multipart or it is already parsed"); #endif return true; } switch (this.mi.header.TopLevelMediaType) { case anmar.SharpMimeTools.MimeTopLevelMediaType.message: this.mi.parts.Parent = this; anmar.SharpMimeTools.SharpMimeMessage message = new anmar.SharpMimeTools.SharpMimeMessage (this.message, this.mi.start_body, this.mi.end ); this.mi.parts.Add (message); break; case anmar.SharpMimeTools.MimeTopLevelMediaType.multipart: this.message.SeekPoint ( this.mi.start_body ); System.String line; #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Format("Looking for multipart {1}, byte {0}", this.mi.start_body, this.mi.header.ContentTypeParameters["boundary"])); #endif this.mi.parts.Parent = this; System.String boundary_start = System.String.Concat("--", this.mi.header.ContentTypeParameters["boundary"]); System.String boundary_end = System.String.Concat("--", this.mi.header.ContentTypeParameters["boundary"], "--"); for ( line=this.message.ReadLine(); line!=null ; line=this.message.ReadLine() ) { // It can't be a boundary line if ( line.Length<3 ) continue; // Match start boundary line if ( line.Length==boundary_start.Length && line==boundary_start ) { if ( this.mi.parts.Count>0 ) { this.mi.parts.Get( this.mi.parts.Count-1 ).mi.end = this.message.Position_preRead; #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Format("End part {1} at byte {0}", this.message.Position_preRead, boundary_start)); #endif } #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Format("Part {1} found at byte {0}", this.message.Position_preRead, boundary_start)); #endif anmar.SharpMimeTools.SharpMimeMessage msg = new anmar.SharpMimeTools.SharpMimeMessage (this.message, this.message.Position ); this.mi.parts.Add (msg); // Match end boundary line } else if ( line.Length==boundary_end.Length && line==boundary_end ) { this.mi.end = this.message.Position_preRead; if ( this.mi.parts.Count>0 ) { this.mi.parts.Get( this.mi.parts.Count-1 ).mi.end = this.message.Position_preRead; #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Format("End part {1} at byte {0}", this.message.Position_preRead, boundary_end)); #endif #if LOG } else if ( log.IsDebugEnabled ) { log.Debug (System.String.Format("End part {1} at byte {0}", this.mi.end, boundary_end)); #endif } break; } } break; } return !error; } /// /// Gets header fields for this entity /// /// field name /// Field names is case insentitive public System.String this[ System.Object name ] { get { return this.mi.header[ name.ToString()]; } } /// /// /// /// public System.String Body { get { this.parse(); if ( this.mi.parts.Count == 0 ) { this.message.Encoding = this.mi.header.Encoding; if ( this.mi.end ==-1 ) { return this.message.ReadAll(this.mi.start_body); } else { return this.message.ReadLines(this.mi.start_body, this.mi.end); } } else { return null; } } } /// /// /// /// public System.String BodyDecoded { get { switch (this.Header.ContentTransferEncoding) { case "quoted-printable": System.String body = this.Body; anmar.SharpMimeTools.SharpMimeTools.QuotedPrintable2Unicode ( this.mi.header.Encoding, ref body ); return body; case "base64": System.Byte[] tmp = null; try { tmp = System.Convert.FromBase64String(this.Body); #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error("Error dumping body", e); #else } catch ( System.Exception ) { #endif } if ( tmp!=null ) return this.mi.header.Encoding.GetString(tmp); else return System.String.Empty; } return this.Body; } } /// /// /// /// public System.String Disposition { get { return this.Header.ContentDispositionParameters["Content-Disposition"]; } } /// /// /// /// public anmar.SharpMimeTools.SharpMimeHeader Header { get { return this.mi.header; } } /// /// /// /// public bool IsBrowserDisplay { get { switch (this.mi.header.TopLevelMediaType) { case anmar.SharpMimeTools.MimeTopLevelMediaType.audio: case anmar.SharpMimeTools.MimeTopLevelMediaType.image: case anmar.SharpMimeTools.MimeTopLevelMediaType.text: case anmar.SharpMimeTools.MimeTopLevelMediaType.video: return true; default: return false; } } } /// /// /// /// public bool IsMultipart { get { switch (this.mi.header.TopLevelMediaType) { case anmar.SharpMimeTools.MimeTopLevelMediaType.multipart: case anmar.SharpMimeTools.MimeTopLevelMediaType.message: return true; default: return false; } } } /// /// /// /// public bool IsTextBrowserDisplay { get { if ( this.mi.header.TopLevelMediaType.Equals(anmar.SharpMimeTools.MimeTopLevelMediaType.text) && this.mi.header.SubType.Equals("plain") ) { return true; } else { return false; } } } /// /// /// /// public System.String Name { get { this.parse(); System.String param = this.Header.ContentDispositionParameters["filename"]; if ( param==null ) { param = this.Header.ContentTypeParameters["name"]; } if ( param==null ) { param = this.Header.ContentLocationParameters["Content-Location"]; } return anmar.SharpMimeTools.SharpMimeTools.GetFileName(param); } } internal anmar.SharpMimeTools.SharpMimeMessageCollection Parts { get { this.parse(); return this.mi.parts; } } /// /// /// /// public int PartsCount { get { this.parse(); return this.mi.parts.Count; } } /// /// /// /// public long Size { get { this.parse(); return this.mi.end - this.mi.start_body; } } } }