XPRCVMSG.C

/* 
- X P R C V M S G . C
-
* Purpose:
* Code to support the MAPI Transport SPI entry points for
* message reception. This module contains the following
* SPI entry points:
*
* Poll()
* StartMessage()
*
* Additional support functions found here:
*
* HrIMsgFromTextMsg()
* HrBuildSenderProps()
* HrAddRecipToAdrList()
* HrAddRecipToReplyList()
* HrMakeSearchKey()
* HrGetLine()
* FGetTagAndToken()
* FileTimeFromSzTime()
* SetFromMeFlag()
*
* Also, the Idle() code in XPQUEUE.C will call into this module to
* find out if there's any mail to tell SpoolerNotify() about.
*
* Copyright 1992-1995 Microsoft Corporation. All Rights Reserved.
*/

#include "xppch.h"
#include <tnef.h>
#include <stdlib.h>
#include "xpsof.h"
#include "xptxtmsg.h"

/* Local function prototype(s) */

VOID SetFromMeFlag(LPXPL lpxpl, LPMESSAGE lpMsg);


/*
- Poll
-
* Purpose:
* Called by the Spooler periodically in its idle loop. Also called from
* the Idle() entry point code.
*
* Parameters:
* lpulIncoming Pointer to a ULONG.
*
* Returns:
* *lpulIncoming Nonzero if any messages are found;
* Zero if no messages were found.
*
* Operation:
* This routine uses FindFirst/FindNext to look for message container
* files in the inbound directory. Because its FIND_DATA structure and
* the associated Handle is contained in the session structure, it's
* easy to keep context and pass it along to StartMessage().
*
* Because it can be called from multiple places, and there's no
* guarantee that StartMessage() will be called between the time we
* signal the presence of inbound mail and the next time we're called,
* we maintain a boolean in the session structure which we set to TRUE
* when we find a message, and which StartMessage() sets to FALSE after
* processing that message. If we see it as TRUE when called, we return
* immediately with *lpulIncoming set.
*
* Otherwise, we do a FindNext if a find handle is open. If it fails, we
* close the handle.
*
* If we have no open handle (either because one wasn't open or because
* we just closed one), we do a FindFirst.
*
* If we find a message container file, we set the boolean and
* *lpulIncoming and return. Else we close the find handle.
*
* This function always returns SUCCESS_SUCCESS.
*/

STDMETHODIMP
XPL_Poll(LPXPL lpxpl, ULONG * lpulIncoming)
{
HANDLE *lpPollHandle;
LPWIN32_FIND_DATA lpFindData;
LPMAPISUP lpMAPISup;
LPSPropValue lpPropArray = lpxpl->lpPropArray;
BOOL fFound = FALSE;
DWORD dwIgnoreAttrs =
FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_DIRECTORY |
FILE_ATTRIBUTE_TEMPORARY;

lpPollHandle = &lpxpl->hInFindHandle;
lpFindData = &lpxpl->wfdInFindData;

lpMAPISup = lpxpl->lpMAPISup;

/* Start out with default of no incoming message */

*lpulIncoming = 0;

/* Is inbound enabled for this session? */

if (!(lpxpl->ulTransportStatus & STATUS_INBOUND_ENABLED))
goto ret;

/* See if we found one on a previous pass through here and haven't
done a StartMessage() yet... */

if (lpxpl->fFoundInMessage)
{
*lpulIncoming = 1;
goto ret;
}

/* One of the following is now true:

A) We have a search already active, use FindNextFile.
If it fails, we will want to restart the search
(in case something appeared in the search behind
us.

B) We don't have a search active, use FindFirstFile.
*/

/* Try the active search */

if (*lpPollHandle != INVALID_HANDLE_VALUE)
{
fFound = FindNextFile(*lpPollHandle, lpFindData);

/* If we fail, close the old search so we can start
a new one below. */

if (!fFound)
{
FindClose(*lpPollHandle);
*lpPollHandle = INVALID_HANDLE_VALUE;
}
}

/* If there was no search or if the old one was just closed above */

if (*lpPollHandle == INVALID_HANDLE_VALUE)
{
HANDLE hFindT;
TCHAR chFileName[MAX_PATH];

/* Copy the directory name. Note that we trust the value in the
profile to be correct, since the UI should have enforced a
syntax that included a trailing : or \ in the spec. */

lstrcpy(chFileName,
(ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray)).Value.LPSZ);

lstrcat(chFileName, TEXT("TNF*.TMP"));

hFindT = FindFirstFile(chFileName, lpFindData);

/* If nothing's found, we're done here. */

if (hFindT == INVALID_HANDLE_VALUE)
goto ret;

/* Found something, continue along. */

fFound = TRUE;
*lpPollHandle = hFindT;
}

/*
Here on a match. Exclude unwanted files.

Any match with DIRECTORY, READONLY, SYSTEM or TEMPORARY attribute
is ignored. Keep trying until we exhaust the current supply or we
find a file without these attributes. Also, ignore files smaller
than some arbitrary size, they're probably trash.
*/

while (fFound)
{
/* We found a file. Does it have any of the attributes we
want to ignore? If not, get out. If so, try another. */

#define MIN_USEFUL_FILESIZE ((DWORD) 64)

if ((!((lpFindData)->dwFileAttributes & dwIgnoreAttrs)) &&
((lpFindData->nFileSizeHigh != 0) ||
(lpFindData->nFileSizeLow >= MIN_USEFUL_FILESIZE)))
break;

fFound = FindNextFile(*lpPollHandle, lpFindData);
}

if (fFound)
lpxpl->fFoundInMessage = TRUE;
else
{
FindClose(*lpPollHandle);
*lpPollHandle = INVALID_HANDLE_VALUE;
}

ret:
if (lpxpl->fFoundInMessage)
{
/* Got a hit. If fFound is set, we found it this time. If fFound
is not set, we got it before and we were called again before
StartMessage(). */

*lpulIncoming = 1;
DebugTrace("XPL_Poll returns *lpulIncoming=%lx\n", *lpulIncoming);
}
else if (lpxpl->ulTransportStatus & STATUS_INBOUND_FLUSH)
{
lpxpl->ulTransportStatus &= ~STATUS_INBOUND_FLUSH;
(void)HrUpdateTransportStatus(lpxpl, 0L);
}
return hrSuccess;
}


