/*
 * MAPI Utility functions
 *
 * Copyright 2004 Jon Griffiths
 * Copyright 2009 Owen Rudge for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>
#include <stdio.h>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winuser.h"
#include "winerror.h"
#include "winternl.h"
#include "objbase.h"
#include "shlwapi.h"
#include "wine/debug.h"
#include "mapival.h"
#include "xcmc.h"
#include "msi.h"
#include "util.h"

WINE_DEFAULT_DEBUG_CHANNEL(mapi);

static const BYTE digitsToHex[] = {
  0,1,2,3,4,5,6,7,8,9,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,14,15,
  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,
  14,15 };

MAPI_FUNCTIONS mapiFunctions;

/**************************************************************************
 *  ScInitMapiUtil (MAPI32.33)
 *
 * Initialise Mapi utility functions.
 *
 * PARAMS
 *  ulReserved [I] Reserved, pass 0.
 *
 * RETURNS
 *  Success: S_OK. Mapi utility functions may be called.
 *  Failure: MAPI_E_INVALID_PARAMETER, if ulReserved is not 0.
 *
 * NOTES
 *  Your application does not need to call this function unless it does not
 *  call MAPIInitialize()/MAPIUninitialize().
 */
SCODE WINAPI ScInitMapiUtil(ULONG ulReserved)
{
    if (mapiFunctions.ScInitMapiUtil)
        return mapiFunctions.ScInitMapiUtil(ulReserved);

    FIXME("(0x%08x)stub!\n", ulReserved);
    if (ulReserved)
        return MAPI_E_INVALID_PARAMETER;
    return S_OK;
}

/**************************************************************************
 *  DeinitMapiUtil (MAPI32.34)
 *
 * Uninitialise Mapi utility functions.
 *
 * PARAMS
 *  None.
 *
 * RETURNS
 *  Nothing.
 *
 * NOTES
 *  Your application does not need to call this function unless it does not
 *  call MAPIInitialize()/MAPIUninitialize().
 */
VOID WINAPI DeinitMapiUtil(void)
{
    if (mapiFunctions.DeinitMapiUtil)
        mapiFunctions.DeinitMapiUtil();
    else
        FIXME("()stub!\n");
}

typedef LPVOID *LPMAPIALLOCBUFFER;

/**************************************************************************
 *  MAPIAllocateBuffer   (MAPI32.12)
 *  MAPIAllocateBuffer@8 (MAPI32.13)
 *
 * Allocate a block of memory.
 *
 * PARAMS
 *  cbSize    [I] Size of the block to allocate in bytes
 *  lppBuffer [O] Destination for pointer to allocated memory
 *
 * RETURNS
 *  Success: S_OK. *lppBuffer is filled with a pointer to a memory block of
 *           length cbSize bytes.
 *  Failure: MAPI_E_INVALID_PARAMETER, if lppBuffer is NULL.
 *           MAPI_E_NOT_ENOUGH_MEMORY, if the memory allocation fails.
 *
 * NOTES
 *  Memory allocated with this function should be freed with MAPIFreeBuffer().
 *  Further allocations of memory may be linked to the pointer returned using
 *  MAPIAllocateMore(). Linked allocations are freed when the initial pointer
 *  is feed.
 */
SCODE WINAPI MAPIAllocateBuffer(ULONG cbSize, LPVOID *lppBuffer)
{
    LPMAPIALLOCBUFFER lpBuff;

    TRACE("(%d,%p)\n", cbSize, lppBuffer);

    if (mapiFunctions.MAPIAllocateBuffer)
        return mapiFunctions.MAPIAllocateBuffer(cbSize, lppBuffer);

    if (!lppBuffer)
        return E_INVALIDARG;

    lpBuff = HeapAlloc(GetProcessHeap(), 0, cbSize + sizeof(*lpBuff));
    if (!lpBuff)
        return MAPI_E_NOT_ENOUGH_MEMORY;

    TRACE("initial allocation:%p, returning %p\n", lpBuff, lpBuff + 1);
    *lpBuff++ = NULL;
    *lppBuffer = lpBuff;
    return S_OK;
}

/**************************************************************************
 *  MAPIAllocateMore    (MAPI32.14)
 *  MAPIAllocateMore@12 (MAPI32.15)
 *
 * Allocate a block of memory linked to a previous allocation.
 *
 * PARAMS
 *  cbSize    [I] Size of the block to allocate in bytes
 *  lpOrig    [I] Initial allocation to link to, from MAPIAllocateBuffer()
 *  lppBuffer [O] Destination for pointer to allocated memory
 *
 * RETURNS
 *  Success: S_OK. *lppBuffer is filled with a pointer to a memory block of
 *           length cbSize bytes.
 *  Failure: MAPI_E_INVALID_PARAMETER, if lpOrig or lppBuffer is invalid.
 *           MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails.
 *
 * NOTES
 *  Memory allocated with this function and stored in *lppBuffer is freed
 *  when lpOrig is passed to MAPIFreeBuffer(). It should not be freed independently.
 */
