XPQUEUE.C
上传用户:bangxh
上传日期:2007-01-31
资源大小:42235k
文件大小:54k
源码类别:

Windows编程

开发平台:

Visual C++

  1. /*
  2.  -  X P Q U E U E . C
  3.  -
  4.  *  Purpose:
  5.  *      Code to support the background queueing mechanism for the Sample
  6.  *      Transport Provider.
  7.  *      This module contains the following SPI entry points:
  8.  *
  9.  *          Idle()
  10.  *
  11.  *      Additional support functions found here:
  12.  *
  13.  *          HrBuildAdrList()
  14.  *          HrSendOneMessage()
  15.  *          HrIMsgToTextMsg()
  16.  *          HrPrepareRecipientTable()
  17.  *          HrCrackSenderEID()
  18.  *          FPropIndex()
  19.  *          FormatFileTime()
  20.  *          FreeMyAdrList()
  21.  *          ScLoadTnef()
  22.  *
  23.  *      Also present in this module is the transmit list management code and
  24.  *      all the recipient list manipulation logic.
  25.  *
  26.  *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved.
  27.  */
  28. #include "xppch.h"
  29. #include <tnef.h>
  30. #include "xpsof.h"
  31. #include "xptxtmsg.h"
  32. #include "xpresrc.h"
  33. BOOL FIsTextizedProp(ULONG ulPropTag);
  34. /* Generic BAD_VALUE for use in comparisons below */
  35. #define BAD_VALUE (0xFFFFFFFF)
  36. /*
  37.  -  lpxpl->lpVtbl->Idle
  38.  -
  39.  *  Purpose:
  40.  *      Called by the Spooler periodically in its idle loop.
  41.  *
  42.  *      The Transport will determine if there's any incoming mail for the
  43.  *      Spooler and will TransportNotify (NOTIFY_NEWMAIL) if so.
  44.  *
  45.  *  Parameters:
  46.  *      ulFlags             Flags. None are currently defined.
  47.  *
  48.  *  Returns:
  49.  *      (HRESULT)           Errors encountered if any.
  50.  *
  51.  *  Operation:
  52.  *      If inbound operation is currently enabled, call the Poll() entry point
  53.  *      to see if there's anything there. If we find something with Poll(), then
  54.  *      SpoolerNotify(NOTIFY_NEWMAIL).
  55.  */
  56. STDMETHODIMP
  57. XPL_Idle(LPXPL lpxpl, ULONG ulFlags)
  58. {
  59.     HRESULT hResult = 0;
  60.     LPDEFMSG lpDefMsg;
  61.     /* Incoming messages? */
  62.     if (lpxpl->ulTransportStatus & STATUS_INBOUND_ENABLED)
  63.     {
  64.         ULONG ulT = 0;
  65.         if ((hResult = XPL_Poll(lpxpl, &ulT)) != 0)
  66.             goto ret;
  67.         if (ulT != 0)
  68.         {
  69.             /* SpoolerNotify() could theoretically return a nonzero
  70.             HRESULT, but it's much easier and unambiguous to ignore
  71.             that possibility and field Spooler errors elsewhere. */
  72.             (void)lpxpl->lpMAPISup->lpVtbl->SpoolerNotify(lpxpl->lpMAPISup, NOTIFY_NEWMAIL, NULL);
  73.         }
  74.     }
  75.     if (lpxpl->fResendDeferred)
  76.     {
  77.         while (lpxpl->lpDeferredList)
  78.         {
  79.             lpDefMsg = lpxpl->lpDeferredList;
  80.             lpxpl->lpDeferredList = lpDefMsg->lpNext;
  81.             lpxpl->lpMAPISup->lpVtbl->SpoolerNotify(lpxpl->lpMAPISup,
  82.                 NOTIFY_SENTDEFERRED, &(lpDefMsg->sbinEIDDef));
  83.             lpxpl->FreeBuffer(lpDefMsg);
  84.         }
  85.         lpxpl->fResendDeferred = FALSE;
  86.     }
  87. ret:
  88.     DebugTraceResult(XPL_Idle, hResult);
  89.     return hResult;
  90. }
  91. /*
  92.  -  HrBuildAdrList
  93.  -
  94.  *  Called by outbound and inbound logic.
  95.  *
  96.  *  Traverses a restricted recipient table and collates the rows into
  97.  *  a "done" adrlist structure and a "not done" adrlist structure. The
  98.  *  only distinction between done and not done is the result of a call
  99.  *  to an optional function to attempt to send the open message to
  100.  *  a particular recipient.
  101.  *
  102.  *  Obviously, if the callback isn't specified, the "done" adrlist will
  103.  *  contain all recipients that matched the restriction.
  104.  *
  105.  *  Parameters:
  106.  *      lpxpl               Session in which we're executing
  107.  *      lpMessage           Message containing the recipient table
  108.  *      lpTable             Recipient table
  109.  *      fSetResponsibility  TRUE/FALSE = Set/Clear PR_RESPONSIBILITY
  110.  *                          in good list (clear bad list always)
  111.  *      lpfnCallBack        Routine to call when trying to send
  112.  *      lppMyAdrListGood    Where to store the list of done recips
  113.  *      lppMyAdrListBad     Where to store the list of not done
  114.  *
  115.  *  Returns:
  116.  *      (HRESULT)           Errors encountered if any.
  117.  *
  118.  *
  119.  *  Operation:
  120.  *      This routine builds a pair of MYADRLISTS from the (restricted)
  121.  *      table which is passed to it.
  122.  *
  123.  *      Do a SetColumns so we know where to find PR_EMAIL_ADDRESS, PR_ROWID
  124.  *      and other necessary columns. Included in the SetColumns is
  125.  *      PR_REPORT_TEXT as a placeholder for NDR information.
  126.  *
  127.  *      Then, until we run out of data,
  128.  *
  129.  *      1) QueryRows on the input table
  130.  *      2) For each row:
  131.  *          a) If lpfnCallBack and PR_EMAIL_ADDRESS, invoke callback.
  132.  *          b) If callback wasn't defined or if successful, add row to
  133.  *             "good" list, else add to "bad" list with NDR reason
  134.  *      3) Free RowSet
  135.  *
  136.  *      The caller can tell if any recipients remain by whether the
  137.  *      "not done" set contains any elements.
  138.  *
  139.  *      The "not done" set will set PR_RESPONSIBILITY to FALSE if the caller
  140.  *      wants "done" recipients set,
  141.  */
  142. HRESULT
  143. HrBuildAdrList(LPXPL lpxpl,
  144.     LPSPropValue lpPropArray,
  145.     LPMESSAGE lpMessage,
  146.     LPMAPITABLE lpTable,
  147.     BOOL fSetResponsibility,
  148.     LPMYCALLBACK lpfnCallBack,
  149.     LPMYADRLIST FAR * lppMyAdrListGood,
  150.     LPMYADRLIST FAR * lppMyAdrListBad)
  151. {
  152.     HRESULT hResult = hrSuccess;
  153.     SCODE sc = S_OK;
  154.     LPVOID lpvT;
  155.     TCHAR rgchBuffer[512];
  156.     LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
  157.     LPSRowSet lpRow = NULL;
  158.     LPMYADRLIST lpDone = NULL;
  159.     LPMYADRLIST lpNotDone = NULL;
  160.     LPSPropTagArray lpsptT = NULL;
  161.     LPMYADRLIST FAR *lppMyAdrList;
  162.     LPSPropTagArray lpsptNew;
  163.     LPSPropValue lpspvT;
  164.     ULONG ulT;
  165.     ULONG ulNew;
  166.     ULONG ulRow;
  167.     /*  These are the columns I need to have in the recipient table,
  168.         even if all I get is a placeholder. If you need to add any,
  169.         bump the definition of NUM_REQUIRED_COLS, add new defines
  170.         for the new columns, and add the new column proptags to the
  171.         switch below. If the property isn't necessarily going to be
  172.         in the table, use the PR_NULL trick you see below for the
  173.         PR_REPORT_TEXT property. */
  174.     enum enumColumns
  175.     {
  176.         COLUMN_PR_ROWID,
  177.         COLUMN_PR_EMAIL_ADDRESS,
  178.         COLUMN_PR_RESPONSIBILITY,
  179.         COLUMN_PR_REPORT_TEXT,
  180.         COLUMN_PR_RECIPIENT_TYPE,
  181.         NUM_REQUIRED_COLS
  182.     };
  183.     Assert(lppMyAdrListGood);
  184.     Assert(lppMyAdrListBad);
  185.     /*
  186.         Arrange the columns so that we'll have the recipient properties
  187.         we really want.
  188.         So as not to lose any recipient properties, we need to do it in
  189.         this fashion:
  190.         1)  QueryColumns() on the input table
  191.         2)  Build a new column set based on the properties we care
  192.             about plus all other properties of the input table
  193.         3)  SetColumns() on the input table
  194.         The properties we care about are the following:
  195.         1)  PR_ROWID
  196.         2)  PR_EMAIL_ADDRESS
  197.         3)  PR_RESPONSIBILITY */
  198.     /*  Get the complete column set */
  199.     hResult = lpTable->lpVtbl->QueryColumns(lpTable, TBL_ALL_COLUMNS, &lpsptT);
  200.     if (hResult)
  201.     {
  202.         DebugTrace("QueryColumns failed.n");
  203.         goto ret;
  204.     }
  205.     /*  Gotta have a PR_ROWID. That guarantees at least one column! */
  206.     Assert(lpsptT->cValues >= 1);
  207.     /*  Allocate a new column set, linked to the old one for cleanup */
  208.     ulT = CbNewSPropTagArray(NUM_REQUIRED_COLS + lpsptT->cValues);
  209.     sc = (*lpxpl->AllocateMore) (ulT, (LPVOID) lpsptT, (LPVOID) &lpsptNew);
  210.     if (sc)
  211.     {
  212.         hResult = ResultFromScode(sc);
  213.         DebugTrace("New column set allocation failed.n");
  214.         goto ret;
  215.     }
  216.     /*  Fill in the new column set, required fields first in our order. */
  217.     lpsptNew->aulPropTag[COLUMN_PR_ROWID] = PR_ROWID;
  218.     lpsptNew->aulPropTag[COLUMN_PR_EMAIL_ADDRESS] = PR_EMAIL_ADDRESS;
  219.     lpsptNew->aulPropTag[COLUMN_PR_RESPONSIBILITY] = PR_RESPONSIBILITY;
  220.     lpsptNew->aulPropTag[COLUMN_PR_REPORT_TEXT] = PR_NULL;
  221.     lpsptNew->aulPropTag[COLUMN_PR_RECIPIENT_TYPE] = PR_RECIPIENT_TYPE;
  222.     ulNew = NUM_REQUIRED_COLS;
  223.     for (ulT = 0; ulT < lpsptT->cValues; ulT++)
  224.     {
  225.         switch (lpsptT->aulPropTag[ulT])
  226.         {
  227.         case PR_REPORT_TEXT:
  228.             lpsptNew->aulPropTag[COLUMN_PR_REPORT_TEXT] = PR_REPORT_TEXT;
  229.             break;
  230.         case PR_ROWID:
  231.         case PR_EMAIL_ADDRESS:
  232.         case PR_RESPONSIBILITY:
  233.         case PR_RECIPIENT_TYPE:
  234.             break;
  235.         default:
  236.             lpsptNew->aulPropTag[ulNew++] = lpsptT->aulPropTag[ulT];
  237.             break;
  238.         }
  239.     }
  240.     lpsptNew->cValues = ulNew;
  241.     /*  Set the new columns */
  242.     hResult = lpTable->lpVtbl->SetColumns(lpTable, lpsptNew, 0L);
  243.     if (hResult)
  244.     {
  245.         DebugTrace("SetColumns failed.n");
  246.         goto ret;
  247.     }
  248.     /*  Free both old and new PropTagArrays. */
  249.     (*lpxpl->FreeBuffer) ((LPVOID) lpsptT);
  250.     lpsptT = NULL;
  251.     /*  Build two big ADRLISTs from this table. Do minimal allocations so
  252.         that all the reallocation code will be properly exercised. We can
  253.         optimize later when we've tested.
  254.         The obvious underlying assumption of this code is that it's
  255.         possible to get the entire recipient list into memory at once. */
  256. #ifdef DEBUG
  257. #define GROW_SIZE   1       /* How much we'll add on every reallocation. */
  258. #define QUERY_SIZE  1       /* How much we'll try for on query rows */
  259. #else
  260. #define GROW_SIZE   20
  261. #define QUERY_SIZE  20
  262. #endif
  263.     for (;;)
  264.     {
  265.         /*  Get some rows from the recipient table. */
  266.         hResult = lpTable->lpVtbl->QueryRows(lpTable, QUERY_SIZE, 0L, &lpRow);
  267.         if (hResult)
  268.         {
  269.             DebugTrace("QueryRows failed.n");
  270.             goto ret;
  271.         }
  272.         /*  Are we finished getting rows? */
  273.         if (!lpRow || !(lpRow->cRows))
  274.         {
  275.             /*  Free the row if any */
  276.             (*lpxpl->FreeBuffer) ((LPVOID) lpRow);
  277.             lpRow = NULL;
  278.             /*  We're finished now, break out of the (for (;;)) loop. */
  279.             break;
  280.         }
  281.         /*  Work our way through the RowSet */
  282.         for (ulRow = 0; ulRow < lpRow->cRows; ulRow++)
  283.         {
  284.             BOOL fDone = TRUE;
  285.             Assert(lpRow->aRow[ulRow].cValues);
  286.             Assert(lpRow->aRow[ulRow].lpProps);
  287.             lpspvT = lpRow->aRow[ulRow].lpProps;
  288.             /* Check our .2 second timer before processing each row. */
  289.             sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));
  290.             if (sc == MAPI_W_CANCEL_MESSAGE)
  291.             {
  292.                 DebugTrace("Cancelling message delivery.n");
  293.                 goto ret;
  294.             }
  295.             /*  See if we need to do the callback. If so, make the call now */
  296.             if (lpfnCallBack)
  297.             {
  298.                 Assert(lpspvT[COLUMN_PR_EMAIL_ADDRESS].ulPropTag == PR_EMAIL_ADDRESS);
  299.                 Assert(lpspvT[COLUMN_PR_RECIPIENT_TYPE].ulPropTag == PR_RECIPIENT_TYPE);
  300.                 hResult = lpfnCallBack(lpxpl,
  301.                     lpPropArray,
  302.                     lpMessage,
  303.                     lpspvT[COLUMN_PR_RECIPIENT_TYPE].Value.ul,
  304.                     lpspvT[COLUMN_PR_EMAIL_ADDRESS].Value.LPSZ,
  305.                     &fDone);
  306.                 if (hResult)
  307.                 {
  308.                     DebugTrace("IGNORE: Callback function  failed.n");
  309.                     hResult = hrSuccess;
  310.                 }
  311.             }
  312.             /*  Set the PR_RESPONSIBILITY flag to its indicated new state. */
  313.             Assert(lpRow->aRow[ulRow].cValues > COLUMN_PR_RESPONSIBILITY);
  314.             /*  Make the appropriate change to PR_RESPONSIBILITY. If we're
  315.                 setting it, just do it into whatever column we've set;
  316.                 is pointing at, Prop Tag and Value. If we're clearing it,
  317.                 we only need to do anything if there was a discrete column,
  318.                 and all that's needed is to set the Prop Tag to PR_NULL. */
  319.             if (fSetResponsibility)
  320.             {
  321.                 lpspvT[COLUMN_PR_RESPONSIBILITY].ulPropTag = PR_RESPONSIBILITY;
  322.                 lpspvT[COLUMN_PR_RESPONSIBILITY].Value.b = fDone;
  323.             }
  324.             else
  325.             {
  326.                 /*  We want to clear the flag */
  327.                 lpspvT[COLUMN_PR_RESPONSIBILITY].ulPropTag = PR_NULL;
  328.             }
  329.             /*  PR_NULL the PR_ROWID column if this will be a "bad" recipient, also
  330.                 adding NDR information to the row (in a column we set earlier) */
  331.             if (!fDone)
  332.             {
  333.                 TCHAR szReportText[512];
  334.                 lpspvT[COLUMN_PR_ROWID].ulPropTag = PR_NULL;
  335.                 /*  The Spooler will default to a NDR report and will fill in all
  336.                     required properties in the StatusRecips call. The only thing
  337.                     we need to do is to fill in a specific per-recipient text
  338.                     description of the problem (it defaults too but it's good
  339.                     to have real info from the horse's mouth) */
  340.                 LoadString(lpxpl->lpxppParent->hInst, IDS_REPORT_TEXT_MSG,
  341.                     szReportText, sizeof(szReportText));
  342.                 wsprintf(rgchBuffer, "%s%s", szReportText,
  343.                     lpspvT[COLUMN_PR_EMAIL_ADDRESS].Value.LPSZ);
  344.                 ulT = Cbtszsize(rgchBuffer);
  345.                 sc = lpxpl->AllocateMore(ulT, lpspvT, &lpvT);
  346.                 if (sc)
  347.                 {
  348.                     hResult = ResultFromScode(sc);
  349.                     DebugTrace("NDR text allocation failed.n");
  350.                     goto ret;
  351.                 }
  352.                 /*  Memory allocated, copy the formatted string and hook it into
  353.                     the pre-allocated column. */
  354.                 lstrcpy((LPTSTR) lpvT, rgchBuffer);
  355.                 lpspvT[COLUMN_PR_REPORT_TEXT].ulPropTag = PR_REPORT_TEXT;
  356.                 lpspvT[COLUMN_PR_REPORT_TEXT].Value.LPSZ = (LPTSTR) lpvT;
  357.             }
  358.             /*  Now point ourselves at one of the two LPMYADRLIST pointers */
  359.             lppMyAdrList = (fDone ? &lpDone : &lpNotDone);
  360.             /*  If we didn't already have a LPMYADRLIST, allocate one now. */
  361.             if (*lppMyAdrList == NULL)
  362.             {
  363.                 /*  Allocate an initial MYADRLIST structure. Then allocate
  364.                     enough memory for (GROW_SIZE) ADRENTRYs and store
  365.                     it into the structure. Initialize the members. */
  366.                 sc = (*lpxpl->AllocateBuffer) (sizeof(MYADRLIST), &lpvT);
  367.                 if (sc)
  368.                 {
  369.                     hResult = ResultFromScode(sc);
  370.                     DebugTrace("Initial MYADRLIST allocation failed.n");
  371.                     goto ret;
  372.                 }
  373.                 /*  Hook up the MYADRLIST with no entries. */
  374.                 *lppMyAdrList = (LPMYADRLIST) lpvT;
  375.                 (*lppMyAdrList)->cMaxEntries = 0;
  376.                 (*lppMyAdrList)->lpAdrList = NULL;
  377.                 /* Now allocate a ADRLIST with GROW_SIZE entries in it. */
  378.                 sc = (*lpxpl->AllocateBuffer) (CbNewADRLIST(GROW_SIZE), &lpvT);
  379.                 if (sc)
  380.                 {
  381.                     hResult = ResultFromScode(sc);
  382.                     DebugTrace("Initial ADRLIST allocation failed.n");
  383.                     goto ret;
  384.                 }
  385.                 /*  Now hook up this ADRLIST into the MYADRLIST. */
  386.                 (*lppMyAdrList)->lpAdrList = (LPADRLIST) lpvT;
  387.                 (*lppMyAdrList)->cMaxEntries = GROW_SIZE;
  388.                 (*lppMyAdrList)->lpAdrList->cEntries = 0;
  389.             }
  390.             /*  Make sure that the selected MYADRLIST has room for another row */
  391.             ulT = (*lppMyAdrList)->lpAdrList->cEntries + 1;
  392.             Assert(ulT <= (*lppMyAdrList)->cMaxEntries + 1);
  393.             if (ulT > (*lppMyAdrList)->cMaxEntries)
  394.             {
  395.                 LPADRLIST lpAdrListT;
  396.                 /*  Not enough space, we need to create a new ADRLIST. */
  397.                 ulT = CbNewADRLIST((*lppMyAdrList)->cMaxEntries + GROW_SIZE);
  398.                 sc = (*lpxpl->AllocateBuffer) (ulT, &lpvT);
  399.                 if (sc)
  400.                 {
  401.                     hResult = ResultFromScode(sc);
  402.                     DebugTrace("Reallocation of ADRLIST failed.n");
  403.                     goto ret;
  404.                 }
  405.                 /*  Got it, now copy the data from the old one */
  406.                 lpAdrListT = (LPADRLIST) lpvT;
  407.                 ulT = CbNewADRLIST((*lppMyAdrList)->cMaxEntries);
  408.                 if (ulT)
  409.                     memcpy(lpAdrListT, (*lppMyAdrList)->lpAdrList, (UINT) ulT);
  410.                 (*lppMyAdrList)->cMaxEntries += GROW_SIZE;
  411.                 /*  Exchange the pointers */
  412.                 lpvT = (LPVOID) (*lppMyAdrList)->lpAdrList;
  413.                 (*lppMyAdrList)->lpAdrList = lpAdrListT;
  414.                 /*  Free the old memory */
  415.                 lpxpl->FreeBuffer(lpvT);
  416.             }
  417.             /*  We have room now so store the new ADRENTRY. As part of the
  418.                 storage, we're going to copy the SRow pointer from the SRowSet
  419.                 into the ADRENTRY. Once we've done this, we won't need the
  420.                 SRowSet any more ... and the SRow will be deallocated when
  421.                 we unwind the ADRLIST. */
  422.             /*  Calculate location in ADRLIST for the new entry. */
  423.             ulT = (*lppMyAdrList)->lpAdrList->cEntries++;
  424.             /*  Copy the data from the SRowSet. */
  425.             (*lppMyAdrList)->lpAdrList->aEntries[ulT].cValues = lpRow->aRow[ulRow].cValues;
  426.             (*lppMyAdrList)->lpAdrList->aEntries[ulT].rgPropVals = lpRow->aRow[ulRow].lpProps;
  427.             /*  Now that we are finished with this row (eg it is in the
  428.                 adrlist) we want to disassociate it from the rowset. */
  429.             lpRow->aRow[ulRow].lpProps = NULL;
  430.         }
  431.         /*  We're finished with the SRowSet (since it is allocated
  432.             separately from the SRow). Deallocate it. */
  433.         lpxpl->FreeBuffer((LPVOID) lpRow);
  434.         lpRow = NULL;
  435.     }                           /* End of big ADRLIST builder loop */
  436.     /*  Fall into exit with hResult set if something failed. */
  437. ret:
  438.     /*  Clean up RowSet if any is left */
  439.     FreeProws(lpRow);
  440.     /*  Clean up column stuff if any's left */
  441.     lpxpl->FreeBuffer((LPVOID) lpsptT);
  442.     /*  If sc is set, feed it into hResult. Afterwards, hResult is the
  443.         determinant of whether there was an error or not. */
  444.     if (hResult)
  445.     {
  446.         /*  This memory should only be hanging around in the error case. */
  447.         FreeMyAdrList(lpxpl, lpDone);
  448.         FreeMyAdrList(lpxpl, lpNotDone);
  449.         *lppMyAdrListGood = NULL;
  450.         *lppMyAdrListBad = NULL;
  451.     }
  452.     else
  453.     {
  454.         /*  Success, pass the adrlists we built back to the caller */
  455.         *lppMyAdrListGood = lpDone;
  456.         *lppMyAdrListBad = lpNotDone;
  457.     }
  458.     DebugTraceResult(HrBuildAdrList, hResult);
  459.     return hResult;
  460. }
  461. /*
  462.  -  HrSendOneMessage
  463.  -
  464.  *  Purpose:
  465.  *      Called by HrBuildAdrList() to send a message to a recipient
  466.  *      that matched all of the caller's restrictions. Also called by
  467.  *      XPL_SubmitMessage() if transport is not peer-to-peer, in which
  468.  *      case the "Email address" is just our outbound directory.
  469.  *
  470.  *  Parameters:
  471.  *      lpxpl               The Transports logon object for this session
  472.  *      lpPropArray         Logon properties that have been copied from lpxpl
  473.  *      lpMessage           Message to send
  474.  *      ulRecipType         MAPI_TO, MAPI_CC, etc. If 0, not a recip.
  475.  *      lpszEmailAddress    Email address of this recipient.
  476.  *      lpfSent             Pointer to the boolean result
  477.  *                          of the operation.
  478.  *
  479.  *  Returns:
  480.  *      (HRESULT)           Set if an error encountered.
  481.  *      *lpfSent            FALSE if sending failed,
  482.  *                          TRUE if sending succeeded.
  483.  *
  484.  *  Operation:
  485.  *      Initializes result in *lpfSent to FALSE.  Open a stream interface
  486.  *      on the destination message file.  Write all the textized envelope
  487.  *      properties into the stream, then use TNEF to encapsulate the remaining
  488.  *      message properties into the stream.
  489.  */
  490. HRESULT
  491. HrSendOneMessage(LPXPL lpxpl,
  492.     LPSPropValue lpPropArray,
  493.     LPMESSAGE lpMessage,
  494.     ULONG ulRecipType,
  495.     LPTSTR lpszEmailAddress,
  496.     BOOL FAR * lpfSent)
  497. {
  498.     TCHAR rgchOutFileName[MAX_PATH];
  499.     HRESULT hResult = 0;
  500.     SCODE sc = 0;
  501.     ULONG ulT;
  502.     ULONG cValues;
  503.     BOOL fFromMe;
  504.     LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
  505.     LPIID lpidMessage = (LPIID) &IID_IMessage;
  506.     SPropValue spvDestMsgProps[3];
  507.     LPSPropProblemArray lpProblems = NULL;
  508.     LPSPropTagArray lpPropTagArray = NULL;
  509.     LPSTnefProblemArray lptpa = NULL;
  510.     WORD wKey = 0;
  511.     LPITNEF lpTnef = (LPITNEF) NULL;
  512.     LPSTREAM lpSof = (LPSTREAM) NULL;
  513.     LPSTREAM lpXPSof = (LPSTREAM) NULL;
  514.     /* Assume the worst, we'll fix it later if need be */
  515.     *lpfSent = FALSE;
  516.     /* Build file name of outgoing message. Because loopback doesn't
  517.        always work on all platforms, we check the email address against
  518.        our own and just substitute the inbox path if it matches. */
  519.     if (!lstrcmpi(lpszEmailAddress,
  520.             ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ))
  521.     {
  522.         DebugTrace("Email Address is mine, doing my own internal loopbackn");
  523.         lstrcpy(rgchOutFileName, ArrayIndex(PR_SAMPLE_INBOUND_DIR, lpPropArray).Value.LPSZ);
  524.         fFromMe = TRUE;
  525.     }
  526.     else
  527.     {
  528.         lstrcpy(rgchOutFileName, lpszEmailAddress);
  529.         /*  If this is a real email address, it won't have a
  530.             trailing backslash. But if it's our outbound dir (when
  531.             we're not p2p) it wiil have one. Only add one if
  532.             it's needed. */
  533.         ulT = lstrlen(rgchOutFileName);
  534.         Assert(ulT > 1);
  535.         if (lstrcmp(&rgchOutFileName[ulT], TEXT("\")))
  536.             lstrcat(rgchOutFileName, TEXT("\"));
  537.         fFromMe = FALSE;
  538.     }
  539.     hResult = OpenStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
  540.         STGM_CREATE | STGM_READWRITE | SOF_UNIQUEFILENAME,
  541.         rgchOutFileName, TEXT("TNF"), &lpSof);
  542.     if (HR_FAILED(hResult))
  543.     {
  544.         DebugTrace("OpenStreamOnFile(%s) failed.n",rgchOutFileName);
  545.         PrintfTransportLog(TEXT("Delivery failed in OpenStreamOnFile."));
  546.         goto ret;
  547.     }
  548.     /* Wrap the Stream-On-File object in our buffered wrapper. */
  549.     hResult = HrWrapStreamOnFile(lpxpl->AllocateBuffer, lpxpl->FreeBuffer,
  550.             XPSOF_READWRITE, lpSof, &lpXPSof);
  551.     if (HR_FAILED(hResult))
  552.     {
  553.         DebugTrace("HrWrapStreamOnFile() failedn");
  554.         goto ret;
  555.     }
  556.     /* Write all non-TNEF properties to the text file */
  557.     hResult = HrIMsgToTextMsg(lpxpl, lpPropArray, lpMessage, lpXPSof);
  558.     if (HR_FAILED(hResult))
  559.     {
  560.         DebugTrace("HrIMsgToTextMsg() failed.n");
  561.         PrintfTransportLog(TEXT("Delivery failed in HrIMsgToTextMsg."));
  562.         goto ret;
  563.     }
  564.     hResult = hrSuccess;
  565.     /* Check our .2 second timer after writting textized properties. */
  566.     sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));
  567.     if (sc == MAPI_W_CANCEL_MESSAGE)
  568.     {
  569.         DebugTrace("Cancelling message delivery.n");
  570.         goto ret;
  571.     }
  572.     /* Open a TNEF encapsulation on the StreamOnFile interface */
  573.     hResult = OpenTnefStream(lpMAPISup, lpXPSof, TEXT("MAPIMAIL.DAT"),
  574.             TNEF_ENCODE, lpMessage, 0x01AF, &lpTnef);
  575.     if (HR_FAILED(hResult))
  576.     {
  577.         DebugTrace("OpenTNEFStream() failed.n");
  578.         PrintfTransportLog(TEXT("Delivery failed in OpenTNEFStream."));
  579.         goto ret;
  580.     }
  581.     /* Find out what properties there are, so we can exclude the
  582.        "nontransmittables" and the properties we've textized. */
  583.     hResult = lpMessage->lpVtbl->GetPropList(lpMessage, 0, /* ansi */
  584.             &lpPropTagArray);
  585.     if (hResult)
  586.     {
  587.         DebugTrace("GetPropList failed.n");
  588.         goto ret;
  589.     }
  590.     Assert(lpPropTagArray);
  591.     /* Build a new prop tag array on the memory we just got back. This
  592.        prop tag array will only contain nontransmittable properties. */
  593.     cValues = 0;
  594.     for (ulT = 0; ulT < lpPropTagArray->cValues; ulT++)
  595.     {
  596.         ULONG ulPropTagT = lpPropTagArray->aulPropTag[ulT];
  597.         /*  FIsTransmittable is a macro in the MAPI headers. Makes it
  598.             really easy to determine transmittable/not when we need to */
  599.         if ((!FIsTransmittable(ulPropTagT) || FIsTextizedProp(ulPropTagT)) &&
  600.             (ulPropTagT != PR_MESSAGE_ATTACHMENTS))
  601.         {
  602.             lpPropTagArray->aulPropTag[cValues++] = ulPropTagT;
  603.         }
  604.     }
  605.     lpPropTagArray->cValues = cValues;
  606.     /* Exclude selected properties from our TNEF encapsulation. */
  607.     hResult = lpTnef->lpVtbl->AddProps(lpTnef,
  608.         TNEF_PROP_EXCLUDE, 0L, NULL, lpPropTagArray);
  609.     if (HR_FAILED(hResult))
  610.     {
  611.         DebugTrace("AddProps failed.n");
  612.         goto ret;
  613.     }
  614.     /* Check our .2 second timer! */
  615.     sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));
  616.     if (sc == MAPI_W_CANCEL_MESSAGE)
  617.     {
  618.         DebugTrace("Cancelling message delivery.n");
  619.         goto ret;
  620.     }
  621.     /* If we had a recipient type, we should help the receiver side out
  622.        with some properties, as follows:
  623.        PR_MESSAGE_TO_ME should be set TRUE if ulRecipType == MAPI_TO
  624.        PR_MESSAGE_CC_ME should be set TRUE if ulRecipType == MAPI_CC
  625.        PR_MESSAGE_RECIP_ME should be set TRUE for either of the above */
  626.     if (ulRecipType)
  627.     {
  628.         spvDestMsgProps[0].ulPropTag = PR_MESSAGE_TO_ME;
  629.         spvDestMsgProps[0].Value.b = (ulRecipType == MAPI_TO);
  630.         spvDestMsgProps[1].ulPropTag = PR_MESSAGE_CC_ME;
  631.         spvDestMsgProps[1].Value.b = (ulRecipType == MAPI_CC);
  632.         spvDestMsgProps[2].ulPropTag = PR_MESSAGE_RECIP_ME;
  633.         spvDestMsgProps[2].Value.b = ((ulRecipType == MAPI_TO) || (ulRecipType == MAPI_CC));
  634.         lpxpl->FreeBuffer(lpProblems);
  635.         lpProblems = NULL;
  636.         hResult = lpTnef->lpVtbl->SetProps(lpTnef, 0L, 0L, 3,
  637.             spvDestMsgProps);
  638.         if (HR_FAILED(hResult))
  639.         {
  640.             DebugTrace("SetProps failed");
  641.             goto ret;
  642.         }
  643.     }
  644.     /* OK. All the properties are copied over. Save Changes on the
  645.        message we created. */
  646.     hResult = lpTnef->lpVtbl->Finish(lpTnef, 0L, &wKey, &lptpa);
  647.     lpxpl->FreeBuffer(lptpa);
  648.     if (HR_FAILED(hResult))
  649.     {
  650.         DebugTrace("Finish failed.n");
  651.         goto ret;
  652.     }
  653.     hResult = lpXPSof->lpVtbl->Commit(lpXPSof, STGC_DEFAULT);
  654.     if (HR_FAILED(hResult))
  655.     {
  656.         DebugTrace("Commit stream failed.n");
  657.         goto ret;
  658.     }
  659.     /* Check our .2 second timer! */
  660.     sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE));
  661.     if (sc == MAPI_W_CANCEL_MESSAGE)
  662.     {
  663.         DebugTrace("Cancelling message delivery.n");
  664.         goto ret;
  665.     }
  666.     *lpfSent = TRUE;
  667.     /* Log successful transmission. */
  668.     PrintfTransportLog(TEXT("Delivery Complete: %s"), rgchOutFileName);
  669. ret:
  670.     /* Release any open object */
  671.     UlRelease(lpTnef);
  672.     UlRelease(lpXPSof);
  673.     UlRelease(lpSof);
  674.     if (HR_FAILED(hResult) && lpSof)
  675.         DeleteFile(rgchOutFileName);
  676.     /* Release the prop tag array and/or problem array if any */
  677.     lpxpl->FreeBuffer(lpPropTagArray);
  678.     DebugTraceResult(HrSendOneMessage, hResult);
  679.     return hResult;
  680. }
  681. /* Stuff to support the textized message formatting */
  682. const static SizedSPropTagArray(4, sptMsgProps) =
  683. {
  684.     4,
  685.     {
  686.         PR_CLIENT_SUBMIT_TIME,
  687.         PR_SUBJECT,
  688.         PR_PRIORITY,
  689.         PR_BODY
  690.     }
  691. };
  692. const static SizedSPropTagArray(5, sptRecipProps) =
  693. {
  694.     5,
  695.     {
  696.         PR_RECIPIENT_TYPE,
  697.         PR_EMAIL_ADDRESS,
  698.         PR_ADDRTYPE,
  699.         PR_DISPLAY_NAME,
  700.         PR_RESPONSIBILITY
  701.     }
  702. };
  703. const static SizedSPropTagArray(3, sptSenderDelegate) =
  704. {
  705.     3,
  706.     {
  707.         PR_SENT_REPRESENTING_ENTRYID,
  708.         PR_SENDER_ENTRYID,
  709.         PR_REPLY_RECIPIENT_ENTRIES,
  710.     }
  711. };
  712. TCHAR rgszTags[NUM_TAGS][MAX_TAG_LEN] =
  713. {
  714.     TEXT("Message: "),
  715.     TEXT("From: "),
  716.     TEXT("Representing: "),
  717.     TEXT("Reply To: "),
  718.     TEXT("Date: "),
  719.     TEXT("To: "),
  720.     TEXT("Cc: "),
  721.     TEXT("Bcc: "),
  722.     TEXT("Subject: "),
  723.     TEXT("Priority Urgent: "),
  724.     TEXT("Priority Normal: "),
  725.     TEXT("Priority Low: "),
  726.     TEXT("Contents: "),
  727.     TEXT("Text Item: "),
  728.     TEXT("File Item: ")
  729. };
  730. TCHAR szCRLF[3] = {TEXT("rn")};
  731. TCHAR szCRLFCRLF[5] = {TEXT("rnrn")};
  732. /*
  733.  -  HrIMsgToTextMsg
  734.  -
  735.  *  Purpose:
  736.  *      Called by HrSendOneMessage() to write the envelope properties
  737.  *      to the destination message text file.  Uses the StreamOnFile
  738.  *      interface for all the file access.  This stream interface
  739.  *      is then passed to the TNEF encoder, with its current file
  740.  *      pointer un-modified, so the non-envelope properties can be
  741.  *      encapsulated in a TNEF encapsulation in a "File Item" section.
  742.  *
  743.  *  Parameters:
  744.  *      lpxpl               Pointer to Transport Logon object
  745.  *      lpPropArray         Copy of the sessions logon properties
  746.  *      lpMessage           Message to send
  747.  *      lpSof               Pointer to the stream interface
  748.  *
  749.  *  Returns:
  750.  *      (HRESULT)           Set if an error encountered.
  751.  *
  752.  *  Operation:
  753.  *      Call GetProps() on the message to extract PR_SUBMIT_DATE, PR_SUBJECT,
  754.  *      PR_PRIORITY, and PR_BODY.  Then call GetRecipientTable() to extract
  755.  *      the PR_DISPLAY_NAME and PR_EMAIL_ADDRESS for each type of
  756.  *      PR_RECIPIENT_TYPE.  Write all this info to the stream (after
  757.  *      formatting appropriately).
  758.  */
  759. HRESULT
  760. HrIMsgToTextMsg(LPXPL lpxpl, LPSPropValue lpPropArray, LPMESSAGE lpMessage, LPSTREAM lpSof)
  761. {
  762.     HRESULT hr = hrSuccess;
  763.     ULONG cMsgVals = 0;
  764.     ULONG cSndrVals = 0;
  765.     LPSPropValue lpMsgProps = NULL;
  766.     LPSPropValue lpSndrProps = NULL;
  767.     LPSPropValue lpPropT = NULL;
  768.     LPMAPITABLE lpTbl = NULL;
  769.     LPSRowSet lpRows = NULL;
  770.     ULONG uli;
  771.     ULONG cbOut;
  772.     ULONG cbRead;
  773.     ULONG cbCRLF = lstrlen(szCRLF);
  774.     ULONG cbCRLFCRLF = lstrlen(szCRLFCRLF);
  775.     TCHAR szRep[128];
  776.     TCHAR szFrom[128];
  777.     TCHAR szOutBuf[128];
  778.     TCHAR rgchStrmBuf[MAX_STRM_BUF];
  779.     LPTSTR lpszT1;
  780.     LPTSTR lpszT2;
  781.     LPSTREAM lpStrm = NULL;
  782.     /* Get the 4 Message properties to be textized */
  783.     hr = lpMessage->lpVtbl->GetProps(lpMessage,
  784.             (LPSPropTagArray) &sptMsgProps, 0, /* ansi */
  785.             &cMsgVals, &lpMsgProps);
  786.     if (HR_FAILED(hr) || !cMsgVals)
  787.     {
  788.         DebugTrace("GetProps() MsgProps failed.n");
  789.         goto ret;
  790.     }
  791.     /* Get the 3 Sender/Delegate properties to be textized */
  792.     hr = lpMessage->lpVtbl->GetProps(lpMessage,
  793.             (LPSPropTagArray) &sptSenderDelegate, 0, /* ansi */
  794.             &cSndrVals, &lpSndrProps);
  795.     if (HR_FAILED(hr) || !cSndrVals)
  796.     {
  797.         DebugTrace("GetProps()  SndrProps failed.n");
  798.         goto ret;
  799.     }
  800.     /* Returns the Recipient Table in a well defined state
  801.        (i.e. minimal column set and restricted on our AddrType). */
  802.     hr = HrPrepareRecipientTable(lpPropArray, lpMessage, &lpTbl);
  803.     if (HR_FAILED(hr))
  804.     {
  805.         DebugTrace("HrPrepareRecipientTable()  failed.n");
  806.         goto ret;
  807.     }
  808.     /* Write Message: field */
  809.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagMessage],
  810.             lstrlen(rgszTags[tagMessage]), &cbOut), ret);
  811.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  812.             cbCRLFCRLF, &cbOut), ret);
  813.     /* Write From: and Representing: fields */
  814.     /* The following code sets the Sender/Delegate */
  815.     /* properties as follows:
  816.         1) If no PR_SENT_REPRESENTING_??? in message,
  817.             a)  If no PR_SENDER_??? in message,
  818.                     PR_SENDER_??? = transport identities
  819.             b)  PR_SENT_REPRESENTING_??? = PR_SENDER_???
  820.         2) (else) If there was a PR_SENT_REPRESENTING_??? in message,
  821.             a)  If no PR_SENDER_??? in message,
  822.                     PR_SENDER_??? = PR_SENT_REPRESENTING_???
  823.         3)  If no PR_REPLY_RECIPIENT_???,
  824.                 PR_REPLY_RECIPIENT_ENTRIES = PR_SENT_REPRESENTING_ENTRYID
  825.                 PR_REPLY_RECIPIENT_NAMES = PR_SENT_REPRESENTING_NAME
  826.        This just works because HrCrackSenderEID() doesn't change the
  827.        szFrom or szRep memory unless it is successful.  It is for this
  828.        reason that we don't check the return from HrCrackSenderEID() */
  829.     hr = hrSuccess;
  830.     *szFrom = '';
  831.     *szRep = '';
  832.     if (lpSndrProps[0].ulPropTag == PR_SENT_REPRESENTING_ENTRYID)
  833.         HrCrackSenderEID(lpxpl, lpSndrProps[0].Value.bin.cb,
  834.             lpSndrProps[0].Value.bin.lpb, szRep);
  835.     if (lpSndrProps[1].ulPropTag == PR_SENDER_ENTRYID)
  836.         HrCrackSenderEID(lpxpl, lpSndrProps[1].Value.bin.cb,
  837.             lpSndrProps[1].Value.bin.lpb, szFrom);
  838.     if (!*szFrom && !*szRep)
  839.     {
  840.         lpszT1 = ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ;
  841.         lpszT2 = ArrayIndex(PR_SAMPLE_EMAIL_ADDRESS, lpPropArray).Value.LPSZ;
  842.         wsprintf(szFrom, TEXT("%s[%s]"), lpszT1, lpszT2);
  843.         lstrcpy(szRep, szFrom);
  844.     }
  845.     else if (*szFrom && !*szRep)
  846.     {
  847.         lstrcpy(szRep, szFrom);
  848.     }
  849.     else
  850.     {
  851.         lstrcpy(szFrom, szRep);
  852.     }
  853.     wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagFrom], szFrom);
  854.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  855.             lstrlen(szOutBuf), &cbOut), ret);
  856.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  857.             cbCRLFCRLF, &cbOut), ret);
  858.     wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagRepresenting], szRep);
  859.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  860.             lstrlen(szOutBuf), &cbOut), ret);
  861.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  862.             cbCRLFCRLF, &cbOut), ret);
  863.     /* Write Reply To: fields */
  864.     if (lpSndrProps[2].ulPropTag == PR_REPLY_RECIPIENT_ENTRIES)
  865.     {
  866.         LPFLATENTRYLIST lpList = (LPFLATENTRYLIST) lpSndrProps[2].Value.bin.lpb;
  867.         LPBYTE lpb;
  868.         ULONG cEntries;
  869.         /* Attempt some level of validation for this property */
  870.         if (!lpList
  871.             || IsBadReadPtr(lpList, CbNewFLATENTRYLIST(0))
  872.             || !lpList->abEntries
  873.             || IsBadReadPtr(lpList, (UINT) CbFLATENTRYLIST(lpList))
  874.             || !lpList->cEntries
  875.             || (lpList->cEntries * sizeof(GUID) > lpList->cbEntries))
  876.         {
  877.             DebugTrace("Bad PR_REPLY_RECIPIENT_ENTRIES!n");
  878.             DebugTrace("Skipping the Reply To: field.n");
  879.         }
  880.         else
  881.         {
  882.             lpb = lpList->abEntries;
  883.             cEntries = lpList->cEntries;
  884.             while (cEntries--)
  885.             {
  886.                 LPFLATENTRY lpEntry = (LPFLATENTRY) lpb;
  887.                 ULONG ulSize;
  888.                 if (IsBadReadPtr(lpEntry, CbNewFLATENTRY(0))
  889.                     || IsBadReadPtr(lpEntry, (UINT) CbFLATENTRY(lpEntry)))
  890.                 {
  891.                     DebugTrace("Bad entry inside PR_REPLY_RECIPIENT_ENTRIES!n");
  892.                     break;
  893.                 }
  894.                 ulSize = lpEntry->cb;
  895.                 hr = HrCrackSenderEID(lpxpl, ulSize, lpEntry->abEntry, szFrom);
  896.                 if (!hr)
  897.                 {
  898.                     wsprintf(szOutBuf, TEXT("%s%s"),
  899.                         rgszTags[tagReplyTo], szFrom);
  900.                     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  901.                             lstrlen(szOutBuf), &cbOut), ret);
  902.                     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  903.                             cbCRLF, &cbOut), ret);
  904.                 }
  905.                 lpb += offsetof (FLATENTRY, abEntry) + ((ulSize + 3) & -4L);
  906.             }
  907.             /* Add one more CR/LF pair after Reply Recipients */
  908.             TraceFailedWrite(lpSof->lpVtbl->Write(lpSof,
  909.                     szCRLF, cbCRLF, &cbOut), ret);
  910.         }
  911.     }
  912.     else
  913.     {
  914.         wsprintf(szOutBuf, TEXT("%s%s"), rgszTags[tagReplyTo], szFrom);
  915.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  916.                 lstrlen(szOutBuf), &cbOut), ret);
  917.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  918.                 cbCRLFCRLF, &cbOut), ret);
  919.     }
  920.     /* Write Date: field */
  921.     if (FPropIndex(lpMsgProps, cMsgVals, PR_CLIENT_SUBMIT_TIME, &uli))
  922.     {
  923.         /* Property exists in message; write it to the stream */
  924.         FormatFileTime(&lpMsgProps[uli].Value.ft, szOutBuf);
  925.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagDate],
  926.                 lstrlen(rgszTags[tagDate]), &cbOut), ret);
  927.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  928.                 lstrlen(szOutBuf), &cbOut), ret);
  929.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  930.                 cbCRLFCRLF, &cbOut), ret);
  931.     }
  932.     /* Write To: & Cc: fields */
  933.     while (TRUE)
  934.     {
  935.         /* Get a row from the Recipient Table */
  936.         hr = lpTbl->lpVtbl->QueryRows(lpTbl, 1, 0, &lpRows);
  937.         if (hr || !lpRows || (lpRows->cRows != 1))
  938.             break;
  939.         lpPropT = lpRows->aRow[0].lpProps;
  940.         /* Throw away MAPI_ORIG and P1 Recipient Types */
  941.         if ((lpPropT[0].Value.l != MAPI_TO) &&
  942.             (lpPropT[0].Value.l != MAPI_CC))
  943.         {
  944.             FreeProws(lpRows);
  945.             lpRows = NULL;
  946.             lpPropT = NULL;
  947.             continue;
  948.         }
  949.         /* Write Recipients as:
  950.             '{To: | Cc: } Display Name [email-address]' */
  951.         Assert((lpPropT[0].Value.l == MAPI_TO) ||
  952.             (lpPropT[0].Value.l == MAPI_CC));
  953.         wsprintf(szOutBuf, TEXT("%s%s[%s]"),
  954.             rgszTags[tagDate + lpPropT[0].Value.l],
  955.             lpPropT[3].Value.LPSZ, lpPropT[1].Value.LPSZ);
  956.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szOutBuf,
  957.                 lstrlen(szOutBuf), &cbOut), ret);
  958.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  959.                 cbCRLF, &cbOut), ret);
  960.         /* Clean-Up */
  961.         FreeProws(lpRows);
  962.         lpRows = NULL;
  963.         lpPropT = NULL;
  964.     }
  965.     /* Add one more CR/LF pair after recipients */
  966.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF, cbCRLF, &cbOut), ret);
  967.     /* Write Subject: field */
  968.     if (FPropIndex(lpMsgProps, cMsgVals, PR_SUBJECT, &uli))
  969.     {
  970.         /* Property exists and is small enough to not require a
  971.            stream interface to be opened on it; just write it. */
  972.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagSubject],
  973.                 lstrlen(rgszTags[tagSubject]), &cbOut), ret);
  974.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  975.                 cbCRLF, &cbOut), ret);
  976.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, lpMsgProps[uli].Value.LPSZ,
  977.                 lstrlen(lpMsgProps[uli].Value.LPSZ), &cbOut), ret);
  978.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  979.                 cbCRLFCRLF, &cbOut), ret);
  980.     }
  981.     else if (!(hr = lpMessage->lpVtbl->OpenProperty(lpMessage, PR_SUBJECT,
  982.                 (LPIID) &IID_IStream, 0, 0, (LPUNKNOWN *) &lpStrm)))
  983.     {
  984.         /* Property exists and requires a stream interface to
  985.            access all the data.  Copy between streams! */
  986.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagSubject],
  987.                 lstrlen(rgszTags[tagSubject]), &cbOut), ret);
  988.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  989.                 cbCRLF, &cbOut), ret);
  990.         while (TRUE)
  991.         {
  992.             hr = lpStrm->lpVtbl->Read(lpStrm, (LPVOID) rgchStrmBuf,
  993.                 MAX_STRM_BUF, &cbRead);
  994.             if (hr || (cbRead == 0))
  995.                 break;          /* There's nothing to write; we're done! */
  996.             TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, (LPVOID) rgchStrmBuf,
  997.                     cbRead, &cbOut), ret);
  998.             if (cbRead < MAX_STRM_BUF)
  999.                 break;          /* We've exhausted the stream; we're done! */
  1000.         }
  1001.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  1002.                 cbCRLFCRLF, &cbOut), ret);
  1003.         lpStrm->lpVtbl->Release(lpStrm);
  1004.         lpStrm = NULL;
  1005.     }
  1006.     /* Write Priority: field */
  1007.     if (FPropIndex(lpMsgProps, cMsgVals, PR_PRIORITY, &uli))
  1008.     {
  1009.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof,
  1010.                 rgszTags[tagPrioNormal - lpMsgProps[uli].Value.l],
  1011.                 lstrlen(rgszTags[tagPrioNormal - lpMsgProps[uli].Value.l]),
  1012.                 &cbOut), ret);
  1013.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  1014.                 cbCRLFCRLF, &cbOut), ret);
  1015.     }
  1016.     /* Write Contents: field */
  1017.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagContents],
  1018.             lstrlen(rgszTags[tagContents]), &cbOut), ret);
  1019.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  1020.             cbCRLFCRLF, &cbOut), ret);
  1021.     /* Text Item: */
  1022.     if (FPropIndex(lpMsgProps, cMsgVals, PR_BODY, &uli))
  1023.     {
  1024.         /* Property exists and is small enough to not require
  1025.            a stream interface to be opened on it; just copy it */
  1026.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagTextItem],
  1027.                 lstrlen(rgszTags[tagTextItem]), &cbOut), ret);
  1028.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  1029.                 cbCRLF, &cbOut), ret);
  1030.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, lpMsgProps[uli].Value.LPSZ,
  1031.                 lstrlen(lpMsgProps[uli].Value.LPSZ), &cbOut), ret);
  1032.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  1033.                 cbCRLFCRLF, &cbOut), ret);
  1034.     }
  1035.     else if (!(hr = lpMessage->lpVtbl->OpenProperty(lpMessage, PR_BODY,
  1036.                 (LPIID) &IID_IStream, 0, 0, (LPUNKNOWN *) &lpStrm)))
  1037.     {
  1038.         /* Property exists and requires a stream interface to
  1039.            access all the data.  Copy between streams! */
  1040.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagTextItem],
  1041.                 lstrlen(rgszTags[tagTextItem]), &cbOut), ret);
  1042.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  1043.                 cbCRLF, &cbOut), ret);
  1044.         while (TRUE)
  1045.         {
  1046.             hr = lpStrm->lpVtbl->Read(lpStrm, (LPVOID) rgchStrmBuf,
  1047.                 MAX_STRM_BUF, &cbRead);
  1048.             if (hr || (cbRead == 0))
  1049.                 break;          /* There's nothing to write; we're done! */
  1050.             TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, (LPVOID) rgchStrmBuf,
  1051.                     cbRead, &cbOut), ret);
  1052.             if (cbRead < MAX_STRM_BUF)
  1053.                 break;          /* We've exhausted the stream; we're done! */
  1054.         }
  1055.         TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLFCRLF,
  1056.                 cbCRLFCRLF, &cbOut), ret);
  1057.         lpStrm->lpVtbl->Release(lpStrm);
  1058.         lpStrm = NULL;
  1059.     }
  1060.     /* File Item: */
  1061.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, rgszTags[tagFileItem],
  1062.             lstrlen(rgszTags[tagFileItem]), &cbOut), ret);
  1063.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, TEXT("MESSAGE.TNF"),
  1064.             lstrlen(TEXT("MESSAGE.TNF")), &cbOut), ret);
  1065.     TraceFailedWrite(lpSof->lpVtbl->Write(lpSof, szCRLF,
  1066.             cbCRLF, &cbOut), ret);
  1067. ret:
  1068.     lpxpl->FreeBuffer(lpMsgProps);
  1069.     lpxpl->FreeBuffer(lpSndrProps);
  1070.     FreeProws(lpRows);
  1071.     UlRelease(lpTbl);
  1072.     DebugTraceResult(HrIMsgToTextMsg(), hr);
  1073.     return hr;
  1074. }
  1075. /*
  1076.  -  HrPrepareRecipientTable
  1077.  -
  1078.  *  Purpose:
  1079.  *      Gets the Recipient Table from an IMAPIMessage and sets its
  1080.  *      columns, restricts the view, and sorts it (if sorts are
  1081.  *      supported).  All this in preparation of writing recipients
  1082.  *      to the destination message file.
  1083.  *
  1084.  *  Parameters:
  1085.  *      lpPropArray         Pointer to Transport Logon object
  1086.  *      lpMsg               Message to GetRecipientTable on
  1087.  *      lppTbl              Receives the RecipientTable
  1088.  *
  1089.  *  Returns:
  1090.  *      hr                  Indicating Success/Failure
  1091.  */
  1092. HRESULT
  1093. HrPrepareRecipientTable(LPSPropValue lpPropArray, LPMESSAGE lpMsg, LPMAPITABLE * lppTbl)
  1094. {
  1095.     HRESULT hr = hrSuccess;
  1096.     LPMAPITABLE lpTbl = NULL;
  1097.     SizedSSortOrderSet(2, rgSort) =
  1098.     {
  1099.         2, 0, 0,
  1100.         {
  1101.             PR_RECIPIENT_TYPE, TABLE_SORT_ASCEND,
  1102.                 PR_ROWID, TABLE_SORT_ASCEND
  1103.         }
  1104.     };
  1105.     *lppTbl = NULL;
  1106.     hr = lpMsg->lpVtbl->GetRecipientTable(lpMsg, 0, &lpTbl);
  1107.     if (HR_FAILED(hr))
  1108.     {
  1109.         DebugTrace("GetRecipientTable() failed in HrPrepareRecipientTable()");
  1110.         goto ret;
  1111.     }
  1112.     /* SetColumns to: PR_RECIPIENT_TYPE, PR_EMAIL_ADDRESS, PR_ADDRTYPE,
  1113.        PR_DISPLAY_NAME, and PR_RESPONSIBILITY in that order */
  1114.     hr = lpTbl->lpVtbl->SetColumns(lpTbl, (LPSPropTagArray) &sptRecipProps,
  1115.         TBL_BATCH);
  1116.     if (HR_FAILED(hr))
  1117.     {
  1118.         DebugTrace("SetColumns() failed in HrPrepareRecipientTable()");
  1119.         goto ret;
  1120.     }
  1121.     /* Sort by: PR_RECIPIENT_TYPE (i.e. MAPI_TO, MAPI_CC, MAPI_BCC)
  1122.             and PR_ROWID, both in acsending order */
  1123.     hr = lpTbl->lpVtbl->SortTable(lpTbl, (LPSSortOrderSet) &rgSort, TBL_BATCH);
  1124.     if (hr)
  1125.     {
  1126.         /* Don't fail the call for no support!  Just return
  1127.            the table in whatever order it may be in by default. */
  1128.         if (GetScode(hr) == MAPI_E_NO_SUPPORT)
  1129.             hr = 0;
  1130.         else
  1131.             DebugTrace("SortTable() failed in HrPrepareRecipientTable()");
  1132.     }
  1133.     /* Seek to the beginning since sorting may have moved out position */
  1134.     hr = lpTbl->lpVtbl->SeekRow(lpTbl, BOOKMARK_BEGINNING, 0, NULL);
  1135.     if (hr)
  1136.     {
  1137.         DebugTrace("SeekRow() failed in HrPrepareRecipientTable()");
  1138.     }
  1139. ret:
  1140.     if (HR_FAILED(hr))
  1141.     {
  1142.         UlRelease(lpTbl);
  1143.     }
  1144.     else
  1145.         *lppTbl = lpTbl;
  1146.     DebugTraceResult(HrPrepareRecipientTable(), hr);
  1147.     return hr;
  1148. }
  1149. /*
  1150.  -  HrCrackSenderEID
  1151.  -
  1152.  *  Purpose:
  1153.  *      Does an OpenEntry() on the EntryID and GetProps() PR_DISPLAY_NAME
  1154.  *      and PR_EMAIL_ADDRESS.  Then formats the return string like:
  1155.  *          "Display Name[email-address]"
  1156.  *
  1157.  *  Parameters:
  1158.  *      lpxpl           Pointer to Transport Logon object
  1159.  *      cb              Count of bytes in EntryID
  1160.  *      lpb             Pointer to EntryID
  1161.  *      lpsz            Receives the formatted Name/Address pair
  1162.  *
  1163.  *  Returns:
  1164.  *      hr              Indicating Success/Failure
  1165.  *
  1166.  *  Note:
  1167.  *      No parameter validation!  I assume the caller knew what was
  1168.  *      being passed in and ensured all was well.
  1169.  */
  1170. HRESULT
  1171. HrCrackSenderEID(LPXPL lpxpl, ULONG cb, LPBYTE lpb, LPTSTR lpsz)
  1172. {
  1173.     HRESULT hr = hrSuccess;
  1174.     ULONG ulObjType;
  1175.     LPMAPISUP lpMAPISup = lpxpl->lpMAPISup;
  1176.     LPMAPIPROP lpMAPIProp = NULL;
  1177.     ULONG cVals = 0;
  1178.     LPSPropValue lpProps = NULL;
  1179.     const static SizedSPropTagArray(2, sptCracked) =
  1180.     {
  1181.         2,
  1182.         {
  1183.             PR_DISPLAY_NAME,
  1184.             PR_EMAIL_ADDRESS
  1185.         }
  1186.     };
  1187.     /* Open a Property Interface on this EntryID */
  1188.     hr = lpMAPISup->lpVtbl->OpenEntry(lpMAPISup, cb, (LPENTRYID) lpb,
  1189.         NULL, 0, &ulObjType, (LPUNKNOWN *) &lpMAPIProp);
  1190.     if (hr)
  1191.     {
  1192.         DebugTrace("OpenEntry() Failed in HrCrackSenderEID().n");
  1193.         goto ret;
  1194.     }
  1195.     /* Get the 2 properties we need from this object */
  1196.     hr = lpMAPIProp->lpVtbl->GetProps(lpMAPIProp,
  1197.         (LPSPropTagArray) &sptCracked, 0, /* ansi */
  1198.         &cVals, &lpProps);
  1199.     if (hr || !cVals)
  1200.     {
  1201.         DebugTrace("GetProps() Failed in HrCrackSenderEID().n");
  1202.         goto ret;
  1203.     }
  1204.     /* Assert that all went well so far!!! */
  1205.     Assert(lpProps);
  1206.     Assert(cVals == 2);
  1207.     Assert(lpProps[0].ulPropTag == PR_DISPLAY_NAME);
  1208.     Assert(lpProps[1].ulPropTag == PR_EMAIL_ADDRESS);
  1209.     /* Format our Name/Address pair as desired */
  1210.     wsprintf(lpsz, "%s[%s]", lpProps[0].Value.LPSZ, lpProps[1].Value.LPSZ);
  1211. ret:
  1212.     lpxpl->FreeBuffer(lpProps);
  1213.     UlRelease(lpMAPIProp);
  1214.     DebugTraceResult(HrCrackSenderEID(), hr);
  1215.     return hr;
  1216. }
  1217. /*
  1218.  -  FPropIndex
  1219.  -
  1220.  *  Purpose:
  1221.  *      Finds and returns (if it exists) the index of ulPropTag
  1222.  *      in the lpProps SPropValue array.
  1223.  *
  1224.  *  Parameters:
  1225.  *      lpProps         PropValue array to search through
  1226.  *      cVals           Count of properties in lpProps
  1227.  *      ulPropTag       PropTag to search for
  1228.  *      puli            Receives the index of ulPropTag in lpProps
  1229.  *
  1230.  *  Returns:
  1231.  *      TRUE/FALSE      TRUE if found, FALSE otherwise
  1232.  */
  1233. BOOL
  1234. FPropIndex(LPSPropValue lpProps, ULONG cVals, ULONG ulPropTag, ULONG * puli)
  1235. {
  1236.     Assert(lpProps);
  1237.     Assert(cVals);
  1238.     Assert(puli);
  1239.     while (cVals--)
  1240.     {
  1241.         if (lpProps[cVals].ulPropTag == ulPropTag)
  1242.         {
  1243.             *puli = cVals;
  1244.             return TRUE;
  1245.         }
  1246.     }
  1247.     return FALSE;
  1248. }
  1249. /*
  1250.  -  FormatFileTime
  1251.  -
  1252.  *  Purpose:
  1253.  *      Formats a Windows NT file time as a MAPI date/time string
  1254.  *      of the format:  yyyy/mm/dd hh:mm
  1255.  *
  1256.  *  Parameters:
  1257.  *      pft             Pointer to FILETIME to convert
  1258.  *      szTime          Destination string
  1259.  *
  1260.  *  Returns:
  1261.  *      void.
  1262.  */
  1263. void
  1264. FormatFileTime(FILETIME * pft, LPTSTR szTime)
  1265. {
  1266.     SYSTEMTIME systime;
  1267.     FileTimeToSystemTime(pft, &systime);
  1268.     wsprintf(szTime, "%04.4d/%02.2d/%02.2d %02.2d:%02.2d",
  1269.         systime.wYear, systime.wMonth, systime.wDay,
  1270.         systime.wHour, systime.wMinute);
  1271. }
  1272. /*
  1273.  -  FIsTextizedProp
  1274.  -
  1275.  *  Purpose:
  1276.  *      Used to determine if the property is one we wish to
  1277.  *      exclude from the TNEF encapsulation (i.e. one we've
  1278.  *      textized in the envelope of the message file).
  1279.  *
  1280.  *  Parameters:
  1281.  *      ulPropTag       PropTag to test
  1282.  *
  1283.  *  Return:
  1284.  *      BOOL            Indicating if the property is textized
  1285.  */
  1286. BOOL
  1287. FIsTextizedProp(ULONG ulPropTag)
  1288. {
  1289.     ULONG i;
  1290.     static SizedSPropTagArray(16, spta) =
  1291.     {
  1292.         16,
  1293.         {
  1294.             PR_SENDER_NAME,
  1295.             PR_SENDER_ENTRYID,
  1296.             PR_SENDER_SEARCH_KEY,
  1297.             PR_SENDER_EMAIL_ADDRESS,
  1298.             PR_SENDER_ADDRTYPE,
  1299.             PR_SENT_REPRESENTING_NAME,
  1300.             PR_SENT_REPRESENTING_ENTRYID,
  1301.             PR_SENT_REPRESENTING_SEARCH_KEY,
  1302.             PR_SENT_REPRESENTING_EMAIL_ADDRESS,
  1303.             PR_SENT_REPRESENTING_ADDRTYPE,
  1304.             PR_REPLY_RECIPIENT_ENTRIES,
  1305.             PR_REPLY_RECIPIENT_NAMES,
  1306.             PR_SUBJECT,
  1307.             PR_CLIENT_SUBMIT_TIME,
  1308.             PR_BODY,
  1309.             PR_PRIORITY
  1310.         }
  1311.     };
  1312.     for (i = 0; i < spta.cValues; i++)
  1313.         if (spta.aulPropTag[i] == ulPropTag)
  1314.             return TRUE;
  1315.     return FALSE;
  1316. }
  1317. /*
  1318.  -  FreeMyAdrList
  1319.  -
  1320.  *  Purpose:
  1321.  *      Called by anyone who winds up with a MYADRLIST structure
  1322.  *      and wants to free it.
  1323.  *
  1324.  *  Parameters:
  1325.  *      lpxpl               Session context.
  1326.  *      lpMyAdrList         Structure to free.
  1327.  *
  1328.  *  Returns:
  1329.  *      void                Data in lpMyAdrList freed.
  1330.  *
  1331.  *  Operation:
  1332.  *      Walks through adrlist and frees all the memory.
  1333.  */
  1334. void
  1335. FreeMyAdrList(LPXPL lpxpl, LPMYADRLIST lpMyAdrList)
  1336. {
  1337.     ULONG ulT;
  1338.     LPSPropValue lpspvT;
  1339.     LPADRLIST lpAdrListT;
  1340.     /*  Clean up any adrlist stuff that's lying around. */
  1341.     if (lpMyAdrList)
  1342.     {
  1343.         lpAdrListT = lpMyAdrList->lpAdrList;
  1344.         if (lpAdrListT && lpAdrListT->cEntries)
  1345.         {
  1346.             for (ulT = 0; ulT < lpAdrListT->cEntries; ulT++)
  1347.             {
  1348.                 lpspvT = (lpAdrListT->aEntries[ulT]).rgPropVals;
  1349.                 lpxpl->FreeBuffer((LPVOID) lpspvT);
  1350.             }
  1351.         }
  1352.         lpxpl->FreeBuffer((LPVOID) lpAdrListT);
  1353.         lpxpl->FreeBuffer((LPVOID) lpMyAdrList);
  1354.     }
  1355. }
  1356. CHAR lpszEOM[] = "n*** End of Message ***n";
  1357. #define cchEOM (sizeof(lpszEOM) - 1)
  1358. STDMETHODIMP
  1359. PreprocessMessage (LPMAPISESSION lpSession,
  1360.     LPMESSAGE lpMessage,
  1361.     LPADRBOOK lpAdrBook,
  1362.     LPMAPIFOLDER lpFolder,
  1363.     LPALLOCATEBUFFER AllocateBuffer,
  1364.     LPALLOCATEMORE AllocateMore,
  1365.     LPFREEBUFFER FreeBuffer,
  1366.     ULONG FAR *lpcOutbound,
  1367.     LPMESSAGE FAR * FAR * lpppMessage,
  1368.     LPADRLIST FAR *lppRecipList)
  1369. {
  1370.     HRESULT hr;
  1371.     LPSTREAM lpstrm = NULL;
  1372.     ULONG cb;
  1373.     LARGE_INTEGER liTo = {0};
  1374.     hr = lpMessage->lpVtbl->OpenProperty (lpMessage,
  1375.                                     PR_BODY_A,
  1376.                                     (LPIID)&IID_IStream,
  1377.                                     0,
  1378.                                     MAPI_MODIFY,
  1379.                                     (LPUNKNOWN FAR *)&lpstrm);
  1380.     if (!HR_FAILED (hr))
  1381.     {
  1382.         hr = lpstrm->lpVtbl->Seek (lpstrm, liTo, STREAM_SEEK_END, NULL);
  1383.         if (!HR_FAILED (hr))
  1384.         {
  1385.             hr = lpstrm->lpVtbl->Write (lpstrm, lpszEOM, cchEOM, &cb);
  1386.             if (!HR_FAILED (hr) && (cb == cchEOM))
  1387.             {
  1388.                 if (!HR_FAILED (hr = lpstrm->lpVtbl->Commit (lpstrm, 0L)))
  1389.                     hr = lpMessage->lpVtbl->SaveChanges (lpMessage, KEEP_OPEN_READWRITE);
  1390.             }
  1391.         }
  1392.         UlRelease (lpstrm);
  1393.     }
  1394.     
  1395.     *lpcOutbound = 0;
  1396.     *lpppMessage = NULL;
  1397.     
  1398.     DebugTraceResult (PreprocessMessage(), hr);
  1399.     return hr;
  1400. }
  1401. STDMETHODIMP
  1402. RemovePreprocessInfo (LPMESSAGE lpMessage)
  1403. {
  1404.     CHAR lpszBuf[sizeof(lpszEOM)] = {0};
  1405.     HRESULT hr;
  1406.     LARGE_INTEGER liTo;
  1407.     LPSTREAM lpstrm = NULL;
  1408.     ULARGE_INTEGER liSize;
  1409.     ULONG cb;
  1410.     BOOL fUpd = FALSE;
  1411.     hr = lpMessage->lpVtbl->OpenProperty (lpMessage,
  1412.                                     PR_BODY_A,
  1413.                                     (LPIID)&IID_IStream,
  1414.                                     0,
  1415.                                     MAPI_MODIFY,
  1416.                                     (LPUNKNOWN FAR *)&lpstrm);
  1417.     if (!HR_FAILED (hr))
  1418.     {
  1419.         liTo.HighPart =  -1;
  1420.         liTo.LowPart = (ULONG)(-(LONG)(cchEOM));
  1421.         hr = lpstrm->lpVtbl->Seek (lpstrm, liTo, STREAM_SEEK_END, &liSize);
  1422.         if (!HR_FAILED (hr))
  1423.         {
  1424.             hr = lpstrm->lpVtbl->Read (lpstrm, lpszBuf, cchEOM, &cb);
  1425.             if (!HR_FAILED (hr) && (cb == cchEOM))
  1426.             {
  1427.                 if (!lstrcmpiA (lpszEOM, lpszBuf))
  1428.                 {
  1429.                     if (!HR_FAILED (hr = lpstrm->lpVtbl->SetSize (lpstrm, liSize)) &&
  1430.                         !HR_FAILED (hr = lpstrm->lpVtbl->Commit (lpstrm, 0L)))
  1431.                         hr = lpMessage->lpVtbl->SaveChanges (lpMessage, KEEP_OPEN_READWRITE);
  1432.                 }
  1433.             }
  1434.         }
  1435.     }
  1436.     UlRelease (lpstrm);
  1437.     DebugTraceResult (RemovePreprocessInfo(), hr);
  1438.     return hr;
  1439. }