MSPMISC.C

/* 
* M S P M I S C . C
*
* Utility functions needed by the MAPI Sample Store Provider.
*
* Copyright 1992-1995 Microsoft Corporation. All Rights Reserved.
*/

#include "msp.h"

/*
* Memory allocation and release functions. They use the lpMalloc
* interface supplied by MAPI during provider initialization.
*
* For internal memory allocations the Sample Store Provider uses
* the lpMalloc passed by MAPI on MSProviderInit(). For simplicity
* of coding we keep the pointer to the lpMalloc in a
* per-process global (which requires mucho work on Win16) instead
* of forcing ScAlloc() calls to pass in the pointer on each and every
* allocation. We trade off code size with speed (on Win16) because
* of the searching necessary in finding the per-instance globals.
*
* When multiple MSProviderInit's are done on the same process
* (by opening one or more stores on two different MAPI Profiles
* on the same process) we use the lpMalloc from the first init
* for all allocations, keeping a refcount of MSProviderInit's
* that have been done in order to know when to free the lpMalloc.
*
* As a result we don't necessary use the lpMalloc for store X that
* was passed in when store X was opened, but as long as MAPI gives
* us the Component Object's lpMalloc on each open we're fine.
*/

SCODE ScAlloc(ULONG lcb, LPVOID * ppv)
{
PINST pinst = (PINST) PvGetInstanceGlobals();

Assert(pinst);
Assert(pinst->lpmalloc);

*ppv = pinst->lpmalloc->lpVtbl->Alloc(pinst->lpmalloc, lcb);
if (*ppv != NULL)
return S_OK;

DebugTraceSc(ScAlloc, MAPI_E_NOT_ENOUGH_MEMORY);
return (MAPI_E_NOT_ENOUGH_MEMORY);
}

SCODE ScAllocZ(ULONG lcb, LPVOID * ppv)
{
PINST pinst = (PINST) PvGetInstanceGlobals();

Assert(pinst);
Assert(pinst->lpmalloc);

*ppv = pinst->lpmalloc->lpVtbl->Alloc(pinst->lpmalloc, lcb);
if (*ppv != NULL)
{
memset(*ppv, 0, (size_t) lcb);
return S_OK;
}

DebugTraceSc(ScAllocZ, MAPI_E_NOT_ENOUGH_MEMORY);
return (MAPI_E_NOT_ENOUGH_MEMORY);
}

SCODE ScRealloc(ULONG lcb, LPVOID pvOrig, LPVOID * ppv)
{
PINST pinst = (PINST) PvGetInstanceGlobals();
LPVOID pvNew;

Assert(pinst);
Assert(pinst->lpmalloc);

pvNew = pinst->lpmalloc->lpVtbl->Realloc(pinst->lpmalloc, pvOrig, lcb);
if (pvNew != NULL)
{
*ppv = pvNew;
return S_OK;
}

DebugTraceSc(ScRealloc, MAPI_E_NOT_ENOUGH_MEMORY);
return (MAPI_E_NOT_ENOUGH_MEMORY);
}

void FreeNull(LPVOID pv)
{
if (pv)
{
PINST pinst = (PINST) PvGetInstanceGlobals();

Assert(pinst);
Assert(pinst->lpmalloc);

pinst->lpmalloc->lpVtbl->Free(pinst->lpmalloc, pv);
}
}

/* Linked Memory Utilities ------------------------------------------------- */

SCODE LMAllocZ(PLMR plmr, ULONG lcb, LPVOID * ppv)
{
SCODE sc;

sc = LMAlloc(plmr, lcb, ppv);
if (sc != S_OK)
return (sc);

memset(*ppv, 0, (size_t) lcb);
return (S_OK);
}

/*
* ScInitMSInstance
*
* Remember the given lpMalloc in a per-instance variable that
* can be looked up again in ScAlloc and FreeNull.
*/
SCODE ScInitMSInstance(LPMALLOC lpmalloc)
{
PINST pinst = (PINST) PvGetInstanceGlobals();
SCODE sc;

Assert(lpmalloc);

if (pinst)
{
/* A usable allocator is already set. */
/* Ignore the one we were passed in */
/* and instead bump the refcount on the */
/* one we're going to use. */
++(pinst->cRef);
return S_OK;
}

/* In our debugging version wrap the allocator for
* extra help locating memory leaks or other problems.
* In both debug and non-debug versions, this statement
* addrefs the allocator, and the DBGMEM_Shutdown at the
* end does a release on the allocator.
*/
lpmalloc = DBGMEM_Encapsulate(lpmalloc, "Sample Store", 0);

pinst = (PINST) lpmalloc->lpVtbl->Alloc(lpmalloc, sizeof(INST));

if (pinst == NULL)
{
sc = MAPI_E_NOT_ENOUGH_MEMORY;
goto ret;
}

sc = ScSetInstanceGlobals(pinst);
if (sc != S_OK)
goto ret;

pinst->cRef = 1;
pinst->lpmalloc = lpmalloc;

return S_OK;

ret:
if (pinst)
lpmalloc->lpVtbl->Free(lpmalloc, pinst);

DBGMEM_Shutdown(lpmalloc);
DebugTraceSc(ScInitMSInstance, sc);
return sc;
}

void DeinitMSInstance(void)
{
PINST pinst = (PINST) PvGetInstanceGlobals();
LPMALLOC lpmalloc;

if (!pinst || --(pinst->cRef) > 0)
return;

(void)ScSetInstanceGlobals(NULL);
lpmalloc = pinst->lpmalloc;
lpmalloc->lpVtbl->Free(lpmalloc, pinst);
DBGMEM_Shutdown(lpmalloc);
}