/*
- StartMessage
-
* Purpose:
* Called by the Spooler for receipt of an inbound message. This sequence
* of events is set off by a SpoolerNotify (NOTIFY_NEWMAIL) or a Poll()
* returning *lpulIncoming != 0.
*
* Parameters:
* ulFlags Flags from the Spooler. Currently
* there are no StartMessage() flags
* defined in the MAPI 1.0 TSPI.
* lpMessage Pointer to message object into which
* the Spooler wants the transport to
* store the incoming message.
* lpulMsgRef Pointer to where the transport should
* store a unsigned long for use in
* identifying TransportNotify() message
* events. Initialized to 0 by the
* Spooler. We don't do anything with
* message events in this transport, so
* we don't store anything there.
*
* Returns:
* (HRESULT) MAPI_E_BUSY if the Spooler calls
* here again while we're busy, else
* errors encountered if any.
* (*lpMessage) Contains the new input message if any.
*
* Operation:
* Checks for the result of a FindFirst/FindNext operation in the
* session's FIND_DATA buffer. If none, exit without changing the input
* message (this should result in the new message being destroyed when
* the Spooler releases its object).
*
* If a file was found, attempt to open it. If the open fails and the
* reason is not attributable to network locking, return the error; if
* attributable to network locking (like if a peer's transmit code is
* writing a container file), return no error. In either case, exit
* without changing the input message.
*
* Open a stream interface on the input message file. Pass this off
* to HrIMsgFromTxtMsg() to convert all the textized envelope properties
* to SPropValues which are then set on the spoolers IMessage. When
* we return, the input stream will be pointing to the beginning of
* the embedded TNEF encapsulation in the input message file. Make
* the appropriate calls to TNEF to extract the properties from the
* encapsulation and set them on the message.
*
* Finally, SaveChanges() on the Spooler's message to retain the result,
* delete the container file, and reset the "found message" flag for the
* benefit of Poll().
*/

STDMETHODIMP
XPL_StartMessage(LPXPL lpxpl,
ULONG ulFlags,
LPMESSAGE lpMessage,
ULONG * lpulMsgRef)
{
LPWIN32_FIND_DATA lpFindData;
LPSPropValue lpMyIDArray = NULL;
LPSPropValue lpPropArray = NULL;
SPropValue rgDelegateProps[3];
SPropValue spvTime;
LPSPropProblemArray lpProblems = NULL;
TCHAR rgchFileName[MAX_PATH];
LPTSTR lptMyDir;
HRESULT hResult = 0;
SCODE sc = 0;
BOOL fUpdatedStatus = FALSE;
LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
WORD wKey = 0;
SPropTagArray sptExcludeNone = {0};
LPITNEF lpTnef = (LPITNEF) NULL;
LPSTREAM lpSof = (LPSTREAM) NULL;
LPSTREAM lpXPSof = (LPSTREAM) NULL;
LPSTnefProblemArray lptpa = NULL;

/* Reset our .2 second timer before starting. */

HrCheckSpoolerYield(lpMAPISup, TRUE);

/* Do this first so we know at exit time if we tried to open a file */

rgchFileName[0] = '\0';

/* Simple re-entrancy test. Should never happen anyway. */

if (lpxpl->ulTransportStatus & STATUS_INBOUND_ACTIVE)
{
hResult = ResultFromScode(MAPI_E_BUSY);
DebugTrace("XPL_StartMessage reentrancy test failed\n");
goto ret;
}

/* Signal that we're downloading. */

*lpulMsgRef = 1L; /* This is good enough. */
lpxpl->ulTransportStatus |= STATUS_INBOUND_ACTIVE;
hResult = HrUpdateTransportStatus(lpxpl, 0L);
if (hResult)
{
DebugTrace("Update of status row failed\n");
goto ret;
}
fUpdatedStatus = TRUE;

/* Get the current findfirst/findnext buffer */

lpFindData = &lpxpl->wfdInFindData;

/* Is there actually a message available? If not, go away. */

if (!lpxpl->fFoundInMessage)
goto ret;

sc = ScCopySessionProps(lpxpl, &lpPropArray, &lpMyIDArray);

if (FAILED(sc))
{
hResult = ResultFromScode(sc);
goto ret;
}

/* Build file name of incoming message */

lptMyDir = ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray).Value.LPSZ;

lstrcpy(rgchFileName, lptMyDir);
lstrcat(rgchFileName, lpFindData->cFileName);

PrintfTransportLog(TEXT("Start Incoming: %s"), rgchFileName);

hResult = OpenStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
STGM_READ, rgchFileName, NULL, &lpSof);

if (hResult)
{
sc = GetScode(hResult);
DebugTrace("OpenStreamOnFile() failed in StartMessage()\n");
PrintfTransportLog(TEXT("OpenStreamOnFile(%s) returns %lx"), rgchFileName, sc);

/* If "Access Denied" just don't do anything.
It's usually a situation that will clear up (when another
instance of this transport closes the msg file or when the
other system comes online) */

if (sc == MAPI_E_NO_ACCESS)
hResult = hrSuccess;

/* If "Not Found", clear fFoundInMessage so that we'll do a
FindNext. */

if (sc == MAPI_E_NOT_FOUND)
lpxpl->fFoundInMessage = FALSE;

goto ret;
}

/* Wrap the Stream-On-File object in our buffered wrapper. */

hResult = HrWrapStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
XPSOF_READ, lpSof, &lpXPSof);

if (HR_FAILED(hResult))
{
DebugTrace("HrWrapStreamOnFile() failed\n");
goto ret;
}

/* Check our .2 second timer before attempting to receive */

sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

if (sc == MAPI_W_CANCEL_MESSAGE)
{
DebugTrace("Cancelling message download.\n");
goto ret;
}

hResult = HrIMsgFromTextMsg(lpxpl, lpPropArray, lpMessage, lpXPSof);

if (HR_FAILED(hResult))
{
DebugTrace("HrIMsgFromTextMsg() failed\n");
goto ret;
}

/* Check our .2 second timer again. */

sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

if (sc == MAPI_W_CANCEL_MESSAGE)
{
DebugTrace("Cancelling message download.\n");
goto ret;
}

/* The 0x01AF if a key used to identify the TNEF. A real transport
should generate a pseudo-random sequence for this field. */

hResult = OpenTnefStream(lpxpl->lpMAPISup, lpXPSof,
TEXT("MAPIMAIL.DAT"), TNEF_DECODE, lpMessage, 0x01AF, &lpTnef);

if (HR_FAILED(hResult))
{
DebugTrace("OpenTNEF() failed.\n");
goto ret;
}

/* Extract properties from the incomming message and add them to
the target message. */

