// -----------------------------------------------------------------------
//
// 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:
///
/// - [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).
///
///
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; i0 ) {
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.
///
/// A specifying 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 is returned.
/// Once the file is successfully saved, the stream is closed and property is updated.
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;
}
///
/// 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 instance
public System.String MimeMediaSubType {
get { return this._sub_type; }
set { this._sub_type = value; }
}
///
/// Gets or sets SubType for this instance
///
/// SubType from Content-Type header field of this instance
public anmar.SharpMimeTools.MimeTopLevelMediaType MimeTopLevelMediaType {
get { return this._top_level_media_type; }
set { this._top_level_media_type = value; }
}
///
/// Gets or sets size (in bytes) for this instance.
///
/// Size of this instance
public long Size {
get { return this._size; }
set { this._size = value; }
}
///
/// Gets the of the saved file.
///
/// The of the saved file.
public System.IO.FileInfo SavedFile {
get { return this._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 for reading.
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;
}
}
}
}