/*
* SzBaseName
*
* Purpose
* return the base name of the object with the given eid
*
* Parameters
* peid Pointer to entryID of object
*
* Returns
* LPTSTR base name, ie sequence number and extension
*
* Note no memory is allocated, returned value must be
* copied if it is to be saved.
*/
LPTSTR SzBaseName(PEID peid)
{
LPTSTR szName; /* local name of object in lpEntryID */
LPTSTR szLastSlash; /* position of last slash in peid */

Assert(peid);

/* Note that the entryid pathname is always relative to the root of */
/* the store, and will therefore never contain a drive letter. */

szLastSlash = SzFindLastCh(peid->szPath, '\\');

if (NULL == szLastSlash)
{
szName = (LPTSTR) peid->szPath;
}
else
{
szName = szLastSlash + 1;
}

return szName;
}

/*
* FCheckEIDType
*
* Purpose
* This function checks the pathname extension portion of the given
* entryid against the string given. Since files in the sample store
* have extensions that depend on the type of object within, comparing
* the extension will tell the caller whether the entryid is of the type
* specified by the extension passed in.
*
* Parameters
* peid: A pointer to the entryid to check.
* szExt: A pointer to the string containing the extension to compare.
*
* Returns: TRUE if the extensions matched, FALSE otherwise.
*/
BOOL FCheckEIDType(PEID peid, LPSTR szExt)
{
BYTE *pb;

if (peid == NULL)
return FALSE;

pb = (BYTE *) peid;
pb += CbEID(peid);
pb -= ((CCH_EXT + 1) * sizeof(TCHAR));

return (lstrcmpi((LPTSTR) pb, szExt) == 0);
}

/*
* FIsRoot
*
* Purpose Determine if an EID is one for the root folder
*
* Parameters peid
*
* Returns TRUE or FALSE
*/
BOOL FIsRoot(PEID peid)
{
return (NULL == peid) || *(peid->szPath) == '\0';
}

/*
* FIsFolder
*
* Parameters peid pointer to entryid of object
*
* Purpose return TRUE if the entryID is one for a folder
*
* Returns TRUE If the entryID is one for a folder, FALSE otherwixe
*
*/
BOOL FIsFolder(PEID peid)
{
/* root is a folder */
if (FIsRoot(peid))
return TRUE;

return (FCheckEIDType(peid, FOLDER_EXT));
}

/*
* FIsUnsavedMsg
*
* Purpose Determine if a message has had changes saved
*
* Parameters pimsg: A pointer to the message object to check.
*
* SideEffect
* Will say that a message is saved if another open version
* has saved it.
*
* Returns TRUE or FALSE
*/
BOOL FIsUnsavedMsg(PIMSG pimsg)
{
return (FIsUnsavedEID((PEID) pimsg->peid));
}

/*
* HrDeconstructEID
*
* Purpose:
* Given an EID, return its component parts (MAPIUID, path to
* file, and file name). This is NOT a general path parser.
* Instead, because we know the exact structure of the EIDs
* and paths we construct, we can directly access the
* individual components.
*
* Parameters
* peid EID to deconstruct.
* ppuid Location in which to return a pointer to the
* EID's MAPIUID.
* pszPath Location in which to return a pointer to the
* EID's file path.
* pszFile Location in which to return a pointer to the
* EID's file name.
*
* Returns:
* HRESULT
*
* Size effects:
* None.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate memory for
* some or all of the return
* parameters.
*/
HRESULT HrDeconstructEID(PEID peid, LPMAPIUID *ppuid, LPTSTR *pszPath,
LPTSTR *pszFile)
{
SCODE sc;
LPMAPIUID puid = NULL;
LPTSTR szPath = NULL;
LPTSTR szFile = NULL;
LPTSTR szT = NULL;
LPTSTR szEnd = NULL;

AssertSz(!IsBadReadPtr(peid, CbNewEID(0)), "Bad peid #1");
AssertSz(!IsBadReadPtr(peid, CbEID(peid)), "Bad peid #2");
AssertSz(!IsBadWritePtr(ppuid, sizeof(LPMAPIUID)), "Bad ppuid");
AssertSz(!IsBadWritePtr(pszPath, sizeof(LPTSTR)), "Bad pszPath");
AssertSz(!IsBadWritePtr(pszFile, sizeof(LPTSTR)), "Bad pszFile");

*ppuid = NULL;
*pszPath = NULL;
*pszFile = NULL;

/* Get the UID out */

sc = ScAlloc(sizeof(MAPIUID), &puid);
if (sc != S_OK)
goto exit;
*puid = peid->uidResource;

/* Get the path and file name out */
szT = SzFindLastCh(peid->szPath, '\\');
if (szT)
szT++;
else
szT = peid->szPath;

szEnd = (TCHAR *) ((BYTE *) peid + CbEID(peid));
sc = ScAlloc((szEnd - szT) * sizeof(TCHAR), &szFile);
if (sc != S_OK)
goto exit;

if ((LPBYTE) szEnd - (LPBYTE) szT)
memcpy(szFile, szT, (LPBYTE) szEnd - (LPBYTE) szT);

if (szT != peid->szPath)
{
sc = ScAlloc((LPBYTE) szT - (LPBYTE) (peid->szPath), &szPath);
if (sc != S_OK)
goto exit;

/* We copy the trailing backslash along with the rest of */
/* the string, then overwrite it with the NULL terminator. */
memcpy(szPath, peid->szPath, (LPBYTE) szT - (LPBYTE) (peid->szPath));
*(szPath + (szT - peid->szPath) - 1) = '\0';
}
else
{
sc = ScAlloc(sizeof(TCHAR), (PPV) &szPath);
if (sc != S_OK)
goto exit;

*szPath = '\0';
}

exit:
if (sc)
{
(void)FreeNull((LPVOID) puid);
(void)FreeNull((LPVOID) szPath);
(void)FreeNull((LPVOID) szFile);
}
else
{
*ppuid = puid;
*pszPath = szPath;
*pszFile = szFile;
}

DebugTraceSc(HrDeconstructEID, sc);
return ResultFromScode(sc);
}

