////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// File: MAPIObject.cpp
// Description: Base class for code common to MAPI Items (messages, contacts etc)
//
// Copyright (C) 2005-2011, Noel Dillabough
//
// This source code is free to use and modify provided this notice remains intact and that any enhancements
// or bug fixes are posted to the CodeProject page hosting this class for the community to benefit.
//
// Usage: see the CodeProject article at http://www.codeproject.com/internet/CMapiEx.asp
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Ported to U++ Framework by Koldo. See License.txt file

#include <MapiUtil.h>
#include "MAPIEx.h"

/////////////////////////////////////////////////////////////
// MAPIObject

MAPIObject::MAPIObject() {
	m_pMAPI = NULL;
	m_pItem = NULL;
	m_entryID.cb = 0;
	SetEntryID(NULL);
}

MAPIObject::~MAPIObject() {
}

String MAPIObject::GetHexString(SBinary bin) {
	String strHex;
	if(bin.cb) {
		for(ULONG i = 0; i < bin.cb; i++) 
			strHex += Format("%02X", bin.lpb[i]);
		return strHex;
	}
	return String();
}

String MAPIObject::GetEntryIDString() {
	return GetHexString(m_entryID);
}

void MAPIObject::SetEntryID(SBinary* pEntryID) {
	if(m_entryID.cb) 
		delete [] m_entryID.lpb;
	m_entryID.lpb=NULL;

	if(pEntryID) {
		m_entryID.cb = pEntryID->cb;
		if(m_entryID.cb) {
			m_entryID.lpb = new BYTE[m_entryID.cb];
			memcpy(m_entryID.lpb, pEntryID->lpb, m_entryID.cb);
		}
	} else 
		m_entryID.cb = 0;
}

bool MAPIObject::Open(MAPIEx* pMAPI, SBinary entryID) {
	Close();
	m_pMAPI = pMAPI;
	ULONG ulObjType;
	if(S_OK != m_pMAPI->GetSession()->OpenEntry(entryID.cb, (LPENTRYID)entryID.lpb, NULL, 
										MAPI_BEST_ACCESS, &ulObjType, (LPUNKNOWN*)&m_pItem)) 
		return false;
	SetEntryID(&entryID);
	return true;
}

void MAPIObject::Close() {
	SetEntryID(NULL);
	RELEASE(m_pItem);
	m_pMAPI = NULL;
}

bool MAPIObject::Save(bool bClose) {
	ULONG ulFlags = bClose ? 0 : KEEP_OPEN_READWRITE;
	if(m_pItem && m_pItem->SaveChanges(ulFlags) == S_OK) {
		if(bClose) 
			Close();
		return true;
	}
	return false;
}

int MAPIObject::GetMessageFlags() {
	return GetPropertyValue(PR_MESSAGE_FLAGS, 0);
}

bool MAPIObject::SetMessageFlags(int nFlags) {
	SPropValue prop;
	prop.ulPropTag = PR_MESSAGE_FLAGS;
	prop.Value.l = nFlags;
	return (m_pItem && m_pItem->SetProps(1, &prop, NULL) == S_OK);
}

int MAPIObject::GetSensitivity() {
	return GetPropertyValue(PR_SENSITIVITY, -1);
}

bool MAPIObject::SetSensitivity(int nSensitivity) {
	SPropValue prop;
	prop.ulPropTag = PR_SENSITIVITY;
	prop.Value.l = nSensitivity;
	return (Message()->SetProps(1, &prop, NULL) == S_OK);
}

int MAPIObject::GetImportance() {
	return GetPropertyValue(PR_IMPORTANCE, IMPORTANCE_NORMAL);
}

bool MAPIObject::SetImportance(int nImportance) {
	SPropValue prop;
	prop.ulPropTag = PR_IMPORTANCE;
	prop.Value.l = nImportance;
	return (Message()->SetProps(1, &prop, NULL) == S_OK);
}

String MAPIObject::GetMessageClass() {
	return GetPropertyString(PR_MESSAGE_CLASS);
}