SCODE WINAPI MAPIAllocateMore(ULONG cbSize, LPVOID lpOrig, LPVOID *lppBuffer)
{
    LPMAPIALLOCBUFFER lpBuff = lpOrig;

    TRACE("(%d,%p,%p)\n", cbSize, lpOrig, lppBuffer);

    if (mapiFunctions.MAPIAllocateMore)
        return mapiFunctions.MAPIAllocateMore(cbSize, lpOrig, lppBuffer);

    if (!lppBuffer || !lpBuff || !--lpBuff)
        return E_INVALIDARG;

    /* Find the last allocation in the chain */
    while (*lpBuff)
    {
        TRACE("linked:%p->%p\n", lpBuff, *lpBuff);
        lpBuff = *lpBuff;
    }

    if (SUCCEEDED(MAPIAllocateBuffer(cbSize, lppBuffer)))
    {
        *lpBuff = ((LPMAPIALLOCBUFFER)*lppBuffer) - 1;
        TRACE("linking %p->%p\n", lpBuff, *lpBuff);
    }
    return *lppBuffer ? S_OK : MAPI_E_NOT_ENOUGH_MEMORY;
}

/**************************************************************************
 *  MAPIFreeBuffer   (MAPI32.16)
 *  MAPIFreeBuffer@4 (MAPI32.17)
 *
 * Free a block of memory and any linked allocations associated with it.
 *
 * PARAMS
 *  lpBuffer [I] Memory to free, returned from MAPIAllocateBuffer()
 *
 * RETURNS
 *  S_OK.
 */
ULONG WINAPI MAPIFreeBuffer(LPVOID lpBuffer)
{
    LPMAPIALLOCBUFFER lpBuff = lpBuffer;

    TRACE("(%p)\n", lpBuffer);

    if (mapiFunctions.MAPIFreeBuffer)
        return mapiFunctions.MAPIFreeBuffer(lpBuffer);

    if (lpBuff && --lpBuff)
    {
        while (lpBuff)
        {
            LPVOID lpFree = lpBuff;

            lpBuff = *lpBuff;

            TRACE("linked:%p->%p, freeing %p\n", lpFree, lpBuff, lpFree);
            HeapFree(GetProcessHeap(), 0, lpFree);
        }
    }
    return S_OK;
}

/**************************************************************************
 *  WrapProgress@20 (MAPI32.41)
 */
HRESULT WINAPI WrapProgress(PVOID unk1, PVOID unk2, PVOID unk3, PVOID unk4, PVOID unk5)
{
    /* Native does not implement this function */
    return MAPI_E_NO_SUPPORT;
}

/*************************************************************************
 * HrDispatchNotifications@4 (MAPI32.239)
 */
HRESULT WINAPI HrDispatchNotifications(ULONG flags)
{
    FIXME("(%08x)\n", flags);
    return S_OK;
}

/*************************************************************************
 * HrThisThreadAdviseSink@8 (MAPI32.42)
 *
 * Ensure that an advise sink is only notified in its originating thread.
 *
 * PARAMS
 *  lpSink     [I] IMAPIAdviseSink interface to be protected
 *  lppNewSink [I] Destination for wrapper IMAPIAdviseSink interface
 *
 * RETURNS
 * Success: S_OK. *lppNewSink contains a new sink to use in place of lpSink.
 * Failure: E_INVALIDARG, if any parameter is invalid.
 */
HRESULT WINAPI HrThisThreadAdviseSink(LPMAPIADVISESINK lpSink, LPMAPIADVISESINK* lppNewSink)
{
    if (mapiFunctions.HrThisThreadAdviseSink)
        return mapiFunctions.HrThisThreadAdviseSink(lpSink, lppNewSink);

    FIXME("(%p,%p)semi-stub\n", lpSink, lppNewSink);

    if (!lpSink || !lppNewSink)
        return E_INVALIDARG;

    /* Don't wrap the sink for now, just copy it */
    *lppNewSink = lpSink;
    IMAPIAdviseSink_AddRef(lpSink);
    return S_OK;
}

/*************************************************************************
 * FBinFromHex (MAPI32.44)
 *
 * Create an array of binary data from a string.
 *
 * PARAMS
 *  lpszHex [I] String to convert to binary data
 *  lpOut   [O] Destination for resulting binary data
 *
 * RETURNS
 *  Success: TRUE. lpOut contains the decoded binary data.
 *  Failure: FALSE, if lpszHex does not represent a binary string.
 *
 * NOTES
 *  - lpOut must be at least half the length of lpszHex in bytes.
 *  - Although the Mapi headers prototype this function as both
 *    Ascii and Unicode, there is only one (Ascii) implementation. This
 *    means that lpszHex is treated as an Ascii string (i.e. a single NUL
 *    character in the byte stream terminates the string).
 */
BOOL WINAPI FBinFromHex(LPWSTR lpszHex, LPBYTE lpOut)
{
    LPSTR lpStr = (LPSTR)lpszHex;

    TRACE("(%p,%p)\n", lpszHex, lpOut);

    while (*lpStr)
    {
        if (lpStr[0] < '0' || lpStr[0] > 'f' || digitsToHex[lpStr[0] - '0'] == 0xff ||
            lpStr[1] < '0' || lpStr[1] > 'f' || digitsToHex[lpStr[1] - '0'] == 0xff)
            return FALSE;

        *lpOut++ = (digitsToHex[lpStr[0] - '0'] << 4) | digitsToHex[lpStr[1] - '0'];
        lpStr += 2;
    }
    return TRUE;
}