/*
* HrAppendPath
*
* Purpose:
* Concatenate a path onto another, allocating space for the
* result.
*
* Parameters
* szBase Beginning of concatenated path.
* szAppend End of concatenated path.
* pszFullPath Pointer in which to place a pointer to the
* newly allocated concatenated path.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Unable to allocate memory for
* the return variable.
*/
HRESULT HrAppendPath(LPTSTR szBase, LPTSTR szAppend, LPTSTR * pszFullPath)
{
SCODE sc = S_OK;
TCHAR rgch[512];

AssertSz(szBase, "Bad szBase");
AssertSz(szAppend, "Bad szAppend");
AssertSz(pszFullPath, "Bad pszFullPath");
AssertSz(szAppend[0] != '\\',
"szAppend not relative path");

if (!FAppendPathNoMem(szBase, szAppend, sizeof rgch / sizeof rgch[0],
rgch))
{
sc = MAPI_E_STRING_TOO_LONG;
goto exit;
}

sc = ScAlloc(Cbtszsize(rgch), (PPV) pszFullPath);
if (sc != S_OK)
goto exit;

lstrcpy(*pszFullPath, rgch);

exit:
DebugTraceSc(HrAppendPath, sc);
return ResultFromScode(sc);
}

/*
* FAppendPathNoMem
*
* Purpose:
* Concatenate two parts of a file path, returning result in a
* preallocated buffer.
*
* Parameters
* szBase First part of path to concatenate.
* szAppend Second part of path to concatenate.
* cchFullPath Size of return buffer.
* szFullPath Return buffer.
*
* Returns:
* BOOL. TRUE if the return buffer is large enough to hold
* the resultant string, FALSE if the buffer is not large
* enough (value in szFullPath is undefined).
*
* Side effects:
* None.
*
* Errors:
* None.
*/
BOOL FAppendPathNoMem(LPTSTR szBase, LPTSTR szAppend, ULONG cchFullPath,
LPTSTR szFullPath)
{
UINT cchBase = 0;
UINT cchAppend = 0;
UINT cchFull = 0;
BOOLEAN fPostSlash = FALSE;

AssertSz(szBase, "Bad szBase");
AssertSz(szAppend, "Bad szAppend");
AssertSz(szFullPath, "Bad szFullPath");
AssertSz(szAppend[0] != '\\',
"szAppend not relative path");

cchBase = lstrlen(szBase);
cchAppend = lstrlen(szAppend);

/* Check if szBase has trailing backslash, else we'll need to add it */

if (*(szBase + cchBase - 1) == '\\')
{
fPostSlash = TRUE;
cchFull = cchBase + cchAppend + 1;
}
else
{
fPostSlash = FALSE;
cchFull = cchBase + cchAppend + 2;
}

if (cchFull <= cchFullPath)
{
lstrcpy(szFullPath, szBase);
if (!fPostSlash)
{
lstrcat(szFullPath, TEXT("\\"));
}
lstrcat(szFullPath, szAppend);

return TRUE;
}

return FALSE;
}

/*
* ReplaceExt
*
* Purpose:
* Substitute the filename extension in szFile with the new
* one passed in as szExt. It is the caller's
* responsibility to ensure that the filename has room for the
* new extension. Also, the extension must include the
* period.
*
* Parameters
* szFile Filename on which to operate.
* szExt New extension.
*
* Returns:
* void.
*/
void ReplaceExt(LPTSTR szFile, LPTSTR szExt)
{
LPTSTR szT = NULL;

AssertSz(!IsBadStringPtr(szFile, (UINT) -1), "Bad szFile");
AssertSz(!IsBadStringPtr(szExt, (UINT) -1), "Bad szExt");

szT = SzFindLastCh(szFile, '.');

if (szT)
lstrcpy(szT, szExt);
#ifdef DEBUG
else
TrapSz("No extension found on szFile. Not replacing extension.");
#endif

return;
}

/*
* HrConstructEID
*
* Purpose return an new EntryID for an object
*
* Parameters
* puidStore UID for the store containing the object
* plmr Pointer to the MAPI linked memory allocators.
* szNewName new root relative path name of the object
* ppeidNew address of pointer to new entryID
*
* Returns:
* ULONG, PEID
*/
HRESULT HrConstructEID(LPMAPIUID puidStore, PLMR plmr, LPSTR szNewName,
PEID *ppeidNew)
{
PEID peidNew; /* new entry id */
ULONG cbEID; /* number of bytes in the new entry id */
ULONG cbNewName;
SCODE sc;

cbNewName = lstrlen(szNewName) + 1; /* we count the NULL terminator */
cbEID = CbNewEID(cbNewName);

/* allocate space for the new entry id */
/* Use the MAPI allocator because it may be returned to the client */
/* Note that we zero-fill this allocation so that the entryid produced */
/* by identical input parameters will always be the same. If we didn't */
/* zero fill, the pad bytes after the version would be randomly filled. */

sc = LMAllocZ(plmr, cbEID, (PPV) &peidNew);
if (sc != S_OK)
goto exit;

*(DWORD *) (peidNew->abFlags) = (DWORD) 0;
peidNew->uidResource = *puidStore;
peidNew->bVersion = SMPMS_VERSION;

lstrcpy(peidNew->szPath, szNewName);

if (peidNew->szPath[0])
AnsiLowerBuff(peidNew->szPath, lstrlen(peidNew->szPath));

*ppeidNew = peidNew;

exit:
DebugTraceSc(HrConstructEID, sc);
return ResultFromScode(sc);
}

