// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

#include "precomp.h"

STRA::STRA(
) : m_cchLen( 0 )
{
    *( QueryStr() ) = '\0';
}

STRA::STRA(
    __inout_ecount(cchInit) CHAR* pbInit,
    __in DWORD cchInit
) : m_Buff( pbInit, cchInit * sizeof( CHAR ) ),
    m_cchLen(0)
/*++
    Description:

        Used by STACK_STRA. Initially populates underlying buffer with pbInit.

        pbInit is not freed.

    Arguments:

        pbInit - initial memory to use
        cchInit - count, in characters, of pbInit

    Returns:

        None.

--*/
{
    _ASSERTE( NULL != pbInit );
    _ASSERTE( cchInit > 0 );
    _ASSERTE( pbInit[0] == '\0' );
}

BOOL
STRA::IsEmpty(
    VOID
) const
{
    return ( m_cchLen == 0 );
}

BOOL
STRA::Equals(
    __in PCSTR  pszRhs,
    __in BOOL   fIgnoreCase /*= FALSE*/
) const
{
    _ASSERTE( NULL != pszRhs );

    if( fIgnoreCase )
    {
        return ( 0 == _stricmp( QueryStr(), pszRhs ) );
    }

    return ( 0 == strcmp( QueryStr(), pszRhs ) );
}

BOOL
STRA::Equals(
    __in const STRA *   pstrRhs,
    __in BOOL           fIgnoreCase /*= FALSE*/
) const
{
    _ASSERTE( NULL != pstrRhs );
    return Equals( pstrRhs->QueryStr(), fIgnoreCase );
}

BOOL
STRA::Equals(
    __in const STRA &  strRhs,
    __in BOOL           fIgnoreCase /*= FALSE*/
) const
{
    return Equals( strRhs.QueryStr(), fIgnoreCase );
}

DWORD
STRA::QueryCB(
    VOID
) const
//
// Returns the number of bytes in the string excluding the terminating NULL
//
{
    return m_cchLen * sizeof( CHAR );
}

DWORD
STRA::QueryCCH(
    VOID
) const
//
//  Returns the number of characters in the string excluding the terminating NULL
//
{
    return m_cchLen;
}

DWORD
STRA::QuerySizeCCH(
) const
//
// Returns size of the underlying storage buffer, in characters
//
{
    return m_Buff.QuerySize() / sizeof( CHAR );
}

DWORD
STRA::QuerySize(
) const
//
//  Returns the size of the storage buffer in bytes
//
{
    return m_Buff.QuerySize();
}

__nullterminated
__bcount(this->m_cchLen)
CHAR *
STRA::QueryStr(
    VOID
) const
//
//  Return the string buffer
//
{
    return m_Buff.QueryPtr();
}

VOID
STRA::Reset(
)
//
// Resets the internal string to be NULL string. Buffer remains cached.
//
{
    _ASSERTE( QueryStr() != NULL );
    *(QueryStr()) = '\0';
    m_cchLen = 0;
}