bool MAPIObject::SetMessageClass(String szMessageClass) {
	SPropValue prop;
	prop.ulPropTag = PR_MESSAGE_CLASS;
	prop.Value.LPSZ = (LPSTR)szMessageClass.Begin();
	return (Message()->SetProps(1, &prop, NULL) == S_OK);
}

int MAPIObject::GetMessageEditorFormat() {
	return GetPropertyValue(PR_MSG_EDITOR_FORMAT, EDITOR_FORMAT_DONTKNOW);
}

bool MAPIObject::SetMessageEditorFormat(int nFormat) {
	SPropValue prop;
	prop.ulPropTag = PR_MSG_EDITOR_FORMAT;
	prop.Value.l = nFormat;
	return (m_pItem && m_pItem->SetProps(1, &prop, NULL) == S_OK);
}

bool MAPIObject::Create(MAPIEx &mapi, MAPIFolder &folder) {
	if(!folder.IsOpened()) 
		return false;
	Close();
	m_pMAPI = &mapi;
	if(folder.Folder()->CreateMessage(NULL, 0, (LPMESSAGE*)&m_pItem) == S_OK) {
		LPSPropValue pProp;
		if(GetProperty(PR_ENTRYID, pProp) == S_OK) {
			SetEntryID(&pProp->Value.bin);
			MAPIFreeBuffer(pProp);
		} 
		return true;
	}
	return false;
}

HRESULT MAPIObject::GetProperty(ULONG ulProperty, LPSPropValue& pProp) {
	if(!m_pItem) 
		return E_INVALIDARG;
	ULONG ulPropCount;
	ULONG p[2] = {1, ulProperty};
	return m_pItem->GetProps((LPSPropTagArray)p, MAPIEx::cm_nMAPICode, &ulPropCount, &pProp);
}

String MAPIObject::GetPropertyString(ULONG ulProperty, bool bStream) {
	String strProperty; 
	if(bStream) {
		IStream* pStream;
		if(Message()->OpenProperty(ulProperty, &IID_IStream,STGM_READ, 0, (LPUNKNOWN*)&pStream) 
																					== S_OK) {
			const int BUF_SIZE = 16384;
			TCHAR szBuf[BUF_SIZE+1];
			ULONG ulNumChars;

			do {
				pStream->Read(szBuf, BUF_SIZE*sizeof(TCHAR), &ulNumChars);
				ulNumChars /= sizeof(TCHAR);
				szBuf[min<ULONG>(BUF_SIZE,ulNumChars)] = 0;
				strProperty += szBuf;
			} while(ulNumChars >= BUF_SIZE);

			RELEASE(pStream);
			return strProperty;
		}
	} else {
		if (ulProperty == PR_ENTRYID)
			return GetEntryIDString();
		LPSPropValue pProp;
		if(GetProperty(ulProperty, pProp) == S_OK) {
			strProperty = MAPIEx::GetValidString(*pProp);
			MAPIFreeBuffer(pProp);
			return strProperty;
		} 
	}
	return String();
}

int MAPIObject::GetPropertyValue(ULONG ulProperty, int nDefaultValue) {
	LPSPropValue pProp;
	if(GetProperty(ulProperty, pProp) == S_OK) {
		nDefaultValue = pProp->Value.l;
		MAPIFreeBuffer(pProp);
	}
	return nDefaultValue;
}

const GUID GUIDPublicStrings={0x00020329, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };

bool MAPIObject::GetPropTagArray(LPCTSTR szFieldName, LPSPropTagArray& lppPropTags, int& nFieldType, bool bCreate) {
	if(!m_pItem) 
		return false;

	MAPINAMEID nameID;
	nameID.lpguid = (GUID*)&GUIDPublicStrings;
	nameID.ulKind = MNID_STRING;

	nFieldType = PT_STRING8;
	WCHAR wszFieldName[256];
	MultiByteToWideChar(CP_ACP, 0, szFieldName,-1,wszFieldName,255);
	nameID.Kind.lpwstrName = wszFieldName;

	LPMAPINAMEID lpNameID[1] = {&nameID};

	HRESULT hr = m_pItem->GetIDsFromNames(1, lpNameID, bCreate ? MAPI_CREATE : 0, &lppPropTags);
	return (hr == S_OK);
}

