/****************************** Module Header ******************************\
* Module Name: strings.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* This module contains all the string handling APIs and functions.  Since
* they don't access server-specific data they belong here in the client DLL.
*
* History:
* 10-18-90 DarrinM      Created.
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop

/* LATER these should be in a public header file!!!
 * Assorted defines used to support the standard Windows ANSI code page
 * (now known as code page 1252 and officially registered by IBM).
 * This is intended only for the PDK release.  Subsequent releases will
 * use the NLSAPI and Unicode.
 */
#define LATIN_CAPITAL_LETTER_A_GRAVE    (CHAR)0xc0
#define LATIN_CAPITAL_LETTER_THORN      (CHAR)0xde
#define LATIN_SMALL_LETTER_SHARP_S      (CHAR)0xdf
#define LATIN_SMALL_LETTER_Y_DIAERESIS  (CHAR)0xff
#define DIVISION_SIGN                   (CHAR)0xf7
#define MULTIPLICATION_SIGN             (CHAR)0xd7


/***************************************************************************\
* CharLowerA (API)
*
* Convert either a single character or an entire string to lower case.  The
* two cases are differentiated by checking the high-word of psz.  If it is
* 0 then we just convert the low-word of psz.
*
* History:
* 11-26-90 DarrinM      Created non-NLS version.
* 06-22-91 GregoryW     Modified to support code page 1252.  This is for
*                       the PDK release only.  After the PDK this routine
*                       will be modified to use the NLSAPI.  Also renamed
*                       API to conform to new naming conventions.  AnsiLower
*                       is now a #define which resolves to this routine.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, LPSTR, WINAPI, CharLowerA, LPSTR, psz)
LPSTR WINAPI CharLowerA(
    LPSTR psz)
{
    NTSTATUS st;

    /*
     * Early out for NULL string or '\0'
     */
    if (psz == NULL) {
        return psz;
    }

    if (!IS_PTR(psz)) {
        WCHAR wch;

#ifdef FE_SB // CharLowerA()
        /*
         * if only DBCS Leadbyte was passed, just return the character.
         * Same behavior as Windows 3.1J and Windows 95 FarEast version.
         */
        if (IS_DBCS_ENABLED() && IsDBCSLeadByte((BYTE)(ULONG_PTR)psz)) {
            return psz;
        }
#endif // FE_SB

        //
        // LATER 14 Feb 92 GregoryW
        //    For DBCS code pages is a double byte character ever
        //    passed in the low word of psz or is the high nibble
        //    of the low word always ignored?
        //
        st = RtlMultiByteToUnicodeN(&wch, sizeof(WCHAR), NULL, (PCH)&psz, sizeof(CHAR));
        if (!NT_SUCCESS(st)) {
            /*
             * Failed!  Caller is not expecting failure, CharLowerA does not
             * have a failure indicator, so just return the original character.
             */
            RIPMSG1(RIP_WARNING, "CharLowerA(%#p) failed\n", psz);
        } else {
            /*
             * The next two calls never fail.
             */
            LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, &wch, 1, &wch, 1);
            RtlUnicodeToMultiByteN((PCH)&psz, sizeof(CHAR), NULL, &wch, sizeof(WCHAR));
        }
        return psz;

    }

    /*
     * psz is a null-terminated string
     */
    CharLowerBuffA(psz, strlen(psz)+1);
    return psz;
}