/*
* HrGetParentEID
*
* Purpose construct an entry id for the parent of peid. This
* assumes that all files in the sample message store
* are CCH_NAME chars long (including NULL).
*
* Parameters
* plmr Pointer to the MAPI linked memory allocators.
* peid entry id of object whose parent is requested
* ppeidParent pointer to parent's peid
*
*/
HRESULT HrGetParentEID(PLMR plmr, PEID peid, PEID *ppeidParent)
{
HRESULT hr;
ULONG cbParentName = sizeof(TCHAR); /* number of bytes in szParentName */
LPTSTR szParentName = NULL; /* name of parent */

if (CbEIDPath(peid) > (CCH_NAME * sizeof(TCHAR)))
cbParentName = CbEIDPath(peid) - (CCH_NAME * sizeof(TCHAR));

hr = HrAlloc(cbParentName, (PPV) &szParentName);
if (hr != hrSuccess)
goto exit;

if (cbParentName > sizeof(TCHAR))
memcpy(szParentName, peid->szPath, (UINT) cbParentName);
szParentName[cbParentName - sizeof(TCHAR)] = 0;

hr = HrConstructEID(&(peid->uidResource), plmr, szParentName,
ppeidParent);

exit:
FreeNull(szParentName);
DebugTraceResult(HrGetParentEID, hr);
return hr;
}

/*
* HrOpenParent
*
* Purpose open the parent folder of the given entry id
*
* Parameters
* pims store in which the object is
* peid entry id of object whose parent is to be opened
* ulFlags MAPI_MODIFY if you want write permission on the store
* ppifld pointer to variable to hold open parent
*/
HRESULT HrOpenParent(PIMS pims, PEID peid, ULONG ulFlags, PIFLD * ppifld)
{
HRESULT hr = hrSuccess;
PEID peidParent = NULL;
ULONG ulObjectType;

Assert(pims);
Assert(peid);
Assert(ppifld);

*ppifld = NULL;
hr = HrGetParentEID(&pims->lmr, peid, &peidParent);
if (hr != hrSuccess)
goto exit;

hr = pims->lpVtbl->OpenEntry(pims, CbEID(peidParent),
(LPENTRYID) peidParent, NULL, ulFlags, &ulObjectType,
(LPUNKNOWN *) ppifld);
if (hr != hrSuccess)
goto exit;

Assert(ulObjectType == MAPI_FOLDER);

exit:
LMFree(&pims->lmr, peidParent);

DebugTraceResult(HrOpenParent, hr);
return hr;
}

/*
* FreePropArrays
*
* Purpose deallocate space for PropTag, PropValue and PropAttr arrays
* allocated with HrAllocPropArrays
* Parameters
* ppval address of property value array
* pptaga address of the property tag array
* ppatra address of the property attribute array
*/
void FreePropArrays(LPSPropValue *ppval, LPSPropTagArray *pptaga,
LPSPropAttrArray *ppatra)
{
Assert(ppval);
Assert(pptaga);
Assert(ppatra);

FreeNull(*ppval);
FreeNull(*pptaga);
FreeNull(*ppatra);

*ppval = NULL;
*pptaga = NULL;
*ppatra = NULL;
}

/*
* HrAllocPropArrays
*
* Purpose allocate space for PropTag, PropValue and PropAttr arrays
* Free with FreeNull or FreePropArrays
* Parameters
* cProps number of properties in the arrays
* ppval address of property value array
* pptaga address of the property tag array
* ppatra address of the property attribute array
*/
HRESULT HrAllocPropArrays(ULONG cProps, LPSPropValue *ppval,
LPSPropTagArray *pptaga, LPSPropAttrArray *ppatra)
{
HRESULT hr;

/* All three pointers must be provided. */
Assert(!IsBadWritePtr(ppval, sizeof(LPSPropValue)));
Assert(!IsBadWritePtr(pptaga, sizeof(LPSPropTagArray)));
Assert(!IsBadWritePtr(ppatra, sizeof(LPSPropAttrArray)));

/* All must be zero on entry for our cleanup mechanism. */
AssertSz(!*ppval, "bad ppval");
AssertSz(!*pptaga, "bad pptaga");
AssertSz(!*ppatra, "bad ppatra");

hr = HrAlloc(cProps * sizeof(SPropValue), ppval);
if (hr != hrSuccess)
goto exit;

hr = HrAlloc(CbNewSPropTagArray(cProps), pptaga);
if (hr != hrSuccess)
goto exit;

hr = HrAlloc(CbNewSPropAttrArray(cProps), ppatra);
if (hr != hrSuccess)
goto exit;

exit:
if (hr != hrSuccess)
FreePropArrays(ppval, pptaga, ppatra);

DebugTraceResult(HrAllocPropArrays, hr);
return hr;
}

