Share via


Read.cpp

Read.cpp

Important This sample code may not fully verify that strings passed to it are in fact null-terminated, nor that the referenced string buffers are large enough to store the generated contents. Your production code should always verify the validity and size of data passed as null-terminated strings before using, copying or adding to them. Using more-safe versions of the standard C string-handling functions is also recommended.

//------------------------------------------------------------
//
// File: read.cpp, Implementation of Cread
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Contents: Read Class Implements IMailTransportCategorize, IEventIsCacheable
//
// Classes: Cread
//
// Functions:
//  CAttrEntry::operator new
//  CAttrList::AddEntry
//  Cread::IsCacheable
//  Cread::CheckIfSinkHasInitalised
//  Cread::HrReadRegParams
//  Cread::Register
//  Cread::ProcessItem
//  Cread::AllocAndSetPropIdInfo
//  Cread::SaveAttributeValueOnPropIdInfo
//  ConvertStringtoCLSID
//  ShowMessage
//
//-------------------------------------------------------------

#include "stdafx.h"
#include "CatReader.h"
#include "mailmsgprops.h"
#include "read.h"

/////////////////////////////////////////////////////////////////////////////
//
// Cread
//
// Function: Cread::IsCacheable
//
// Synopsis: Implementation of method for IEventIsCacheable
//           If S_OK is returned, the sink is cacheable (the instance
//           will not be loaded and unloaded every time).
//
// Arguments:
//           None
//
// Returns:
//           S_OK if successful
//
//-------------------------------------------------------------

HRESULT Cread::IsCacheable()
{
    return S_OK;
}

//-------------------------------------------------------------
//
// Function: Cread::CheckIfSinkHasInitalised
//
// Synopsis: Function that checks the initialization state. If initialization
//           is going to be attempted for the first time, the function tries
//           to initialize the sink. It remembers the state, if it succeeded or
//           not, and returns it.
//
//
// Arguments:
//           None
//
// Returns:
//           S_OK        The sink initialization succeeded
//           S_FALSE     The sink initialization failed
//
//-------------------------------------------------------------

HRESULT Cread::CheckIfSinkHasInitalised()
{
    PCFunctEnterEx((LPARAM)this, "Cread::CheckIfSinkHasInitalised");
    HRESULT hr  =   S_OK;
    BOOL    fAcquireCS = FALSE;

    // Initialization is attempted only once.

    if(m_fTrySinkInit == TRUE)
        goto CLEANUP;

    EnterCriticalSection(&m_cs);
    fAcquireCS = TRUE;

    if(m_fTrySinkInit == FALSE) {
        hr = HrReadRegParams(&m_attrList);
        m_fSinkInitSuccess = SUCCEEDED(hr);
        m_fTrySinkInit= TRUE;
        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this,
                "CheckIfSinkHasInitalised failed with 0x%08lx",
                hr);
            goto CLEANUP;
        }
    }

CLEANUP:
    if(fAcquireCS)
        LeaveCriticalSection(&m_cs);
    PCFunctLeaveEx((LPARAM)this);
    return m_fSinkInitSuccess ? S_OK : S_FALSE;
}


//-------------------------------------------------------------
//
// Function: Cread::Register
//
// Synopsis: Register is a Categorizer event. Register is a method of the
//           IMailTransportCategorize interface. This is where you request
//           the attributes that you would like the resolver code (known
//           as the Categorizer) to return. This involves traversing through
//           your list and doing a request for each attribute.
//
// Arguments:
//           [in] ICategorizerParameters * pICatParams
//           (see definition in smtpevent.idl)
//
// Returns:
//           S_OK if the function succeeded
//
//-------------------------------------------------------------
HRESULT Cread::Register(
        ICategorizerParameters * pICatParams)
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPhatCat::Register");
    CAttrEntry *pCurrent = NULL;

    if ( S_OK != CheckIfSinkHasInitalised() ) {
        PCErrorTraceEx( (LPARAM) this, "CatReader Sink Not Initialized! CatReader Sink skipping ...");
        goto CLEANUP;
    }

    pCurrent = m_attrList.GetHeadEntry();
    while(NULL != pCurrent) {
        hr = pICatParams->RequestAttributeA(
                m_attrList.GetAttrName(pCurrent));
        if (FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this, "RequestAttributeA Failed with 0x%08lx",
            hr);
            goto CLEANUP;
        }

        PCDebugTraceEx( (LPARAM) this, "Successfully Requested Client's Attribute %s",
            m_attrList.GetAttrName(pCurrent));

        pCurrent = m_attrList.GetNextEntry(pCurrent);
    }

    CLEANUP:
    PCDebugTraceEx((LPARAM)this, "result %08lx", hr);
    PCFunctLeaveEx((LPARAM)this);
    return SUCCEEDED(hr) ? S_OK : hr;
}