/***************************************************************************\
* CharUpperA (API)
*
* Convert either a single character or an entire string to upper case.  The
* two cases are differentiated by checking the high-word of psz.  If it is
* 0 then we just convert the low-word of psz.
*
* History:
* 12-03-90 IanJa        derived from DarrinM's non-NLS AnsiLower
* 06-22-91 GregoryW     Modified to support code page 1252.  This is for
*                       the PDK release only.  After the PDK this routine
*                       will be modified to use the NLSAPI.  Also renamed
*                       API to conform to new naming conventions.  AnsiUpper
*                       is now a #define which resolves to this routine.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, LPSTR, WINAPI, CharUpperA, LPSTR, psz)
LPSTR WINAPI CharUpperA(
    LPSTR psz)
{
    NTSTATUS st;

    /*
     * Early out for NULL string or '\0'
     */
    if (psz == NULL) {
        return psz;
    }

    if (!IS_PTR(psz)) {
        WCHAR wch;

#ifdef FE_SB // CharLowerA()
        /*
         * if only DBCS Leadbyte was passed, just return the character.
         * Same behavior as Windows 3.1J and Windows 95 FarEast version.
         */
        if (IS_DBCS_ENABLED() && IsDBCSLeadByte((BYTE)(ULONG_PTR)psz)) {
            return psz;
        }
#endif // FE_SB

        //
        // LATER 14 Feb 92 GregoryW
        //    For DBCS code pages is a double byte character ever
        //    passed in the low word of psz or is the high nibble
        //    of the low word always ignored?
        //
        st = RtlMultiByteToUnicodeN(&wch, sizeof(WCHAR), NULL, (PCH)&psz, sizeof(CHAR));
        if (!NT_SUCCESS(st)) {
            /*
             * Failed!  Caller is not expecting failure, CharUpperA does not
             * have a failure indicator, so return the original character.
             */
            RIPMSG1(RIP_WARNING, "CharUpperA(%#p) failed\n", psz);
        } else {
            /*
             * The next two calls never fail.
             */
            LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, &wch, 1, &wch, 1);
            RtlUnicodeToMultiByteN((PCH)&psz, sizeof(CHAR), NULL, &wch, sizeof(WCHAR));
        }
        return psz;

    }

    /*
     * psz is a null-terminated string
     */
    CharUpperBuffA(psz, strlen(psz)+1);
    return psz;
}


/***************************************************************************\
* CharNextA (API)
*
* Move to next character in string unless already at '\0' terminator
* DOES NOT WORK CORRECTLY FOR DBCS (eg: Japanese)
*
* History:
* 12-03-90 IanJa        Created non-NLS version.
* 06-22-91 GregoryW     Renamed API to conform to new naming conventions.
*                       AnsiNext is now a #define which resolves to this
*                       routine.  This routine is only intended to support
*                       code page 1252 for the PDK release.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, LPSTR, WINAPI, CharNextA, LPCSTR, lpCurrentChar)
LPSTR WINAPI CharNextA(
    LPCSTR lpCurrentChar)
{
#ifdef FE_SB // CharNextA(): dbcs enabling
    if (IS_DBCS_ENABLED() && IsDBCSLeadByte(*lpCurrentChar)) {
        lpCurrentChar++;
    }
    /*
     * if we have only DBCS LeadingByte, we will point string-terminaler.
     */
#endif // FE_SB

    if (*lpCurrentChar) {
        lpCurrentChar++;
    }
    return (LPSTR)lpCurrentChar;
}


/***************************************************************************\
* CharNextExA (API)
*
* Move to next character in string unless already at '\0' terminator.
*
* History:
* 05-01-95 GregoryW     Ported from Win95.
\***************************************************************************/


FUNCLOG3(LOG_GENERAL, LPSTR, WINAPI, CharNextExA, WORD, CodePage, LPCSTR, lpCurrentChar, DWORD, dwFlags)
LPSTR WINAPI CharNextExA(
    WORD CodePage,
    LPCSTR lpCurrentChar,
    DWORD dwFlags)
{
    if (lpCurrentChar == (LPSTR)NULL)
    {
        return (LPSTR)lpCurrentChar;
    }

    if (IsDBCSLeadByteEx(CodePage, *lpCurrentChar))
    {
        lpCurrentChar++;
    }

    if (*lpCurrentChar)
    {
        lpCurrentChar++;
    }
    return (LPSTR)lpCurrentChar;

    UNREFERENCED_PARAMETER(dwFlags);
}


/***************************************************************************\
* CharPrevA (API)
*
* Move to previous character in string, unless already at start
* DOES NOT WORK CORRECTLY FOR DBCS (eg: Japanese)
*
* History:
* 12-03-90 IanJa        Created non-NLS version.
* 06-22-91 GregoryW     Renamed API to conform to new naming conventions.
*                       AnsiPrev is now a #define which resolves to this
*                       routine.  This routine is only intended to support
*                       code page 1252 for the PDK release.
\***************************************************************************/


FUNCLOG2(LOG_GENERAL, LPSTR, WINAPI, CharPrevA, LPCSTR, lpStart, LPCSTR, lpCurrentChar)
LPSTR WINAPI CharPrevA(
    LPCSTR lpStart,
    LPCSTR lpCurrentChar)
{
#ifdef FE_SB // CharPrevA : dbcs enabling
    if (lpCurrentChar > lpStart) {
        if (IS_DBCS_ENABLED()) {
            LPCSTR lpChar;
            BOOL bDBC = FALSE;

            for (lpChar = --lpCurrentChar - 1 ; lpChar >= lpStart ; lpChar--) {
                if (!IsDBCSLeadByte(*lpChar))
                    break;
                bDBC = !bDBC;
            }

            if (bDBC)
                lpCurrentChar--;
        }
        else
            lpCurrentChar--;
    }
    return (LPSTR)lpCurrentChar;
#else
    if (lpCurrentChar > lpStart) {
        lpCurrentChar--;
    }
    return (LPSTR)lpCurrentChar;
#endif // FE_SB
}