/*
* ProcessGetProps
*
* Purpose
* Helper routine from HrWrap_GetProps. Folder and message objects in
* the sample store keep a few property values in memory so that when
* we move or copy a folder or message, the entryid, parent entryid,
* and (for copied messages) record key are correct. This routine
* overrides the value returned from IMessage for any in-memory
* properties. We keep a very small placeholder property for each
* of the in-memory properties on disk and mark it read-only and
* not deletable. This keeps SetProps from succeeding on these
* properties. All properties that we override in this function are
* PT_BINARY property types, so HrWrap_GetProps only calls this
* function if the property it is looking for is a PT_BINARY.
*
* This function tries to find the property that the client is
* requesting in the in-memory array of properties stored with the
* object. If the routine finds the property, then it tries to allocate
* and copy the correct data into the client's array. If the allocation
* fails, the routine fills in a PT_ERROR into the client's property
* value. Note that HrWrap_GetProps needs to watch for PT_ERROR coming
* back and return the appropriate warning to the client.
* If the routine doesn't find the property, then it returns to
* HrWrap_GetProps without modifying the client's array at all.
*
* Parameters
* pvalClient: A pointer to the property value to search for in our
* in-memory property array. This function may MODIFY this value.
* cvalInt: The number of in-memory properties on this object. If the
* object has no in-memory properties, this value will be zero.
* pvalInt: A pointer to the object's array of in-memory properties.
* plmr: A pointer to the linked memory allocation routines.
* pvOrig: The original allocated pointer (AllocateMore needs this).
*
* Returns
* None. May have modified the client's property value if there was
* a matching entry in the in-memory array.
*/
static void ProcessGetProps(LPSPropValue pvalClient, ULONG cvalInt,
LPSPropValue pvalInt, PLMR plmr, LPVOID pvOrig)
{
ULONG ulClientID = PROP_ID(pvalClient->ulPropTag);
ULONG ulClientType = PROP_TYPE(pvalClient->ulPropTag);
LPSPropValue pvalT;
LPSPropValue pvalTMac;

pvalT = pvalInt;
pvalTMac = pvalT + cvalInt;

for (; pvalT < pvalTMac; ++pvalT)
{
LPVOID pv;
ULONG cb;
SCODE sc;

AssertSz(PROP_TYPE(pvalT->ulPropTag) == PT_BINARY,
"Code assumes all internal props are PT_BINARY");

if (ulClientID == PROP_ID(pvalT->ulPropTag))
{
cb = pvalT->Value.bin.cb;
pv = pvalT->Value.bin.lpb;

/* Link onto returned data our extra info. */
sc = LMAllocMore(plmr, cb, pvOrig, &pvalClient->Value.bin.lpb);
if (sc != S_OK)
{
pvalClient->Value.err = sc;
pvalClient->ulPropTag = PROP_TAG(PT_ERROR, ulClientID);
}
else
{
pvalClient->Value.bin.cb = cb;
if (cb)
memcpy(pvalClient->Value.bin.lpb, pv, (UINT) cb);
pvalClient->ulPropTag = PROP_TAG(PT_BINARY, ulClientID);
}

break;
}
}

return;
}