bool MAPIObject::GetNamedProperty(LPCTSTR szFieldName, LPSPropValue &pProp) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetPropTagArray(szFieldName, lppPropTags, nFieldType, false)) 
		return false;

	ULONG ulPropCount;
	HRESULT hr = m_pItem->GetProps(lppPropTags, MAPIEx::cm_nMAPICode, &ulPropCount, &pProp);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

bool MAPIObject::GetNamedProperty(LPCTSTR szFieldName, String& strField) {
	LPSPropValue pProp;
	if(GetNamedProperty(szFieldName, pProp)) {
		strField = MAPIEx::GetValidString(*pProp);
		MAPIFreeBuffer(pProp);
		return true;
	}
	return false;
}

bool MAPIObject::GetOutlookPropTagArray(ULONG ulData, ULONG ulProperty, 
								LPSPropTagArray& lppPropTags, int& nFieldType, bool bCreate) {
	if(!m_pItem) 
		return false;

	const GUID guidOutlookEmail1={ulData, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };

	MAPINAMEID nameID;
	nameID.lpguid = (GUID*)&guidOutlookEmail1;
	nameID.ulKind = MNID_ID;
	nameID.Kind.lID = ulProperty;

	nFieldType = PT_STRING8;

	LPMAPINAMEID lpNameID[1] = {&nameID};

	HRESULT hr=m_pItem->GetIDsFromNames(1, lpNameID, bCreate ? MAPI_CREATE : 0, &lppPropTags);
	return (hr==S_OK);
}

// gets a custom outlook property (ie EmailAddress1 of a contact)
bool MAPIObject::GetOutlookProperty(ULONG ulData, ULONG ulProperty, LPSPropValue& pProp) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetOutlookPropTagArray(ulData,ulProperty, lppPropTags, nFieldType, false)) 
		return false;

	ULONG ulPropCount;
	HRESULT hr = m_pItem->GetProps(lppPropTags, MAPIEx::cm_nMAPICode, &ulPropCount, &pProp);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

// gets a custom outlook property value 
bool MAPIObject::GetOutlookPropertyValue(ULONG ulData, ULONG ulProperty, int& nValue) {
	LPSPropValue pProp;
	if(GetOutlookProperty(ulData, ulProperty, pProp)) {
		nValue = pProp->Value.i;
		MAPIFreeBuffer(pProp);
		return true;
	} 
	return false;
}

// gets a custom outlook property string (ie EmailAddress1 of a contact)
bool MAPIObject::GetOutlookPropertyString(ULONG ulData, ULONG ulProperty, String& strProperty) {
	LPSPropValue pProp;
	if(GetOutlookProperty(ulData, ulProperty, pProp)) {
		strProperty = MAPIEx::GetValidString(*pProp);
		MAPIFreeBuffer(pProp);
		return true;
	} 
	return false;
}

bool MAPIObject::SetPropertyString(ULONG ulProperty, String szProperty, bool bStream) {
	if(m_pItem && !IsNull(szProperty)) {
		if(bStream) {
			LPSTREAM pStream = NULL;
			if(m_pItem->OpenProperty(ulProperty, &IID_IStream, 0, MAPI_MODIFY | MAPI_CREATE, 
																(LPUNKNOWN*)&pStream)==S_OK) {
				pStream->Write(szProperty, (szProperty.ToWString().GetCount()+1)*sizeof(wchar), 
																						NULL);
				RELEASE(pStream);
				return true;
			}
		} else {
			SPropValue prop;
			prop.ulPropTag = ulProperty;
			prop.Value.LPSZ = (LPTSTR)szProperty.Begin();
			return (m_pItem->SetProps(1, &prop, NULL) == S_OK);
		}
	}
	return false;
}

bool MAPIObject::SetPropertyValue(ULONG ulProperty, int nValue) {
	if(!m_pItem) 
		return false;
	SPropValue prop;
	prop.ulPropTag = ulProperty;
	prop.Value.l = nValue;
	return (m_pItem->SetProps(1, &prop, NULL) == S_OK);
}