//-------------------------------------------------------------
//
// Function: Cread::ProcessItem
//
// Synopsis: First completion event for an ICategorizerItem after its directory
//           service (DS) lookup (matched with an ICategorizerItemAttributes
//           on success). This item can be the sender or one of the recipients
//           of the message. Cread makes use of this event by intercepting
//           the completion, checking to see if any of the requested attributes
//           were found on the item. If so, it requests a set of mailmsg property
//           IDs and saves the attribute's values on them. This is done so that
//           any client that uses this sink to retrieve its attributes can write
//           another postcat sink (see the catreadex example, postcatreader.cpp)
//           to request back the same property set and therefore access its
//           attribute's values.
//
// Arguments:
//           [in] ICategorizerParameters * pICatParams
//              (see the definition in smtpevent.idl)
//           [in] ICategorizerItem * pICatItem
//              (see the definition in smtpevent.idl)
//
// Returns:
//           S_OK if the function succeeded.
//
//-------------------------------------------------------------
HRESULT Cread::ProcessItem(
          ICategorizerParameters * pICatParams,
          ICategorizerItem * pICatItem)
{
    HRESULT hr = S_OK;
    IMailMsgProperties * pIMailMsgProperties = NULL;
    IMailMsgRecipientsAdd * pIMailMsgRecipientsAdd = NULL;
    ICategorizerItemAttributes * pICategorizerItemAttributes = NULL;
    ICategorizerItemRawAttributes * pIRawAttributes = NULL;
    DWORD dwRecipIndex;
    CAttrEntry *pCurrent = NULL;
    eSourceType SourceType;

    PCFunctEnterEx((LPARAM)this, "CPhatCat::ProcessItem");

    if ( S_OK != CheckIfSinkHasInitalised() ) {
        PCErrorTraceEx( (LPARAM) this, "CatReader Sink Not Initialized! CatReader Sink skipping ...");
        goto CLEANUP;
    }


    // Determine whether this pICatItem is a sender or a recipient.

    hr = pICatItem->GetDWORD(
        ICATEGORIZERITEM_SOURCETYPE,
        (DWORD *) &SourceType);

    if (FAILED(hr))
    {
        PCErrorTraceEx( (LPARAM) this, "Failed pICatItem->GetDWORD 0x%08lx",
            hr);
        goto CLEANUP;
    }

    hr = pICatItem->GetIMailMsgProperties(
            ICATEGORIZERITEM_IMAILMSGPROPERTIES,
            &pIMailMsgProperties);
    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed pICatItem->GetIMailMsgProperties 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // If the pICatItem is the sender, then operations are done on the
    // IMailMsgProperties interface.
    // For message recipients, operations are done on the IMailMsgRecipientsAdd
    // interface.

    if (SourceType == SOURCE_SENDER)
    {
        PCDebugTraceEx((LPARAM)this,"Item processed is the Sender ");
        ShowMessage("\n\n");
        ShowMessage("==========================================================================================");
        ShowMessage("Message");
        ShowMessage("*******");
        TCHAR szSenderSmtpAddress[MAX_EMAIL_LENGTH];

        hr = pIMailMsgProperties->GetStringA(
            IMMPID_MP_SENDER_ADDRESS_SMTP,
            MAX_EMAIL_LENGTH,
            (TCHAR *)szSenderSmtpAddress);

        if(FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND) {
            PCErrorTraceEx((LPARAM)this, "pIMailMsgProperties->GetStringA failed with 0x%08lx",
                hr);
            goto CLEANUP;
        }


        // For debugging, output the sender.

        if (SUCCEEDED(hr))
            ShowMessage("CatReader Detected Sender: %s", szSenderSmtpAddress);

    }
    else if (SourceType == SOURCE_RECIPIENT)    {

        TCHAR szRecipAddress[MAX_EMAIL_LENGTH];
        PCDebugTraceEx((LPARAM)this,"Item processed is a Recipient");
        hr = pICatItem->GetIMailMsgRecipientsAdd(
            ICATEGORIZERITEM_IMAILMSGRECIPIENTSADD,
            &pIMailMsgRecipientsAdd);
        if (FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this, "Failed pICatItem->GetIMailMsgRecipientsAdd 0x%08lx",
                hr);
            goto CLEANUP;
        }


        // Find the index for the recipient in the recipient list.

        hr = pICatItem->GetDWORD(
            ICATEGORIZERITEM_IMAILMSGRECIPIENTSADDINDEX,
            &dwRecipIndex);
        if (FAILED(hr))
        {
            PCErrorTraceEx((LPARAM)this, "Failed GetDWORD for getting ICatItem hr %08lx", hr);
            goto CLEANUP;
        }


        ShowMessage("Recipient");
        ShowMessage("*******");

        hr = pIMailMsgRecipientsAdd->GetStringA(
            dwRecipIndex,
            IMMPID_RP_ADDRESS_SMTP,
            MAX_EMAIL_LENGTH,
            (LPSTR) szRecipAddress);

        if (FAILED(hr) && hr != MAILMSG_E_PROPNOTFOUND)
        {
            PCErrorTraceEx((LPARAM)this, "Failed pIMailMsgRecipientsAdd->GetStringA hr %08lx", hr);
            goto CLEANUP;
        }


        // For debugging, output the recipient of the message.
        if (SUCCEEDED(hr))
            ShowMessage("CatReader Detected Recipient: %s", szRecipAddress);
    }


    // All of the attributes for pICatItem can be accessed here.
    hr = pICatItem->GetICategorizerItemAttributes(
        ICATEGORIZERITEM_ICATEGORIZERITEMATTRIBUTES,
        &pICategorizerItemAttributes);
    if (FAILED(hr)) {

        // This could mean that the object (sender/recipient) was not found in the DS.
        // That means there are no attributes to save.
        ShowMessage("Categorizer did not find this object in the DS");
        hr = S_OK;
        goto CLEANUP;
    }


    // ICategorizerItemRawAttributes stores the values as bervals.
    hr = pICategorizerItemAttributes->QueryInterface(
        IID_ICategorizerItemRawAttributes,
        (PVOID *)&pIRawAttributes);
    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this,
        "Failed pICategorizerItemAttributes->QueryInterface 0x%08lx",
        hr);
        goto CLEANUP;
    }


    // Traverse through the list of requested attributes and check to see if
    // the DS lookup found anything.

    pCurrent = m_attrList.GetHeadEntry();
    while(NULL != pCurrent) {

        hr = FindAndProcessAttributeFromCat(
            SourceType,
            pIMailMsgProperties,
            pIMailMsgRecipientsAdd,
            pIRawAttributes,
            dwRecipIndex,
            pCurrent);

        if(FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this,
            "Failed FindAndProcessAttributeFromCat 0x%08lx",
            hr);
            goto CLEANUP;
        }
        pCurrent = m_attrList.GetNextEntry(pCurrent);
    }

    CLEANUP:

    if (pIMailMsgProperties)
        pIMailMsgProperties->Release();

    if (pIMailMsgRecipientsAdd)
        pIMailMsgRecipientsAdd->Release();

    if (pIRawAttributes)
        pIRawAttributes->Release();

    if (pICategorizerItemAttributes)
        pICategorizerItemAttributes->Release();

    PCDebugTraceEx((LPARAM)this, "result %08lx", hr);
    PCFunctLeaveEx((LPARAM)this);
    return SUCCEEDED(hr) ? S_OK : hr;
}