/*
* HrWrap_GetProps
*
* Purpose
* Adjust return from GetProps (Store, Folder, Message, etc) for wrapped
* values of PR_STORE_ENTRYID, PR_STORE_RECORD_KEY and (if a store)
* PR_ENTRYID. Doesn't allow the return of PR_ENTRYID for an unsaved
* message. Also overwrites values for properties that the object has
* cached in memory using the helper routine ProcessGetProps.
*
* Parameters
* hr HRESULT from the original GetProps call.
* pims pointer to the message store object
* cvalInt the number of property values that are held in memory
* associated with the object.
* pvalInt a pointer to the array of in-memory properties associated
* with the object. May be NULL if cvalInt is 0.
* pcValues The number of values in client's PropValue array
* ppval a pointer to the client's PropValue array
* fStore TRUE if the object given the GetProps call was the
* message store object.
* fTagsSpecified TRUE if the client specified a proptag array on the call;
* FALSE if the client passed NULL for the proptag array.
*
* Coding comments:
* The objects must contain these properties, of the appropriate
* type, but it isn't necessary that their values IN the objects be
* accurate. The KEYS, for example, don't even have to be 16 bytes.
*
* The result code may be adjusted depending on whether this routine
* or the underlying property implementation, ran out of memory
* while dealing with one of these.
*
* The PR_RECORD_KEY (a UID) in a Store IS the right value,
* and does not need to be wrapped.
*/
HRESULT HrWrap_GetProps(HRESULT hr, PIMS pims, ULONG cvalInt,
LPSPropValue pvalInt, ULONG * pcValues, LPSPropValue * ppval,
BOOL fStore, BOOL fTagsSpecified, POBJ pobj)
{
/* Warning: pcValues and ppval parameters may not */
/* have been validated. Do not dereference them unless */
/* the "hr" says everything succeeded so far. */

BOOL fErrors = FALSE;
LPSPropValue pval;
LPSPropValue pvalMac;

/* No work to do unless the GetProps() generally succeeded. */
if (HR_FAILED(hr))
goto exit;

pval = *ppval;
pvalMac = pval + *pcValues;

for (; pval < pvalMac; ++pval)
{
UINT ulType = (UINT) PROP_TYPE(pval->ulPropTag);
UINT ulID;
LPVOID pv;
ULONG cb;
SCODE sc;

if(pval->ulPropTag == PR_ACCESS_LEVEL)
{
pval->Value.l = OBJ_TestFlag(pobj, OBJF_MODIFY) ? MAPI_MODIFY : 0;
}

if(pval->ulPropTag == PR_ACCESS)
{
if(OT_FOLDER == pobj->wType)
{
pval->Value.l = MAPI_ACCESS_READ;

if(OBJ_TestFlag(pobj, OBJF_MODIFY))
pval->Value.l |= MAPI_ACCESS_MODIFY |
MAPI_ACCESS_CREATE_CONTENTS |
MAPI_ACCESS_CREATE_HIERARCHY;

if(OBJ_TestFlag(pobj->pobjParent, OBJF_MODIFY))
pval->Value.l |= MAPI_ACCESS_DELETE;
}
else if(OT_MESSAGE == pobj->wType)
{
pval->Value.l = MAPI_ACCESS_READ;

if(OBJ_TestFlag(pobj, OBJF_MODIFY))
pval->Value.l |= MAPI_ACCESS_MODIFY;

if(OBJ_TestFlag(pobj->pobjParent, OBJF_MODIFY))
pval->Value.l |= MAPI_ACCESS_DELETE;
}
else
{
pval->ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(PR_ACCESS));
pval->Value.err = MAPI_E_NOT_FOUND;
}
}

if (ulType != PT_BINARY && ulType != PT_ERROR)
continue;

if (ulType == PT_ERROR && pval->Value.err == MAPI_E_UNEXPECTED_TYPE)
{
fErrors = TRUE;
continue;
}

if (cvalInt != 0)
{
ProcessGetProps(pval, cvalInt, pvalInt, &pims->lmr,
(LPVOID) *ppval);

/* Recompute the prop type in case ProcessGetProps changed it. */
ulType = (UINT) PROP_TYPE(pval->ulPropTag);
}

/* These values should be computed here just in case ProcessGetProps */
/* modifies pval. */

ulID = (UINT) PROP_ID(pval->ulPropTag);

if (ulID == PROP_ID(PR_STORE_RECORD_KEY))
{
cb = sizeof(pims->uidResource);
pv = &pims->uidResource;

} 
else if (ulID == PROP_ID(PR_STORE_ENTRYID)
|| (fStore && ulID == PROP_ID(PR_ENTRYID)))
{
cb = pims->eidStore.cb;
pv = pims->eidStore.lpb;
}
else if (ulID == PROP_ID(PR_ENTRYID) && ulType != PT_ERROR)
{
/* entryid of a message doesn't exist until SaveChanges(). */
if (FIsUnsavedEID((PEID) pval->Value.bin.lpb))
{
if (fTagsSpecified)
{
fErrors = TRUE;
pval->ulPropTag = PROP_TAG(PT_ERROR, ulID);
pval->Value.err = MAPI_E_NOT_FOUND;
}
else
{
/* The client wants all properties, and did not */
/* specify a prop tag array. The client therefore */
/* doesn't want NOT_FOUND errors. */
/* Overwrite the error entry with the last SPropValue */
/* in the array. */

(*pcValues)--;
pvalMac--;

if (pval < pvalMac)
{
memcpy(pval, pvalMac, sizeof(SPropValue));
--pval; /* redo this value, since we just changed it */
}
}
}
continue;
}
else
{
/* Remember if any errors occur in final array. */
if (ulType == PT_ERROR)
{
if ( pval->Value.err == MAPI_E_NOT_FOUND
&& !fTagsSpecified)
{
/* The client wants all properties, and did not */
/* specify a prop tag array. The client therefore */
/* doesn't want NOT_FOUND errors. */
/* Overwrite the error entry with the last SPropValue */
/* in the array. */

(*pcValues)--;
pvalMac--;

if (pval < pvalMac)
{
memcpy(pval, pvalMac, sizeof(SPropValue));
--pval; /* redo this value, since we just changed it */
}
}
else
fErrors = TRUE;
}
continue;
}

/* Link onto returned data our extra info. */
sc = LMAllocMore(&pims->lmr, cb, (LPVOID) *ppval,
&pval->Value.bin.lpb);
if (sc != S_OK)
{
fErrors = TRUE;
pval->Value.err = sc;
pval->ulPropTag = PROP_TAG(PT_ERROR, ulID);
}
else
{
pval->Value.bin.cb = cb;
if (cb)
memcpy(pval->Value.bin.lpb, pv, (UINT) cb);
pval->ulPropTag = PROP_TAG(PT_BINARY, ulID);
}
}

/* Adjust HRESULT based on PT_ERRORs now. */
if (!fErrors)
hr = hrSuccess;
else if (hr == hrSuccess)
hr = ResultFromScode(MAPI_W_ERRORS_RETURNED);

exit:
#ifdef DEBUG
if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
DebugTraceResult(HrWrap_GetProps, hr);
#endif

return hr;
}

/*
* FIsSubmittedMessage
*
* Purpose return TRUE if the message (specified by entryid) is submitted.
*
* Parameters
* pims A pointer to the message store object.
* peid The entryid of message to check.
*/
BOOL FIsSubmittedMessage(PIMS pims, PEID peid)
{
HRESULT hr;
PIMSG pimsgT = NULL;
ULONG ulObjType;

hr = pims->lpVtbl->OpenEntry(pims, CbEID(peid), (LPENTRYID) peid,
NULL, MAPI_MODIFY, &ulObjType, (LPUNKNOWN *) &pimsgT);

UlRelease(pimsgT);

return (GetScode(hr) == MAPI_E_SUBMITTED);
}