HRESULT
STRA::Resize(
    __in DWORD cchSize
)
{
    if( !m_Buff.Resize( cchSize * sizeof( CHAR ) ) )
    {
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

HRESULT
STRA::SyncWithBuffer(
)
//
// Recalculate the length of the string, etc. because we've modified
// the buffer directly.
//
{
    size_t size;
    HRESULT hr = StringCchLengthA(QueryStr(),
                                  QuerySizeCCH(),
                                  &size);
    if ( SUCCEEDED( hr ) )
    {
        m_cchLen = static_cast<DWORD>(size);
    }
    return hr;
}

HRESULT
STRA::Copy(
    __in PCSTR   pszCopy
)
{
    size_t      cbLen;
    HRESULT hr = StringCbLengthA(pszCopy,
                                 STRSAFE_MAX_CCH,
                                 &cbLen);
    if ( FAILED( hr ) )
    {
        return hr;
    }
    return Copy( pszCopy, cbLen );
}


HRESULT
STRA::Copy(
    __in_ecount(cchLen)
    PCSTR           pszCopy,
    __in SIZE_T     cbLen
)
//
// Copy the contents of another string to this one
//
{
    _ASSERTE( cbLen <= MAXDWORD );

    return AuxAppend(
        pszCopy,
        static_cast<DWORD>(cbLen),
        0
    );
}

HRESULT
STRA::Copy(
    __in const STRA * pstrRhs
)
{
    _ASSERTE( pstrRhs != NULL );
    return Copy( pstrRhs->QueryStr(), pstrRhs->QueryCCH() );
}

HRESULT
STRA::Copy(
    __in const STRA & strRhs
)
{
    return Copy( strRhs.QueryStr(), strRhs.QueryCCH() );
}

HRESULT
STRA::CopyW(
    __in PCWSTR  pszCopyW
)
{
    size_t      cchLen;
    HRESULT hr = StringCchLengthW(pszCopyW,
                                  STRSAFE_MAX_CCH,
                                  &cchLen);
    if ( FAILED( hr ) )
    {
        return hr;
    }
    return CopyW( pszCopyW, cchLen );
}

HRESULT
STRA::CopyWTruncate(
    __in PCWSTR pszCopyWTruncate
)
{
    size_t      cchLen;
    HRESULT hr = StringCchLengthW(pszCopyWTruncate,
                                  STRSAFE_MAX_CCH,
                                  &cchLen);
    if ( FAILED( hr ) )
    {
        return hr;
    }
    return CopyWTruncate( pszCopyWTruncate, cchLen );
}

HRESULT
STRA::CopyWTruncate(
    __in_ecount(cchLen)
    PCWSTR          pszCopyWTruncate,
    __in SIZE_T     cchLen
)
//
// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste
//
{
    _ASSERTE( cchLen <= MAXDWORD );

    return AuxAppendWTruncate(
        pszCopyWTruncate,
        static_cast<DWORD>(cchLen),
        0
    );
}

HRESULT
STRA::Append(
    __in PCSTR pszAppend
)
{
    size_t      cbLen;
    HRESULT hr = StringCbLengthA(pszAppend,
                                 STRSAFE_MAX_CCH,
                                 &cbLen);
    if ( FAILED( hr ) )
    {
        return hr;
    }
    return Append( pszAppend, cbLen );
}

HRESULT
STRA::Append(
    __in_ecount(cchLen)
    PCSTR       pszAppend,
    __in SIZE_T cbLen
)
{
    _ASSERTE( cbLen <= MAXDWORD );
    if ( cbLen == 0 )
    {
        return S_OK;
    }
    return AuxAppend(
        pszAppend,
        static_cast<DWORD>(cbLen),
        QueryCB()
    );
}

HRESULT
STRA::Append(
    __in const STRA * pstrRhs
)
{
    _ASSERTE( pstrRhs != NULL );
    return Append( pstrRhs->QueryStr(), pstrRhs->QueryCCH() );
}

HRESULT
STRA::Append(
    __in const STRA & strRhs
)
{
    return Append( strRhs.QueryStr(), strRhs.QueryCCH() );
}

HRESULT
STRA::AppendWTruncate(
    __in PCWSTR pszAppendWTruncate
)
{
    size_t      cchLen;
    HRESULT hr = StringCchLengthW(pszAppendWTruncate,
                                  STRSAFE_MAX_CCH,
                                  &cchLen);
    if ( FAILED( hr ) )
    {
        return hr;
    }
    return AppendWTruncate( pszAppendWTruncate, cchLen );
}

HRESULT
STRA::AppendWTruncate(
    __in_ecount(cchLen)
    PCWSTR          pszAppendWTruncate,
    __in SIZE_T     cchLen
)
//
// The "Truncate" methods do not do proper conversion. They do a (CHAR) caste
//
{
    _ASSERTE( cchLen <= MAXDWORD );
    if ( cchLen == 0 )
    {
        return S_OK;
    }
    return AuxAppendWTruncate(
        pszAppendWTruncate,
        static_cast<DWORD>(cchLen),
        QueryCB()
    );
}

HRESULT
STRA::CopyToBuffer(
    __out_bcount(*pcb) CHAR*    pszBuffer,
    __inout DWORD *             pcb
) const
//
// Makes a copy of the stored string into the given buffer
//
{
    _ASSERTE( NULL != pszBuffer );
    _ASSERTE( NULL != pcb );

    HRESULT hr          = S_OK;
    DWORD   cbNeeded    = QueryCB() + sizeof( CHAR );

    if( *pcb < cbNeeded )
    {
        hr = HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER );
        goto Finished;
    }

    memcpy( pszBuffer, QueryStr(), cbNeeded );

Finished:

    *pcb = cbNeeded;

    return hr;
}

HRESULT
STRA::SetLen(
    __in DWORD cchLen
)
/*++
 *
Routine Description:

    Set the length of the string and null terminate, if there
    is sufficient buffer already allocated. Will not reallocate.

Arguments:

    cchLen - The number of characters in the new string.

Return Value:

    HRESULT

--*/
{
    if( cchLen >= QuerySizeCCH() )
    {
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    *( QueryStr() + cchLen ) = '\0';
    m_cchLen = cchLen;

    return S_OK;
}


HRESULT
STRA::SafeSnprintf(
    __in __format_string
    PCSTR       pszFormatString,
    ...
)
/*++

Routine Description:

    Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars.

Arguments:

    pszFormatString    - printf format
    ...                - printf args

Return Value:

    HRESULT

--*/
{
    va_list     argsList;
    va_start(   argsList, pszFormatString );

    HRESULT hr = SafeVsnprintf(pszFormatString, argsList);

    va_end( argsList );
    return hr;
}

HRESULT
STRA::SafeVsnprintf(
    __in __format_string
    PCSTR       pszFormatString,
    va_list     argsList
)
/*++

Routine Description:

    Writes to a STRA, growing it as needed. It arbitrarily caps growth at 64k chars.

Arguments:

    pszFormatString    - printf format
    argsList           - printf va_list

Return Value:

    HRESULT

--*/
{
    HRESULT     hr          = S_OK;

    //
    // Format the incoming message using vsnprintf()
    // so that the overflows are captured
    //
    int cchOutput = _vsnprintf_s(
        QueryStr(),
        QuerySizeCCH(),
        QuerySizeCCH() - 1,
        pszFormatString,
        argsList
    );

    if( cchOutput == -1 )
    {
        //
        // Couldn't fit this in the original STRU size.
        //
        int cchNeeded = _vscprintf(pszFormatString, argsList);
        if( cchNeeded > 64 * 1024 )
        {
            //
            // If we're trying to produce a string > 64k chars, then
            // there is probably a problem
            //
            hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
            goto Finished;
        }

        //
        // _vscprintf doesn't include terminating null character
        //
        cchNeeded++;

        hr = Resize( cchNeeded );
        if( FAILED( hr ) )
        {
            goto Finished;
        }

        cchOutput = _vsnprintf_s(
            QueryStr(),
            QuerySizeCCH(),
            QuerySizeCCH() - 1,
            pszFormatString,
            argsList
        );
        if( -1 == cchOutput )
        {
            //
            // This should never happen, cause we should already have correctly sized memory
            //
            _ASSERTE( FALSE );

            hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
            goto Finished;
        }
    }

    //
    // always null terminate at the last WCHAR
    //
    QueryStr()[ QuerySizeCCH() - 1 ] = L'\0';

    //
    // we directly touched the buffer - therefore:
    //
    hr = SyncWithBuffer();
    if( FAILED( hr ) )
    {
        goto Finished;
    }

Finished:

    if( FAILED( hr ) )
    {
        Reset();
    }

    return hr;
}

bool
FShouldEscapeUtf8(
    BYTE ch
    )
{
    if ( ( ch >= 128 ) )
    {
        return true;
    }

    return false;
}

bool
FShouldEscapeUrl(
    BYTE ch
    )
{
    if ( ( ch >= 128   ||
           ch <= 32    ||
           ch == '<'   ||
           ch == '>'   ||
           ch == '%'   ||
           ch == '?'   ||
           ch == '#' ) &&
         !( ch == '\n' || ch == '\r' ) )
    {
        return true;
    }

    return false;
}

HRESULT
STRA::Escape(
    VOID
)
/*++

Routine Description:

    Escapes a STRA

Arguments:

    None

Return Value:

    None

--*/
{
    return EscapeInternal( FShouldEscapeUrl );
}

HRESULT
STRA::EscapeUtf8(
)
/*++

Routine Description:

    Escapes the high-bit chars in a STRA.  LWS, CR, LF & controls are untouched.

Arguments:

    None

Return Value:

    None

--*/
{
    return EscapeInternal( FShouldEscapeUtf8 );
}


HRESULT
STRA::EscapeInternal(
    PFN_F_SHOULD_ESCAPE pfnFShouldEscape
)
/*++

Routine Description:

    Escapes a STRA according to the predicate function passed in

Arguments:

    None

Return Value:

    None

--*/
{
    LPCSTR  pch     = QueryStr();
    __analysis_assume( pch != NULL );
    int     i      = 0;
    BYTE    ch;
    HRESULT hr      = S_OK;
    ULONG64  NewSize = 0;

    // Set to true if any % escaping occurs
    BOOL fEscapingDone = FALSE;

    //
    // If there are any characters that need to be escaped we copy the entire string
    // character by character into straTemp, escaping as we go, then at the end
    // copy all of straTemp over. Don't modify InlineBuffer directly.
    //
    CHAR InlineBuffer[512];
    InlineBuffer[0] = '\0';
    STRA straTemp(InlineBuffer, sizeof(InlineBuffer)/sizeof(*InlineBuffer));

    _ASSERTE( pch );

    while (pch[i] != NULL)
    {
        //
        //  Escape characters that are in the non-printable range
        //  but ignore CR and LF
        //

        ch = pch[i];
        if ( pfnFShouldEscape( ch ) )
        {
            if (FALSE == fEscapingDone)
            {
                // first character in the string that needed escaping
                fEscapingDone = TRUE;

                // guess that the size needs to be larger than
                // what we used to have times two
                NewSize = static_cast<ULONG64>(QueryCCH()) * 2;
                if ( NewSize > MAXDWORD )
                {
                    hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW );
                    return hr;
                }

                hr = straTemp.Resize( static_cast<DWORD>(NewSize) );

                if (FAILED(hr))
                {
                    return hr;
                }

                // Copy all of the previous buffer into buffTemp, only if it is not the first character:

                if ( i > 0)
                {
                    hr = straTemp.Copy(QueryStr(),
                                       i * sizeof(CHAR));
                    if (FAILED(hr))
                    {
                        return hr;
                    }
                }
            }

            // resize the temporary (if needed) with the slop of the entire buffer length
            // this fixes constant reallocation if the entire string needs to be escaped
            NewSize = static_cast<ULONG64>(QueryCCH()) + 2 * sizeof(CHAR) + 1 * sizeof(CHAR);
            if ( NewSize > MAXDWORD )
            {
                hr = HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW );
                return hr;
            }

            BOOL fRet = straTemp.m_Buff.Resize(static_cast<size_t>(NewSize));
            if ( !fRet )
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                return hr;
            }

            //
            //  Create the string to append for the current character
            //

            CHAR chHex[3];
            chHex[0] = '%';

            //
            //  Convert the low then the high character to hex
            //

            UINT nLowDigit = static_cast<UINT>(ch % 16);
            chHex[2] = TODIGIT( nLowDigit );

            ch /= 16;

            UINT nHighDigit = static_cast<UINT>(ch % 16);

            chHex[1] = TODIGIT( nHighDigit );

            //
            // Actually append the converted character to the end of the temporary
            //
            hr = straTemp.Append(chHex, 3);
            if (FAILED(hr))
            {
                return hr;
            }
        }
        else
        {
            // if no escaping done, no need to copy
            if (fEscapingDone)
            {
                // if ANY escaping done, copy current character into new buffer
                straTemp.Append(&pch[i], 1);
            }
        }

        // inspect the next character in the string
        i++;
    }

    if (fEscapingDone)
    {
        // the escaped string is now in straTemp
        hr = Copy(straTemp);
    }

    return hr;

} // EscapeInternal()

