// -----------------------------------------------------------------------
//
// 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
{
///
/// System.IO.FileStream msg = new System.IO.FileStream("message_file.txt", System.IO.FileMode.Open);
/// anmar.SharpMimeTools.SharpMessage message = new anmar.SharpMimeTools.SharpMessage(msg, SharpDecodeOptions.Default|SharpDecodeOptions.DecodeTnef|SharpDecodeOptions.UuDecode);
/// msg.Close();
/// Console.WriteLine(System.String.Concat("From: [", message.From, "][", message.FromAddress, "]"));
/// Console.WriteLine(System.String.Concat("To: [", message.To, "]"));
/// Console.WriteLine(System.String.Concat("Subject: [", message.Subject, "]"));
/// Console.WriteLine(System.String.Concat("Date: [", message.Date, "]"));
/// Console.WriteLine(System.String.Concat("Body: [", message.Body, "]"));
/// if ( message.Attachments!=null ) {
/// foreach ( anmar.SharpMimeTools.SharpAttachment attachment in message.Attachments ) {
/// attachment.Save(System.Environment.CurrentDirectory, false);
/// Console.WriteLine(System.String.Concat("Attachment: [", attachment.SavedFile.FullName, "]"));
/// }
/// }
///
///
/// System.IO.FileStream msg = new System.IO.FileStream("message_file.txt", System.IO.FileMode.Open);
/// anmar.SharpMimeTools.SharpMessage message = new anmar.SharpMimeTools.SharpMessage(msg);
/// msg.Close();
/// Console.WriteLine(System.String.Concat("From: [", message.From, "][", message.FromAddress, "]"));
/// Console.WriteLine(System.String.Concat("To: [", message.To, "]"));
/// Console.WriteLine(System.String.Concat("Subject: [", message.Subject, "]"));
/// Console.WriteLine(System.String.Concat("Date: [", message.Date, "]"));
/// Console.WriteLine(System.String.Concat("Body: [", message.Body, "]"));
///
///
/// anmar.SharpMimeTools.MimeTopLevelMediaType.text, anmar.SharpMimeTools.MimeTopLevelMediaType.multipart and anmar.SharpMimeTools.MimeTopLevelMediaType.message are allowed in any case.
/// In order to have better control over what is parsed, see the other contructors.
///
/// anmar.SharpMimeTools.MimeTopLevelMediaType.text, anmar.SharpMimeTools.MimeTopLevelMediaType.multipart and anmar.SharpMimeTools.MimeTopLevelMediaType.message are allowed in any case.
/// In order to have better control over what is parsed, see the other contructors.
///
/// anmar.SharpMimeTools.MimeTopLevelMediaType.text, anmar.SharpMimeTools.MimeTopLevelMediaType.multipart and anmar.SharpMimeTools.MimeTopLevelMediaType.message are allowed in any case.
/// In order to have better control over what is parsed, see the other contructors.
///
/// If the requested field is not present in this instance,
if ( !this._body_html && this._body.Length>0 ) { this._body = System.String.Concat ("", System.Web.HttpUtility.HtmlEncode(this._body), ""); this._body_html = true; } // Add the anchor this._body = System.String.Concat (this._body, ""); } this.ParseMessage(altenative, types, html, options, preferredtextsubtype, path); } } // TODO: Take into account each subtype of "multipart" } else if ( part.PartsCount>0 ) { foreach ( anmar.SharpMimeTools.SharpMimeMessage item in part ) { this.ParseMessage(item, types, html, options, preferredtextsubtype, path); } } break; case anmar.SharpMimeTools.MimeTopLevelMediaType.text: if ( ( part.Disposition==null || !part.Disposition.Equals("attachment") ) && ( part.Header.SubType.Equals("plain") || part.Header.SubType.Equals("html") ) ) { bool body_was_html = this._body_html; // HTML content not allowed if ( part.Header.SubType.Equals("html") ) { if ( !html ) break; else this._body_html=true; } if ( html && part.Header.Contains("Content-ID") && (options&anmar.SharpMimeTools.SharpDecodeOptions.NamedAnchors)==anmar.SharpMimeTools.SharpDecodeOptions.NamedAnchors ) { this._body_html = true; } if ( this._body_html && !body_was_html && this._body.Length>0 ) { this._body = System.String.Concat ("", System.Web.HttpUtility.HtmlEncode(this._body), ""); } // If message body is html and this part has a Content-ID field // add an anchor to mark this body part if ( this._body_html && part.Header.Contains("Content-ID") && (options&anmar.SharpMimeTools.SharpDecodeOptions.NamedAnchors)==anmar.SharpMimeTools.SharpDecodeOptions.NamedAnchors ) { this._body = System.String.Concat (this._body, ""); } if ( this._body_html && part.Header.SubType.Equals("plain") ) { this._body = System.String.Concat (this._body, "", System.Web.HttpUtility.HtmlEncode(part.BodyDecoded), ""); } else this._body = System.String.Concat (this._body, part.BodyDecoded); } else { if ( (types&anmar.SharpMimeTools.MimeTopLevelMediaType.application)!=anmar.SharpMimeTools.MimeTopLevelMediaType.application ) { #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("Mime-Type [", anmar.SharpMimeTools.MimeTopLevelMediaType.application, "] is not an accepted Mime-Type. Skiping part.")); #endif return; } goto case anmar.SharpMimeTools.MimeTopLevelMediaType.application; } break; case anmar.SharpMimeTools.MimeTopLevelMediaType.application: case anmar.SharpMimeTools.MimeTopLevelMediaType.audio: case anmar.SharpMimeTools.MimeTopLevelMediaType.image: case anmar.SharpMimeTools.MimeTopLevelMediaType.video: // Attachments not allowed. if ( (options&anmar.SharpMimeTools.SharpDecodeOptions.AllowAttachments)!=anmar.SharpMimeTools.SharpDecodeOptions.AllowAttachments ) break; anmar.SharpMimeTools.SharpAttachment attachment = null; // Save to a file if ( path!=null ) { System.IO.FileInfo file = part.DumpBody(path, true); if ( file!=null ) { attachment = new anmar.SharpMimeTools.SharpAttachment(file); attachment.Name = file.Name; attachment.Size = file.Length; } // Save to a stream } else { System.IO.MemoryStream stream = new System.IO.MemoryStream(); if ( part.DumpBody(stream) ) { attachment = new anmar.SharpMimeTools.SharpAttachment(stream); if ( part.Name!=null ) attachment.Name = part.Name; else attachment.Name = System.String.Concat("generated_", part.GetHashCode(), ".", part.Header.SubType); attachment.Size = stream.Length; } stream = null; } if ( attachment!=null && part.Header.SubType=="ms-tnef" && (options&anmar.SharpMimeTools.SharpDecodeOptions.DecodeTnef)==anmar.SharpMimeTools.SharpDecodeOptions.DecodeTnef ) { // Try getting attachments form a tnef stream #if LOG if ( log.IsDebugEnabled ) { log.Debug(System.String.Concat("Decoding ms-tnef stream.")); } #endif System.IO.Stream stream = attachment.Stream; if ( stream!=null && stream.CanSeek ) stream.Seek(0, System.IO.SeekOrigin.Begin); anmar.SharpMimeTools.SharpTnefMessage tnef = new anmar.SharpMimeTools.SharpTnefMessage(stream); if ( tnef.Parse(path) ) { if ( tnef.Attachments!=null ) { this._attachments.AddRange(tnef.Attachments); } attachment.Close(); // Delete the raw tnef file if ( attachment.SavedFile!=null ) { if ( stream!=null && stream.CanRead ) stream.Close(); attachment.SavedFile.Delete(); } attachment = null; tnef.Close(); #if LOG if ( log.IsDebugEnabled ) { log.Debug(System.String.Concat("ms-tnef stream decoded successfully. Found [", ((tnef.Attachments!=null)?tnef.Attachments.Count:0),"] attachments.")); } #endif } else { // The read-only stream is no longer needed and locks the file if ( attachment.SavedFile!=null && stream!=null && stream.CanRead ) stream.Close(); } stream = null; tnef = null; } if ( attachment!=null ) { if ( part.Disposition!=null && part.Disposition=="inline" ) { attachment.Inline = true; } attachment.MimeTopLevelMediaType = part.Header.TopLevelMediaType; attachment.MimeMediaSubType = part.Header.SubType; // Store attachment's CreationTime if ( part.Header.ContentDispositionParameters.ContainsKey("creation-date") ) attachment.CreationTime = anmar.SharpMimeTools.SharpMimeTools.parseDate ( part.Header.ContentDispositionParameters["creation-date"] ); // Store attachment's LastWriteTime if ( part.Header.ContentDispositionParameters.ContainsKey("modification-date") ) attachment.LastWriteTime = anmar.SharpMimeTools.SharpMimeTools.parseDate ( part.Header.ContentDispositionParameters["modification-date"] ); if ( part.Header.Contains("Content-ID") ) attachment.ContentID = part.Header.ContentID; this._attachments.Add(attachment); } break; default: break; } } private System.String ReplaceUrlTokens ( System.String url, anmar.SharpMimeTools.SharpAttachment attachment ) { if ( url==null || url.Length==0 || url.IndexOf('[')==-1 || url.IndexOf(']')==-1 ) return url; if ( url.IndexOf("[MessageID]")!=-1 ) { url = url.Replace("[MessageID]", System.Web.HttpUtility.UrlEncode(anmar.SharpMimeTools.SharpMimeTools.Rfc2392Url(this.MessageID))); } if ( attachment!=null && attachment.ContentID!=null ) { if ( url.IndexOf("[ContentID]")!=-1 ) { url = url.Replace("[ContentID]", System.Web.HttpUtility.UrlEncode(anmar.SharpMimeTools.SharpMimeTools.Rfc2392Url(attachment.ContentID))); } if ( url.IndexOf("[Name]")!=-1 ) { if ( attachment.SavedFile!=null ) { url = url.Replace("[Name]", System.Web.HttpUtility.UrlEncode(attachment.SavedFile.Name)); } else { url = url.Replace("[Name]", System.Web.HttpUtility.UrlEncode(attachment.Name)); } } } return url; } ////// Set the URL used to reference embedded parts from the HTML body (as specified on RFC2392) /// /// URL used to reference embedded parts from the HTML body. ///The supplied URL will be replaced with the following tokens for each attachment: public void SetUrlBase ( System.String attachmentsurl ) { // Not a html boy or not body at all if ( !this._body_html || this._body.Length==0 ) return; // No references found, so nothing to do if ( this._body.IndexOf("cid:")==-1 && this._body.IndexOf("mid:")==-1 ) return; System.String msgid = anmar.SharpMimeTools.SharpMimeTools.Rfc2392Url(this.MessageID); // There is a base url and attachments, so try refererencing them properly if ( attachmentsurl!=null && this._attachments!=null && this._attachments.Count>0 ) { for ( int i=0, count=this._attachments.Count; i
//////
///- [MessageID]: Will be replaced with the
///of the current instance. - [ContentID]: Will be replaced with the
///of the attachment. - [Name]: Will be replaced with the
///of the attachment (or with from if the instance has been already saved to disk). 0 ) { if ( this._body.IndexOf("cid:" + conid)!=-1 ) this._body = this._body.Replace("cid:" + conid, this.ReplaceUrlTokens(attachmentsurl, attachment)); if ( msgid.Length>0 && this._body.IndexOf("mid:" + msgid + "/" + conid)!=-1 ) this._body = this._body.Replace("mid:" + msgid + "/" + conid, this.ReplaceUrlTokens(attachmentsurl, attachment)); // No more references found, so nothing to do if ( this._body.IndexOf("cid:")==-1 && this._body.IndexOf("mid:")==-1 ) break; } } } } // The rest references must be to text parts // so rewrite them to refer to the named anchors added by ParseMessage if ( this._body.IndexOf("cid:")!=-1 ) { this._body = this._body.Replace("cid:", "#" + msgid + "_"); } if ( msgid.Length>0 && this._body.IndexOf("mid:")!=-1 ) { this._body = this._body.Replace("mid:" + msgid + "/", "#" + msgid + "_"); this._body = this._body.Replace("mid:" + msgid, "#" + msgid); } } private void UuDecode ( System.String path ) { if ( this._body.Length==0 || this._body.IndexOf("begin ")==-1 || this._body.IndexOf("end")==-1 ) return; System.Text.StringBuilder sb = new System.Text.StringBuilder(); System.IO.StringReader reader = new System.IO.StringReader(this._body); System.IO.Stream stream = null; anmar.SharpMimeTools.SharpAttachment attachment = null; for ( System.String line=reader.ReadLine(); line!=null; line=reader.ReadLine() ) { if ( stream==null ) { // Found the start point of uuencoded content if ( line.Length>10 && line[0]=='b' && line[1]=='e' && line[2]=='g' && line[3]=='i' && line[4]=='n' && line[5]==' ' && line[9]==' ' ) { System.String name = System.IO.Path.GetFileName(line.Substring(10)); #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("uuencoded content found. name[", name, "]")); #endif // In-Memory decoding if ( path==null ) { attachment = new anmar.SharpMimeTools.SharpAttachment(new System.IO.MemoryStream()); stream = attachment.Stream; // Filesystem decoding } else { attachment = new anmar.SharpMimeTools.SharpAttachment(new System.IO.FileInfo(System.IO.Path.Combine(path, name))); stream = attachment.SavedFile.OpenWrite(); } attachment.Name = name; // Not uuencoded line, so add it to new body } else { sb.Append(line); sb.Append(System.Environment.NewLine); } } else { // Content finished if ( line.Length==3 && line=="end" ) { stream.Flush(); if ( stream.Length>0 ) { #if LOG if ( log.IsDebugEnabled ) log.Debug (System.String.Concat("uuencoded content finished. name[", attachment.Name, "] size[", stream.Length, "]")); #endif attachment.Size = stream.Length; this._attachments.Add(attachment); } // When decoding to a file, close the stream. if ( attachment.SavedFile!=null || stream.Length==0 ) { stream.Close(); } attachment = null; stream = null; // Decode and write uuencoded line } else { anmar.SharpMimeTools.SharpMimeTools.UuDecodeLine(line, stream); } } } if ( stream!=null && stream.CanRead ) { stream.Close(); stream = null; } reader.Close(); reader = null; this._body = sb.ToString(); sb = null; } } /// /// This class provides the basic functionality for handling attachments /// public class SharpAttachment { #if LOG private static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endif private System.DateTime _ctime = System.DateTime.MinValue; private System.String _cid; private bool _inline = false; private System.DateTime _mtime = System.DateTime.MinValue; private System.String _name; private long _size; private System.IO.MemoryStream _stream; private System.IO.FileInfo _saved_file; private System.String _sub_type; private anmar.SharpMimeTools.MimeTopLevelMediaType _top_level_media_type; ////// Initializes a new instance of the ///class based on the supplied . /// instance that contains the attachment content. public SharpAttachment ( System.IO.MemoryStream stream ) { this._stream = stream; } /// /// Initializes a new instance of the ///class based on the supplied . /// instance that contains the info about the attachment. public SharpAttachment ( System.IO.FileInfo file ) { this._saved_file = file; this._ctime = file.CreationTime; this._mtime = file.LastWriteTime; } /// /// Closes the underling stream if it's open. /// public void Close() { if ( this._stream!=null && this._stream.CanRead ) this._stream.Close(); this._stream = null; } ////// Saves of the attachment to a file in the given path. /// /// Aspecifying the path on which to save the attachment. /// true if the destination file can be overwritten; otherwise, false. /// /// of the saved file. null when it fails to save. If the file was already saved, the previous public System.IO.FileInfo Save ( System.String path, bool overwrite ) { if ( path==null || this._name==null ) return null; if ( this._stream==null ) { if ( this._saved_file!=null ) return this._saved_file; else return null; } if ( !this._stream.CanRead ) { #if LOG if ( log.IsErrorEnabled ) log.Error(System.String.Concat("The provided stream does not support reading.")); #endif return null; } System.IO.FileInfo file = new System.IO.FileInfo (System.IO.Path.Combine (path, this._name)); if ( !file.Directory.Exists ) { #if LOG if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Destination folder [", file.Directory.FullName, "] does not exist")); #endif return null; } if ( file.Exists ) { if ( overwrite ) { try { file.Delete(); #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Error deleting existing file[", file.FullName, "]"), e); #else } catch ( System.Exception ) { #endif return null; } } else { #if LOG if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Destination file [", file.FullName, "] already exists")); #endif // Though the file already exists, we set the times if ( this._mtime!=System.DateTime.MinValue && file.LastWriteTime!=this._mtime ) file.LastWriteTime = this._mtime; if ( this._ctime!=System.DateTime.MinValue && file.CreationTime!=this._ctime ) file.CreationTime = this._ctime; return null; } } try { System.IO.FileStream stream = file.OpenWrite(); this._stream.WriteTo(stream); stream.Flush(); stream.Close(); this.Close(); if ( this._mtime!=System.DateTime.MinValue ) file.LastWriteTime = this._mtime; if ( this._ctime!=System.DateTime.MinValue ) file.CreationTime = this._ctime; this._saved_file = file; #if LOG } catch ( System.Exception e ) { if ( log.IsErrorEnabled ) log.Error(System.String.Concat("Error writting file [", file.FullName, "]"), e); #else } catch ( System.Exception ) { #endif return null; } return file; } ///is returned.
/// Once the file is successfully saved, the stream is closed andproperty is updated. /// Gets or sets the Content-ID of this attachment. /// ///Content-ID header of this instance. Or the null reference. public System.String ContentID { get { return this._cid; } set { this._cid = value; } } ////// Gets or sets the time when the file associated with this attachment was created. /// ///The time this attachment was last written to. public System.DateTime CreationTime { get { return this._ctime; } set { this._ctime = value; } } ////// Gets or sets value indicating whether the current instance is an inline attachment. /// ///true is it's an inline attachment; false otherwise. public bool Inline { get { return this._inline; } set { this._inline = value; } } ////// Gets or sets the time when the file associated with this attachment was last written to. /// ///The time this attachment was last written to. public System.DateTime LastWriteTime { get { return this._mtime; } set { this._mtime = value; } } ////// Gets or sets the name of the attachment. /// ///The name of the attachment. public System.String Name { get { return this._name; } set { System.String name = anmar.SharpMimeTools.SharpMimeTools.GetFileName(value); if ( value!=null && name==null && this._name!=null && System.IO.Path.HasExtension(value) ) { name = System.IO.Path.ChangeExtension(this._name, System.IO.Path.GetExtension(value)); } this._name = name; } } ////// Gets or sets top-level media type for this ///instance /// Top-level media type from Content-Type header field of this public System.String MimeMediaSubType { get { return this._sub_type; } set { this._sub_type = value; } } ///instance /// Gets or sets SubType for this ///instance /// SubType from Content-Type header field of this public anmar.SharpMimeTools.MimeTopLevelMediaType MimeTopLevelMediaType { get { return this._top_level_media_type; } set { this._top_level_media_type = value; } } ///instance /// Gets or sets size (in bytes) for this ///instance. /// Size of this public long Size { get { return this._size; } set { this._size = value; } } ///instance /// Gets the ///of the saved file. /// The public System.IO.FileInfo SavedFile { get { return this._saved_file; } } ///of the saved file. /// Gets the ///of the attachment. /// The ///of the attachment. If the underling stream exists, it's returned. If the file has been saved, it opens public System.IO.Stream Stream { get { if ( this._stream!=null ) return this._stream; else if ( this._saved_file!=null ) return this._saved_file.OpenRead(); else return null; } } } }for reading.