// if bCreate is true, the property will be created if necessary otherwise if not present will return false
bool MAPIObject::SetNamedProperty(LPCTSTR szFieldName, LPCTSTR szField, bool bCreate) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetPropTagArray(szFieldName, lppPropTags, nFieldType, bCreate)) 
		return false;

	SPropValue prop;
	prop.ulPropTag = (lppPropTags->aulPropTag[0]|nFieldType);
	prop.Value.LPSZ = (LPTSTR)szField;
	HRESULT hr = m_pItem->SetProps(1, &prop, NULL);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

bool MAPIObject::SetNamedMVProperty(LPCTSTR szFieldName, LPCTSTR* arCategories, int nCount, 
														LPSPropValue &pProp, bool bCreate) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetPropTagArray(szFieldName, lppPropTags, nFieldType, bCreate)) 
		return false;

	HRESULT hr = MAPIAllocateBuffer(sizeof(SPropValue), (LPVOID*)&pProp);
	if(hr == S_OK) {
		pProp->ulPropTag = (lppPropTags->aulPropTag[0]|nFieldType|MV_FLAG);
		pProp->Value.MVi.cValues=nCount;
		if(PROP_TYPE(pProp->ulPropTag)==PT_MV_STRING8) 
			hr = MAPIAllocateMore(sizeof(LPSTR)*nCount, pProp, (LPVOID*)&pProp->Value.MVszA.lppszA);
		else 
			// PT_MV_UNICODE
			hr = MAPIAllocateMore(sizeof(LPSTR)*nCount, pProp, (LPVOID*)&pProp->Value.MVszW.lppszW);
		if(hr == S_OK) {
			for(int i = 0; i < nCount; i++) 
				pProp->Value.MVSZ.LPPSZ[i] = (LPTSTR)arCategories[i];
			hr=m_pItem->SetProps(1, pProp, NULL);
		}
		if(hr != S_OK) 
			MAPIFreeBuffer(pProp);
	}
	return (hr == S_OK);
}

bool MAPIObject::SetOutlookProperty(ULONG ulData, ULONG ulProperty, LPCTSTR szField) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetOutlookPropTagArray(ulData,ulProperty, lppPropTags, nFieldType, false)) 
		return false;

	SPropValue prop;
	prop.ulPropTag = (lppPropTags->aulPropTag[0]|nFieldType);
	prop.Value.LPSZ = (LPTSTR)szField;
	HRESULT hr=m_pItem->SetProps(1, &prop, NULL);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

bool MAPIObject::SetOutlookProperty(ULONG ulData, ULONG ulProperty, int nField, int nFieldType) {
	LPSPropTagArray lppPropTags;
	int nIgnored;
	if(!GetOutlookPropTagArray(ulData,ulProperty, lppPropTags, nIgnored, false)) 
		return false;

	SPropValue prop;
	prop.ulPropTag = (lppPropTags->aulPropTag[0]|nFieldType);
	prop.Value.l = nField;
	HRESULT hr = m_pItem->SetProps(1, &prop, NULL);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

bool MAPIObject::SetOutlookProperty(ULONG ulData, ULONG ulProperty, FILETIME ftField) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetOutlookPropTagArray(ulData,ulProperty, lppPropTags, nFieldType, false)) 
		return false;
	nFieldType = PT_SYSTIME;

	SPropValue prop;
	prop.ulPropTag = (lppPropTags->aulPropTag[0]|nFieldType);
	prop.Value.ft = ftField;
	HRESULT hr = m_pItem->SetProps(1, &prop, NULL);
	MAPIFreeBuffer(lppPropTags);
	return (hr==S_OK);
}

bool MAPIObject::DeleteNamedProperty(LPCTSTR szFieldName) {
	LPSPropTagArray lppPropTags;
	int nFieldType;
	if(!GetPropTagArray(szFieldName, lppPropTags, nFieldType, false)) 
		return false;

	HRESULT hr = m_pItem->DeleteProps(lppPropTags, NULL);
	MAPIFreeBuffer(lppPropTags);
	return (hr == S_OK);
}