VOID
STRA::Unescape(
)
/*++

Routine Description:

    Unescapes a STRA

    Supported escape sequences are:
      %uxxxx unescapes Unicode character xxxx into system codepage
      %xx    unescapes character xx
      %      without following hex digits is ignored

Arguments:

    None

Return Value:

    None

--*/
{
    CHAR   *pScan;
    WCHAR   wch;
    DWORD   dwLen;
    BOOL    fChanged = FALSE;

    //
    // Now take care of any escape characters
    //
    CHAR* pDest = pScan = strchr(QueryStr(), '%');

    while (pScan)
    {
        if ((pScan[1] == 'u' || pScan[1] == 'U') &&
            SAFEIsXDigit(pScan[2]) &&
            SAFEIsXDigit(pScan[3]) &&
            SAFEIsXDigit(pScan[4]) &&
            SAFEIsXDigit(pScan[5]))
        {
            wch = TOHEX(pScan[2]) * 4096 + TOHEX(pScan[3]) * 256
                + TOHEX(pScan[4]) * 16 + TOHEX(pScan[5]);

            dwLen = WideCharToMultiByte(CP_ACP,
                                        WC_NO_BEST_FIT_CHARS,
                                        &wch,
                                        1,
                                        static_cast<LPSTR>(pDest),
                                        6,
                                        NULL,
                                        NULL);

            pDest += dwLen;
            pScan += 6;
            fChanged = TRUE;
        }
        else if (SAFEIsXDigit(pScan[1]) && SAFEIsXDigit(pScan[2]))
        {
            *pDest = TOHEX(pScan[1]) * 16 + TOHEX(pScan[2]);

            pDest ++;
            pScan += 3;
            fChanged = TRUE;
        }
        else   // Not an escaped char, just a '%'
        {
            if (fChanged)
            {
                *pDest = *pScan;
            }

            pDest++;
            pScan++;
        }

        //
        // Copy all the information between this and the next escaped char
        //
        CHAR* pNextScan = strchr(pScan, '%');

        if (fChanged)   // pScan!=pDest, so we have to copy the char's
        {
            if (!pNextScan)   // That was the last '%' in the string
            {
                memmove(pDest,
                        pScan,
                        QueryCCH() - DIFF(pScan - QueryStr()) + 1);
            }
            else
            {
                // There is another '%', move intermediate chars
                if ((dwLen = static_cast<DWORD>(DIFF(pNextScan - pScan))) != 0)
                {
                    memmove(pDest,
                            pScan,
                            dwLen);
                    pDest += dwLen;
                }
            }
        }

        pScan = pNextScan;
    }

    if (fChanged)
    {
        m_cchLen = static_cast<DWORD>(strlen(QueryStr()));  // for safety recalc the length
    }
}