hResult = lpTnef->lpVtbl->ExtractProps(lpTnef,
TNEF_PROP_EXCLUDE, &sptExcludeNone, &lptpa);
lpxpl->FreeBuffer(lptpa);
if (HR_FAILED(hResult))
{
DebugTrace("GetTNEFProps() failed.\n");
goto ret;
}

/* Check our .2 second timer again. */

sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

if (sc == MAPI_W_CANCEL_MESSAGE)
{
DebugTrace("Cancelling message download.\n");
goto ret;
}

/* All the properties have been copied over. Set the PR_RECEIVED_BY
delegate properties (all the others were set by the transmitter) */

if (lpMyIDArray)
{
Assert(!IsBadReadPtr(lpMyIDArray, 3 * sizeof(SPropValue)));
Assert(lpMyIDArray[0].ulPropTag == PR_SENDER_ENTRYID);
Assert(lpMyIDArray[1].ulPropTag == PR_SENDER_NAME);
Assert(lpMyIDArray[2].ulPropTag == PR_SENDER_SEARCH_KEY);

memcpy(rgDelegateProps, lpMyIDArray, 3 * sizeof(SPropValue));
rgDelegateProps[0].ulPropTag = PR_RECEIVED_BY_ENTRYID;
rgDelegateProps[1].ulPropTag = PR_RECEIVED_BY_NAME;
rgDelegateProps[2].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;

/* At this point we have all the delegate properties set. Put them into
the old message and then we'll just get them on the CopyTo(). */

hResult = lpMessage->lpVtbl->SetProps(lpMessage, 3,
rgDelegateProps, &lpProblems);

if (hResult)
{
DebugTrace("SetProps of Receiver ID to message failed.\n");
goto ret;
}

if (lpProblems)
{
/* If there were problems, let's dump them to the debugger. */

DebugTraceProblems("XPL_StartMessage", lpProblems);

lpxpl->FreeBuffer(lpProblems);
lpProblems = NULL;
}
}

/* Set the Received Time to be the last-modified time on the file. */

spvTime.ulPropTag = PR_MESSAGE_DELIVERY_TIME;
spvTime.Value.ft = lpFindData->ftLastWriteTime;

hResult = lpMessage->lpVtbl->SetProps(lpMessage, 1, &spvTime, NULL);

if (hResult)
{
DebugTrace("SetProps of PR_MESSAGE_DELIVERY_TIME failed.\n");
goto ret;
}

/* Finished with all properties and recipients now, SaveChanges
on the message. */

/* Check our .2 second timer again. */

sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

if (sc == MAPI_W_CANCEL_MESSAGE)
{
DebugTrace("Cancelling message download.\n");
goto ret;
}

hResult = lpMessage->lpVtbl->SaveChanges(lpMessage, 0L);

if (hResult)
{
DebugTrace("SaveChanges on incoming message failed.\n");
goto ret;
}

/* Finally, set the found message flag so we'll get another file. */

lpxpl->fFoundInMessage = FALSE;

ret:
/* Log end of incoming if we logged start. */

if (*rgchFileName)
PrintfTransportLog(TEXT("End Incoming: %s"), rgchFileName);

UlRelease(lpTnef);
UlRelease(lpXPSof);
UlRelease(lpSof);

/* If we got the message into the store, delete the inbound file. */

if (!(HR_FAILED (hResult)) && *rgchFileName)
(void)DeleteFile(rgchFileName);

/* Release the prop tag array and/or problem array if any */

lpxpl->FreeBuffer(lpPropArray);
lpxpl->FreeBuffer(lpMyIDArray);

/* Reset download status if set. */

if (fUpdatedStatus)
{
lpxpl->ulTransportStatus &= ~STATUS_INBOUND_ACTIVE;
(void)HrUpdateTransportStatus(lpxpl, 0L);
}

DebugTraceResult(XPL_StartMessage, hResult);
return hResult;
}


/*
- HrIMsgFromTextMsg
-
* Purpose:
* Called by StartMessage() to read a text formatted message file,
* containing a TNEF encapsulation, and converting it into a
* MAPI Message. The TNEF DLL is used to decode the binary portion
* of this message into all the correct IMessage components.
*
* Parameters:
* lpxpl Pointer to Transport Logon object
* lpPropArray Array of the transports logon properties
* lpMessage Message to receive into
* lpSof Pointer to the stream interface
*
* Returns:
* hr Indicating Success/Failure
*
* Operation:
* Read each Tag out of the text file and process its Token according
* to my rules for the Tag ID. PR_SUBJECT and PR_BODY are automatically
* streamed into the message, PR_CLIENT_SUBMIT_TIME, PR_PRIORITY,
* PR_SENDER_NAME, and PR_SENDER_ENTRYID are added with SetProps().
* All To: and Cc: recipients are added by building an AdrList
* and doing a ModifyRecipients() on the message. When we're finished
* here, the file pointer in the input stream will (hopefully) be
* left pointing to the start of the TNEF encapsulation.
*/

HRESULT
HrIMsgFromTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof)
{
SCODE sc;
HRESULT hr = hrSuccess;
BOOL fHaveTagAndToken = FALSE;
TCHAR szLine[MAX_LINE];
ULONG cbRead;
ULONG ulTag;
LPTSTR lpszToken;
ULONG cValues = 0;
LPSPropValue lpMsgProps = NULL;
LPMYADRLIST lpMyRecipList = NULL;
LPTSTR lpszAddrType;
LPTSTR lpszReplyNames = NULL;
ULONG cbReplyEntryList = 0;
LPFLATENTRYLIST lpReplyEntryList = NULL;
LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;

lpszAddrType = ArrayIndex(PR_SAMPLE_EMAIL_ADDR_TYPE, lpPropArray).Value.LPSZ;

sc = lpxpl->AllocateBuffer(MAX_TXTMSG_PROPS * sizeof(SPropValue),
&lpMsgProps);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("Allocation failed.\n");
goto ret;
}

memset(lpMsgProps, 0, MAX_TXTMSG_PROPS * sizeof(SPropValue));

sc = lpxpl->AllocateBuffer(sizeof(MYADRLIST), &lpMyRecipList);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("Allocation failed.\n");
goto ret;
}

memset(lpMyRecipList, 0, sizeof(MYADRLIST));

while (TRUE)
{
/* fHaveTagAndToken gets set only when we return from
HrGetStreamedProp and the call actually gets
the next tagged line and tokenizes it for us. */

if (!fHaveTagAndToken)
{
hr = HrGetLine(lpSof, MAX_LINE, szLine, &cbRead);

if (hr)
break;

if (szLine[0] == '\0')
continue;
}

if (fHaveTagAndToken || FGetTagAndToken(szLine, &ulTag, &lpszToken))
{
fHaveTagAndToken = FALSE;

/* Check our .2 second timer again. */

sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));