/***************************************************************************\
* CharPrevExA (API)
*
* Move to previous character in string, unless already at start.
*
* History:
* 05-01-95 GregoryW     Ported from Win95.
\***************************************************************************/


FUNCLOG4(LOG_GENERAL, LPSTR, WINAPI, CharPrevExA, WORD, CodePage, LPCSTR, lpStart, LPCSTR, lpCurrentChar, DWORD, dwFlags)
LPSTR WINAPI CharPrevExA(
    WORD CodePage,
    LPCSTR lpStart,
    LPCSTR lpCurrentChar,
    DWORD dwFlags)
{
    if (lpCurrentChar > lpStart) {
        LPCSTR lpChar;
        BOOL bDBC = FALSE;

        for (lpChar = --lpCurrentChar - 1 ; lpChar >= lpStart ; lpChar--) {
            if (!IsDBCSLeadByteEx(CodePage, *lpChar))
                break;
            bDBC = !bDBC;
        }

        if (bDBC)
            lpCurrentChar--;
    }
    return (LPSTR)lpCurrentChar;

    UNREFERENCED_PARAMETER(dwFlags);
}


/***************************************************************************\
* CharLowerBuffA (API)
*
* History:
* 14-Jan-1991 mikeke from win 3.0
* 06-22-91 GregoryW     Renamed API to conform to new naming conventions.
*                       AnsiLowerBuff is now a #define which resolves to this
*                       routine.  This routine is only intended to support
*                       code page 1252 for the PDK release.
* 02-20-1992 GregoryW   Modified to use NLS API.
\***************************************************************************/
#define CCH_LOCAL_BUFF 256


FUNCLOG2(LOG_GENERAL, DWORD, WINAPI, CharLowerBuffA, LPSTR, psz, DWORD, nLength)
DWORD WINAPI CharLowerBuffA(
    LPSTR psz,
    DWORD nLength)
{
    ULONG cb;
    WCHAR awchLocal[CCH_LOCAL_BUFF];
    LPWSTR pwszT = awchLocal;
    int cwch;

    if (nLength == 0) {
        return(0);
    }

    /*
     * Convert ANSI to Unicode.
     * Use awchLocal if it is big enough, otherwise allocate space.
     */
    cwch = MBToWCS(
            psz,       // ANSI buffer
            nLength,   // length of buffer
            &pwszT,    // address of Unicode string
            (nLength > CCH_LOCAL_BUFF ? -1 : nLength),
            (nLength > CCH_LOCAL_BUFF) );

    if (cwch != 0) {
        CharLowerBuffW(pwszT, cwch);

        /*
         * This can't fail
         */
        RtlUnicodeToMultiByteN(
                  psz,                   // ANSI string
                  nLength,               // given to us
                  &cb,                   // result length
                  pwszT,                 // Unicode string
                  cwch * sizeof(WCHAR)); // length IN BYTES

        if (pwszT != awchLocal) {
            UserLocalFree(pwszT);
        }

        return (DWORD)cb;
    }

    /*
     * MBToWCS failed!  The caller is not expecting failure,
     * so we convert the string to lower case as best we can.
     */
    RIPMSG2(RIP_WARNING,
            "CharLowerBuffA(%#p, %lx) failed\n", psz, nLength);

    for (cb=0; cb < nLength; cb++) {
#ifdef FE_SB // CharLowerBuffA(): skip double byte character
        if (IS_DBCS_ENABLED() && IsDBCSLeadByte(psz[cb])) {
            cb++;
        } else if (IsCharUpperA(psz[cb])) {
            psz[cb] += 'a'-'A';
        }
#else
        if (IsCharUpperA(psz[cb])) {
            psz[cb] += 'a'-'A';
        }
#endif // FE_SB
    }

    return nLength;
}


/***************************************************************************\
* CharUpperBuffA (API)
*
* History:
* 14-Jan-1991 mikeke from win 3.0
* 06-22-91 GregoryW     Renamed API to conform to new naming conventions.
*                       AnsiUpperBuff is now a #define which resolves to this
*                       routine.  This routine is only intended to support
*                       code page 1252 for the PDK release.
* 02-Feb-1992 GregoryW   Modified to use NLS API.
\***************************************************************************/