HRESULT
STRA::CopyWToUTF8Unescaped(
    __in LPCWSTR cpchStr
)
{
    return STRA::CopyWToUTF8Unescaped(cpchStr, static_cast<DWORD>(wcslen(cpchStr)));
}

HRESULT
STRA::CopyWToUTF8Unescaped(
    __in_ecount(cch)
    LPCWSTR         cpchStr,
    __in DWORD      cch
)
{
    HRESULT hr = S_OK;

    if (cch == 0)
    {
        Reset();
        return S_OK;
    }

    int iRet = ConvertUnicodeToUTF8(cpchStr,
                                    &m_Buff,
                                    cch);
    if (-1 == iRet)
    {
        // could not convert
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    m_cchLen = iRet;

    _ASSERTE(strlen(m_Buff.QueryPtr()) == m_cchLen);
Finished:
    return hr;
}

HRESULT
STRA::CopyWToUTF8Escaped(
    __in LPCWSTR cpchStr
)
{
    return STRA::CopyWToUTF8Escaped(cpchStr, static_cast<DWORD>(wcslen(cpchStr)));
}

HRESULT
STRA::CopyWToUTF8Escaped(
    __in_ecount(cch)
    LPCWSTR         cpchStr,
    __in DWORD      cch
)
{
    HRESULT hr = CopyWToUTF8Unescaped(cpchStr, cch);
    if (FAILED(hr))
    {
        goto Finished;
    }

    hr = Escape();
    if (FAILED(hr))
    {
        goto Finished;
    }

    hr = S_OK;
Finished:
    return hr;
}

HRESULT
STRA::AuxAppend(
    __in_ecount(cbLen)
    LPCSTR          pStr,
    __in DWORD      cbLen,
    __in DWORD      cbOffset
)
{
    _ASSERTE( NULL != pStr );
    _ASSERTE( cbOffset <= QueryCB() );

    ULONGLONG cb64NewSize = static_cast<ULONGLONG>(cbOffset) + cbLen + sizeof( CHAR );
    if( cb64NewSize > MAXDWORD )
    {
        return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW );
    }

    if( m_Buff.QuerySize() < cb64NewSize )
    {
        if( !m_Buff.Resize( static_cast<SIZE_T>(cb64NewSize) ) )
        {
            return E_OUTOFMEMORY;
        }
    }

    memcpy( reinterpret_cast<BYTE*>(m_Buff.QueryPtr()) + cbOffset, pStr, cbLen );

    m_cchLen = cbLen + cbOffset;

    *( QueryStr() + m_cchLen ) = '\0';

    return S_OK;
}