//-------------------------------------------------------------
//
// Function: Cread::FindAndProcessAttributeFromCat
//
// Synopsis: Finds the requested attribute from Cat. If it is found, it will allocate
//           the required number of property IDs and save the attribute values
//           on them.
//
// Arguments:
//           [in]    eSourceType SourceType
//           [in]    IMailMsgProperties *pIMailMsgProperties
//           [in]    IMailMsgRecipientsAdd *pIMailMsgRecipientsAdd
//           [in]    ICategorizerItemRawAttributes * pIRawAttributes
//           [in]    DWORD dwRecipIndex
//           [in]    CAttrEntry * pCurrent
//
// Returns:
//           S_OK if the function succeeded
//
//-------------------------------------------------------------
HRESULT Cread::FindAndProcessAttributeFromCat(eSourceType SourceType,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipientsAdd *pIMailMsgRecipientsAdd,
    ICategorizerItemRawAttributes * pIRawAttributes,
    DWORD dwRecipIndex,
    CAttrEntry * pCurrent)
{
    HRESULT hr = S_OK;
    ATTRIBUTE_ENUMERATOR enumerator;
    DWORD dwAttrValueCount;
    DWORD dwcbAttributeValue;
    PVOID  pvAttributeValue = NULL;
    DWORD dwPropIdOffset;
    DWORD dwCount;
    BOOL fEnumerator = FALSE;

    PCFunctEnterEx((LPARAM)this, "CPhatCat::ProcessAttributesFromCat");

    // Start by getting the enumerator for the requested attribute.
    hr = pIRawAttributes->BeginRawAttributeEnumeration(
            m_attrList.GetAttrName(pCurrent),
            &enumerator);

    if(FAILED(hr)) {

        // The Categorizer did not find the attribute on this recipient/sender. This
        // is NOT a failure.
        PCDebugTraceEx((LPARAM)this,
            "Categorizer did not Find attribute %s. BeginRawAttributeEnumeration returned 0x%08lx",
            m_attrList.GetAttrName(pCurrent),
            hr);


        // Set AttrValueCount to 0 and look up the client's other attributes.
        dwAttrValueCount = 0;
        hr = S_OK;
    }
    else {

        // The requested attribute was found. Determine how many values it has.

        fEnumerator = TRUE;
        hr = pIRawAttributes->CountRawAttributeValues(
            &enumerator,
            &dwAttrValueCount);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this,
                "Failed pIRawAttributes->CountAttributeValues %08lx",
                hr);
            goto CLEANUP;
        }
    }

    PCDebugTraceEx((LPARAM)this,
        "CatReader Found %ld for attribute %s",
        dwAttrValueCount,
        m_attrList.GetAttrName(pCurrent));


    // The requested attribute(s) were found on this recipent/sender.
    // Now request a set of mailmsg property IDs. This function handles multivalued
    // attributes, and cases where the attribute was not found.

    hr = AllocAndSetPropIdInfo(
        SourceType,
        pIMailMsgProperties,
        pIMailMsgRecipientsAdd,
        dwRecipIndex,
        pCurrent,
        dwAttrValueCount,
        &dwPropIdOffset);

    if(FAILED(hr)) {
        PCErrorTraceEx((LPARAM)this,
            "Failed AllocAndSetPropIdInfo %08lx",
            hr);
        goto CLEANUP;
    }

    PCDebugTraceEx((LPARAM)this,
        "Found Attribute %s - Allocated %ld propids from offset starting %ld",
        m_attrList.GetAttrName(pCurrent),
        dwAttrValueCount,
        dwPropIdOffset);

    ShowMessage("CatReader %s Attribute %s - Allocated %ld propids from offset starting %ld",
        dwAttrValueCount? "Found" : "Did Not Find",
        m_attrList.GetAttrName(pCurrent),
        dwAttrValueCount,
        dwPropIdOffset);


    // Loop in to save all of the attribute values.
    for(dwCount = 0; dwCount &#060 dwAttrValueCount; dwCount++) {

        // Get the next value pointed by the enumerator. Because the exact count
        // of values is known, this method cannot fail.

        hr = pIRawAttributes->GetNextRawAttributeValue(
                &enumerator,
                &dwcbAttributeValue,
                &pvAttributeValue);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this,
                "Failed GetNextRawAttributeValue hr %08lx", hr);
            goto CLEANUP;
        }


        // Save the attribute value on the allocated mailmsg property ID.

        hr = SaveAttributeValueOnPropIdInfo(
            SourceType,
            pIMailMsgProperties,
            pIMailMsgRecipientsAdd,
            dwRecipIndex,
            dwPropIdOffset+dwCount,
            dwcbAttributeValue,
            pvAttributeValue);

        if(FAILED(hr)) {
            PCErrorTraceEx((LPARAM)this, "Failed SaveAttributeValuesOnPropIdInfo hr %08lx", hr);
            goto CLEANUP;
        }

        PCDebugTraceEx((LPARAM)this,
            "Attribute %s - Saved Value#%ld at Propid %ld",
            m_attrList.GetAttrName(pCurrent),
            dwCount+1,
            dwPropIdOffset+dwCount);

        ShowMessage("CatReader Attribute %s - Saved Value#%ld at PropId %ld",
            m_attrList.GetAttrName(pCurrent),
            dwCount+1,
            dwPropIdOffset+dwCount);
    }