/*************************************************************************
 * HexFromBin (MAPI32.45)
 *
 * Create a string from an array of binary data.
 *
 * PARAMS
 *  lpHex   [I] Binary data to convert to string
 *  iCount  [I] Length of lpHex in bytes
 *  lpszOut [O] Destination for resulting hex string
 *
 * RETURNS
 *  Nothing.
 *
 * NOTES
 *  - lpszOut must be at least 2 * iCount + 1 bytes characters long.
 *  - Although the Mapi headers prototype this function as both
 *    Ascii and Unicode, there is only one (Ascii) implementation. This
 *    means that the resulting string is not properly NUL terminated
 *    if the caller expects it to be a Unicode string.
 */
void WINAPI HexFromBin(LPBYTE lpHex, int iCount, LPWSTR lpszOut)
{
    static const char hexDigits[] = { "0123456789ABCDEF" };
    LPSTR lpStr = (LPSTR)lpszOut;

    TRACE("(%p,%d,%p)\n", lpHex, iCount, lpszOut);

    while (iCount-- > 0)
    {
        *lpStr++ = hexDigits[*lpHex >> 4];
        *lpStr++ = hexDigits[*lpHex & 0xf];
        lpHex++;
    }
    *lpStr = '\0';
}

/*************************************************************************
 * SwapPlong@8 (MAPI32.47)
 *
 * Swap the bytes in a ULONG array.
 *
 * PARAMS
 *  lpData [O] Array to swap bytes in
 *  ulLen  [I] Number of ULONG element to swap the bytes of
 *
 * RETURNS
 *  Nothing.
 */
VOID WINAPI SwapPlong(PULONG lpData, ULONG ulLen)
{
    ULONG i;

    for (i = 0; i < ulLen; i++)
        lpData[i] = RtlUlongByteSwap(lpData[i]);
}

/*************************************************************************
 * SwapPword@8 (MAPI32.48)
 *
 * Swap the bytes in a USHORT array.
 *
 * PARAMS
 *  lpData [O] Array to swap bytes in
 *  ulLen  [I] Number of USHORT element to swap the bytes of
 *
 * RETURNS
 *  Nothing.
 */
VOID WINAPI SwapPword(PUSHORT lpData, ULONG ulLen)
{
    ULONG i;

    for (i = 0; i < ulLen; i++)
        lpData[i] = RtlUshortByteSwap(lpData[i]);
}

/**************************************************************************
 *  MNLS_lstrlenW@4 (MAPI32.62)
 *
 * Calculate the length of a Unicode string.
 *
 * PARAMS
 *  lpszStr [I] String to calculate the length of
 *
 * RETURNS
 *  The length of lpszStr in Unicode characters.
 */
ULONG WINAPI MNLS_lstrlenW(LPCWSTR lpszStr)
{
    TRACE("(%s)\n", debugstr_w(lpszStr));
    return lstrlenW(lpszStr);
}

/*************************************************************************
 * MNLS_lstrcmpW@8 (MAPI32.63)
 *
 * Compare two Unicode strings.
 *
 * PARAMS
 *  lpszLeft  [I] First string to compare
 *  lpszRight [I] Second string to compare
 *
 * RETURNS
 *  An integer less than, equal to or greater than 0, indicating that
 *  lpszLeft is less than, the same, or greater than lpszRight.
 */
INT WINAPI MNLS_lstrcmpW(LPCWSTR lpszLeft, LPCWSTR lpszRight)
{
    TRACE("(%s,%s)\n", debugstr_w(lpszLeft), debugstr_w(lpszRight));
    return lstrcmpW(lpszLeft, lpszRight);
}

/*************************************************************************
 * MNLS_lstrcpyW@8 (MAPI32.64)
 *
 * Copy a Unicode string to another string.
 *
 * PARAMS
 *  lpszDest [O] Destination string
 *  lpszSrc  [I] Source string
 *
 * RETURNS
 *  The length lpszDest in Unicode characters.
 */
ULONG WINAPI MNLS_lstrcpyW(LPWSTR lpszDest, LPCWSTR lpszSrc)
{
    ULONG len;

    TRACE("(%p,%s)\n", lpszDest, debugstr_w(lpszSrc));
    len = (lstrlenW(lpszSrc) + 1) * sizeof(WCHAR);
    memcpy(lpszDest, lpszSrc, len);
    return len;
}

/*************************************************************************
 * MNLS_CompareStringW@12 (MAPI32.65)
 *
 * Compare two Unicode strings.
 *
 * PARAMS
 *  dwCp      [I] Code page for the comparison
 *  lpszLeft  [I] First string to compare
 *  lpszRight [I] Second string to compare
 *
 * RETURNS
 *  CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN, indicating that
 *  lpszLeft is less than, the same, or greater than lpszRight.
 */
INT WINAPI MNLS_CompareStringW(DWORD dwCp, LPCWSTR lpszLeft, LPCWSTR lpszRight)
{
    INT ret;

    TRACE("0x%08x,%s,%s\n", dwCp, debugstr_w(lpszLeft), debugstr_w(lpszRight));
    ret = MNLS_lstrcmpW(lpszLeft, lpszRight);
    return ret < 0 ? CSTR_LESS_THAN : ret ? CSTR_GREATER_THAN : CSTR_EQUAL;
}