FUNCLOG2(LOG_GENERAL, DWORD, WINAPI, CharUpperBuffA, LPSTR, psz, DWORD, nLength)
DWORD WINAPI CharUpperBuffA(
    LPSTR psz,
    DWORD nLength)
{
    DWORD cb;
    WCHAR awchLocal[CCH_LOCAL_BUFF];
    LPWSTR pwszT = awchLocal;
    int cwch;

    if (nLength==0) {
        return(0);
    }

    /*
     * Convert ANSI to Unicode.
     * Use awchLocal if it is big enough, otherwise allocate space.
     */
    cwch = MBToWCS(
            psz,       // ANSI buffer
            nLength,   // length of buffer
            &pwszT,    // address of Unicode string
            (nLength > CCH_LOCAL_BUFF ? -1 : nLength),
            (nLength > CCH_LOCAL_BUFF) );

    if (cwch != 0) {
        CharUpperBuffW(pwszT, cwch);

        RtlUnicodeToMultiByteN(
                  psz,                   // address of ANSI string
                  nLength,               // given to us
                  &cb,                   // result length
                  pwszT,                 // Unicode string
                  cwch * sizeof(WCHAR)); // length IN BYTES

        if (pwszT != awchLocal) {
            UserLocalFree(pwszT);
        }

        return (DWORD)cb;
    }

    /*
     * MBToWCS failed!  The caller is not expecting failure,
     * so we convert the string to upper case as best we can.
     */
    RIPMSG2(RIP_WARNING,
            "CharLowerBuffA(%#p, %lx) failed\n", psz, nLength);

    for (cb=0; cb < nLength; cb++) {
#ifdef FE_SB // CharUpperBuffA(): skip double byte characters
        if (IS_DBCS_ENABLED() && IsDBCSLeadByte(psz[cb])) {
            cb++;
        } else if (IsCharLowerA(psz[cb]) &&
                   /*
                    * Sometime, LATIN_xxxx code is DBCS LeadingByte depending on ACP.
                    * In that case, we never come here...
                    */
                   (psz[cb] != LATIN_SMALL_LETTER_SHARP_S) &&
                   (psz[cb] != LATIN_SMALL_LETTER_Y_DIAERESIS)) {
            psz[cb] += 'A'-'a';
        }
#else
        if (IsCharLowerA(psz[cb]) &&
            (psz[cb] != LATIN_SMALL_LETTER_SHARP_S) &&
            (psz[cb] != LATIN_SMALL_LETTER_Y_DIAERESIS)) {
            psz[cb] += 'A'-'a';
        }
#endif // FE_SB
    }

    return nLength;
}


/***************************************************************************\
* IsCharLowerA (API)
*
* History:
* 14-Jan-1991 mikeke from win 3.0
* 22-Jun-1991 GregoryW   Modified to support code page 1252 (Windows ANSI
*                        code page).  This is for the PDK only.  After the
*                        PDK this routine will be rewritten to use the
*                        NLSAPI.
* 02-Feb-1992 GregoryW   Modified to use NLS API.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, BOOL, WINAPI, IsCharLowerA, char, cChar)
BOOL WINAPI IsCharLowerA(
    char cChar)
{
    WORD ctype1info = 0;
    WCHAR wChar = 0;

#ifdef FE_SB // IsCharLowerA()
    /*
     * if only DBCS Leadbyte was passed, just return FALSE.
     * Same behavior as Windows 3.1J and Windows 95 FarEast version.
     */
    if (IS_DBCS_ENABLED() && IsDBCSLeadByte(cChar)) {
        return FALSE;
    }
#endif // FE_SB

    /*
     * The following 2 calls cannot fail here
     */
    RtlMultiByteToUnicodeN(&wChar, sizeof(WCHAR), NULL, &cChar, sizeof(CHAR));
    GetStringTypeW(CT_CTYPE1, &wChar, 1, &ctype1info);
    return (ctype1info & C1_LOWER) == C1_LOWER;
}