CLEANUP:

    if (fEnumerator) {

        // After starting the enumeration, call EndRawAttributeEnumeration to
        // free memory.
        hr = pIRawAttributes->EndRawAttributeEnumeration(&enumerator);
        if(FAILED(hr))
            PCErrorTraceEx((LPARAM)this,
                "Failed EndRawAttributeEnumeration hr %08lx", hr);
    }
    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: Cread::AllocAndSetPropIdInfo
//
// Synopsis: This function starts by allocating and requesting a set of only two mailmsg
//           property IDs. The first is always used to persist the number of values.
//           If the requested attribute is single-valued, this function will return
//           the second property ID as the placeholder to save the value. If the
//           attribute is multivalued, this function creates a new GUID and, based
//           on the new GUID, it requests a second set of mailmsg property IDs.
//           The function then returns this set as a placeholder to save the multiple
//           values. The client postcategorizer implementation must walk through the
//           same steps to retrieve the values. (See the catreadex example,
//           postcatreader.cpp.)
//
// Arguments:
//      [in]
//      eSourceType SourceType                      Source type indicates whether the ICatItem
//                                                  processed is a sender or a recipient            //
//      IMailMsgProperties *pIMailMsgProperties
//      IMailMsgRecipientsAdd *pIMailMsgRecipientsAdd
//      DWORD dwRecipIndex
//      CAttrEntry * pCurrent
//      DWORD  dwAttrValueCount                    Number of attribute Values
//
//      [out]
//      DWORD *pdwPropIdOffset                     Starting offset of available property ID
//
// Returns:
//          S_OK if the function succeeded
//
//-------------------------------------------------------------
HRESULT Cread::AllocAndSetPropIdInfo(
    eSourceType SourceType,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipientsAdd *pIMailMsgRecipientsAdd,
    DWORD dwRecipIndex,
    CAttrEntry * pCurrent,
    DWORD  dwAttrValueCount,
    DWORD *pdwPropIdOffset)
{
    IMailMsgPropertyManagement *pIMailMsgPropertyManagement = NULL;
    DWORD   dwStart;
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPhatCat::AllocAndGetPropIdInfo");

    *pdwPropIdOffset = 0;

    hr = pIMailMsgProperties->QueryInterface(
            IID_IMailMsgPropertyManagement,
            (PVOID *)&pIMailMsgPropertyManagement);

    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgProperties->QueryInterface 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // Start by requesting two mailmsg property IDs.

    hr = pIMailMsgPropertyManagement->AllocPropIDRange(
            m_attrList.GetAttrGuid(pCurrent),
            2,
            &dwStart);
    if (FAILED(hr)) {
        PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgProperties->QueryInterface 0x%08lx",
            hr);
        goto CLEANUP;
    }

    // Save the attribute value count in the first allocated property ID.

    if(SourceType == SOURCE_RECIPIENT)
       hr = pIMailMsgRecipientsAdd->PutDWORD(
                dwRecipIndex,
                dwStart,
                dwAttrValueCount);
    else
       hr = pIMailMsgProperties->PutDWORD(
                dwStart,
                dwAttrValueCount);

    if (FAILED(hr))        {
        PCErrorTraceEx((LPARAM)this,"Failed %s->PutDWORD hr %08lx",
            SourceType == SOURCE_RECIPIENT ? "pIMailMsgRecipientsAdd" : "pIMailMsgProperties",
            hr);
        goto CLEANUP;
    }



    // Check if the attribute is single-valued or multivalued.
    if (dwAttrValueCount > 1) {

        // The attribute is multivalued. Create a unique GUID.

        GUID gNewGuid;
        hr = CoCreateGuid(
         &gNewGuid);
        if (FAILED(hr))
        {
            PCErrorTraceEx((LPARAM)this,"Failed CoCreateGuid hr %08lx", hr);
            goto CLEANUP;
        }


        // Use the next allocated property ID to store your unique GUID.

        if(SourceType == SOURCE_RECIPIENT)
            hr = pIMailMsgRecipientsAdd->PutProperty(
                dwRecipIndex,
                dwStart+1,
                sizeof(GUID),
               (BYTE *) &gNewGuid);
        else
            hr = pIMailMsgProperties->PutProperty(
                dwStart+1,
                sizeof(GUID),
               (BYTE *) &gNewGuid);

        if (FAILED(hr))
        {
            PCErrorTraceEx((LPARAM)this,"Failed %s PutProperty hr %08lx",
                SourceType == SOURCE_RECIPIENT ? "pIMailMsgRecipientsAdd" : "pIMailMsgProperties",
                hr);
            goto CLEANUP;
        }

        // Using the new GUID, request the second set of property IDs.
        // The number you request equals the number of attribute values.

        hr = pIMailMsgPropertyManagement->AllocPropIDRange(
            gNewGuid,
            dwAttrValueCount,
            pdwPropIdOffset);

        if (FAILED(hr)) {
            PCErrorTraceEx( (LPARAM) this, "Failed pIMailMsgProperties->QueryInterface 0x%08lx",
            hr);
            goto CLEANUP;
        }

    }
    else

        // The attribute is single-valued, therefore return the next
        // available property ID in order to store the value itself.
        *pdwPropIdOffset = dwStart+1;

    CLEANUP:

    if (pIMailMsgPropertyManagement)
        pIMailMsgPropertyManagement->Release();

    PCDebugTraceEx((LPARAM)this, "result %08lx", hr);
    PCFunctLeaveEx((LPARAM)this);
    return hr;
}