if (sc == MAPI_W_CANCEL_MESSAGE)
{
DebugTrace("Cancelling message download.\n");
goto ret;
}

switch (ulTag)
{
case tagFrom:
case tagRepresenting:
/* Create an addressing triplet (DisplayName, EntryID,
SearchKey) from this line and add these properties
to the array of props. */

hr = HrBuildSenderProps(lpxpl, lpPropArray, ulTag, lpszToken,
lpszAddrType, lpMessage, &cValues, lpMsgProps);

if (hr)
{
DebugTrace("HrBuildSenderProps() failed.\n");
goto ret;
}
break;

case tagReplyTo:
hr = HrAddRecipToReplyList(lpxpl, lpszToken, lpszAddrType,
&lpszReplyNames, &cbReplyEntryList, &lpReplyEntryList);

if (hr)
goto ret;
break;

case tagDate:
lpMsgProps[cValues].ulPropTag = PR_CLIENT_SUBMIT_TIME;
FileTimeFromSzTime(lpszToken, &lpMsgProps[cValues++].Value.ft);
break;

case tagTo:
case tagCc:
hr = HrAddRecipToAdrList(lpxpl, (LONG) ulTag - tagDate, lpszToken,
lpszAddrType, lpMyRecipList);
if (hr)
goto ret;
break;

case tagSubject:
case tagTextItem:
hr = HrGetStreamedProp(lpxpl, lpSof, lpMessage,
((ulTag == tagSubject) ? PR_SUBJECT : PR_BODY),
&cValues, lpMsgProps, szLine, &ulTag, &lpszToken);

if (HR_FAILED(hr))
goto ret;

if(S_OK ==GetScode(hr))
fHaveTagAndToken = TRUE;
break;

case tagPrioLow:
case tagPrioNormal:
case tagPrioUrgent:
lpMsgProps[cValues].ulPropTag = PR_PRIORITY;
lpMsgProps[cValues++].Value.l = tagPrioNormal - ulTag;
break;

case tagFileItem:
goto ret;

case tagMessage:
case tagBcc:
case tagContents:
default:
break;
} /* end switch() */
} /* end if() */
} /* end while() */

ret:
if (lpszReplyNames && lpReplyEntryList)
{
lpMsgProps[cValues].ulPropTag = PR_REPLY_RECIPIENT_NAMES;
lpMsgProps[cValues++].Value.LPSZ = lpszReplyNames;

lpMsgProps[cValues].ulPropTag = PR_REPLY_RECIPIENT_ENTRIES;
lpMsgProps[cValues].Value.bin.cb = cbReplyEntryList;
lpMsgProps[cValues++].Value.bin.lpb = (LPBYTE) lpReplyEntryList;
}

if (cValues)
lpMessage->lpVtbl->SetProps(lpMessage, cValues, lpMsgProps, NULL);

if (lpMyRecipList && lpMyRecipList->lpAdrList)
{
hr = lpMessage->lpVtbl->ModifyRecipients(lpMessage,
MODRECIP_ADD, lpMyRecipList->lpAdrList);

FreeMyAdrList(lpxpl, lpMyRecipList);

if (hr)
DebugTrace("ModifyRecipients failed.\n");
}
else
lpxpl->FreeBuffer(lpMyRecipList);

lpxpl->FreeBuffer(lpMsgProps);
lpxpl->FreeBuffer(lpszReplyNames);
lpxpl->FreeBuffer(lpReplyEntryList);

DebugTraceResult(HrIMsgFromTextMsg(), hr);

return hr;
}


/*
- HrBuildSenderProps
-
* Purpose:
* Creates the 5 identity properties: PR_***_SEARCH_KEY,
* PR_***_NAME, PR_***_ENTRYID, PR_***_EMAIL_ADDRESS,
* PR_***_ADDRTYPE, where *** is either SENDER or
* SENT_REPRESENTING.
*
* Parameters:
* lpxpl The transports logon object
* lpPropArray The session logon properties
* ulTag Either tagFrom or tagRepresenting
* lpszToken Display name and address for triplet
* lpszAddrType This transports address type
* lpMessage The spoolers IMessage object
* lpcValues Count of and index into lpMsgProps
* lpMsgProps Array of message properties we are building
*
* Returns:
* hr Indicating Success/Failure
*/

HRESULT
HrBuildSenderProps(LPXPL lpxpl,
LPSPropValue lpPropArray,
ULONG ulTag,
LPTSTR lpszToken,
LPTSTR lpszAddrType,
LPMESSAGE lpMessage,
ULONG * lpcValues,
LPSPropValue lpMsgProps)
{
SCODE sc;
HRESULT hr;
LPTSTR lpszDisplayName = NULL;
LPTSTR lpszAddress = NULL;
LPTSTR lpsz;
ULONG cbEntryID = 0;
LPBYTE lpEntryID = NULL;
LPBYTE lpb = NULL;
ULONG cbSK;
LPBYTE lpSearchKey;
LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
ULONG cValues = *lpcValues;

lpsz = strtok(lpszToken, "[");

sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1,
lpMsgProps, &lpszDisplayName);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("AllocateMore failed.\n");
goto ret;
}

lstrcpy(lpszDisplayName, lpsz);

lpsz = strtok(NULL, "]");

sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1,
lpMsgProps, &lpszAddress);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("AllocateMore failed.\n");
goto ret;
}

lstrcpy(lpszAddress, lpsz);

/* Create OneOff Entry ID for Sender/Delegate */

hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
lpszDisplayName, lpszAddrType, lpszAddress, 0,
&cbEntryID, (LPENTRYID FAR *) &lpEntryID);

if (hr)
{
DebugTrace("CreateOneOff() failed.\n");
goto ret;
}

/* Chain the EntryID to the lpMsgProp block */

sc = lpxpl->AllocateMore(cbEntryID, lpMsgProps, &lpb);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("AllocateMore failed.\n");
goto ret;
}

if (cbEntryID)
memcpy(lpb, lpEntryID, (size_t) cbEntryID);

lpxpl->FreeBuffer(lpEntryID);
lpEntryID = NULL;

/* Make the PR_***_SEARCH_KEY */

hr = HrMakeSearchKey(lpxpl, lpMsgProps, lpszAddrType,
lpszAddress, &cbSK, &lpSearchKey);

if (hr)
{
DebugTrace("HrMakeSearchKey() failed.\n");
goto ret;
}

