// -----------------------------------------------------------------------
//
// 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
{
///
/// Decodes ms-tnef streams (those winmail.dat attachments).
///
/// Only tnef attributes related to attachments are decoded right now. All the MAPI properties encoded in the stream (rtf body, ...) are ignored.
/// While decoding, the cheksum of each attribute is verified to ensure the tnef stream is not corupt.
public class SharpTnefMessage {
#if LOG
private static log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endif
private System.IO.BinaryReader _reader;
private System.Collections.ArrayList _attachments;
private System.String _body;
///
/// Initializes a new instance of the class based on the supplied .
///
/// that contains the ms-tnef strream.
/// The tnef stream isn't automatically parsed, you must call or .
public SharpTnefMessage ( System.IO.Stream input ) {
if ( input!=null && input.CanRead ) {
if ( input is System.IO.BufferedStream )
this._reader = new System.IO.BinaryReader(input);
else
this._reader = new System.IO.BinaryReader(new System.IO.BufferedStream(input));
}
}
///
/// Gets a instance that contains the attachments found in the tnef stream.
///
/// instance that contains the found in the tnef stream. The null reference is retuned when no attachments found.
/// Each attachment is a instance.
public System.Collections.ArrayList Attachments {
get { return this._attachments; }
}
///
/// Gets a the text body from the ms-tnef stream (BODY tnef attribute).
///
/// Text body from the ms-tnef stream (BODY tnef attribute). Or the null reference if the attribute is not part of the stream.
public System.String TextBody {
get { return this._body; }
}
///
/// Closes and releases the reading resources associated with this instance.
///
/// Be carefull before calling this method, as it also close the underlying .
public void Close () {
if ( this._reader!=null )
this._reader.Close();
this._reader = null;
}
///
/// Parses the ms-tnef stream from the provided .
///
/// true if parsing is successful. false otherwise.
/// The attachments found are saved in memory as instances.
public bool Parse () {
return this.Parse(null);
}
///
/// Parses the ms-tnef stream from the provided .
///
/// A specifying the path on which to save the attachments found. Specify the null reference to save them in memory as instances instead.
/// true if parsing is successful. false otherwise.
public bool Parse ( System.String path ) {
if ( this._reader==null || !this._reader.BaseStream.CanRead )
return false;
int sig = this.ReadInt();
if ( sig!=TnefSignature ) {
#if LOG
if ( logger.IsErrorEnabled )
logger.Error(System.String.Concat("Tnef signature not matched [", sig, "]<>[", TnefSignature, "]"));
#endif
return false;
}
bool error = false;
this._attachments = new System.Collections.ArrayList();
ushort key = this.ReadUInt16();
System.Text.Encoding enc = anmar.SharpMimeTools.SharpMimeHeader.EncodingDefault;
anmar.SharpMimeTools.SharpAttachment attachment_cur = null;
for ( System.Byte cur=this.ReadByte(); cur!=System.Byte.MinValue; cur=ReadByte() ) {
TnefLvlType lvl = (TnefLvlType)anmar.SharpMimeTools.SharpMimeTools.ParseEnum(typeof(TnefLvlType), cur, TnefLvlType.Unknown);
// Type
int type = this.ReadInt();
// Size
int size = this.ReadInt();
// Attibute name and type
TnefAttribute att_n = (TnefAttribute)anmar.SharpMimeTools.SharpMimeTools.ParseEnum(typeof(TnefAttribute), (ushort)((type<<16)>>16), TnefAttribute.Unknown);
TnefDataType att_t = (TnefDataType)anmar.SharpMimeTools.SharpMimeTools.ParseEnum(typeof(TnefDataType), (ushort)(type>>16), TnefDataType.Unknown);
if ( lvl==TnefLvlType.Unknown || att_n==TnefAttribute.Unknown || att_t==TnefDataType.Unknown ) {
#if LOG
if ( logger.IsErrorEnabled )
logger.Error(System.String.Concat("Attribute data is not valid [", lvl ,"] [type=", type, "->(", att_n, ",", att_t, ")] [size=", size, "]"));
#endif
error = true;
break;
}
// Read data
System.Byte[] buffer = this.ReadBytes(size);
// Read checkSum
ushort checksum = this.ReadUInt16();
// Verify checksum
if ( !this.VerifyChecksum(buffer, checksum) ) {
#if LOG
if ( logger.IsErrorEnabled )
logger.Error(System.String.Concat("Checksum validation failed [", lvl ,"] [type=", type, "->(", att_n, ",", att_t, ")] [size=", size, "(", (buffer!=null)?buffer.Length:0, ")] [checksum=", checksum, "]"));
#endif
error = true;
break;
}
size = buffer.Length;
#if LOG
if ( logger.IsDebugEnabled )
logger.Debug(System.String.Concat("[", lvl ,"] [type=", type, "->(", att_n, ",", att_t, ")] [size=", size, "(", (buffer!=null)?buffer.Length:0, ")] [checksum=", checksum, "]"));
#endif
if ( lvl==TnefLvlType.Message ) {
// Text body
if ( att_n==TnefAttribute.Body ) {
if ( att_t==TnefDataType.atpString ) {
this._body = enc.GetString(buffer, 0, size);
}
// Message mapi props (html body, rtf body, ...)
} else if ( att_n==TnefAttribute.MapiProps ) {
this.ReadMapi(buffer, size);
// Stream Codepage
} else if ( att_n==TnefAttribute.OEMCodepage ) {
uint codepage1 = (uint)(buffer[0] + (buffer[1]<<8) +(buffer[2]<<16) + (buffer[3]<<24));
if ( codepage1>0 ) {
try {
enc = System.Text.Encoding.GetEncoding((int)codepage1);
#if LOG
if ( logger.IsDebugEnabled ) {
logger.Debug(System.String.Concat("Now using [", enc.EncodingName, "] encoding to decode strings."));
}
#endif
} catch ( System.Exception ) {}
}
}
} else if ( lvl==TnefLvlType.Attachment ) {
// Attachment start
if ( att_n==TnefAttribute.AttachRendData ) {
System.String name = System.String.Concat("generated_", key, "_", (this._attachments.Count+1), ".binary" );
if ( path==null ) {
attachment_cur = new anmar.SharpMimeTools.SharpAttachment(new System.IO.MemoryStream());
} else {
attachment_cur = new anmar.SharpMimeTools.SharpAttachment(new System.IO.FileInfo(System.IO.Path.Combine(path, name)));
}
attachment_cur.Name = name;
// Attachment name
} else if ( att_n==TnefAttribute.AttachTitle ) {
if ( attachment_cur!=null && att_t==TnefDataType.atpString && buffer!=null && this._attachments.Count>0 ) {
// NULL terminated string
if ( buffer[size-1]=='\0' ) {
size--;
}
if ( size>0 ) {
System.String name = enc.GetString(buffer, 0, size);
if ( name.Length>0 ) {
attachment_cur.Name = name;
// Content already saved, so we have to rename
if ( attachment_cur.SavedFile!=null && attachment_cur.SavedFile.Exists ) {
try {
attachment_cur.SavedFile.MoveTo(System.IO.Path.Combine(path, attachment_cur.Name));
} catch ( System.Exception ) {}
}
}
}
}
// Modification and creation date
} else if ( att_n==TnefAttribute.AttachModifyDate || att_n==TnefAttribute.AttachCreateDate ) {
if ( attachment_cur!=null && att_t==TnefDataType.atpDate && buffer!=null && size==14 ) {
int pos = 0;
System.DateTime date = new System.DateTime((buffer[pos++]+(buffer[pos++]<<8)), (buffer[pos++]+(buffer[pos++]<<8)), (buffer[pos++]+(buffer[pos++]<<8)), (buffer[pos++]+(buffer[pos++]<<8)), (buffer[pos++]+(buffer[pos++]<<8)), (buffer[pos++]+(buffer[pos++]<<8)));
if ( att_n==TnefAttribute.AttachModifyDate ) {
attachment_cur.LastWriteTime = date;
} else if ( att_n==TnefAttribute.AttachCreateDate ) {
attachment_cur.CreationTime = date;
}
}
// Attachment data
} else if ( att_n==TnefAttribute.AttachData ) {
if ( attachment_cur!=null && att_t==TnefDataType.atpByte && buffer!=null ) {
if ( attachment_cur.SavedFile!=null ) {
System.IO.FileStream stream = null;
try {
stream = attachment_cur.SavedFile.OpenWrite();
} catch ( System.Exception) {
#if LOG
if ( logger.IsErrorEnabled )
logger.Error(System.String.Concat("Error writting file [", attachment_cur.SavedFile.FullName, "]"), e);
#endif
error = true;
break;
}
stream.Write(buffer, 0, size);
stream.Flush();
attachment_cur.Size = stream.Length;
stream.Close();
stream = null;
attachment_cur.SavedFile.Refresh();
// Is name has changed, we have to rename
if ( attachment_cur.SavedFile.Name!=attachment_cur.Name )
try {
attachment_cur.SavedFile.MoveTo(System.IO.Path.Combine(path, attachment_cur.Name));
} catch ( System.Exception ) {}
} else {
attachment_cur.Stream.Write(buffer, 0, size);
attachment_cur.Stream.Flush();
attachment_cur.Size = attachment_cur.Stream.Length;
}
this._attachments.Add(attachment_cur);
}
// Attachment mapi props
} else if ( att_n==TnefAttribute.Attachment ) {
this.ReadMapi(buffer, size);
}
}
}
if ( this._attachments.Count==0 )
this._attachments = null;
return !error;
}
private System.Byte ReadByte () {
System.Byte cur;
try {
cur = this._reader.ReadByte();
} catch ( System.Exception ) {
cur = System.Byte.MinValue;
}
return cur;
}
private System.Byte[] ReadBytes ( int length ) {
if ( length<=0 )
return null;
System.Byte[] buffer = null;
try {
buffer = this._reader.ReadBytes(length);
} catch ( System.Exception ) {}
return buffer;
}
private int ReadBytesTo ( int length, System.IO.Stream stream ) {
if ( length<=0 || stream==null || !stream.CanWrite )
return -1;
int written=0;
ushort checksum_calc = 0;
while ( length>0 ) {
System.Byte[] buffer = this.ReadBytes((length>4096)?4096:length);
if ( buffer!=null ) {
stream.Write(buffer, 0, buffer.Length);
written+=buffer.Length;
length-=buffer.Length;
// Checksum for this data portion
for ( int i=0, j=buffer.Length; i