//-------------------------------------------------------------
//
// Function: Cread::SaveAttributeValueOnPropIdInfo
//
// Synopsis: Saves the attribute value onto the mailmsg property ID.
//           For simplicity in this example, all property values are
//           stored as binary properties. Types like strings are not
//           NULL terminated.
//
// Arguments:
//      [in]
//      eSourceType SourceType,
//      IMailMsgProperties *pIMailMsgProperties,
//      IMailMsgRecipientsAdd * pIMailMsgRecipientsAdd,
//      DWORD dwRecipIndex,
//      DWORD dwPropIdOffset,
//      DWORD dwcbAttributeValue,        - Size of Value
//      PVOID pvAttributeValue)          - Pointer to Value
//
//
// Returns:
//            S_OK if the function succeeded
//
//-------------------------------------------------------------
HRESULT Cread::SaveAttributeValueOnPropIdInfo(
    eSourceType SourceType,
    IMailMsgProperties *pIMailMsgProperties,
    IMailMsgRecipientsAdd * pIMailMsgRecipientsAdd,
    DWORD dwRecipIndex,
    DWORD dwPropIdOffset,
    DWORD dwcbAttributeValue,
    PVOID pvAttributeValue)
{
    HRESULT hr = S_OK;
    PCFunctEnterEx((LPARAM)this, "CPhatCat::AllocAndGetPropIdInfo");

    if(SourceType == SOURCE_RECIPIENT)
        hr = pIMailMsgRecipientsAdd->PutProperty(
           dwRecipIndex,
           dwPropIdOffset,
           dwcbAttributeValue,
           (BYTE *) pvAttributeValue);
    else
        hr = pIMailMsgProperties->PutProperty(
           dwPropIdOffset,
           dwcbAttributeValue,
           (BYTE *) pvAttributeValue);

    if (FAILED(hr))
    {
        PCErrorTraceEx((LPARAM)this,"Failed %s->PutProperty hr %08lx",
            SourceType == SOURCE_RECIPIENT ? "pIMailMsgRecipientsAdd" : "pIMailMsgProperties",
            hr);
        goto CLEANUP;
    }

    CLEANUP:
    PCDebugTraceEx((LPARAM)this, "result %08lx", hr);
    PCFunctLeaveEx((LPARAM)this);
    return hr;

}