HRESULT
STRA::AuxAppendW(
    __in_ecount(cchAppendW)
    PCWSTR          pszAppendW,
    __in DWORD      cchAppendW,
    __in DWORD      cbOffset,
    __in UINT       CodePage,
    __in BOOL       fFailIfNoTranslation,
    __in DWORD      dwFlags
)
{
    HRESULT hr          = S_OK;
    DWORD   cbRet       = 0;
    DWORD cbAvailable   = 0;
    UNREFERENCED_PARAMETER(fFailIfNoTranslation);

    //
    // There are only two expect places to append
    //
    _ASSERTE( 0 == cbOffset || QueryCB() == cbOffset );

    if ( cchAppendW == 0 )
    {
        goto Finished;
    }

    //
    // start by assuming 1 char to 1 char will be enough space
    //
    if( !m_Buff.Resize( cbOffset + cchAppendW + sizeof( CHAR ) ) )
    {
        hr = E_OUTOFMEMORY;
        goto Finished;
    }

    cbAvailable = m_Buff.QuerySize() - cbOffset;

    cbRet = WideCharToMultiByte(
        CodePage,
        dwFlags,
        pszAppendW,
        cchAppendW,
        QueryStr() + cbOffset,
        cbAvailable,
        NULL,
        NULL
    );
    if( 0 != cbRet )
    {
        if(!m_Buff.Resize(cbOffset + cbRet + 1))
        {
            hr = E_OUTOFMEMORY;
        }

        //
        // not zero --> success, so we're done
        //
        goto Finished;
    }

    //
    // We only know how to handle ERROR_INSUFFICIENT_BUFFER
    //
    hr = HRESULT_FROM_WIN32( GetLastError() );
    if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
    {
        goto Finished;
    }

    //
    // Reset HResult because we need to get the number of bytes needed
    //
    hr = S_OK;
    cbRet = WideCharToMultiByte(
        CodePage,
        dwFlags,
        pszAppendW,
        cchAppendW,
        NULL,
        0,
        NULL,
        NULL
    );
    if( 0 == cbRet )
    {
        //
        // no idea how we could ever reach here
        //
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Finished;
    }

    if( !m_Buff.Resize( cbOffset + cbRet + 1) )
    {
        hr = E_OUTOFMEMORY;
        goto Finished;
    }

    cbAvailable = m_Buff.QuerySize() - cbOffset;

    cbRet = WideCharToMultiByte(
        CodePage,
        dwFlags,
        pszAppendW,
        cchAppendW,
        QueryStr() + cbOffset,
        cbAvailable,
        NULL,
        NULL
    );
    if( 0 == cbRet )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Finished;
    }