/*
* HrOpenIMsgSession
*
* Purpose:
* Open an IMsgSession, and return the pointer to the caller.
*
* Parameters
* ppmsgsess: Pointer to the location to return the opened msg session.
*
* Returns:
* HRESULT
*/
HRESULT HrOpenIMsgSession(LPMSGSESS *ppmsgsess)
{
PINST pinst = (PINST) PvGetInstanceGlobals();

Assert(pinst);
Assert(pinst->lpmalloc);

if (pinst == NULL)
return ResultFromScode(MAPI_E_CALL_FAILED);

return ResultFromScode(OpenIMsgSession(pinst->lpmalloc, 0L, ppmsgsess));
}

/*
* HrOpenIMsg
*
* Purpose:
* Open the file given as a docfile, and then create an IMSG.DLL
* object on top of the storage.
*
* Parameters
* pmsgsess The message session to open the message within.
* May be NULL (for ConfirmCred in msplogon to work).
* szFile The file to open.
* plmr a pointer to the linked memory routines.
* psup a pointer to the MAPI support object.
* fCreate TRUE means the caller wants to create the storage.
* FALSE means open an existing storage.
* fModify TRUE means the caller wants read/write access.
* FALSE means read-only access. (This argument is
* ignored when fCreate is TRUE; in that case, the
* file is always opened read/write.)
* fExclusive TRUE means the caller wants exclusive access to the
* storage, and to fail creation if the storage already
* exists.
* ppmsg Address of a location in which to return a
* pointer to the newly opened IMessage instance.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* IMessage on IStorage opening errors.
*/
HRESULT HrOpenIMsg(LPMSGSESS pmsgsess, LPSTR szFile, PLMR plmr, LPMAPISUP psup,
BOOL fCreate, BOOL fModify, BOOL fExclusive, LPMESSAGE *ppmsg)
{
HRESULT hr;
SCODE sc;
DWORD grfMode;
LPSTORAGE lpstg = NULL;
PINST pinst = (PINST) PvGetInstanceGlobals();

#ifdef _WIN32
OLE_CHAR szOle[MAX_PATH];
int cbFile = 0L;
#else
OLE_CHAR *szOle = NULL;
#endif

Assert(pinst);
Assert(pinst->lpmalloc);

#ifdef _WIN32
cbFile = 1 + lstrlen(szFile);
Assert(cbFile < MAX_PATH);

MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szFile, cbFile, szOle, cbFile);
#else
szOle = szFile;
#endif

grfMode = STGM_TRANSACTED;

if (fExclusive)
{
grfMode |= STGM_SHARE_EXCLUSIVE;

if (fCreate)
grfMode |= STGM_FAILIFTHERE;
}
else
grfMode |= STGM_SHARE_DENY_NONE;

if (fCreate)
{
grfMode |= (STGM_READWRITE | STGM_CREATE);

hr = StgCreateDocfile(szOle, grfMode, 0, &lpstg);

/* Commit docfile changes. If we don't do this now, the file on */
/* disk will NOT be a docfile (i.e. OLE2 will not recognize it as */
/* a docfile) if opened again with no other changes made to it. */

if (hr == hrSuccess)
hr = lpstg->lpVtbl->Commit(lpstg, 0);
}
else
{
if (fModify)
grfMode |= STGM_READWRITE;
else
grfMode |= STGM_READ;

hr = StgOpenStorage(szOle, NULL, grfMode, NULL, 0, &lpstg);
}

if (hr != hrSuccess)
{
sc = MapStorageSCode(GetScode(hr));
goto exit;
}

sc = OpenIMsgOnIStg(pmsgsess, plmr->lpAllocBuf, plmr->lpAllocMore,
plmr->lpFreeBuf, pinst->lpmalloc, psup, lpstg, NULL, 0, 0, ppmsg);

UlRelease(lpstg);

exit:
if (sc != S_OK && fCreate)
DeleteFile(szFile);

DebugTraceSc(HrOpenIMsg, sc);
return ResultFromScode(sc);
}

/*
* HrSetOneROProp
*
* Purpose
* The sample store needs to change properties that the client isn't
* allowed to change. This function allows the sample store to change
* a single property in the underlying IMessage object by first
* setting the attributes on that property to allow it to be written,
* then writing the property, and finally, setting the attributes back
* to once again only allow reading. Note that if the sample store
* calls this routine on a property that is writable, this routine
* will make it non-writable.
*
* Parameters
* lpmsg: A pointer to the IMessage object in which to set the property.
* plmr: A pointer to the linked memory allocation routines.
* ulPT: The property tag to set within the object.
* pv: A pointer to the property value to set.
*/
HRESULT HrSetOneROProp(LPMESSAGE lpmsg, PLMR plmr, ULONG ulPT, LPVOID pv)
{
HRESULT hr;
LPSPropAttrArray patra = NULL;
LPSPropProblemArray pprba = NULL;

SizedSPropTagArray(1, spta);

/* Should be changing the pval array inside the object. */

AssertSz( ulPT != PR_ENTRYID
&& ulPT != PR_PARENT_ENTRYID
&& ulPT != PR_RECORD_KEY
&& ulPT != PR_INSTANCE_KEY, "Changing internal props in IMSG");

spta.cValues = 1;
spta.aulPropTag[0] = ulPT;

/* Get current attributes and make the properties writable */

hr = GetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, &patra);
if (hr != hrSuccess)
goto exit;

patra->aPropAttr[0] |= PROPATTR_WRITEABLE;

hr = SetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, patra, &pprba);
if (hr != hrSuccess || pprba)
goto exit;

hr = HrSetSingleProp((LPMAPIPROP) lpmsg, plmr, ulPT, pv);
if (hr != hrSuccess)
goto exit;