/**************************************************************************
 *  FEqualNames@8 (MAPI32.72)
 *
 * Compare two Mapi names.
 *
 * PARAMS
 *  lpName1 [I] First name to compare to lpName2
 *  lpName2 [I] Second name to compare to lpName1
 *
 * RETURNS
 *  TRUE, if the names are the same,
 *  FALSE, Otherwise.
 */
BOOL WINAPI FEqualNames(LPMAPINAMEID lpName1, LPMAPINAMEID lpName2)
{
    TRACE("(%p,%p)\n", lpName1, lpName2);

    if (!lpName1 || !lpName2 ||
        !IsEqualGUID(lpName1->lpguid, lpName2->lpguid) ||
        lpName1->ulKind != lpName2->ulKind)
        return FALSE;

    if (lpName1->ulKind == MNID_STRING)
        return !lstrcmpW(lpName1->Kind.lpwstrName, lpName2->Kind.lpwstrName);

    return lpName1->Kind.lID == lpName2->Kind.lID;
}

/**************************************************************************
 *  IsBadBoundedStringPtr@8 (MAPI32.71)
 *
 * Determine if a string pointer is valid.
 *
 * PARAMS
 *  lpszStr [I] String to check
 *  ulLen   [I] Maximum length of lpszStr
 *
 * RETURNS
 *  TRUE, if lpszStr is invalid or longer than ulLen,
 *  FALSE, otherwise.
 */
BOOL WINAPI IsBadBoundedStringPtr(LPCSTR lpszStr, ULONG ulLen)
{
    if (!lpszStr || IsBadStringPtrA(lpszStr, -1) || strlen(lpszStr) >= ulLen)
        return TRUE;
    return FALSE;
}

/**************************************************************************
 *  FtAddFt@16 (MAPI32.121)
 *
 * Add two FILETIME's together.
 *
 * PARAMS
 *  ftLeft  [I] FILETIME to add to ftRight
 *  ftRight [I] FILETIME to add to ftLeft
 *
 * RETURNS
 *  The sum of ftLeft and ftRight
 */
LONGLONG WINAPI MAPI32_FtAddFt(FILETIME ftLeft, FILETIME ftRight)
{
    LONGLONG *pl = (LONGLONG*)&ftLeft, *pr = (LONGLONG*)&ftRight;

    return *pl + *pr;
}

/**************************************************************************
 *  FtSubFt@16 (MAPI32.123)
 *
 * Subtract two FILETIME's together.
 *
 * PARAMS
 *  ftLeft  [I] Initial FILETIME
 *  ftRight [I] FILETIME to subtract from ftLeft
 *
 * RETURNS
 *  The remainder after ftRight is subtracted from ftLeft.
 */
LONGLONG WINAPI MAPI32_FtSubFt(FILETIME ftLeft, FILETIME ftRight)
{
    LONGLONG *pl = (LONGLONG*)&ftLeft, *pr = (LONGLONG*)&ftRight;

    return *pr - *pl;
}

/**************************************************************************
 *  FtMulDw@12 (MAPI32.124)
 *
 * Multiply a FILETIME by a DWORD.
 *
 * PARAMS
 *  dwLeft  [I] DWORD to multiply with ftRight
 *  ftRight [I] FILETIME to multiply with dwLeft
 *
 * RETURNS
 *  The product of dwLeft and ftRight
 */
LONGLONG WINAPI MAPI32_FtMulDw(DWORD dwLeft, FILETIME ftRight)
{
    LONGLONG *pr = (LONGLONG*)&ftRight;

    return (LONGLONG)dwLeft * (*pr);
}

/**************************************************************************
 *  FtMulDwDw@8 (MAPI32.125)
 *
 * Multiply two DWORD, giving the result as a FILETIME.
 *
 * PARAMS
 *  dwLeft  [I] DWORD to multiply with dwRight
 *  dwRight [I] DWORD to multiply with dwLeft
 *
 * RETURNS
 *  The product of ftMultiplier and ftMultiplicand as a FILETIME.
 */
LONGLONG WINAPI MAPI32_FtMulDwDw(DWORD dwLeft, DWORD dwRight)
{
    return (LONGLONG)dwLeft * (LONGLONG)dwRight;
}

/**************************************************************************
 *  FtNegFt@8 (MAPI32.126)
 *
 * Negate a FILETIME.
 *
 * PARAMS
 *  ft [I] FILETIME to negate
 *
 * RETURNS
 *  The negation of ft.
 */
LONGLONG WINAPI MAPI32_FtNegFt(FILETIME ft)
{
    LONGLONG *p = (LONGLONG*)&ft;

    return - *p;
}

/**************************************************************************
 *  UlAddRef@4 (MAPI32.128)
 *
 * Add a reference to an object.
 *
 * PARAMS
 *  lpUnk [I] Object to add a reference to.
 *
 * RETURNS
 *  The new reference count of the object, or 0 if lpUnk is NULL.
 *
 * NOTES
 * See IUnknown_AddRef.
 */
