#include "pch.h"
#include <urlmon.h>
#pragma hdrstop


/*-----------------------------------------------------------------------------
/ Internal only string APIs
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ StringToDWORD
/ -------------
/   Scan the string converting it to a DWORD, cope with hex and decimal alike,
/   more than likely we will receive a hex number though.
/
/ In:
/   pString -> string to parse
/
/ Out:
/   DWORD
/----------------------------------------------------------------------------*/
DWORD StringToDWORD(LPWSTR pString)
{
    DWORD dwResult = 0x0;
    USES_CONVERSION;

    TraceEnter(TRACE_COMMONAPI, "StringToDWORD");
    Trace(TEXT("pString %s"), W2T(pString));

    // Is the leading sequence 0x?  If so then lets parse as hex, otherwise
    // we can pass to StrToInt.

    if ( pString[0] == L'0' && pString[1] == L'x' )
    {
        for ( pString += 2; *pString; pString++ )
        {
            WCHAR ch = *pString;
        
            if ( InRange(ch, L'0', L'9') )
            {
                dwResult = (dwResult << 4) | (ch - L'0');
            }
            else if ( InRange(ch | (L'a'-L'A'), L'a', L'f') )
            {
                dwResult = (dwResult << 4) | (ch - L'a' + 10);
            }
            else
            {
                break;          // tread non 0-9, A-F as end of string
            }
        }
    }
    else
    {
        dwResult = (DWORD)StrToIntW(pString);
    }

    Trace(TEXT("DWORD result is %08x"), dwResult);

    TraceLeaveValue(dwResult);
}