/***************************************************************************\
* IsCharUpperA (API)
*
* History:
* 22-Jun-1991 GregoryW   Created to support code page 1252 (Windows ANSI
*                        code page).  This is for the PDK only.  After the
*                        PDK this routine will be rewritten to use the
*                        NLSAPI.
* 02-Feb-1992 GregoryW   Modified to use NLS API.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, BOOL, WINAPI, IsCharUpperA, char, cChar)
BOOL WINAPI IsCharUpperA(
    char cChar)
{
    WORD ctype1info = 0;
    WCHAR wChar = 0;

#ifdef FE_SB // IsCharUpperA()
    /*
     * if only DBCS Leadbyte was passed, just return FALSE.
     * Same behavior as Windows 3.1J and Windows 95 FarEast version.
     */
    if (IS_DBCS_ENABLED() && IsDBCSLeadByte(cChar)) {
        return FALSE;
    }
#endif // FE_SB

    /*
     * The following 2 calls cannot fail here
     */
    RtlMultiByteToUnicodeN(&wChar, sizeof(WCHAR), NULL, &cChar, sizeof(CHAR));
    GetStringTypeW(CT_CTYPE1, &wChar, 1, &ctype1info);
    return (ctype1info & C1_UPPER) == C1_UPPER;
}


/***************************************************************************\
* IsCharAlphaNumericA (API)
*
* Returns TRUE if character is alphabetical or numerical, otherwise FALSE
*
* History:
* 12-03-90 IanJa        Created non-NLS stub version.
* 06-22-91 GregoryW     Modified to support code page 1252 (Windows ANSI
*                       code page).  This is for the PDK only.  After the
*                       PDK this routine will be rewritten to use the
*                       NLSAPI.
* 02-20-92 GregoryW     Modified to use the NLS API.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, BOOL, WINAPI, IsCharAlphaNumericA, char, cChar)
BOOL WINAPI IsCharAlphaNumericA(
    char cChar)
{
    WORD ctype1info = 0;
    WCHAR wChar = 0;

    /*
     * The following 2 calls cannot fail here
     */
    RtlMultiByteToUnicodeN(&wChar, sizeof(WCHAR), NULL, &cChar, sizeof(CHAR));
    GetStringTypeW(CT_CTYPE1, &wChar, 1, &ctype1info);
#ifdef FE_SB // IsCharAlphaNumericA()
    if (ctype1info & C1_ALPHA) {
        WORD ctype3info = 0;
        if (!IS_DBCS_ENABLED()) {
            return TRUE;
        }
        /*
         * We don't want to return TRUE for halfwidth katakana.
         * Katakana is linguistic character (C1_ALPHA), but it is not
         * alphabet character.
         */
        GetStringTypeW(CT_CTYPE3, &wChar, 1, &ctype3info);
        return ((ctype3info & (C3_KATAKANA|C3_HIRAGANA)) ? FALSE : TRUE);
    }
    /* Otherwise, it might be digits ? */
    return !!(ctype1info & C1_DIGIT);
#else
    return (ctype1info & C1_ALPHA) || (ctype1info & C1_DIGIT);
#endif // FE_SB
}


/***************************************************************************\
* IsCharAlphaA (API)
*
* Returns TRUE if character is alphabetical, otherwise FALSE
*
* History:
* 06-22-91 GregoryW     Created to support code page 1252 (Windows ANSI
*                       code page).  This is for the PDK only.  After the
*                       PDK this routine will be rewritten to use the
*                       NLSAPI.
* 02-20-92 GregoryW     Modified to use the NLS API.
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, BOOL, WINAPI, IsCharAlphaA, char, cChar)
BOOL WINAPI IsCharAlphaA(
    char cChar)
{
    WORD ctype1info = 0;
    WCHAR wChar = 0;

    /*
     * The following 2 calls cannot fail here
     */
    RtlMultiByteToUnicodeN(&wChar, sizeof(WCHAR), NULL, &cChar, sizeof(CHAR));
    GetStringTypeW(CT_CTYPE1, &wChar, 1, &ctype1info);
#ifdef FE_SB // IsCharAlphaA()
    if ((ctype1info & C1_ALPHA) == C1_ALPHA) {
        WORD ctype3info = 0;
        if (!IS_DBCS_ENABLED()) {
            return TRUE;
        }
        /*
         * We don't want to return TRUE for halfwidth katakana.
         * Katakana is linguistic character (C1_ALPHA), but it is not
         * alphabet character.
         */
        GetStringTypeW(CT_CTYPE3, &wChar, 1, &ctype3info);
        return ((ctype3info & (C3_KATAKANA|C3_HIRAGANA)) ? FALSE : TRUE);
    }
    return (FALSE);
#else
    return (ctype1info & C1_ALPHA) == C1_ALPHA;
#endif // FE_SB
}