ULONG WINAPI UlAddRef(void *lpUnk)
{
    TRACE("(%p)\n", lpUnk);

    if (!lpUnk)
        return 0UL;
    return IUnknown_AddRef((LPUNKNOWN)lpUnk);
}

/**************************************************************************
 *  UlRelease@4 (MAPI32.129)
 *
 * Remove a reference from an object.
 *
 * PARAMS
 *  lpUnk [I] Object to remove reference from.
 *
 * RETURNS
 *  The new reference count of the object, or 0 if lpUnk is NULL. If lpUnk is
 *  non-NULL and this function returns 0, the object pointed to by lpUnk has
 *  been released.
 *
 * NOTES
 * See IUnknown_Release.
 */
ULONG WINAPI UlRelease(void *lpUnk)
{
    TRACE("(%p)\n", lpUnk);

    if (!lpUnk)
        return 0UL;
    return IUnknown_Release((LPUNKNOWN)lpUnk);
}

/**************************************************************************
 *  UFromSz@4 (MAPI32.133)
 *
 * Read an integer from a string
 *
 * PARAMS
 *  lpszStr [I] String to read the integer from.
 *
 * RETURNS
 *  Success: The integer read from lpszStr.
 *  Failure: 0, if the first character in lpszStr is not 0-9.
 *
 * NOTES
 *  This function does not accept whitespace and stops at the first non-digit
 *  character.
 */
UINT WINAPI UFromSz(LPCSTR lpszStr)
{
    ULONG ulRet = 0;

    TRACE("(%s)\n", debugstr_a(lpszStr));

    if (lpszStr)
    {
        while (*lpszStr >= '0' && *lpszStr <= '9')
        {
            ulRet = ulRet * 10 + (*lpszStr - '0');
            lpszStr++;
        }
    }
    return ulRet;
}

/*************************************************************************
 * OpenStreamOnFile@24 (MAPI32.147)
 *
 * Create a stream on a file.
 *
 * PARAMS
 *  lpAlloc    [I] Memory allocation function
 *  lpFree     [I] Memory free function
 *  ulFlags    [I] Flags controlling the opening process
 *  lpszPath   [I] Path of file to create stream on
 *  lpszPrefix [I] Prefix of the temporary file name (if ulFlags includes SOF_UNIQUEFILENAME)
 *  lppStream  [O] Destination for created stream
 *
 * RETURNS
 * Success: S_OK. lppStream contains the new stream object
 * Failure: E_INVALIDARG if any parameter is invalid, or an HRESULT error code
 *          describing the error.
 */
HRESULT WINAPI OpenStreamOnFile(LPALLOCATEBUFFER lpAlloc, LPFREEBUFFER lpFree,
                                ULONG ulFlags, LPWSTR lpszPath, LPWSTR lpszPrefix,
                                LPSTREAM *lppStream)
{
    WCHAR szBuff[MAX_PATH];
    DWORD dwMode = STGM_READWRITE, dwAttributes = 0;
    HRESULT hRet;

    TRACE("(%p,%p,0x%08x,%s,%s,%p)\n", lpAlloc, lpFree, ulFlags,
          debugstr_a((LPSTR)lpszPath), debugstr_a((LPSTR)lpszPrefix), lppStream);

    if (mapiFunctions.OpenStreamOnFile)
        return mapiFunctions.OpenStreamOnFile(lpAlloc, lpFree, ulFlags, lpszPath, lpszPrefix, lppStream);

    if (lppStream)
        *lppStream = NULL;

    if (ulFlags & SOF_UNIQUEFILENAME)
    {
        FIXME("Should generate a temporary name\n");
        return E_INVALIDARG;
    }

    if (!lpszPath || !lppStream)
        return E_INVALIDARG;

    /* FIXME: Should probably munge mode and attributes, and should handle
     *        Unicode arguments (I assume MAPI_UNICODE is set in ulFlags if
     *        we are being passed Unicode strings; MSDN doesn't say).
     *        This implementation is just enough for Outlook97 to start.
     */
    MultiByteToWideChar(CP_ACP, 0, (LPSTR)lpszPath, -1, szBuff, MAX_PATH);
    hRet = SHCreateStreamOnFileEx(szBuff, dwMode, dwAttributes, TRUE,
                                  NULL, lppStream);
    return hRet;
}

/*************************************************************************
 * UlFromSzHex@4 (MAPI32.155)
 *
 * Read an integer from a hexadecimal string.
 *
 * PARAMS
 *  lpSzHex [I] String containing the hexadecimal number to read
 *
 * RETURNS
 * Success: The number represented by lpszHex.
 * Failure: 0, if lpszHex does not contain a hex string.
 *
 * NOTES
 *  This function does not accept whitespace and stops at the first non-hex
 *  character.
 */
ULONG WINAPI UlFromSzHex(LPCWSTR lpszHex)
{
    LPCSTR lpStr = (LPCSTR)lpszHex;
    ULONG ulRet = 0;

    TRACE("(%s)\n", debugstr_a(lpStr));

    while (*lpStr)
    {
        if (lpStr[0] < '0' || lpStr[0] > 'f' || digitsToHex[lpStr[0] - '0'] == 0xff ||
            lpStr[1] < '0' || lpStr[1] > 'f' || digitsToHex[lpStr[1] - '0'] == 0xff)
            break;

        ulRet = ulRet * 16 + ((digitsToHex[lpStr[0] - '0'] << 4) | digitsToHex[lpStr[1] - '0']);
        lpStr += 2;
    }
    return ulRet;
}

