// -----------------------------------------------------------------------
//
// 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;
}
}
}
}