/* Restore the attribute */

patra->aPropAttr[0] &= ~PROPATTR_WRITEABLE;

hr = SetAttribIMsgOnIStg(lpmsg, (LPSPropTagArray) &spta, patra, &pprba);
if (hr != hrSuccess || pprba)
goto exit;

exit:
if (pprba)
{
LMFree(plmr, pprba);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
}

LMFree(plmr, patra);

DebugTraceResult(HrSetOneROProp, hr);
return hr;
}

/*
* HrGetSingleProp
*
* Purpose
* Gets a property from an object, and returns the value by stuffing
* it into a separately passed pointer. This function is nice because
* it doesn't require the caller to have an SPropValue around simply
* to retrieve the value of a property that is a known size.
*
* Parameters
* pmprop The property object to get the property from.
* plmr Pointer to MAPI's linked memory routines.
* ulPT The property tag to get.
* pv A pointer to the location to place the value of the property.
*
* Returns
* HRESULT. No warnings are returned because this function retrieves
* only one property at a time.
*/
HRESULT HrGetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv)
{
LPSPropValue pval = NULL;
SCODE sc;
HRESULT hr;
ULONG cValues;

SizedSPropTagArray(1, spta);

spta.cValues = 1;
spta.aulPropTag[0] = ulPT;

hr = pmprop->lpVtbl->GetProps(pmprop, (LPSPropTagArray) &spta, 0, /* ansi */
&cValues, &pval);

sc = GetScode(hr);

if ((sc != S_OK)
&& (sc != MAPI_W_ERRORS_RETURNED))
goto exit;

switch (PROP_TYPE(pval->ulPropTag))
{
case PT_I2:
case PT_BOOLEAN:
Assert(!IsBadWritePtr(pv, sizeof(short)));
*(short *)pv = pval->Value.i;
break;

case PT_LONG:
case PT_R4:
Assert(!IsBadWritePtr(pv, sizeof(LONG)));
*(LONG *) pv = pval->Value.l;
break;

case PT_DOUBLE:
case PT_APPTIME:
case PT_SYSTIME:
case PT_I8:
case PT_CURRENCY:
Assert(!IsBadWritePtr(pv, sizeof(LARGE_INTEGER)));
*(LARGE_INTEGER *) pv = pval->Value.li;
break;

case PT_ERROR:
sc = pval->Value.err;
break;

default:
TrapSz1("Unimplemented PROP_TYPE %08lX passed in.",
PROP_TYPE(pval->ulPropTag));
}

LMFree(plmr, pval);

exit:
AssertSz1(sc <= 0, "Logic error: sc %s returned from HrGetSingleProp.",
SzDecodeScode(sc));

DebugTraceSc(HrGetSingleProp, sc);
return (ResultFromScode(sc));
}

/*
* HrSetSingleProp
*
* Purpose:
* Sets one property and its separately passed value. This function
* is nice because it doesn't require the caller to have a SPropValue
* to pass in.
*
* Parameters
* pmprop The property object to set the property into.
* plmr Pointer to MAPI's linked memory routines.
* ulPT The property to set
* pv A pointer to the value of the property
*
* Returns:
* Any errors from SetProps. Note that no warnings or problem arrays are
* returned because this routine only sets one property.
*/
HRESULT HrSetSingleProp(LPMAPIPROP pmprop, PLMR plmr, ULONG ulPT, LPVOID pv)
{
HRESULT hr;
SPropValue sval;
LPSPropProblemArray pprba = NULL;

sval.ulPropTag = ulPT;

switch (PROP_TYPE(ulPT))
{
case PT_I2:
case PT_BOOLEAN:
sval.Value.i = *(short *)pv;
break;

case PT_LONG:
case PT_R4:
case PT_UNICODE:
case PT_STRING8:
case PT_CLSID:
AssertSz(sizeof(LPVOID) == sizeof(LONG),
"Pointers are not the size of a long on this machine");
sval.Value.l = *(LONG *) pv;
break;

case PT_DOUBLE:
case PT_APPTIME:
case PT_SYSTIME:
case PT_I8:
case PT_CURRENCY:
case PT_OBJECT:
case PT_BINARY:
sval.Value.li = *(LARGE_INTEGER *) pv;
break;

default:
TrapSz1("Unimplemented PROP_TYPE %08lX passed in.", PROP_TYPE(ulPT));
}

hr = pmprop->lpVtbl->SetProps(pmprop, 1, &sval, &pprba);

if (hr == hrSuccess && pprba)
hr = ResultFromScode(pprba->aProblem[0].scode);

LMFree(plmr, pprba);

DebugTraceResult(HrSetSingleProp, hr);
return hr;
}

/*
* FContainsProp
*
* Purpose:
* returns whether or not a PropTag exists in a PropTagArray
*
* Parameters
* ulPropTag The property to search for.
* ptaga A pointer to the SPropTagArray to search. May be null,
* in which case, the function will return FALSE.
*
* Returns:
* TRUE if ulPropTag is in ptaga
* FALSE if not
*/
BOOL FContainsProp(ULONG ulPropTag, LPSPropTagArray ptaga)
{
ULONG *pulPropTag;
ULONG *pulPropMax;

if (!ptaga)
return FALSE;

pulPropTag = ptaga->aulPropTag;
pulPropMax = pulPropTag + ptaga->cValues;

while (pulPropTag < pulPropMax)
{
if (ulPropTag == *pulPropTag)
return TRUE;

pulPropTag++;
}

return FALSE;
}