int MAPIObject::GetAttachmentCount() {
	ULONG ulCount = 0;
	LPSPropValue pProp;
	if(GetProperty(PR_HASATTACH, pProp) == S_OK) {
		if(pProp->Value.b) {
			LPMAPITABLE pAttachTable=NULL;
			if(Message()->GetAttachmentTable(0, &pAttachTable) == S_OK) {
#ifndef _WIN32_WCE
				if(pAttachTable->GetRowCount(0, &ulCount) != S_OK) 
					ulCount = 0;
#else
				enum {PROP_ATTACH_CONTENT_ID, ATTACH_COLS};
				static SizedSPropTagArray(ATTACH_COLS, Columns) = {ATTACH_COLS, PR_ATTACH_CONTENT_ID};
				if(pAttachTable->SetColumns((LPSPropTagArray)&Columns, 0) == S_OK) {
					ULONG cRows;
					do {
						LPSRowSet pRows = NULL;
						if(pAttachTable->QueryRows(1, 0, &pRows)!=S_OK) {
							MAPIFreeBuffer(pRows);
							break;
						}
						cRows = pRows->cRows;
						ulCount += cRows;
						FreeProws(pRows);
					} while(cRows);
				}
#endif
				RELEASE(pAttachTable);
			}
		}
		MAPIFreeBuffer(pProp);
	}
	return ulCount;
}

bool MAPIObject::GetAttachment(MAPIAttachment& attachment, int nIndex) {
	return attachment.Open(Message(), nIndex);
}

bool MAPIObject::AddAttachment(MAPIAttachment& attachment) {
	return attachment.Create(Message());
}

// use nIndex of -1 to save all attachments to szFolder
bool MAPIObject::SaveAttachment(LPCTSTR szFolder, int nIndex, LPCTSTR szFileName) {
	LPMAPITABLE pAttachTable = NULL;
	if(Message()->GetAttachmentTable(0, &pAttachTable) != S_OK) 
		return false;

	String strPath;
	bool bResult = false;
	enum {PROP_ATTACH_NUM, PROP_ATTACH_LONG_FILENAME, PROP_ATTACH_FILENAME, ATTACH_COLS};
	static SizedSPropTagArray(ATTACH_COLS, Columns) =
					{ATTACH_COLS, PR_ATTACH_NUM, PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME};
	if(pAttachTable->SetColumns((LPSPropTagArray)&Columns, 0) == S_OK) {
		LPSRowSet pRows = NULL;
		MAPIAttachment attachment;
		while(true) {
			strPath = "";
			if(nIndex>=0 && pAttachTable->SeekRow(BOOKMARK_BEGINNING, nIndex, NULL) != S_OK) 
				break; 
			if(pAttachTable->QueryRows(1, 0, &pRows) == S_OK) {
				if(!pRows->cRows) 
					break;
				LPATTACH pAttachment;
				if(Message()->OpenAttach(pRows->aRow[0].lpProps[PROP_ATTACH_NUM].Value.bin.cb, 
															NULL, 0, &pAttachment) == S_OK) {
					if (szFileName != NULL)
						strPath += Format("%s\\%s", szFolder, szFileName);
					else {
						if(!IsNull(MAPIEx::GetValidString(pRows->aRow[0].lpProps[PROP_ATTACH_LONG_FILENAME]))) 
							strPath += Format("%s\\%s", szFolder, pRows->aRow[0].lpProps[PROP_ATTACH_LONG_FILENAME].Value.LPSZ);
						else if(!IsNull(MAPIEx::GetValidString(pRows->aRow[0].lpProps[PROP_ATTACH_FILENAME]))) 
							strPath += Format("%s\\%s", szFolder, pRows->aRow[0].lpProps[PROP_ATTACH_FILENAME].Value.LPSZ);
						else 
							strPath += Format("%s\\Attachment.dat", szFolder);
					}
					strPath = FromSystemCharset(strPath);
					attachment.Attach(pAttachment);
					//attachment.SaveAttachment(strPath);
					bResult = attachment.SaveAttachment(strPath);
				}
			}
			FreeProws(pRows);
			if(nIndex >= 0) 
				break;
		}
	}
	RELEASE(pAttachTable);
	return bResult;
}