/************************************************************************
 * FBadEntryList@4 (MAPI32.190)
 *
 * Determine is an entry list is invalid.
 *
 * PARAMS
 *  lpEntryList [I] List to check
 *
 * RETURNS
 *  TRUE, if lpEntryList is invalid,
 *  FALSE, otherwise.
 */
BOOL WINAPI FBadEntryList(LPENTRYLIST lpEntryList)
{
    ULONG i;

    if (IsBadReadPtr(lpEntryList, sizeof(*lpEntryList)) ||
        IsBadReadPtr(lpEntryList->lpbin,
                     lpEntryList->cValues * sizeof(*lpEntryList->lpbin)))
        return TRUE;

    for (i = 0; i < lpEntryList->cValues; i++)
        if(IsBadReadPtr(lpEntryList->lpbin[i].lpb, lpEntryList->lpbin[i].cb))
            return TRUE;

    return FALSE;
}

/*************************************************************************
 * CbOfEncoded@4 (MAPI32.207)
 *
 * Return the length of an encoded string.
 *
 * PARAMS
 *  lpSzEnc [I] Encoded string to get the length of.
 *
 * RETURNS
 * The length of the encoded string in bytes.
 */
ULONG WINAPI CbOfEncoded(LPCSTR lpszEnc)
{
    ULONG ulRet = 0;

    TRACE("(%s)\n", debugstr_a(lpszEnc));

    if (lpszEnc)
        ulRet = (((strlen(lpszEnc) | 3) >> 2) + 1) * 3;
    return ulRet;
}

/*************************************************************************
 * cmc_query_configuration (MAPI32.235)
 *
 * Retrieves the configuration information for the installed CMC
 *
 * PARAMS
 *  session          [I]   MAPI session handle
 *  item             [I]   Enumerated variable that identifies which 
 *                         configuration information is being requested
 *  reference        [O]   Buffer where configuration information is written
 *  config_extensions[I/O] Path of file to create stream on
 *
 * RETURNS
 * A CMD define
 */
CMC_return_code WINAPI cmc_query_configuration(
  CMC_session_id session,
  CMC_enum item,
  CMC_buffer reference,
  CMC_extension  *config_extensions)
{
	FIXME("stub\n");
	return CMC_E_NOT_SUPPORTED;
}

/**************************************************************************
 *  FGetComponentPath   (MAPI32.254)
 *  FGetComponentPath@20 (MAPI32.255)
 *
 * Return the installed component path, usually to the private mapi32.dll.
 *
 * PARAMS
 *  component       [I] Component ID
 *  qualifier       [I] Application LCID
 *  dll_path        [O] returned component path
 *  dll_path_length [I] component path length
 *  install         [I] install mode
 *
 * RETURNS
 *  Success: TRUE.
 *  Failure: FALSE.
 *
 * NOTES
 *  Previously documented in Q229700 "How to locate the correct path
 *  to the Mapisvc.inf file in Microsoft Outlook".
 */
BOOL WINAPI FGetComponentPath(LPCSTR component, LPCSTR qualifier, LPSTR dll_path,
                              DWORD dll_path_length, BOOL install)
{
    BOOL ret = FALSE;
    HMODULE hmsi;

    TRACE("%s %s %p %u %d\n", component, qualifier, dll_path, dll_path_length, install);

    if (mapiFunctions.FGetComponentPath)
        return mapiFunctions.FGetComponentPath(component, qualifier, dll_path, dll_path_length, install);

    dll_path[0] = 0;

    hmsi = LoadLibraryA("msi.dll");
    if (hmsi)
    {
        UINT (WINAPI *pMsiProvideQualifiedComponentA)(LPCSTR, LPCSTR, DWORD, LPSTR, LPDWORD);

        pMsiProvideQualifiedComponentA = (void *)GetProcAddress(hmsi, "MsiProvideQualifiedComponentA");
        if (pMsiProvideQualifiedComponentA)
        {
            static const char * const fmt[] = { "%d\\NT", "%d\\95", "%d" };
            char lcid_ver[20];
            UINT i;

            for (i = 0; i < ARRAY_SIZE(fmt); i++)
            {
                /* FIXME: what's the correct behaviour here? */
                if (!qualifier || qualifier == lcid_ver)
                {
                    sprintf(lcid_ver, fmt[i], GetUserDefaultUILanguage());
                    qualifier = lcid_ver;
                }

                if (pMsiProvideQualifiedComponentA(component, qualifier,
                        install ? INSTALLMODE_DEFAULT : INSTALLMODE_EXISTING,
                        dll_path, &dll_path_length) == ERROR_SUCCESS)
                {
                    ret = TRUE;
                    break;
                }

                if (qualifier != lcid_ver) break;
            }
        }
        FreeLibrary(hmsi);
    }
    return ret;
}

/**************************************************************************
 *  HrQueryAllRows   (MAPI32.75)
 */