if (ulTag == tagFrom)
{
lpMsgProps[cValues].ulPropTag = PR_SENDER_NAME;
lpMsgProps[cValues + 1].ulPropTag = PR_SENDER_ENTRYID;
lpMsgProps[cValues + 2].ulPropTag = PR_SENDER_SEARCH_KEY;
lpMsgProps[cValues + 3].ulPropTag = PR_SENDER_ADDRTYPE;
lpMsgProps[cValues + 4].ulPropTag = PR_SENDER_EMAIL_ADDRESS;

if (!lstrcmpi(lpszAddress,
ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS,
lpPropArray).Value.LPSZ))
SetFromMeFlag(lpxpl, lpMessage);
}
else
{
Assert(ulTag == tagRepresenting);
lpMsgProps[cValues].ulPropTag = PR_SENT_REPRESENTING_NAME;
lpMsgProps[cValues + 1].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
lpMsgProps[cValues + 2].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
lpMsgProps[cValues + 3].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
lpMsgProps[cValues + 4].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
}

lpMsgProps[cValues++].Value.LPSZ = lpszDisplayName;
lpMsgProps[cValues].Value.bin.cb = cbEntryID;
lpMsgProps[cValues++].Value.bin.lpb = lpb;
lpMsgProps[cValues].Value.bin.cb = cbSK;
lpMsgProps[cValues++].Value.bin.lpb = lpSearchKey;
lpMsgProps[cValues++].Value.LPSZ = lpszAddrType;
lpMsgProps[cValues++].Value.LPSZ = lpszAddress;

*lpcValues = cValues;

ret:
lpxpl->FreeBuffer(lpEntryID);

DebugTraceResult(HrBuildSenderProps(), hr);
return hr;
}


/*
- HrGetStreamedProp
-
* Purpose:
* Reads either the PR_BODY or PR_SUBJECT from the message file and

*      adds it to the spoolers IMessage. 
*
* Parameters:
* lpSof The stream we are reading the message from
* lpMsg The spoolers IMessage we are building up
* ulPropTag Either PR_SUBJECT or PR_BODY
* lpcValues Current size of the lpMsgProps array
* lpMsgProps The message property array we are building
* lpszLine Char array we use to read lines into
* lpulTag The text file tag we return as a side effect
* lppszToken The token we return as a side effect
*
* Returns:
* hr Indicating Success/Failure
*
* 10/5/95 the functon can return S_FALSE when the call to OpenProperty fails.
* in this case the property still gets set using SetProps, but we don't
* get the next token for the caller. So S_FALSE is to inform the
* caller that we didn't get token for him and he has to do it himself.
*/

HRESULT
HrGetStreamedProp(LPXPL lpxpl,
LPSTREAM lpSof,
LPMESSAGE lpMsg,
ULONG ulPropTag,
ULONG * lpcValues,
LPSPropValue lpMsgProps,
LPTSTR lpszLine,
ULONG * lpulTag,
LPTSTR * lppszToken)
{
SCODE sc;
HRESULT hr;
LPSTREAM lpStrm = NULL;
LPTSTR lpsz = NULL;
ULONG cbCRLF = lstrlen(szCRLF);
ULONG cbRead;
ULONG cbWritten;

#define cbSubjMax 4096
LPSTR szSubj = NULL;

sc = lpxpl->AllocateBuffer(cbSubjMax, &szSubj);
if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("Allocation failed.\n");
goto ret;
}

*szSubj = '\0';

hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

if (hr)
{
DebugTrace("HrGetLine failed.\n");
goto ret;
}

if (ulPropTag == PR_SUBJECT)
{
/* Once in the context of the subject, go until we reach the
next tag. Then return and process the next tag and token */

UINT cbCurrentSubj = 0;
while (!FGetTagAndToken(lpszLine, lpulTag, lppszToken))
{
if(cbCurrentSubj < cbSubjMax)
{
cbCurrentSubj += lstrlen(lpszLine);

if(cbCurrentSubj < cbSubjMax)
lstrcat(szSubj, lpszLine);
}

hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

if (hr)
goto ret;
}

/* this is in order to get inside the 'if' clause after OpenProperty*/
hr = 1;
}
else
{
/* If we fail to open a stream on the property, then slam
this line into the property array and break outta here */

hr = lpMsg->lpVtbl->OpenProperty(lpMsg, ulPropTag,
(LPIID) &IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY,
(LPUNKNOWN *) &lpStrm);
}

if (hr)
{
LPSTR szBuf = (ulPropTag == PR_SUBJECT) ? szSubj : lpszLine;
sc = lpxpl->AllocateMore(lstrlen(szBuf) + 1, lpMsgProps, &lpsz);

if (sc)
{
hr = ResultFromScode(sc);
DebugTrace("Allocation failed.\n");
goto ret;
}

lstrcpy(lpsz, szBuf);
lpMsgProps[*lpcValues].ulPropTag = ulPropTag;
lpMsgProps[(*lpcValues)++].Value.LPSZ = lpsz;

hr = (ulPropTag == PR_SUBJECT) ? hrSuccess : ResultFromScode(S_FALSE);
goto ret;
}

/* Once we're in the context of the body, the only
valid Tag is the TNEF File Item Tag. So, we
ignore all other message text lines that may
contain a Tag (by chance). */

while (TRUE)
{
FGetTagAndToken(lpszLine, lpulTag, lppszToken);

if ((*lpulTag == tagFileItem) &&
!lstrcmp(*lppszToken, "MESSAGE.TNF"))
break;

TraceFailedWrite(hr = lpStrm->lpVtbl->Write(lpStrm, lpszLine,
lstrlen(lpszLine), &cbWritten), ret);

if (cbRead < MAX_LINE - 1)
{
TraceFailedWrite(lpStrm->lpVtbl->Write(lpStrm,
szCRLF, cbCRLF, &cbWritten), ret);
}

hr = HrGetLine(lpSof, MAX_LINE, lpszLine, &cbRead);

if (hr)
break;
}


ret:
UlRelease(lpStrm);

lpxpl->FreeBuffer(szSubj);

DebugTraceResult(HrGetStreamedProp(), hr);
return hr;
}


/*
- HrAddRecipToAdrList
-
* Purpose:
* Called by HrIMsgFromTextMsg() to add a single recipient to the
* AdrList. Pre-allocates space (cMaxEntries) for 10 AdrEntrys the
* first time called. If all cMaxEntries slots are filled, then we
* ReAlloc for (cMaxEntries + cMaxEntries/2) more slots and continue
* adding the recipient. New memory is allocated for the attributes
* passed in and chained-up to the rgProps array.
*
* Parameters:
* lpxpl Needed for access to support obj and mem allocators
* ulRecipType MAPI_TO or MAPI_CC
* lpszNameAddr recipient token (Format: Display Name[email-address] )
* lpszAddrType This transports Address Type
* lpMyAdrList Pointer to the Recipient List
*
* Returns:
* hr Indicating Suucess/Failure
*/