// use nIndex of -1 to delete all attachments
bool MAPIObject::DeleteAttachment(int nIndex)
{
	LPMAPITABLE pAttachTable = NULL;
	if(Message()->GetAttachmentTable(0, &pAttachTable) != S_OK) 
		return false;

	bool bResult=false;
	enum {PROP_ATTACH_NUM, ATTACH_COLS};
	static SizedSPropTagArray(ATTACH_COLS, Columns) = {ATTACH_COLS, PR_ATTACH_NUM };
	if(pAttachTable->SetColumns((LPSPropTagArray)&Columns, 0) == S_OK) {
		int i = 0;
		LPSRowSet pRows = NULL;
		while(true) {
			if(pAttachTable->QueryRows(1, 0, &pRows) == S_OK) {
				if(!pRows->cRows) 
					break;
				else if(i<nIndex) {
					i++;
					continue;
				} else
					bResult = (Message()->DeleteAttach(pRows->aRow[0].lpProps[PROP_ATTACH_NUM].Value.ul, 
																		0, NULL, 0) == S_OK);
				FreeProws(pRows);
				if(!bResult || nIndex == i) 
					break;
			}
			break;
		}
	}
	RELEASE(pAttachTable);
	return bResult;
}

bool MAPIObject::AddAttachment(const String &szPath, String szName, String szCID) {
	if (!FileExists(szPath))
		return false;

	String szFileName = szName;
	if(IsNull(szFileName)) {
		szFileName = szPath;
		for(int i = szPath.GetCount() - 1; i >= 0; i--) 
			if(szPath[i] == '\\' || szPath[i] == '/') {
				szFileName = szPath.Mid(i+1);
				break;
			}
	}

	MAPIAttachment attachment;
	if(attachment.Create(Message())) {
		if(IsNull(szCID) || szCID != CONTACT_PICTURE) {
			attachment.SetFileName(szFileName);
			attachment.SetLongFileName(szFileName);
			if(!IsNull(szCID)) 
				attachment.SetCID(szCID);
		} else {
			attachment.SetFileName(szCID);
			attachment.SetLongFileName(szCID);
			attachment.SetPropertyValue(PR_ATTACHMENT_UNKNOWN, true);
		}
		if(attachment.LoadAttachment(szPath)) {
			attachment.Save();
			return true;
		}
	}
	return false;
}

// Gets the body of the item, if bAutoDetect is set, uses the MessageEditorFormat to try to determine which property to return 
String MAPIObject::GetBody(bool bAutoDetect) {
	String strBody;
	if(bAutoDetect) {
		int nFormat = GetMessageEditorFormat();
		if(nFormat == EDITOR_FORMAT_DONTKNOW || nFormat == EDITOR_FORMAT_RTF) 
			return GetRTF();
		else if(nFormat == EDITOR_FORMAT_HTML) 
			return GetHTML();
	}
	return GetPropertyString(PR_BODY, true);
}

bool MAPIObject::SetBody(const String& strBody) {
	if(SetPropertyString(PR_BODY, strBody, true)) {
		SetMessageEditorFormat(EDITOR_FORMAT_PLAINTEXT);
		return true;
	}
	return false;
}

String MAPIObject::GetHTML() {
	return GetPropertyString(PR_BODY_HTML, true);
}

bool MAPIObject::SetHTML(const String &strHTML) {
	// does this Message Store support HTML directly?
	ULONG ulSupport = m_pMAPI ? m_pMAPI->GetMessageStoreSupport() : 0;
	if(ulSupport&STORE_HTML_OK) {
		if(SetPropertyString(PR_BODY_HTML, strHTML, true)) {
			SetMessageEditorFormat(EDITOR_FORMAT_HTML);
			return true;
		}
	} else {
		// otherwise lets encode it into RTF 
		const char *szCodePage = "1252"; // default codepage is ANSI - Latin I
		GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, (CHAR*)szCodePage, strlen(szCodePage));

		String strRTF;
		strRTF = Format("{\\rtf1\\ansi\\ansicpg%s\\fromhtml1 {\\*\\htmltag1 ", szCodePage);
		strRTF += strHTML;
		strRTF += " }}";

		return SetRTF(strRTF);
	}
	return false;
}