HRESULT WINAPI HrQueryAllRows(LPMAPITABLE lpTable, LPSPropTagArray lpPropTags,
    LPSRestriction lpRestriction, LPSSortOrderSet lpSortOrderSet,
    LONG crowsMax, LPSRowSet *lppRows)
{
    if (mapiFunctions.HrQueryAllRows)
        return mapiFunctions.HrQueryAllRows(lpTable, lpPropTags, lpRestriction, lpSortOrderSet, crowsMax, lppRows);

    FIXME("(%p, %p, %p, %p, %d, %p): stub\n", lpTable, lpPropTags, lpRestriction, lpSortOrderSet, crowsMax, lppRows);
    *lppRows = NULL;
    return MAPI_E_CALL_FAILED;
}

/**************************************************************************
 *  WrapCompressedRTFStream   (MAPI32.186)
 */
HRESULT WINAPI WrapCompressedRTFStream(LPSTREAM compressed, ULONG flags, LPSTREAM *uncompressed)
{
    if (mapiFunctions.WrapCompressedRTFStream)
        return mapiFunctions.WrapCompressedRTFStream(compressed, flags, uncompressed);

    FIXME("(%p, 0x%08x, %p): stub\n", compressed, flags, uncompressed);
    return MAPI_E_NO_SUPPORT;
}

static HMODULE mapi_provider;
static HMODULE mapi_ex_provider;

/**************************************************************************
 *  load_mapi_provider
 *
 * Attempts to load a MAPI provider from the specified registry key.
 *
 * Returns a handle to the loaded module in `mapi_provider' if successful.
 */
static void load_mapi_provider(HKEY hkeyMail, LPCWSTR valueName, HMODULE *mapi_provider)
{
    static const WCHAR mapi32_dll[] = {'m','a','p','i','3','2','.','d','l','l',0 };

    DWORD dwType, dwLen = 0;
    LPWSTR dllPath;

    /* Check if we have a value set for DLLPath */
    if ((RegQueryValueExW(hkeyMail, valueName, NULL, &dwType, NULL, &dwLen) == ERROR_SUCCESS) &&
        ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) && (dwLen > 0))
    {
        dllPath = HeapAlloc(GetProcessHeap(), 0, dwLen);

        if (dllPath)
        {
            RegQueryValueExW(hkeyMail, valueName, NULL, NULL, (LPBYTE)dllPath, &dwLen);

            /* Check that this value doesn't refer to mapi32.dll (eg, as Outlook does) */
            if (lstrcmpiW(dllPath, mapi32_dll) != 0)
            {
                if (dwType == REG_EXPAND_SZ)
                {
                    DWORD dwExpandLen;
                    LPWSTR dllPathExpanded;

                    /* Expand the path if necessary */
                    dwExpandLen = ExpandEnvironmentStringsW(dllPath, NULL, 0);
                    dllPathExpanded = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * dwExpandLen + 1);

                    if (dllPathExpanded)
                    {
                        ExpandEnvironmentStringsW(dllPath, dllPathExpanded, dwExpandLen + 1);

                        HeapFree(GetProcessHeap(), 0, dllPath);
                        dllPath = dllPathExpanded;
                    }
                }

                /* Load the DLL */
                TRACE("loading %s\n", debugstr_w(dllPath));
                *mapi_provider = LoadLibraryW(dllPath);
            }

            HeapFree(GetProcessHeap(), 0, dllPath);
        }
    }
}

/**************************************************************************
 *  load_mapi_providers
 *
 * Scans the registry for MAPI providers and attempts to load a Simple and
 * Extended MAPI library.
 *
 * Returns TRUE if at least one library loaded, FALSE otherwise.
 */