HRESULT
HrAddRecipToAdrList(LPXPL lpxpl, LONG lRecipType,
LPTSTR lpszNameAddr, LPTSTR lpszAddrType, LPMYADRLIST lpMyAdrList)
{
HRESULT hr = hrSuccess;
SCODE sc;
BOOL fAlloc = FALSE;
BOOL fReAlloc = FALSE;
ULONG cb;
ULONG cMaxEntries;
LPADRLIST lpAdrList = NULL;
LPSPropValue rgProps = NULL;
LPTSTR lpsz;
LPTSTR lpszDisplayName = NULL;
LPTSTR lpszAddress = NULL;
LPTSTR lpszAddrTypeT = NULL;
ULONG cbEntryID = 0;
LPBYTE lpEntryID = NULL;
LPBYTE lpb = NULL;
LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
enum enumRecipProps
{
iRecipType,
iDisplayName,
iAddrType,
iAddress,
iEntryID,
cRecipProps
};

/* Determine if we need to Allocate or ReAllocate memory */

if (!lpMyAdrList->cMaxEntries)
{
/* First time in; list should be NULL */
Assert(lpMyAdrList->lpAdrList == NULL);
fAlloc = TRUE;
cMaxEntries = 10;
}
else if (lpMyAdrList->lpAdrList &&
(lpMyAdrList->lpAdrList->cEntries == lpMyAdrList->cMaxEntries))
{
/* List is full; we need to ReAlloc */
fReAlloc = TRUE;
cMaxEntries = lpMyAdrList->cMaxEntries + lpMyAdrList->cMaxEntries / 2;
}
else
{
/* List exists and is not full; just point to it */
Assert(lpMyAdrList->lpAdrList);
Assert(lpMyAdrList->lpAdrList->cEntries < lpMyAdrList->cMaxEntries);
lpAdrList = lpMyAdrList->lpAdrList;
}

/* If the list was NULL or full we'll Alloc/ReAlloc */

if (fAlloc || fReAlloc)
{
cb = CbNewADRLIST(cMaxEntries);
sc = lpxpl->AllocateBuffer(cb, &lpAdrList);
if (sc)
{
DebugTrace("AllocateBuffer() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Zero-out new list */

memset(lpAdrList, 0, (size_t) cb);

if (fReAlloc)
{
/* We're ReAllocing; copy old list into new memory */

cb = CbNewADRLIST(lpMyAdrList->lpAdrList->cEntries);
if (cb)
memcpy(lpAdrList, lpMyAdrList->lpAdrList, (size_t) cb);

/* Free old list */

lpxpl->FreeBuffer(lpMyAdrList->lpAdrList);
}

/* Fix-up size and pointer elements */

lpMyAdrList->cMaxEntries = cMaxEntries;
lpMyAdrList->lpAdrList = lpAdrList;
}

/* Allocate room for cRecipProps PropValues and chain memory needed
for AdrEntry data to the rgProps block to make freeing easier. */

sc = lpxpl->AllocateBuffer(cRecipProps * sizeof(SPropValue), &rgProps);

if (sc)
{
DebugTrace("AllocateBuffer() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Allocate memory for AddrType */

sc = lpxpl->AllocateMore(lstrlen(lpszAddrType) + 1, rgProps, &lpszAddrTypeT);

if (sc)
{
DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Copy AddrType into chained memory buffer */

lstrcpy(lpszAddrTypeT, lpszAddrType);

/* Break lpszNameAddr into lpszDisplayName and lpszAddress */

lpsz = strtok(lpszNameAddr, "[");

sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1, rgProps, &lpszDisplayName);

if (sc)
{
DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Copy Display Name into chained memory buffer */

lstrcpy(lpszDisplayName, lpsz);

lpsz = strtok(NULL, "]");

sc = lpxpl->AllocateMore(lstrlen(lpsz) + 1, rgProps, &lpszAddress);

if (sc)
{
DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Copy Address into chained memory buffer */

lstrcpy(lpszAddress, lpsz);

/* Create OneOff Entry ID */

hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
lpszDisplayName, lpszAddrType, lpszAddress, 0,
&cbEntryID, (LPENTRYID FAR *) &lpEntryID);

if (hr)
{
DebugTrace("CreateOneOff() failed in HrAddRecipToAdrList()");
goto ret;
}

/* We need to copy the EntryID into chained memory */

sc = lpxpl->AllocateMore(cbEntryID, rgProps, &lpb);

if (sc)
{
DebugTrace("AllocateMore() failed in HrAddRecipToAdrList()");
goto ret;
}

/* Copy EntryID into chained memory buffer */

if (cbEntryID)
memcpy(lpb, lpEntryID, (size_t) cbEntryID);

lpxpl->FreeBuffer(lpEntryID);
lpEntryID = NULL;

/* Now, build the PropValue array */

rgProps[iRecipType].ulPropTag = PR_RECIPIENT_TYPE;
rgProps[iRecipType].Value.l = lRecipType;

rgProps[iDisplayName].ulPropTag = PR_DISPLAY_NAME;
rgProps[iDisplayName].Value.LPSZ = lpszDisplayName;

rgProps[iAddrType].ulPropTag = PR_ADDRTYPE;
rgProps[iAddrType].Value.LPSZ = lpszAddrTypeT;

rgProps[iAddress].ulPropTag = PR_EMAIL_ADDRESS;
rgProps[iAddress].Value.LPSZ = lpszAddress;

rgProps[iEntryID].ulPropTag = PR_ENTRYID;
rgProps[iEntryID].Value.bin.cb = cbEntryID;
rgProps[iEntryID].Value.bin.lpb = lpb;

/* It's now safe to hook in the new AdrEntry */

lpAdrList->aEntries[lpAdrList->cEntries].cValues = cRecipProps;
lpAdrList->aEntries[lpAdrList->cEntries++].rgPropVals = rgProps;

return hrSuccess;

ret:
lpxpl->FreeBuffer(rgProps);
lpxpl->FreeBuffer(lpEntryID);

if (lpAdrList != lpMyAdrList->lpAdrList)
lpxpl->FreeBuffer(lpAdrList);

if (!hr && sc)
hr = ResultFromScode(sc);

DebugTraceResult(HrAddRecipToAdrList()Failed !, hr);

return hr;
}


/*
- HrAddRecipToReplyList
-
* Purpose:
* Builds the PR_REPLY_RECIPIENT_NAMES and PR_REPLY_RECIPIENT_ENTRIES
* properties by re-allocing as new ones are added to the list.
*
* Parameters:
* lpxpl Points to Transport Logon object
* lpszToken Contains Display Name and E-Mail Address
* lpszAddrType Address Type for this transport
* lppszNames Semi-colon delimited list of Display Names
* lpcbEIDList Current size of EntryList
* lppEIDList Pointer to current EntryList
*
* Returns:
* hr Indicating Suucess/Failure
*/

HRESULT
HrAddRecipToReplyList(LPXPL lpxpl, LPTSTR lpszToken, LPTSTR lpszAddrType,
LPTSTR * lppszNames, ULONG * lpcbEIDList, LPFLATENTRYLIST * lppEIDList)
{
SCODE sc;
HRESULT hr = hrSuccess;
LPTSTR lpszAddress;
LPTSTR lpszName;
LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
ULONG cbEID;
LPBYTE lpEID = NULL;
LPFLATENTRYLIST lpOld = *lppEIDList;
ULONG cbOld;
LPFLATENTRYLIST lpNew = NULL;
LPFLATENTRY lpEntry;
ULONG cbNew;
LPTSTR lpszNewNames = NULL;

lpszName = strtok(lpszToken, "[");

lpszAddress = strtok(NULL, "]");

/* Create OneOff Entry ID for this Recipient */

hr = lpMAPISup->lpVtbl->CreateOneOff(lpMAPISup,
lpszName, lpszAddrType, lpszAddress, 0,
&cbEID, (LPENTRYID FAR *) &lpEID);

if (hr)
goto ret;

/* Determine size of new list and allocate memory for it.
The "+ 3) & -4L" will round up the allocation to be a
multiple of 4 bytes. */

if (lpOld)
{
Assert(!IsBadReadPtr(lpOld, CbNewFLATENTRYLIST(0)));
Assert(!IsBadReadPtr(lpOld->abEntries, (UINT) lpOld->cbEntries));

cbOld = lpOld->cbEntries;
cbNew = (cbOld + offsetof(FLATENTRY, abEntry) + cbEID + 3) & -4L;
}
else
{
cbNew = cbOld = (cbEID + offsetof(FLATENTRY, abEntry) + 3) & -4L;
}

sc = lpxpl->AllocateBuffer(cbNew + offsetof(FLATENTRYLIST, abEntries), &lpNew);

if (sc)
{
hr = ResultFromScode(sc);
goto ret;
}

/* If Re-Allocing then copy old list and new EID, else build new list */

if (lpOld)
{
ULONG cbNewOff = (cbOld + 3) & -4L;

/* Copy the old data to the new structure */

lpNew->cEntries = lpOld->cEntries + 1;
lpNew->cbEntries = cbNew;

if (cbOld)
memcpy(lpNew->abEntries, lpOld->abEntries, (size_t) cbOld);

/* Resolve the pointer to the new FLATENTRY */

lpEntry = (LPFLATENTRY) & lpNew->abEntries[cbNewOff];

}
else
{
lpNew->cEntries = 1;
lpNew->cbEntries = cbNew;

/* Resolve the pointer to the new FLATENTRY */

lpEntry = (LPFLATENTRY) lpNew->abEntries;
}

/* Add in the new FLATENTRY */

lpEntry->cb = cbEID;
if (cbEID)
memcpy(lpEntry->abEntry, lpEID, (size_t) cbEID);

/* Now, build the Display Name(s) String */

if (*lppszNames)
{
/* We're Re-Allocing: copy old string and cat new one */

sc = lpxpl->AllocateBuffer(lstrlen(*lppszNames) + lstrlen(lpszName) + 3,
&lpszNewNames);

if (sc)
{
hr = ResultFromScode(sc);
goto ret;
}

lstrcpy(lpszNewNames, *lppszNames);
lstrcat(lpszNewNames, "; ");
lstrcat(lpszNewNames, lpszName);

lpxpl->FreeBuffer(*lppszNames);
}
else
{
/* First name; just alloc and copy... */

sc = lpxpl->AllocateBuffer(lstrlen(lpszName) + 1, &lpszNewNames);

if (sc)
{
hr = ResultFromScode(sc);
goto ret;
}

lstrcpy(lpszNewNames, lpszName);
}

/* It's now safe to hook in the new list. */
/* Free old list and pass back new one. */

lpxpl->FreeBuffer(lpOld);

*lppEIDList = lpNew;
*lpcbEIDList = cbNew + offsetof(FLATENTRYLIST, abEntries);
*lppszNames = lpszNewNames;

ret:
lpxpl->FreeBuffer(lpEID);

if (hr)
{
lpxpl->FreeBuffer(lpNew);
lpxpl->FreeBuffer(lpszNewNames);
}
return hr;
}

/*
- HrMakeSearchKey
-
* Purpose:
* Makes a Search Key (for the PR_???_SEARCH_KEY property) from
* the values passed in. Memory is chained to some parent block.
* SearchKeys look like: ADDRTYPE:EMAILADDRESS.
*
* Parameters:
* lpxpl Points to Transport Logon object
* lpParent Memory block to chain Search Key to
* lpszAddrType Address Type
* lpszAddress E-mail Address
* lpcbSK Returned size of Search Key
* lppSK The returned Search Key
*
* Returns:
* hr Indicating Suucess/Failure
*/

HRESULT
HrMakeSearchKey(LPXPL lpxpl, LPVOID lpParent, LPTSTR lpszAddrType,
LPTSTR lpszAddress, ULONG * lpcbSK, LPBYTE * lppSK)
{
SCODE sc;
HRESULT hr = hrSuccess;
LPBYTE lpb = NULL;
ULONG ulSize;

/* The 2 is for the colon and the NULL terminator */

ulSize = sizeof(TCHAR) * (2 + lstrlen(lpszAddrType) + lstrlen(lpszAddress));

sc = lpxpl->AllocateMore(ulSize, lpParent, &lpb);

if (sc)
{
hr = ResultFromScode(sc);
goto ret;
}

/* We need to convert to upper case, that's the law! */

wsprintf((LPTSTR) lpb, "%s:%s", lpszAddrType, lpszAddress);
CharUpperBuff((LPTSTR) lpb, (UINT) (ulSize - sizeof(TCHAR)));

*lpcbSK = ulSize;
*lppSK = lpb;

ret:
return hr;
}

/*
- HrGetLine
-
* Purpose:
* Kind of like fgets() except it strips CR\LF pairs for us.
* Reads cbDest bytes from the lpSof stream or until a CR/LF
* pair is reached, whichever comes first. Returns count of
* bytes read into lpsz.
*
* Parameters:
* lpSof Points to an OLE 2.0 Stream (to be read from)
* cbDest Size of memory pointed to by lpsz
* lpsz Points to a chunck of memory that receives the
* line being read in. Must be of size cbDest.
* pcbRead Receives the count of bytes actually read in.
*
* Returns:
* hr Indicating Suucess/Failure
*/

HRESULT
HrGetLine(LPSTREAM lpSof, ULONG cbDest, LPTSTR lpsz, ULONG * pcbRead)
{
HRESULT hr = S_OK;
BOOL fCRLF = FALSE;
TCHAR rgch1[1];
TCHAR rgch2[1];
ULONG cbRead;

if (!lpSof || (cbDest == 0) || !lpsz || !pcbRead)
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

for (*pcbRead = 0; *pcbRead < cbDest - sizeof(TCHAR);)
{
/* read one TCHAR from stream */

hr = lpSof->lpVtbl->Read(lpSof, (LPVOID) rgch1, sizeof(TCHAR), &cbRead);

if (hr || (cbRead != sizeof(TCHAR)))
break;

/* Test for CR/LF pair; if not then add to line */

if (*rgch1 == '\r')
{
hr = lpSof->lpVtbl->Read(lpSof, (LPVOID) rgch2, sizeof(TCHAR), &cbRead);

if (hr)
break;

if (cbRead == sizeof(TCHAR))
{
if (*rgch2 == '\n')
{
fCRLF = TRUE;
break;
}
}
else
{
*lpsz++ = *rgch1;
*lpsz++ = *rgch2;
*pcbRead += 2 * sizeof(TCHAR);
}
}
else
{
*lpsz++ = *rgch1;
*pcbRead += sizeof(TCHAR);
}
}

/* NULL terminate and leave */

*lpsz = '\0';

/* Test for the EOF case. Since the stream
won't return errors, we will!!!! */

if (!fCRLF && !*pcbRead)
hr = ResultFromScode(MAPI_E_CALL_FAILED);

return hr;
}


/*
- FGetTagAndToken
-
* Purpose:
* Breaks a line read from the input stream into Tag and Token.
* If no Tag is found, then returns FALSE indicating this is not
* a Tag'd line.
*
* Parameters:
* lpsz A line from the messsage file that may have
* the format: 'Tag: Token'. Must be of size MAX_LINE.
* pulTag Will receive the index of lpsz's Tag in rgszTag
* lppszToken Will point to the token in lpsz
*
* Returns:
* TRUE If lpsz starts with a valid Tag
* FALSE otherwise
*/

BOOL
FGetTagAndToken(LPTSTR lpsz, ULONG * pulTag, LPTSTR * lppszToken)
{
ULONG uli;
TCHAR chT;
LPTSTR lpszT;

if (!lpsz || !pulTag || !lppszToken)
return FALSE;

/* Tags end with ':' If lpsz has a ':' then it MIGHT be
a Tag'd line, else it's definitely NOT a Tag'd line. */

lpszT = strchr(lpsz, ':');

if (!lpszT)
return FALSE;

/* Check that we're not at the MAX_LINE extent of lpsz. If we are
then just return, cause this can't possibly be a Tag'd line!
The '3' accounts for the space, colon, and null terminator. */

if ((lpszT - lpsz) > (MAX_LINE - 3))
return FALSE;

/* Swap *(lpszT+2) with a NULL to seperate Tag from Token */

lpszT += 2;
chT = *lpszT;
*lpszT = '\0';

/* Look-Up 'Potential' Tag in Tag Table */

for (uli = 0; uli < NUM_TAGS; uli++)
{
if (!lstrcmp(lpsz, rgszTags[uli]))
{
/* Found! Remember index */

*pulTag = uli;
break;
}
}

/* Swap that NULL out. lpszT now points to the token (maybe) */

*lpszT = chT;

if (uli == NUM_TAGS)
return FALSE; /* Tag wasn't found; it's just a line */

*lppszToken = lpszT;

return TRUE;
}


/*
- FileTimeFromSzTime
-
* Purpose:
* Converts the textized data field in the text file format
* to a FILETIME struct format. If we encounter errors in
* parsing the lpszDateTime string, we jump to the conversion
* call and will translate as much as we've filled in so far.
*
* Parameters:
* lpszDateTime Date/Time in the format: yyyy/mm/dd hh:mm
* pft Pointer to a FILETIME struct
*/

void
FileTimeFromSzTime(LPTSTR lpszDateTime, FILETIME * pft)
{
SYSTEMTIME systime =
{0, 0, 0, 0, 0, 0, 0, 0};
LPTSTR lpsz;

/* Feeble attempt at parameter validation! */

if (!lpszDateTime || !pft)
return;

/* Grab the Year */

lpsz = strtok(lpszDateTime, "/");
if (!lpsz)
goto ret;

systime.wYear = atoi(lpsz);

/* Grab the Month */

lpsz = strtok(NULL, "/");
if (!lpsz)
goto ret;

systime.wMonth = atoi(lpsz);

/* Grab the Day */

lpsz = strtok(NULL, " ");
if (!lpsz)
goto ret;

systime.wDay = atoi(lpsz);

/* Grab the Hour */

lpsz = strtok(NULL, ":");
if (!lpsz)
goto ret;

systime.wHour = atoi(lpsz);

/* Grab the Minutes */

lpsz = strtok(NULL, "\r\n:");
if (!lpsz)
goto ret;

systime.wMinute = atoi(lpsz);

ret:
SystemTimeToFileTime(&systime, pft);
}


/*
- SetFromMeFlag
-
* Purpose:
* Sets the PR_MESSAGE_FLAGS MSGFLAG_FROMME bit to on.
*
*/

VOID
SetFromMeFlag(LPXPL lpxpl, LPMESSAGE lpMsg)
{
HRESULT hResult;
LPSPropValue lpProp = NULL;

/* Get the current state of the Message Flags */

hResult = HrGetOneProp((LPMAPIPROP)lpMsg, PR_MESSAGE_FLAGS, &lpProp);

if (HR_FAILED(hResult))
goto ret;

/* Add the FromMe bit */

lpProp->Value.l |= MSGFLAG_FROMME;

hResult = HrSetOneProp((LPMAPIPROP)lpMsg, lpProp);

ret:
lpxpl->FreeBuffer(lpProp);
}