String MAPIObject::GetRTF() {
	String strRTF;
	IStream* pStream;

	const int BUF_SIZE = 16384;
	char szBuf[BUF_SIZE+1];
	ULONG ulNumChars;

#ifdef _WIN32_WCE
	int nMessageStatus = GetMessageStatus();
	if(nMessageStatus & MSGSTATUS_PARTIAL) 
		return Null;

	if(nMessageStatus & MSGSTATUS_HAS_PR_BODY_HTML) {
		if(Message()->OpenProperty(PR_BODY_HTML_A, &IID_IStream, STGM_READ, NULL, (LPUNKNOWN*)&pStream)!=S_OK) 
			return Null;

		do {
			pStream->Read(szBuf, BUF_SIZE, &ulNumChars);
			szBuf[min(BUF_SIZE,ulNumChars)] = 0;
			strRTF += szBuf;
		} while(ulNumChars >= BUF_SIZE);

		RELEASE(pStream);
	} else if(nMessageStatus & MSGSTATUS_HAS_PR_BODY) {
		if(Message()->OpenProperty(PR_BODY_A, &IID_IStream, STGM_READ, NULL, (LPUNKNOWN*)&pStream)!=S_OK) 
			return Null;
		do {
			pStream->Read(szBuf, BUF_SIZE, &ulNumChars);
			szBuf[min(BUF_SIZE,ulNumChars)] = 0;
			strRTF += szBuf;
		} while(ulNumChars >= BUF_SIZE);

		RELEASE(pStream);
	}
#else
	if(Message()->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream,STGM_READ, 0, (LPUNKNOWN*)&pStream)!=S_OK) 
		return Null;

	IStream *pUncompressed;
	if(WrapCompressedRTFStream(pStream, 0, &pUncompressed) == S_OK) {
		do {
			pUncompressed->Read(szBuf, BUF_SIZE, &ulNumChars);
			szBuf[min<ULONG>(BUF_SIZE,ulNumChars)]=0;
			strRTF += szBuf;
		} while(ulNumChars >= BUF_SIZE);
		RELEASE(pUncompressed);
	}
	RELEASE(pStream);
#endif

	// does this RTF contain encoded HTML? If so decode it
	// code taken from Lucian Wischik's example at http://www.wischik.com/lu/programmer/mapi_utils.html
	if(strRTF.Find("\\fromhtml") != -1) 
		return GetHTML();
	else if(strRTF.Find("\\fromtext") != -1) 
		return GetBody(false);

	return strRTF;
}

bool MAPIObject::SetRTF(const String &strRTF) {
	LPSTREAM pStream = NULL;
	if(Message()->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, STGM_CREATE | STGM_WRITE, 
									MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*)&pStream) == S_OK) {
#ifndef _WIN32_WCE
		IStream *pUncompressed;
		if(WrapCompressedRTFStream(pStream,MAPI_MODIFY, &pUncompressed) == S_OK) {
			pUncompressed->Write(strRTF.ToWString(), strRTF.ToWString().GetCount()*sizeof(wchar), NULL);
			if(pUncompressed->Commit(STGC_DEFAULT) == S_OK) 
				pStream->Commit(STGC_DEFAULT);
			RELEASE(pUncompressed);
		}
#endif
		RELEASE(pStream);
		SetMessageEditorFormat(EDITOR_FORMAT_RTF);
		return true;
	}
	return false;
}

Time MAPIObject::GetLastModified(SYSTEMTIME &tmLastModified) {
	LPSPropValue pProp;
	if(GetProperty(PR_LAST_MODIFICATION_TIME, pProp) == S_OK) {
		SYSTEMTIME tm;
		FileTimeToSystemTime(&pProp->Value.ft, &tm);
		SystemTimeToTzSpecificLocalTime(NULL, &tm, &tmLastModified);

		MAPIFreeBuffer(pProp);
		return MAPIEx::GetSystemTime(tm);
	}
	return Null;
}

bool MAPIObject::SetLastModified(const Time &tm) {
	SPropValue prop;
	prop.ulPropTag = PR_LAST_MODIFICATION_TIME;
	SYSTEMTIME st;
	MAPIEx::SetSystemTime(st, tm);
	SystemTimeToFileTime(&st, &prop.Value.ft);
	return (m_pItem && m_pItem->SetProps(1, &prop, NULL) == S_OK);
}