void load_mapi_providers(void)
{
    static const WCHAR regkey_mail[] = {
        'S','o','f','t','w','a','r','e','\\','C','l','i','e','n','t','s','\\',
        'M','a','i','l',0 };

    static const WCHAR regkey_dllpath[] = {'D','L','L','P','a','t','h',0 };
    static const WCHAR regkey_dllpath_ex[] = {'D','L','L','P','a','t','h','E','x',0 };
    static const WCHAR regkey_backslash[] = { '\\', 0 };

    HKEY hkeyMail;
    DWORD dwType, dwLen = 0;
    LPWSTR appName = NULL, appKey = NULL;

    TRACE("()\n");

    /* Open the Mail key */
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, regkey_mail, 0, KEY_READ, &hkeyMail) != ERROR_SUCCESS)
        return;

    /* Check if we have a default value set, and the length of it */
    if ((RegQueryValueExW(hkeyMail, NULL, NULL, &dwType, NULL, &dwLen) != ERROR_SUCCESS) ||
        !((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) || (dwLen == 0))
        goto cleanUp;

    appName = HeapAlloc(GetProcessHeap(), 0, dwLen);

    if (!appName)
        goto cleanUp;

    /* Get the value, and get the path to the app key */
    RegQueryValueExW(hkeyMail, NULL, NULL, NULL, (LPBYTE)appName, &dwLen);

    TRACE("appName: %s\n", debugstr_w(appName));

    appKey = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (lstrlenW(regkey_mail) +
        lstrlenW(regkey_backslash) + lstrlenW(appName) + 1));

    if (!appKey)
        goto cleanUp;

    lstrcpyW(appKey, regkey_mail);
    lstrcatW(appKey, regkey_backslash);
    lstrcatW(appKey, appName);

    RegCloseKey(hkeyMail);

    TRACE("appKey: %s\n", debugstr_w(appKey));

    /* Open the app's key */
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, appKey, 0, KEY_READ, &hkeyMail) != ERROR_SUCCESS)
        goto cleanUp;

    /* Try to load the providers */
    load_mapi_provider(hkeyMail, regkey_dllpath, &mapi_provider);
    load_mapi_provider(hkeyMail, regkey_dllpath_ex, &mapi_ex_provider);

    /* Now try to load our function pointers */
    ZeroMemory(&mapiFunctions, sizeof(mapiFunctions));

    /* Simple MAPI functions */
    if (mapi_provider)
    {
        mapiFunctions.MAPIAddress = (void*) GetProcAddress(mapi_provider, "MAPIAddress");
        mapiFunctions.MAPIDeleteMail = (void*) GetProcAddress(mapi_provider, "MAPIDeleteMail");
        mapiFunctions.MAPIDetails = (void*) GetProcAddress(mapi_provider, "MAPIDetails");
        mapiFunctions.MAPIFindNext = (void*) GetProcAddress(mapi_provider, "MAPIFindNext");
        mapiFunctions.MAPILogoff = (void*) GetProcAddress(mapi_provider, "MAPILogoff");
        mapiFunctions.MAPILogon = (void*) GetProcAddress(mapi_provider, "MAPILogon");
        mapiFunctions.MAPIReadMail = (void*) GetProcAddress(mapi_provider, "MAPIReadMail");
        mapiFunctions.MAPIResolveName = (void*) GetProcAddress(mapi_provider, "MAPIResolveName");
        mapiFunctions.MAPISaveMail = (void*) GetProcAddress(mapi_provider, "MAPISaveMail");
        mapiFunctions.MAPISendDocuments = (void*) GetProcAddress(mapi_provider, "MAPISendDocuments");
        mapiFunctions.MAPISendMail = (void*) GetProcAddress(mapi_provider, "MAPISendMail");
        mapiFunctions.MAPISendMailW = (void*) GetProcAddress(mapi_provider, "MAPISendMailW");
    }

    /* Extended MAPI functions */
    if (mapi_ex_provider)
    {
        mapiFunctions.MAPIInitialize = (void*) GetProcAddress(mapi_ex_provider, "MAPIInitialize");
        mapiFunctions.MAPILogonEx = (void*) GetProcAddress(mapi_ex_provider, "MAPILogonEx");
        mapiFunctions.MAPIUninitialize = (void*) GetProcAddress(mapi_ex_provider, "MAPIUninitialize");

        mapiFunctions.DeinitMapiUtil = (void*) GetProcAddress(mapi_ex_provider, "DeinitMapiUtil@0");
        mapiFunctions.DllCanUnloadNow = (void*) GetProcAddress(mapi_ex_provider, "DllCanUnloadNow");
        mapiFunctions.DllGetClassObject = (void*) GetProcAddress(mapi_ex_provider, "DllGetClassObject");
        mapiFunctions.FGetComponentPath = (void*) GetProcAddress(mapi_ex_provider, "FGetComponentPath");
        mapiFunctions.HrThisThreadAdviseSink = (void*) GetProcAddress(mapi_ex_provider, "HrThisThreadAdviseSink@8");
        mapiFunctions.HrQueryAllRows = (void*) GetProcAddress(mapi_ex_provider, "HrQueryAllRows@24");
        mapiFunctions.MAPIAdminProfiles = (void*) GetProcAddress(mapi_ex_provider, "MAPIAdminProfiles");
        mapiFunctions.MAPIAllocateBuffer = (void*) GetProcAddress(mapi_ex_provider, "MAPIAllocateBuffer");
        mapiFunctions.MAPIAllocateMore = (void*) GetProcAddress(mapi_ex_provider, "MAPIAllocateMore");
        mapiFunctions.MAPIFreeBuffer = (void*) GetProcAddress(mapi_ex_provider, "MAPIFreeBuffer");
        mapiFunctions.MAPIGetDefaultMalloc = (void*) GetProcAddress(mapi_ex_provider, "MAPIGetDefaultMalloc@0");
        mapiFunctions.MAPIOpenLocalFormContainer = (void *) GetProcAddress(mapi_ex_provider, "MAPIOpenLocalFormContainer");
        mapiFunctions.OpenStreamOnFile = (void*) GetProcAddress(mapi_ex_provider, "OpenStreamOnFile@24");
        mapiFunctions.ScInitMapiUtil = (void*) GetProcAddress(mapi_ex_provider, "ScInitMapiUtil@4");
        mapiFunctions.WrapCompressedRTFStream = (void*) GetProcAddress(mapi_ex_provider, "WrapCompressedRTFStream@12");
    }

cleanUp:
    RegCloseKey(hkeyMail);
    HeapFree(GetProcessHeap(), 0, appKey);
    HeapFree(GetProcessHeap(), 0, appName);
}

/**************************************************************************
 *  unload_mapi_providers
 *
 * Unloads any loaded MAPI libraries.
 */
void unload_mapi_providers(void)
{
    TRACE("()\n");

    FreeLibrary(mapi_provider);
    FreeLibrary(mapi_ex_provider);
}