Finished:

    if( SUCCEEDED( hr ) && 0 != cbRet )
    {
        m_cchLen = cbRet + cbOffset;
    }

    //
    // ensure we're still NULL terminated in the right spot
    // (regardless of success or failure)
    //
    QueryStr()[m_cchLen] = '\0';

    return hr;
}

HRESULT
STRA::AuxAppendWTruncate(
    __in_ecount(cchAppendW)
    __in PCWSTR     pszAppendW,
    __in DWORD      cchAppendW,
    __in DWORD      cbOffset
)
//
// Cheesey WCHAR --> CHAR conversion
//
{
    _ASSERTE( NULL != pszAppendW );
    _ASSERTE( 0 == cbOffset || cbOffset == QueryCB() );

    if( !pszAppendW )
    {
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    ULONGLONG cbNeeded = static_cast<ULONGLONG>(cbOffset) + cchAppendW + sizeof( CHAR );
    if( cbNeeded > MAXDWORD )
    {
        return HRESULT_FROM_WIN32( ERROR_ARITHMETIC_OVERFLOW );
    }

    if( !m_Buff.Resize( static_cast<SIZE_T>(cbNeeded) ) )
    {
        return E_OUTOFMEMORY;
    }

    //
    // Copy/convert the UNICODE string over (by making two bytes into one)
    //
    CHAR* pszBuffer = QueryStr() + cbOffset;
    for( DWORD i = 0; i < cchAppendW; i++ )
    {
        pszBuffer[i] = static_cast<CHAR>(pszAppendW[i]);
    }

    m_cchLen = cchAppendW + cbOffset;
    *( QueryStr() + m_cchLen ) = '\0';

    return S_OK;
}

// static
int
STRA::ConvertUnicodeToCodePage(
    __in_ecount(dwStringLen)
    LPCWSTR                     pszSrcUnicodeString,
    __inout BUFFER_T<CHAR,1> *  pbufDstAnsiString,
    __in DWORD                  dwStringLen,
    __in UINT                   uCodePage
)
{
    _ASSERTE(NULL != pszSrcUnicodeString);
    _ASSERTE(NULL != pbufDstAnsiString);

    DWORD dwFlags;

    if (uCodePage == CP_ACP)
    {
        dwFlags = WC_NO_BEST_FIT_CHARS;
    }
    else
    {
        dwFlags = 0;
    }

    int iStrLen = WideCharToMultiByte(uCodePage,
                                      dwFlags,
                                      pszSrcUnicodeString,
                                      dwStringLen,
                                      static_cast<LPSTR>(pbufDstAnsiString->QueryPtr()),
                                      static_cast<int>(pbufDstAnsiString->QuerySize()),
                                      NULL,
                                      NULL);
    if ((iStrLen == 0) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
        iStrLen = WideCharToMultiByte(uCodePage,
                                      dwFlags,
                                      pszSrcUnicodeString,
                                      dwStringLen,
                                      NULL,
                                      0,
                                      NULL,
                                      NULL);
        if (iStrLen != 0) {
            // add one just for the extra NULL
            BOOL bTemp = pbufDstAnsiString->Resize(iStrLen + 1);
            if (!bTemp)
            {
                iStrLen = 0;
            }
            else
            {
                iStrLen = WideCharToMultiByte(uCodePage,
                                              dwFlags,
                                              pszSrcUnicodeString,
                                              dwStringLen,
                                              static_cast<LPSTR>(pbufDstAnsiString->QueryPtr()),
                                              static_cast<int>(pbufDstAnsiString->QuerySize()),
                                              NULL,
                                              NULL);
            }

        }
    }

    if (0 != iStrLen &&
        pbufDstAnsiString->Resize(iStrLen + 1))
    {
        // insert a terminating NULL into buffer for the dwStringLen+1 in the case that the dwStringLen+1 was not a NULL.
        static_cast<CHAR*>(pbufDstAnsiString->QueryPtr())[iStrLen] = '\0';
    }
    else
    {
        iStrLen = -1;
    }

    return iStrLen;
}

// static
HRESULT
STRA::ConvertUnicodeToMultiByte(
    __in_ecount(dwStringLen)
    LPCWSTR                     pszSrcUnicodeString,
    __in BUFFER_T<CHAR,1> *     pbufDstAnsiString,
    __in DWORD                  dwStringLen
)
{
    return ConvertUnicodeToCodePage( pszSrcUnicodeString,
                                      pbufDstAnsiString,
                                      dwStringLen,
                                      CP_ACP );
}

// static
HRESULT
STRA::ConvertUnicodeToUTF8(
    __in_ecount(dwStringLen)
    LPCWSTR                     pszSrcUnicodeString,
    __in BUFFER_T<CHAR,1> *     pbufDstAnsiString,
    __in DWORD                  dwStringLen
)
{
    return ConvertUnicodeToCodePage( pszSrcUnicodeString,
                                      pbufDstAnsiString,
                                      dwStringLen,
                                      CP_UTF8 );
}

/*++

Routine Description:

    Removes leading and trailing whitespace

--*/

VOID
STRA::Trim()
{
    PSTR    pszString               = QueryStr();
    DWORD   cchNewLength            = m_cchLen;
    DWORD   cchLeadingWhitespace    = 0;

    for (LONG ixString = m_cchLen - 1; ixString >= 0; ixString--)
    {
        if (isspace(static_cast<unsigned char>(pszString[ixString])) != 0)
        {
            pszString[ixString] = '\0';
            cchNewLength--;
        }
        else
        {
            break;
        }
    }

    DWORD cchTempLength = cchNewLength;
    for (DWORD ixString = 0; ixString < cchTempLength; ixString++)
    {
        if (isspace(static_cast<unsigned char>(pszString[ixString])) != 0)
        {
            cchLeadingWhitespace++;
            cchNewLength--;
        }
        else
        {
            break;
        }
    }

    if (cchNewLength == 0)
    {

        Reset();
    }
    else if (cchLeadingWhitespace > 0)
    {
        memmove(pszString, pszString + cchLeadingWhitespace, cchNewLength * sizeof(CHAR));
        pszString[cchNewLength] = '\0';
    }

    SyncWithBuffer();
}

/*++

Routine Description:

    Compares the string to the provided prefix to check for equality

Arguments:

    pStraPrefix - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if prefix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::StartsWith(
    __in const STRA *   pStraPrefix,
    __in bool           fIgnoreCase) const
{
    _ASSERTE( pStraPrefix != NULL );
    return StartsWith(pStraPrefix->QueryStr(), fIgnoreCase);
}

/*++

Routine Description:

    Compares the string to the provided prefix to check for equality

Arguments:

    straPrefix  - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if prefix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::StartsWith(
    __in const STRA &   straPrefix,
    __in bool           fIgnoreCase) const
{
    return StartsWith(straPrefix.QueryStr(), fIgnoreCase);
}

/*++

Routine Description:

    Compares the string to the provided prefix to check for equality

Arguments:

    pszPrefix   - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if prefix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::StartsWith(
    __in PCSTR          pszPrefix,
    __in bool           fIgnoreCase) const
{
    if (pszPrefix == NULL)
    {
        return FALSE;
    }

    size_t  cchPrefix   = 0;
    HRESULT hr = StringCchLengthA(pszPrefix, STRSAFE_MAX_CCH, &cchPrefix);
    if (FAILED(hr))
    {
        return FALSE;
    }

    _ASSERTE( cchPrefix <= MAXDWORD );
    if (cchPrefix > m_cchLen)
    {
        return FALSE;
    }

    BOOL fMatch = FALSE;
    if ( fIgnoreCase )
    {
        fMatch = ( 0 == _strnicmp( QueryStr(), pszPrefix, cchPrefix ) );
    }
    else
    {
        fMatch = ( 0 == strncmp( QueryStr(), pszPrefix, cchPrefix ) );
    }

    return fMatch;
}

/*++

Routine Description:

    Compares the string to the provided suffix to check for equality

Arguments:

    pStraSuffix - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if suffix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::EndsWith(
    __in const STRA *   pStraSuffix,
    __in bool           fIgnoreCase) const
{
    _ASSERTE( pStraSuffix != NULL );
    return EndsWith(pStraSuffix->QueryStr(), fIgnoreCase);
}


/*++

Routine Description:

    Compares the string to the provided suffix to check for equality

Arguments:

    straSuffix  - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if suffix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::EndsWith(
    __in const STRA &   straSuffix,
    __in bool           fIgnoreCase) const
{
    return EndsWith(straSuffix.QueryStr(), fIgnoreCase);
}


/*++

Routine Description:

    Compares the string to the provided suffix to check for equality

Arguments:

    pszSuffix   - string to compare with
    fIgnoreCase - indicates whether the string comparison should be case-sensitive

Return Value:

    TRUE if suffix string matches with internal string, FALSE otherwise

--*/
BOOL
STRA::EndsWith(
    __in PCSTR          pszSuffix,
    __in bool           fIgnoreCase) const
{
    PSTR      pszString   = QueryStr();

    if (pszSuffix == NULL)
    {
        return FALSE;
    }

    size_t    cchSuffix   = 0;
    HRESULT hr = StringCchLengthA(pszSuffix, STRSAFE_MAX_CCH, &cchSuffix);
    if (FAILED(hr))
    {
        return FALSE;
    }

    _ASSERTE( cchSuffix <= MAXDWORD );
    if (cchSuffix > m_cchLen)
    {
        return FALSE;
    }

    ptrdiff_t ixOffset = m_cchLen - cchSuffix;
    _ASSERTE(ixOffset >= 0 && ixOffset <= MAXDWORD);

    BOOL fMatch = FALSE;
    if ( fIgnoreCase )
    {
        fMatch = ( 0 == _strnicmp( pszString + ixOffset, pszSuffix, cchSuffix ) );
    }
    else
    {
        fMatch = ( 0 == strncmp( pszString + ixOffset, pszSuffix, cchSuffix ) );
    }

    return fMatch;
}


/*++

Routine Description:

    Searches the string for the first occurrence of the specified character.

Arguments:

    charValue       - character to find
    dwStartIndex    - the initial index.

Return Value:

    The index for the first character occurrence in the string.

    -1 if not found.

--*/
INT
STRA::IndexOf(
    __in CHAR           charValue,
    __in DWORD          dwStartIndex
    ) const
{
    INT nIndex = -1;

    // Make sure that there are no buffer overruns.
    if ( dwStartIndex >= QueryCCH() )
    {
        return nIndex;
    }

    const CHAR* pChar = strchr( QueryStr() + dwStartIndex, charValue );

    // Determine the index if found
    if ( pChar )
    {
        // nIndex will be set to -1 on failure.
        (VOID)SizeTToInt( pChar - QueryStr(), &nIndex );
    }

    return nIndex;
}


/*++

Routine Description:

    Searches the string for the first occurrence of the specified substring.

Arguments:

    pszValue        - substring to find
    dwStartIndex    - initial index.

Return Value:

    The index for the first character occurrence in the string.

    -1 if not found.

--*/
INT
STRA::IndexOf(
    __in PCSTR          pszValue,
    __in DWORD          dwStartIndex
    ) const
{
    INT nIndex = -1;

    // Validate input parameters
    if( dwStartIndex >= QueryCCH() || !pszValue )
    {
        return nIndex;
    }

    const CHAR* pChar = strstr( QueryStr() + dwStartIndex, pszValue );

    // Determine the index if found
    if( pChar )
    {
        // nIndex will be set to -1 on failure.
        (VOID)SizeTToInt( pChar - QueryStr(), &nIndex );
    }

    return nIndex;
}


/*++

Routine Description:

    Searches the string for the last occurrence of the specified character.

Arguments:

    charValue       - character to find
    dwStartIndex    - initial index.

Return Value:

    The index for the last character occurrence in the string.

    -1 if not found.

--*/
INT
STRA::LastIndexOf(
    __in CHAR           charValue,
    __in DWORD          dwStartIndex
    ) const
{
    INT nIndex = -1;

    // Make sure that there are no buffer overruns.
    if( dwStartIndex >= QueryCCH() )
    {
        return nIndex;
    }

    const CHAR* pChar = strrchr( QueryStr() + dwStartIndex, charValue );

    // Determine the index if found
    if( pChar )
    {
        // nIndex will be set to -1 on failure.
        (VOID)SizeTToInt( pChar - QueryStr(), &nIndex );
    }

    return nIndex;
}