/*-----------------------------------------------------------------------------
/ StringToURL
/ -----------
/   Convert a string to URL format, mashing the characters as required.
/
/ In:
/   pString -> string to be converted
/   ppResult -> receives a pointer to the new string (free using LocalFreeString).
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT StringToURL(LPCTSTR pString, LPTSTR* ppResult)
{
    HRESULT hr;
    TCHAR szEncodedURL[INTERNET_MAX_URL_LENGTH];
    DWORD dwLen = ARRAYSIZE(szEncodedURL);
    int i;

    TraceEnter(TRACE_COMMONAPI, "StringToURL");
    TraceAssert(pString);
    TraceAssert(ppResult);

    *ppResult = NULL;               // incase of failure

    if ( !InternetCanonicalizeUrl(pString, szEncodedURL, &dwLen, 0) )
        ExitGracefully(hr, E_FAIL, "Failed to convert URL to encoded format");

    hr = LocalAllocString(ppResult, szEncodedURL);
    FailGracefully(hr, "Failed to allocate copy of URL");

    hr = S_OK;                      // success

exit_gracefully:

    if ( FAILED(hr) && *ppResult )
        LocalFreeString(ppResult);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ Exported APIs
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ StringDPA_InsertString
/ ----------------------
/   Make a copy of the given string and place it into the DPA.  It can then
/   be accessed using the StringDPA_GetString, or free'd using the 
/   StringDPA_Destroy/StringDPA_DeleteString.
/
/ In:
/   hdpa = DPA to put string into
/   i = index to insert at
/   pString -> string to be inserted
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI StringDPA_InsertStringA(HDPA hdpa, INT i, LPCSTR pString)
{
    if ( hdpa && pString )
    {
        LPSTR pStringCopy = NULL;

        HRESULT hr = LocalAllocStringA(&pStringCopy, pString);
        if ( FAILED(hr) )
            return hr;

        if ( -1 == DPA_InsertPtr(hdpa, i, pStringCopy) )
        {
            LocalFreeStringA(&pStringCopy);
            return E_OUTOFMEMORY;
        }
    }

    return S_OK;
}

STDAPI StringDPA_InsertStringW(HDPA hdpa, INT i, LPCWSTR pString)
{
    if ( hdpa && pString )
    {
        LPWSTR pStringCopy = NULL;

        HRESULT hr = LocalAllocStringW(&pStringCopy, pString);
        if ( FAILED(hr) )
            return hr;

        if ( -1 == DPA_InsertPtr(hdpa, i, pStringCopy) )
        {
            LocalFreeStringW(&pStringCopy);
            return E_OUTOFMEMORY;
        }
    }

    return S_OK;
}


/*-----------------------------------------------------------------------------
/ StringDPA_AppendString
/ ----------------------
/   Make a copy of the given string and place it into the DPA.  It can then
/   be accessed using the StringDPA_GetString, or free'd using the 
/   StringDPA_Destroy/StringDPA_DeleteString.
/
/ In:
/   hdpa = DPA to put string into
/   pString -> string to be append
/   pres = resulting index
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI StringDPA_AppendStringA(HDPA hdpa, LPCSTR pString, PUINT_PTR pres)
{
    HRESULT hr;
    INT ires = 0;
    LPSTR pStringCopy = NULL;

    TraceEnter(TRACE_COMMONAPI, "StringDPA_AppendStringA");
    TraceAssert(hdpa);
    TraceAssert(pString);

    if ( hdpa && pString )
    {
        hr = LocalAllocStringA(&pStringCopy, pString);
        FailGracefully(hr, "Failed to allocate string copy");

        ires = DPA_AppendPtr(hdpa, pStringCopy);
        if ( -1 == ires )
            ExitGracefully(hr, E_OUTOFMEMORY, "Failed to add string to DPA");

        if ( pres )
            *pres = ires;
    }

    hr = S_OK;

exit_gracefully:

    if ( FAILED(hr) )
        LocalFreeStringA(&pStringCopy);

    TraceLeaveResult(hr);
}

STDAPI StringDPA_AppendStringW(HDPA hdpa, LPCWSTR pString, PUINT_PTR pres)
{
    HRESULT hr;
    INT ires = 0;
    LPWSTR pStringCopy = NULL;

    TraceEnter(TRACE_COMMONAPI, "StringDPA_AppendStringW");
    TraceAssert(hdpa);
    TraceAssert(pString);

    if ( hdpa && pString )
    {
        hr = LocalAllocStringW(&pStringCopy, pString);
        FailGracefully(hr, "Failed to allocate string copy");

        ires = DPA_AppendPtr(hdpa, pStringCopy);
        if ( -1 == ires )
            ExitGracefully(hr, E_OUTOFMEMORY, "Failed to add string to DPA");

        if ( pres )
            *pres = ires;
    }

    hr = S_OK;

exit_gracefully:

    if ( FAILED(hr) )
        LocalFreeStringW(&pStringCopy);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ StringDPA_DeleteString
/ ----------------------
/   Delete the specified index from the DPA, freeing the string element
/   that we have dangling from the index.
/
/ In:
/   hdpa -> handle to DPA to be destroyed
/   index = index of item to free
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
STDAPI_(VOID) StringDPA_DeleteString(HDPA hdpa, INT index)
{
    TraceEnter(TRACE_COMMONAPI, "StringDPA_DeleteString");

    if ( hdpa && (index < DPA_GetPtrCount(hdpa)) )
    {
// assumes LocalAllocString uses LocalAlloc (fair enough I guess)            
        LocalFree((HLOCAL)DPA_FastGetPtr(hdpa, index));
        DPA_DeletePtr(hdpa, index);
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ StringDPA_Destroy
/ -----------------
/   Take the given string DPA and destory it.
/
/ In:
/   pHDPA -> handle to DPA to be destroyed
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

INT _DestroyStringDPA(LPVOID pItem, LPVOID pData)
{
// assumes that LocalAllocString does just that, 
// to store the string.
    LocalFree((HLOCAL)pItem);
    return 1;
}

STDAPI_(VOID) StringDPA_Destroy(HDPA* pHDPA)
{
    TraceEnter(TRACE_COMMONAPI, "StringDPA_Destroy");
    
    if ( pHDPA && *pHDPA )
    {
        DPA_DestroyCallback(*pHDPA, _DestroyStringDPA, NULL);
        *pHDPA = NULL;
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ LocalAllocString
/ ------------------
/   Allocate a string, and initialize it with the specified contents.
/
/ In:
/   ppResult -> recieves pointer to the new string
/   pString -> string to initialize with
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI LocalAllocStringA(LPSTR* ppResult, LPCSTR pString)
{
    *ppResult = NULL;

    if ( pString )
    {
        *ppResult = (LPSTR)LocalAlloc(LPTR, StringByteSizeA(pString));
        if ( !*ppResult )
            return E_OUTOFMEMORY;

        StrCpyA(*ppResult, pString);
    }

    return S_OK;
}

STDAPI LocalAllocStringW(LPWSTR* ppResult, LPCWSTR pString)
{
    *ppResult = NULL;

    if ( pString )
    {
        *ppResult = (LPWSTR)LocalAlloc(LPTR, StringByteSizeW(pString));
        if ( !*ppResult )
            return E_OUTOFMEMORY;

        StrCpyW(*ppResult, pString);
    }

    return S_OK;
}


/*----------------------------------------------------------------------------
/ LocalAllocStringLen
/ -------------------
/   Given a length return a buffer of that size.
/
/ In:
/   ppResult -> receives the pointer to the string
/   cLen = length in characters to allocate
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI LocalAllocStringLenA(LPSTR* ppResult, UINT cLen)
{
    *ppResult = (LPSTR)LocalAlloc(LPTR, (cLen+1)*SIZEOF(CHAR));
    return (*ppResult) ? S_OK:E_OUTOFMEMORY;
}

STDAPI LocalAllocStringLenW(LPWSTR* ppResult, UINT cLen)
{
    *ppResult = (LPWSTR)LocalAlloc(LPTR, (cLen+1)*SIZEOF(WCHAR));
    return (*ppResult) ? S_OK:E_OUTOFMEMORY;
}


/*-----------------------------------------------------------------------------
/ LocalFreeString
/ -----------------
/   Release the string pointed to be *ppString (which can be null) and
/   then reset the pointer back to NULL.   
/
/ In:
/   ppString -> pointer to string pointer to be free'd
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

VOID LocalFreeStringA(LPSTR* ppString)
{
    LocalFreeStringW((LPWSTR*)ppString);
}

VOID LocalFreeStringW(LPWSTR* ppString)
{
    if ( ppString )
    {
        if ( *ppString )
            LocalFree((HLOCAL)*ppString);

        *ppString = NULL;
    }
}


/*-----------------------------------------------------------------------------
/ LocalQueryString
/ ------------------
/   Hit the registry returning the wide version of the given string,
/   we dynamically allocate the buffer to put the result into,
/   this should be free'd by calling LocalFreeString.
/
/ In:
/   ppString -> receives the string point
/   hkey = key to query from
/   pSubKey -> pointer to sub key identifier
/   
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

STDAPI _LocalQueryString(LPTSTR* ppResult, HKEY hKey, LPCTSTR pSubKey)
{
    HRESULT hr;
    DWORD dwSize = NULL;

    TraceEnter(TRACE_COMMONAPI, "_LocalQueryString");

    *ppResult = NULL;

    if ( ERROR_SUCCESS != RegQueryValueEx(hKey, pSubKey, NULL, NULL, NULL, &dwSize) )
        ExitGracefully(hr, E_FAIL, "Failed when querying for key size");

    dwSize += SIZEOF(TCHAR);               
    *ppResult = (LPTSTR)LocalAlloc(LPTR, dwSize);

    if ( !*ppResult )
        ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate buffer for value");

    if ( ERROR_SUCCESS != RegQueryValueEx(hKey, pSubKey, NULL, NULL, (LPBYTE)*ppResult, &dwSize) )
        ExitGracefully(hr, E_FAIL, "Failed to read key value into buffer");

    hr = S_OK;

exit_gracefully:

    if ( FAILED(hr) )
        LocalFreeString(ppResult);

    TraceLeaveResult(hr);
}

// Query string as ANSI, converting to ANSI if build UNICODE

STDAPI LocalQueryStringA(LPSTR* ppResult, HKEY hKey, LPCTSTR pSubKey)
{
    HRESULT hr;
    LPTSTR pResult = NULL;
    USES_CONVERSION;
    
    TraceEnter(TRACE_COMMONAPI, "LocalQueryStringA");

    *ppResult = NULL;       // incase of failure

#ifdef UNICODE
    hr = _LocalQueryString(&pResult, hKey, pSubKey);
    FailGracefully(hr, "Failed to read the UNICODE version of string");

    hr = LocalAllocStringW2A(ppResult, pResult);
    FailGracefully(hr, "Failed to allocate ANSI version of string");
#else
    hr = _LocalQueryString(ppResult, hKey, pSubKey);
    FailGracefully(hr, "Failed to get key value");
#endif

exit_gracefully:

    if ( FAILED(hr) )
        LocalFreeStringA(ppResult);

    LocalFreeString(&pResult);

    TraceLeaveResult(hr);
}

// Query string as UNICODE, converting to UNICODE if built ANSI

STDAPI LocalQueryStringW(LPWSTR* ppResult, HKEY hKey, LPCTSTR pSubKey)
{
    HRESULT hr;
    LPTSTR pResult = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_COMMONAPI, "LocalQueryStringW");

    *ppResult = NULL;                   // incase of failure

#ifdef UNICODE
    hr = _LocalQueryString(ppResult, hKey, pSubKey);
    FailGracefully(hr, "Falied to get key value");
#else
    hr = _LocalQueryString(&pResult, hKey, pSubKey);
    FailGracefully(hr, "Failed to query key as ANSI string");

    hr = LocalAllocStringA2W(ppResult, pResult);
    FailGracefully(hr, "Failed to allocate UNICODE version of string");
#endif

exit_gracefully:

    if ( FAILED(hr) )
        LocalFreeStringW(ppResult);

    LocalFreeString(&pResult);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ LocalAllocStringA2W / W2A
/ -------------------------
/   Alloc a string converting using MultiByteToWideChar or vice versa.  This
/   allows in place thunking of strings without extra buffer usage.
/
/ In:
/   ppResult -> receives the string point
/   pString -> source string
/   
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI LocalAllocStringA2W(LPWSTR* ppResult, LPCSTR pString)
{
    HRESULT hr;
    INT iLen;

    TraceEnter(TRACE_COMMONAPI, "LocalAllocStringA2W");

    if ( !ppResult && !pString )
        ExitGracefully(hr, E_INVALIDARG, "Bad args for thunked allocate");

    iLen = MultiByteToWideChar(CP_ACP, 0, pString, -1, NULL, 0);

    hr = LocalAllocStringLenW(ppResult, iLen);
    FailGracefully(hr, "Failed to allocate buffer for string");

    MultiByteToWideChar(CP_ACP, 0, pString, -1, *ppResult, iLen+1);

    hr = S_OK;

exit_gracefully:

    TraceLeaveResult(hr);
}

STDAPI LocalAllocStringW2A(LPSTR* ppResult, LPCWSTR pString)
{
    HRESULT hr;
    INT iLen;

    TraceEnter(TRACE_COMMONAPI, "LocalAllocStringW2A");

    if ( !ppResult && !pString )
        ExitGracefully(hr, E_INVALIDARG, "Bad args for thunked allocate");

    iLen = WideCharToMultiByte(CP_ACP, 0, pString, -1, NULL, 0, NULL, NULL);
    
    hr = LocalAllocStringLenA(ppResult, iLen);
    FailGracefully(hr, "Failed to allocate buffer for string");

    WideCharToMultiByte(CP_ACP, 0, pString, -1, *ppResult, iLen+1, NULL, NULL);

    hr = S_OK;

exit_gracefully:

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ PutStringElement
/ -----------------
/   Add a string to the given buffer, always updating the cLen to indicate
/   how many characters would have been added
/
/ In:
/   pBuffer -> buffer to append to
/   pLen -> length value (updated)
/   pString -> string to add to buffer
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
STDAPI_(VOID) PutStringElementA(LPSTR pBuffer, UINT* pLen, LPCSTR pElement)
{
    TraceEnter(TRACE_COMMONAPI, "PutStringElementA");

    if ( pElement )
    {
        if ( pBuffer )
            lstrcatA(pBuffer, pElement);

        if ( pLen )
            *pLen += lstrlenA(pElement);
    }

    TraceLeave();
}

STDAPI_(VOID) PutStringElementW(LPWSTR pBuffer, UINT* pLen, LPCWSTR pElement)
{
    TraceEnter(TRACE_COMMONAPI, "PutStringElementW");

    if ( pElement )
    {
        if ( pBuffer )
            StrCatW(pBuffer, pElement);

        if ( pLen )
            *pLen += lstrlenW(pElement);
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ GetStringElement
/ ----------------
/   Extract the n'th element from the given string.  Each element is assumed
/   to be terminated with either a "," or a NULL.
/
/ In:
/   pString -> string to parse
/   index = element to retrieve
/   pBuffer, cchBuffer = buffer to fill 
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

STDAPI GetStringElementA(LPSTR pString, INT index, LPSTR pBuffer, INT cchBuffer)
{
    HRESULT hr = E_FAIL;
    USES_CONVERSION;

    TraceEnter(TRACE_COMMONAPI, "GetStringElement");
    Trace(TEXT("pString %s, index %d"), A2T(pString), index);

    *pBuffer = '\0';

    for ( ; index > 0 ; index-- )
    {
        while ( (*pString != ',') && (*pString != '\0') )
            pString++;

        if ( *pString == ',' )
            pString++;
    }

    if ( !index )
    {
        while ( *pString == ' ' )
            pString++;

        while ( cchBuffer-- && (*pString != ',') && (*pString != '\0') )
            *pBuffer++ = *pString++;
    
        if ( cchBuffer )
            *pBuffer = '\0';

        hr = cchBuffer ? S_OK:E_FAIL;
    }

    TraceLeaveResult(hr);
}

STDAPI GetStringElementW(LPWSTR pString, INT index, LPWSTR pBuffer, INT cchBuffer)
{
    HRESULT hr = E_FAIL;
    USES_CONVERSION;

    TraceEnter(TRACE_COMMONAPI, "GetStringElement");
    Trace(TEXT("pString %s, index %d"), W2T(pString), index);

    *pBuffer = L'\0';

    for ( ; index > 0 ; index-- )
    {
        while ( *pString != L',' && *pString != L'\0' )
            pString++;

        if ( *pString == L',' )
            pString++;
    }

    if ( !index )
    {
        while ( *pString == L' ' )
            pString++;

        while ( cchBuffer-- && (*pString != L',') && (*pString != L'\0') )
            *pBuffer++ = *pString++;
    
        if ( cchBuffer )
            *pBuffer = L'\0';

        hr = cchBuffer ? S_OK:E_FAIL;
    }

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ FormatMsgResource
/ -----------------
/   Load a string resource and pass it to format message, allocating a buffer
/   as we go.
/
/ In:
/   ppString -> receives the string point
/   hInstance = module handle for template string
/   uID = template string
/   ... = format parameters
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI FormatMsgResource(LPTSTR* ppString, HINSTANCE hInstance, UINT uID, ...)
{
    HRESULT hr;
    TCHAR szBuffer[MAX_PATH];
    va_list va;
    
    TraceEnter(TRACE_COMMONAPI, "FormatMsgResource");

    va_start(va, uID);

    if ( !LoadString(hInstance, uID, szBuffer, ARRAYSIZE(szBuffer)) )
        ExitGracefully(hr, E_FAIL, "Failed to load template string");

    if ( !FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, 
                        (LPVOID)szBuffer, 0, 0, 
                        (LPTSTR)ppString, SIZEOF(ppString), 
                        &va) )
    {
        ExitGracefully(hr, E_OUTOFMEMORY, "Failed to format the message");
    }

    Trace(TEXT("Resulting string: %s"), *ppString);
    hr = S_OK;                                          // success

exit_gracefully:
    
    va_end(va);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ FormatMsgBox
/ ------------
/   Call FormatMessage and MessageBox together having built a suitable
/   string to display to the user.
/
/ In:
/   ppString -> receives the string point
/   hInstance = module handle for template string
/   uID = template string
/   ... = format parameters
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI_(INT) FormatMsgBox(HWND hWnd, HINSTANCE hInstance, UINT uidTitle, UINT uidPrompt, UINT uType, ...)
{
    INT iResult = -1;                   // failure
    LPTSTR pPrompt = NULL;
    TCHAR szTitle[MAX_PATH];
    TCHAR szBuffer[MAX_PATH];
    va_list va;
    
    TraceEnter(TRACE_COMMONAPI, "FormatMsgBox");

    va_start(va, uType);

    LoadString(hInstance, uidTitle, szTitle, ARRAYSIZE(szTitle));
    LoadString(hInstance, uidPrompt, szBuffer, ARRAYSIZE(szBuffer));

    if ( FormatMessage(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, 
                       (LPVOID)szBuffer, 0, 0, 
                       (LPTSTR)&pPrompt, SIZEOF(pPrompt), 
                       &va) )
    {
        Trace(TEXT("Title: %s"), szTitle);
        Trace(TEXT("Prompt: %s"), pPrompt);

        iResult = MessageBox(hWnd, pPrompt, szTitle, uType);
        LocalFree(pPrompt);
    }

    Trace(TEXT("Result is %d"), iResult);

    va_end(va);

    TraceLeaveValue(iResult);
}


/*-----------------------------------------------------------------------------
/ FormatDirectoryName
/ -------------------
/   Collect the directory name and format it using a text resource specified.
/
/ In:
/   ppString = receives the string pointer for the result
/   clisdNamespace = namespace instance
/   hInstance = instance handle to load resource from
/   uID = resource ID for string
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI FormatDirectoryName(LPTSTR* ppString, HINSTANCE hInstance, UINT uID)
{
    HRESULT hr;
    TCHAR szBuffer[MAX_PATH];
    LPTSTR pDisplayName = NULL;
    HKEY hKey = NULL;

    TraceEnter(TRACE_COMMONAPI, "FormatDirectoryName");

    // No IDsFolder then lets ensure that we have one

    hr = GetKeyForCLSID(CLSID_MicrosoftDS, NULL, &hKey);
    FailGracefully(hr, "Failed to open namespace's registry key");

    hr = LocalQueryString(&pDisplayName, hKey, NULL);
    FailGracefully(hr, "Failed to get the namespace display name");

    Trace(TEXT("Display name is: %s"), pDisplayName);

    if ( hInstance )
    {
        hr = FormatMsgResource(ppString, hInstance, uID, pDisplayName);
        FailGracefully(hr, "Failed to format from resource");
    }
    else
    {
        *ppString = pDisplayName;
        pDisplayName = NULL;
    }

    hr = S_OK;                   // success

exit_gracefully:

    LocalFreeString(&pDisplayName);

    if ( hKey )
        RegCloseKey(hKey);

    TraceLeaveResult(hr);
}
