
Table of contents
The application has the following features:
- List local folders.
- Create / rename / delete local folder.
- List messages of a local folder.
- Get message properties.
- Get message source.
- Create / copy / move / delete messages.
- Mark messages as Read or Unread.
- List body properties or headers.
- Get / set property values.
- Navigate body structure.
- Get / set body content.
- Insert bodies.
- List / add / remove attachments.
This code was written to provide an initial example of the IStoreFolder
/ IStoreNamespace
classes. Then, an example of IMimeMessage
/ IMimeMessageTree
/ IMimeBody
/ IMimePropertySet
was added to the application. The idea of this article is to document, with a complete example, all these interfaces to show how Outlook Express storage could be accessed.
In the initial dialog, all the local folders of the main identity are listed to let the user modify them. In the message dialog, you will see all the messages of the selected folder. Identifying the message source and other operations can be done here. In the 'Bodies' dialog, you will be able to view the message structure and modify it.
// add all the folders to the list box recursively
void CDemoDlg::AddFolders(STOREFOLDERID dwFolderId)
{
FOLDERPROPS props;
HENUMSTORE hEnum;
int nIndex;
hEnum = NULL;
// set the size of the structure
// or the function return error
props.cbSize = sizeof(FOLDERPROPS);
HRESULT hr =
m_pStoreNamespace->GetFirstSubFolder(dwFolderId,
&props, &hEnum);
while(SUCCEEDED(hr) && hr !=
S_FALSE && hEnum != NULL) {
nIndex = m_listFolder.AddString(props.szName);
if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {
// set the folder id as the data of the item
m_listFolder.SetItemData(nIndex, props.dwFolderId);
// add children of this folder too
AddFolders(props.dwFolderId);
}
hr = m_pStoreNamespace->GetNextSubFolder(hEnum, &props);
}
// close the enum
if(hEnum) {
m_pStoreNamespace->GetSubFolderClose(hEnum);
}
}
//List messages of folder and add
//all 'Subject' and 'From' to the list box
MESSAGEPROPS msgProps;
HENUMSTORE hEnumMsg;
CString item;
int nIndex;
hEnumMsg = NULL;
// set the size of the structure
// or the function return error
msgProps.cbSize = sizeof(MESSAGEPROPS);
// as we want the subject and other
// staff we get all the properties.
// you can use MSGPROPS_FAST as first parameter
// to get only a few properties of the message.
HRESULT hr = m_pStoreFolder->GetFirstMessage(0,
0,
MESSAGEID_FIRST,
&msgProps,
&hEnumMsg);
while(SUCCEEDED(hr) && hr != S_FALSE) {
item = msgProps.pszDisplayFrom;
item += _T(" ");
item += msgProps.pszNormalSubject;
// message subject and from is displayed in the list box.
// data of each item is the message id.
nIndex = m_listMsg.AddString(item);
if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {
m_listMsg.SetItemData(nIndex, msgProps.dwMessageId);
}
// free the message properties
// as they are allocated by IStoreFolder.
m_pStoreFolder->FreeMessageProps(&msgProps);
hr = m_pStoreFolder->GetNextMessage(hEnumMsg,
0, &msgProps);
}
// close the enum
if(hEnumMsg) {
m_pStoreFolder->GetMessageClose(hEnumMsg);
}
// this function displays the source
// of the selected message in the list box
void CMsgDlg::OnView()
{
ULONG ulReaded = 0;
int nIndex;
STOREFOLDERID dwSelMsg;
HRESULT hr;
IStream *pTextStream;
char buffer[4096];
// Get selected folder id
nIndex = m_listMsg.GetCurSel();
if(nIndex == LB_ERR) {
MessageBox(_T("Select a message first."),
_T("Demo Error"));
return;
}
dwSelMsg = m_listMsg.GetItemData(nIndex);
// create a IStream from the message
hr = m_pStoreFolder->OpenMessage(dwSelMsg,
IID_IStream, (VOID **) &pTextStream);
if(FAILED(hr)) {
MessageBox(_T("Error opening message."),
_T("Demo Error"));
return;
}
CMsgSrcDlg msgSrcDlg;
// read all the message
do {
hr = pTextStream->Read(buffer,
sizeof(buffer)-1, &ulReaded);
if(FAILED(hr)) {
MessageBox(_T("Error reading message."),
_T("Demo Error"));
}
else {
buffer[ulReaded] = 0;
msgSrcDlg.AddMessageSource(buffer);
}
} while(SUCCEEDED(hr) && ulReaded != 0);
if(SUCCEEDED(hr)) {
// display message
msgSrcDlg.DoModal();
}
pTextStream->Release();
}
IStream *newMail = NULL;
MESSAGEID msgId;
HRESULT hr;
ULONG len;
CString msgSource;
// Set msgSource to contain
// the source of the new message
...
// Create the IStream to write the new message
// this function returns the id of the new message
hr = m_pFolder->CreateStream(0, 0, &newMail, &msgId);
if(FAILED(hr)) {
MessageBox(_T("Cannot Create Stream."),
_T("Demo Error"));
return;
}
// write message source in the IStream
hr = newMail->Write((const char *) msgSource,
msgSource.GetLength(), &len);
if(FAILED(hr)) {
MessageBox(_T("Cannot Write message."),
_T("Demo Error"));
newMail->Release();
return;
}
// Commit the IStream in the folder
// and use the returned msgId
hr = m_pFolder->CommitStream(0, 0, 0,
newMail, msgId, NULL);
if(FAILED(hr)) {
MessageBox(_T("Cannot Commit Stream."),
_T("Demo Error"));
newMail->Release();
return;
}
// release the IStream
newMail->Release();
// add property names to the combo box
// first get IMimeMessage interface
// using IStoreFolder and the message id.
hr = pFolder->OpenMessage(msgId,
IID_IMimeMessage,
(LPVOID*) &m_pMimeMsg);
if(FAILED(hr)) {
OutputDebugString("CMessageTreeDlg::"
"SetMessage: OpenMessage.\n");
return;
}
// get root body of the message.
hr = m_pMimeMsg->GetBody(IBL_ROOT, 0, &m_hCurBody);
if(FAILED(hr)) {
OutputDebugString("OEMessage::SetMessage:"
" Cannot get root body.\n");
return;
}
...
// bind the body to the IMimePropertySet interface.
hr = m_pMimeMsg->BindToObject(m_hCurBody,
IID_IMimePropertySet, (LPVOID *) &m_pPropertySet);
if(FAILED(hr)) {
OutputDebugString("OEMessage::UpdateBodyInfo:"
" BindToObject IID_IMimePropertySet.\n");
return;
}
...
IMimeEnumProperties *pEnum = NULL;
ENUMPROPERTY eProp = {0};
ULONG cFetched;
HRESULT hr;
m_propNames.ResetContent();
// enum properties of the body.
hr = m_pPropertySet->EnumProps(0, &pEnum);
if(FAILED(hr)) {
OutputDebugString("OEMessage::"
"FillCombo: EnumProps.\n");
return;
}
hr = pEnum->Next(1, &eProp, &cFetched);
while(SUCCEEDED(hr) && hr != S_FALSE) {
m_propNames.AddString(eProp.pszName);
hr = m_pAllocator->FreeEnumPropertyArray(1,
&eProp, FALSE);
if(FAILED(hr)) {
OutputDebugString("OEMessage::FillCombo:"
" FreeEnumPropertyArray.\n");
}
hr = pEnum->Next(1, &eProp, &cFetched);
}
if(pEnum) {
pEnum->Release();
}
ULONG attachCount, i, j;
HBODY *bodyAttachs = NULL;
HRESULT hr;
IMimeBody *pMimeBody;
LPSTR display;
int nItem;
m_attachs.ResetContent();
hr = m_pMimeMsg->GetAttachments(&attachCount,
&bodyAttachs);
if(FAILED(hr)) {
MessageBox(_T("Cannot get attachments:")
_T(" GetAttachments."), _T("Demo Error"), MB_OK);
return;
}
// keep only bodies of type IBT_ATTACHMENT.
for(i=0; i<attachCount;) {
hr = m_pMimeMsg->IsBodyType(bodyAttachs[i],
IBT_ATTACHMENT);
if(hr != S_OK) {
for(j=i+1; j<attachCount; j++) {
bodyAttachs[j-1] = bodyAttachs[j];
}
attachCount--;
}
else {
// for the attachments, get display
// name of the body to add to the listbox.
hr = m_pMimeMsg->BindToObject(bodyAttachs[i],
IID_IMimeBody,
(LPVOID *) &pMimeBody);
if(SUCCEEDED(hr)) {
hr = pMimeBody->GetDisplayName(&display);
if(SUCCEEDED(hr)) {
nItem = m_attachs.AddString(display);
m_attachs.SetItemData(nItem,
(DWORD) bodyAttachs[i]);
CoTaskMemFree(display);
}
}
i++;
}
}
if(bodyAttachs) {
CoTaskMemFree(bodyAttachs);
}
while(1) { // just to save code!
// bind body handle to a IMimeBody interface.
hr = m_pMimeMsg->BindToObject(m_hCurBody,
IID_IMimeBody,
(LPVOID *) &pMimeBody);
if(FAILED(hr)) {
OutputDebugString("CMessageTreeDlg::"
"UpdateBodyInfo: BindToObject\n");
break;
}
...
encType = IET_BINARY;
m_isTextBody = FALSE;
m_cntType = GetContentType(m_hCurBody);
// if the body is a 'text' treat as a text.
// Otherwise, read it as a buffer char
// by char.
if(m_cntType.Find(_T("text")) == 0) {
encType = IET_UNICODE;
m_isTextBody = TRUE;
}
...
m_bodyContent = _T("");
// Get body as a stream
hr = pMimeBody->GetData(IET_UNICODE,
&pBodyStream);
if(FAILED(hr)) {
OutputDebugString("OEMessage::GetBodyText: GetData\n");
break;
}
// if it is a text when we read it it comes unicode.
if(encType == IET_UNICODE) {
// for text bodies
do {
// Read the IStream into our buffer
hr = pBodyStream->Read(lpszwBody,
sizeof(lpszwBody)-sizeof(WCHAR),
&ulRead);
if(FAILED(hr)) {
OutputDebugString("OEMessage::GetBodyText: Read\n");
}
else if(ulRead != 0) {
// Null terminate it
lpszwBody[ulRead/2] = '\0';
m_bodyContent += (WCHAR *) lpszwBody;
}
} while(ulRead != 0);
}
else {
do {
// Read the IStream into our buffer.
// It can be binary so it could
// be displayed truncated.
hr = pBodyStream->Read(lpszBody,
sizeof(lpszBody)-sizeof(char),
&ulRead);
if(FAILED(hr)) {
OutputDebugString("OEMessage::GetBodyText: Read\n");
}
else if(ulRead != 0) {
// Null terminate it
lpszBody[ulRead] = '\0';
m_bodyContent += lpszBody;
}
} while(ulRead != 0);
}
pBodyStream->Release();
break;
}
if(pMimeBody) {
pMimeBody->Release();
}
HRESULT hr;
ULONG ulLength, ulWritten;
BSTR bstr = NULL;
IStream *pStream = NULL;
IMimeBody *pMimeBody = NULL;
PROPVARIANT propValue;
UpdateData(TRUE);
while(1) {
// Create a new stream to write in the new body
hr = CreateStreamOnHGlobal(NULL,
TRUE,
&pStream);
if(FAILED(hr)) {
MessageBox(_T("Cannot set content:" )
_T(" CreateStreamOnHGlobal."),
_T("Demo Error"), MB_OK);
break;
}
// compute the new body length + the
// zero that terminates the string
ulLength = m_bodyContent.GetLength() + 1;
// there are better ways
// to do it but this is the easiest
bstr = m_bodyContent.AllocSysString();
// write in the new body
hr = pStream->Write((LPWSTR) bstr,
ulLength * sizeof(WCHAR),
&ulWritten);
if(FAILED(hr)) {
MessageBox(_T("Cannot set content: Write."),
_T("Demo Error"), MB_OK);
break;
}
// Commit the stream
hr = pStream->Commit(STGC_DEFAULT);
if(FAILED(hr)) {
MessageBox(_T("Cannot set content: Commit."),
_T("Demo Error"), MB_OK);
break;
}
// bind body handle to IMimeBody interface
hr = m_pMimeMsg->BindToObject(m_hCurBody,
IID_IMimeBody,
(LPVOID *) &pMimeBody);
if(FAILED(hr)) {
MessageBox(_T("Cannot set content:")
_T(" Commit."), _T("Demo Error"), MB_OK);
break;
}
CString priCon, secCon;
propValue.vt = VT_LPSTR;
// get content-type property to save the body
hr = m_pMimeMsg->GetBodyProp(m_hCurBody,
PIDTOSTR(PID_HDR_CNTTYPE), 0, &propValue);
if(FAILED(hr) || hr == S_FALSE) {
MessageBox(_T("Cannot set content:")
_T(" GetBodyProp."), _T("Demo Error"), MB_OK);
break;
}
// this property has the format
// of 'primaryType/secondaryType'
char *sep = strchr(propValue.pszVal, '/');
if(sep == NULL) {
MessageBox(_T("Cannot set content:")
_T("Content Type error."),
_T("Demo Error"), MB_OK);
PropVariantClear(&propValue);
break;
}
secCon = sep+1;
*sep = 0;
priCon = propValue.pszVal;
PropVariantClear(&propValue);
// save the data in this new stream
// into the body using
// the save conent-type it had before
hr = pMimeBody->SetData(IET_UNICODE,
priCon,
secCon,
IID_IStream,
pStream);
if(FAILED(hr)) {
MessageBox(_T("Cannot set content: SetData."),
_T("Demo Error"), MB_OK);
break;
}
break;
}
if(bstr) {
::SysFreeString(bstr);
}
if(pMimeBody) {
pMimeBody->Release();
}
if(pStream) {
pStream->Release();
}
You can use this freely, leaving the copyright notice at the top of the files.
- 28-Dec-2004 - First released:
IStoreFolder
/ IStoreNamespace
.
- 25-Mar-2006 - Update:
IMimeMessage
/ IMimeMessageTree
/ IMimePropertySet
/ IMimeBody
.