/*++

Copyright (c) 1990-1998,  Microsoft Corporation  All rights reserved.

Module Name:

    parse.c

Abstract:

    This module contains the parse routines for the Win32 common dialogs.

Revision History:

--*/


// precompiled headers
#include "precomp.h"
#pragma hdrstop

#include "fileopen.h"

//
//  Global Variables.
//
extern TCHAR szCaption[];
extern TCHAR szWarning[];




////////////////////////////////////////////////////////////////////////////
//
//  ParseFileNew
//
//  On the return, pnExtOffset is the offset to the dot.
//
////////////////////////////////////////////////////////////////////////////

int ParseFileNew(
    LPTSTR pszPath,
    int *pnExtOffset,
    BOOL bWowApp,
    BOOL bNewStyle)
{
    int lRet = ParseFile(pszPath, TRUE, bWowApp, bNewStyle);

    if (pnExtOffset)
    {
        int nExt;

        nExt = (int)(SHORT)HIWORD(lRet);
        *pnExtOffset = ((nExt) && *(pszPath + nExt)) ? nExt : 0;
    }

    return ((int)(SHORT)LOWORD(lRet));
}


////////////////////////////////////////////////////////////////////////////
//
//  ParseFileOld
//
//  On return, pnExtOffset is the offset to the the dot and
//  pnOldExt is the offset to the character following the dot.
//
////////////////////////////////////////////////////////////////////////////

int ParseFileOld(
    LPTSTR pszPath,
    int *pnExtOffset,
    int *pnOldExt,
    BOOL bWowApp,
    BOOL bNewStyle)
{
    int lRet = ParseFile(pszPath, TRUE, bWowApp, bNewStyle);

    int nExt = (int)(SHORT)HIWORD(lRet);
    *pnExtOffset = nExt;
    *pnOldExt = ((nExt) && *(pszPath + nExt)) ? nExt + 1 : 0;

    return ((int)(SHORT)LOWORD(lRet));
}


////////////////////////////////////////////////////////////////////////////
//
//  ParseFile
//
//  Determines if the filename is a legal dos name.
//
//  Circumstances checked:
//      1) Valid as directory name, but not as file name
//      2) Empty String
//      3) Illegal Drive label
//      4) Period in invalid location (in extension, 1st in file name)
//      5) Missing directory character
//      6) Illegal character
//      7) Wildcard in directory name
//      8) Double slash beyond 1st 2 characters
//      9) Space character in the middle of the name (trailing spaces OK)
//         -->> no longer applies : spaces are allowed in LFN
//      10) Filename greater than 8 characters : NOT APPLICABLE TO LONG FILE NAMES
//      11) Extension greater than 3 characters: NOT APPLICABLE TO LONG FILE NAMES
//
//  lpstrFileName - ptr to a single file name
//
//  Returns:
//      LONG - LOWORD = char offset to filename,
//             HIWORD = char offset to extension (dot),
//      LONG - LOWORD is error code (<0), HIWORD is approx. place of problem
//
////////////////////////////////////////////////////////////////////////////

DWORD ParseFile(
    LPTSTR lpstrFileName,
    BOOL bLFNFileSystem,
    BOOL bWowApp,
    BOOL bNewStyle)
{
    SHORT nFile, nExt, nFileOffset, nExtOffset = 0;
    BOOL bExt;
    BOOL bWildcard;
    SHORT nNetwork = 0;
    BOOL bUNCPath = FALSE;
    LPTSTR lpstr = lpstrFileName;

    //Check if the string is empty
    if (!*lpstr)
    {
        nFileOffset = PARSE_EMPTYSTRING;
        goto ParseFile_Failure;
    }

    //Check if the string is of form  c:\foo1\foo2
    if (*(lpstr + 1) == CHAR_COLON)
    {
        //Yes. Get the drive letter
        TCHAR cDrive = CharLowerChar(*lpstr);

        //
        //  Test to see if the drive is legal.
        //
        //  Note: Does not test that drive exists.
        //
        if ((cDrive < CHAR_A) || (cDrive > CHAR_Z))
        {
            nFileOffset = PARSE_INVALIDDRIVE;
            goto ParseFile_Failure;
        }

        //Move string past drive letter and ':'
        lpstr = CharNext(CharNext(lpstr));
    }

    if ((*lpstr == CHAR_BSLASH) || (*lpstr == CHAR_SLASH && !bNewStyle))
    {
        //
        //  Cannot have "c:\."
        //
        if (*++lpstr == CHAR_DOT)
        {
            //
            //  Except that "c:\.\" is allowed.
            //
            if ((*++lpstr != CHAR_BSLASH) && (*lpstr != CHAR_SLASH || bNewStyle))
            {
                //
                //  It's the root directory.
                //
                if (!*lpstr)
                {
                    goto MustBeDir;
                }
                else
                {
                    lpstr--;
                }
            }
            else
            {
                //
                //  It's saying top dir (once again), thus allowed.
                //
                ++lpstr;
            }
        }
        else if ((*lpstr == CHAR_BSLASH) && (*(lpstr - 1) == CHAR_BSLASH))
        {
            //
            //  It seems that for a full network path, whether a drive is
            //  declared or not is insignificant, though if a drive is given,
            //  it must be valid (hence the code above should remain there).
            //

            //
            //  ...since it's the first slash, 2 are allowed.
            //
            ++lpstr;

            //
            //  Must receive server and share to be real.
            //
            nNetwork = -1;

            //
            //  No wildcards allowed if UNC name.
            //
            bUNCPath = TRUE;
        }
        else if (*lpstr == CHAR_SLASH && !bNewStyle)
        {
            nFileOffset = PARSE_INVALIDDIRCHAR;
            goto ParseFile_Failure;
        }
    }
    else if (*lpstr == CHAR_DOT)
    {
        //
        //  Up one directory.
        //
        if (*++lpstr == CHAR_DOT)
        {
            ++lpstr;
        }

        if (!*lpstr)
        {
            goto MustBeDir;
        }
        if ((*lpstr != CHAR_BSLASH) && (*lpstr != CHAR_SLASH || bNewStyle))
        {
            //
            //  Jumping to Failure here will skip the parsing that causes
            //  ".xxx.txt" to return with nFileOffset = 2.
            //
            nFileOffset = 0;
            goto ParseFile_Failure;
        }
        else
        {
            //
            //  Allow directory.
            //
            ++lpstr;
        }
    }

    if (!*lpstr)
    {
        goto MustBeDir;
    }

    //
    //  Should point to first char in filename by now.
    //
    nFileOffset = nExtOffset = nFile = nExt = 0;
    bWildcard = bExt = FALSE;
    while (*lpstr)
    {
        //
        //  Anything below the "Space" character is invalid.
        //
#ifdef UNICODE
        if (*lpstr < CHAR_SPACE)
#else
        if (((UCHAR)*lpstr) < CHAR_SPACE)
#endif
        {
            nFileOffset = PARSE_INVALIDCHAR;
            goto ParseFile_Failure;
        }
        switch (*lpstr)
        {
            case ( CHAR_COLON ) :
            case ( CHAR_BAR ) :
            case ( CHAR_LTHAN ) :
            case ( CHAR_QUOTE ) :
            {
                //
                //  Invalid characters for all file systems.
                //
                nFileOffset = PARSE_INVALIDCHAR;
                goto ParseFile_Failure;
            }
            case ( CHAR_SEMICOLON ) :
            case ( CHAR_COMMA ) :
            case ( CHAR_PLUS ) :
            case ( CHAR_LBRACKET ) :
            case ( CHAR_RBRACKET ) :
            case ( CHAR_EQUAL ) :
            {
                if (!bLFNFileSystem)
                {
                    nFileOffset = PARSE_INVALIDCHAR;
                    goto ParseFile_Failure;
                }
                else
                {
                    goto RegularCharacter;
                }
            }
            case ( CHAR_SLASH ) :
            {
                if (bNewStyle)
                {
                    nFileOffset = PARSE_INVALIDCHAR;
                    goto ParseFile_Failure;
                }

                // fall thru...
            }
            case ( CHAR_BSLASH ) :
            {
                //
                //  Subdir indicators.
                //
                nNetwork++;
                if (bWildcard)
                {
                    nFileOffset = PARSE_WILDCARDINDIR;
                    goto ParseFile_Failure;
                }

                //
                // if nFile==0 means that we are seeing this backslash right next to a backslash  
                // which is not allowed.
                if (nFile == 0)
                {
                    nFileOffset = PARSE_INVALIDDIRCHAR;
                    goto ParseFile_Failure;
                }
                else
                {
                    //Move over the BSLASH/SLASH character.
                    ++lpstr;

                    //Check if the path is valid network path name
                    if (!nNetwork && !*lpstr)
                    {
                        nFileOffset = PARSE_INVALIDNETPATH;
                        goto ParseFile_Failure;
                    }

                    
                    //We assume that the  characters we are seeing are filename characters. This BSLASH/SLASH
                    //character tells that characters we have seen so far specifies the name of a directory in the
                    //path. Reset flags so that we can start looking for filename again.
                    nFile = nExt = 0;
                    nExtOffset = 0;
                    bExt = FALSE;
                }
                break;
            }
            case ( CHAR_SPACE ) :
            {
                LPTSTR lpSpace = lpstr;

                if (bLFNFileSystem)
                {
                    // In Long file name file system space characters  are O.K
                    goto RegularCharacter;
                }



                //We are not interested in the trailing spaces so null terminate it.
                *lpSpace = CHAR_NULL;


                // In non long file name file systems, space characters are OK at the end of file
                // name. Check to see if all the characters that follows are spaces. if thats the case
                // then its valid. if we have any non space character after the first space then its a 
                // invalid file name.
                
                while (*++lpSpace)
                {
                    if (*lpSpace != CHAR_SPACE)
                    {
                        *lpstr = CHAR_SPACE;
                        nFileOffset = PARSE_INVALIDSPACE;
                        goto ParseFile_Failure;
                    }
                }

                break;
            }
            
            case ( CHAR_DOT ) :
            {

                // In newstyle nExtOffset points to the dot and not to the first character of extension.
                if (bNewStyle)
                {
                    nExtOffset = (SHORT)(lpstr - lpstrFileName);
                    goto RegularCharacter;
                }
                
                if (nFile == 0)
                {
                    nFileOffset = (SHORT)(lpstr - lpstrFileName);
                    if (*++lpstr == CHAR_DOT)
                    {
                        ++lpstr;
                    }
                    if (!*lpstr)
                    {
                        goto MustBeDir;
                    }

                    //
                    //  Flags already set.
                    //
                    nFile++;
                    ++lpstr;
                }
                else
                {
                    nExtOffset = 0;
                    ++lpstr;
                    bExt = TRUE;
                }
                break;
            }
            case ( CHAR_STAR ) :
            case ( CHAR_QMARK ) :
            {
                bWildcard = TRUE;

                //  Fall thru...
            }
            default :
            {
RegularCharacter:

                //Are we in extension part ?
                if (bExt)
                {
                    //Is this first character in extension part
                    if (++nExt == 1)
                    {
                        //Yes, then get the Extension offset
                        nExtOffset = (SHORT)(lpstr - lpstrFileName);
                    }
                }

                //We are still in file name part. 
                //Is this the first character in filename part ?
                else if (++nFile == 1)
                {
                    //Yes. Get the filename offset
                    nFileOffset = (SHORT)(lpstr - lpstrFileName);
                }

                //Move to the next character
                lpstr = CharNext(lpstr);
                break;
            }
        }
    }

    if (nNetwork == -1)
    {
        nFileOffset = PARSE_INVALIDNETPATH;
        goto ParseFile_Failure;
    }
    else if (bUNCPath)
    {
        if (!nNetwork)
        {
            //
            //  Server and share only.(e.g \\server\foo)
            //
            *lpstr = CHAR_NULL;
            nFileOffset = PARSE_DIRECTORYNAME;
            goto ParseFile_Failure;
        }
        else if ((nNetwork == 1) && !nFile)
        {
            //
            //  Server and share root.(e.g \\server\foo\)
            //
            *lpstr = CHAR_NULL;
            nFileOffset = PARSE_DIRECTORYNAME;
            goto ParseFile_Failure;
        }
    }

    if (!nFile)
    {
MustBeDir:
        nFileOffset = PARSE_DIRECTORYNAME;
        goto ParseFile_Failure;
    }

    //
    //  If bNewStyle is true, no ext. wanted.
    //
    if (!bNewStyle)
    {
        if ((bWowApp) &&
            (*(lpstr - 1) == CHAR_DOT) &&
            (*CharNext(lpstr - 2) == CHAR_DOT))
        {
            //
            //  Remove terminating period.
            //
            *(lpstr - 1) = CHAR_NULL;
        }
        else if (!nExt)
        {
ParseFile_Failure:
            //
            //  Need to recheck bNewStyle since we can jump here.
            //
            if (!bNewStyle)
            {
                nExtOffset = (SHORT)(lpstr - lpstrFileName);
            }
        }
    }

    return (MAKELONG(nFileOffset, nExtOffset));
}


////////////////////////////////////////////////////////////////////////////
//
//  PathRemoveBslash
//
//  Removes a trailing backslash from the given path.
//
//  Returns:
//      Pointer to NULL that replaced the backslash   OR
//      Pointer to the last character if it isn't a backslash
//
////////////////////////////////////////////////////////////////////////////

LPTSTR PathRemoveBslash(
    LPTSTR lpszPath)
{
    int len = lstrlen(lpszPath) - 1;

#ifndef UNICODE
    if (IsDBCSLeadByte(*CharPrev(lpszPath, lpszPath + len + 1)))
    {
        len--;
    }
#endif

    if (!PathIsRoot(lpszPath) && (lpszPath[len] == CHAR_BSLASH))
    {
        lpszPath[len] = CHAR_NULL;
    }

    return (lpszPath + len);
}


////////////////////////////////////////////////////////////////////////////
//
//  IsWild
//
////////////////////////////////////////////////////////////////////////////

BOOL IsWild(
    LPCTSTR lpsz)
{
    return (StrChr(lpsz, CHAR_STAR) || StrChr(lpsz, CHAR_QMARK));
}


////////////////////////////////////////////////////////////////////////////
//
//  AppendExt
//
//  Appends default extension onto path name.
//  It assumes the current path name doesn't already have an extension.
//  lpExtension does not need to be null terminated.
//
////////////////////////////////////////////////////////////////////////////

VOID AppendExt(
    LPTSTR lpszPath,
    LPCTSTR lpExtension,
    BOOL bWildcard)
{
    WORD wOffset;
    SHORT i;
    TCHAR szExt[MAX_PATH + 1];

    if (lpExtension && *lpExtension)
    {
        wOffset = (WORD)lstrlen(lpszPath);
        if (bWildcard)
        {
            *(lpszPath + wOffset++) = CHAR_STAR;
        }

        //
        //  Add a period.
        //
        *(lpszPath + wOffset++) = CHAR_DOT;
        for (i = 0; *(lpExtension + i) && i < MAX_PATH; i++)
        {
            szExt[i] = *(lpExtension + i);
        }
        szExt[i] = 0;

        //
        //  Remove leading / trailing blanks in the extension.
        //
        PathRemoveBlanks(szExt);

        //
        //  Add the rest.
        //
        lstrcpy(lpszPath + wOffset, szExt);
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  IsUNC
//
//  Determines if the given path is a UNC path.
//
//  Returns:
//      TRUE    if path starts with "\\" or "X:\\"
//      FALSE   otherwise
//
////////////////////////////////////////////////////////////////////////////

BOOL IsUNC(
    LPCTSTR lpszPath)
{
    return ( DBL_BSLASH(lpszPath) ||
             ((lpszPath[1] == CHAR_COLON) && DBL_BSLASH(lpszPath + 2)) );
}


////////////////////////////////////////////////////////////////////////////
//
//  PortName
//
////////////////////////////////////////////////////////////////////////////

#define PORTARRAY 14

BOOL PortName(
    LPTSTR lpszFileName)
{
    static TCHAR *szPorts[PORTARRAY] = { TEXT("LPT1"),
                                         TEXT("LPT2"),
                                         TEXT("LPT3"),
                                         TEXT("LPT4"),
                                         TEXT("COM1"),
                                         TEXT("COM2"),
                                         TEXT("COM3"),
                                         TEXT("COM4"),
                                         TEXT("EPT"),
                                         TEXT("NUL"),
                                         TEXT("PRN"),
                                         TEXT("CLOCK$"),
                                         TEXT("CON"),
                                         TEXT("AUX"),
                                       };
    short i;
    TCHAR cSave, cSave2;


    cSave = *(lpszFileName + 4);
    if (cSave == CHAR_DOT)
    {
        *(lpszFileName + 4) = CHAR_NULL;
    }

    //
    //  For "EPT".
    //
    cSave2 = *(lpszFileName + 3);
    if (cSave2 == CHAR_DOT)
    {
      *(lpszFileName + 3) = CHAR_NULL;
    }

    for (i = 0; i < PORTARRAY; i++)
    {
        if (!lstrcmpi(szPorts[i], lpszFileName))
        {
            break;
        }
    }
    *(lpszFileName + 4) = cSave;
    *(lpszFileName + 3) = cSave2;

    return (i != PORTARRAY);
}


////////////////////////////////////////////////////////////////////////////
//
//  IsDirectory
//
////////////////////////////////////////////////////////////////////////////

BOOL IsDirectory(
    LPTSTR pszPath)
{
    DWORD dwAttributes;

    //
    //  Clean up for GetFileAttributes.
    //
    PathRemoveBslash(pszPath);

    dwAttributes = GetFileAttributes(pszPath);
    return ( (dwAttributes != (DWORD)(-1)) &&
             (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) );
}


////////////////////////////////////////////////////////////////////////////
//
//  WriteProtectedDirCheck
//
//  This function takes a full filename, strips the path, and creates
//  a temp file in that directory.  If it can't, the directory is probably
//  write protected.
//
//  Returns:
//      error code if writeprotected
//      0 if successful creation of file.
//
//  Assumptions:
//    Full Path name on input with space for full filename appended.
//
//  Note: Do NOT use this on a floppy, it's too slow!
//
////////////////////////////////////////////////////////////////////////////

int WriteProtectedDirCheck(
    LPCTSTR lpszFile)
{
    SHORT nFileOffset;
    TCHAR szFile[MAX_PATH + 1];
    TCHAR szBuf[MAX_PATH + 1];

    lstrcpyn(szFile, lpszFile, MAX_PATH + 1);
    nFileOffset = (SHORT)(int)LOWORD(ParseFile(szFile, TRUE, FALSE, TRUE));

    szFile[nFileOffset - 1] = CHAR_NULL;
    if (!GetTempFileName(szFile, TEXT("TMP"), 0, szBuf))
    {
        return (GetLastError());
    }
    else
    {
        DeleteFile(szBuf);
        return (0);               // success
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  FOkToWriteOver
//
//  Verifies that the user really does want to destroy the file,
//  replacing its contents with new stuff.
//
////////////////////////////////////////////////////////////////////////////

BOOL FOkToWriteOver(
    HWND hDlg,
    LPTSTR szFileName)
{
    if (!CDLoadString( g_hinst,
                     iszOverwriteQuestion,
                     szCaption,
                     WARNINGMSGLENGTH - 1 ))
    {
        return (FALSE);
    }

    //
    //  Since we're passed in a valid filename, if the 3rd & 4th characters
    //  are both slashes, weve got a dummy drive as the 1st two characters.
    //
    if (DBL_BSLASH(szFileName + 2))
    {
        szFileName = szFileName + 2;
    }

    wsprintf(szWarning, szCaption, szFileName);

    GetWindowText(hDlg, szCaption, cbCaption);
    return (MessageBox( hDlg,
                        szWarning,
                        szCaption,
                        MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION ) == IDYES);
}


////////////////////////////////////////////////////////////////////////////
//
//  CreateFileDlg
//
////////////////////////////////////////////////////////////////////////////

int CreateFileDlg(
    HWND hDlg,
    LPTSTR szPath)
{
    //
    //  Since we're passed in a valid filename, if the 3rd & 4th
    //  characters are both slashes, we've got a dummy drive as the
    //  1st two characters.
    //
    if (DBL_BSLASH(szPath + 2))
    {
        szPath = szPath + 2;
    }

    if (!CDLoadString(g_hinst, iszCreatePrompt, szCaption, TOOLONGLIMIT))
    {
        return (IDNO);
    }
    if (lstrlen(szPath) > TOOLONGLIMIT)
    {
        *(szPath + TOOLONGLIMIT) = CHAR_NULL;
    }

    wsprintf(szWarning, szCaption, szPath);

    GetWindowText(hDlg, szCaption, TOOLONGLIMIT);

    return (MessageBox( hDlg,
                        szWarning,
                        szCaption,
                        MB_YESNO | MB_ICONQUESTION ));
}


#ifndef UNICODE

////////////////////////////////////////////////////////////////////////////
//
//  EliminateString
//
//  Chops the string by the specified length.  If a DBCS lead byte is
//  left as the last char, then it is removed as well.
//
//  NOTE: For non-Unicode strings only.
//
////////////////////////////////////////////////////////////////////////////

VOID EliminateString(
    LPSTR lpStr,
    int nLen)
{
    LPSTR lpChar;
    BOOL bFix = FALSE;

    *(lpStr + nLen) = CHAR_NULL;
    for (lpChar = lpStr + nLen - 1; lpChar >= lpStr; lpChar--)
    {
        if (!IsDBCSLeadByte(*lpChar))
        {
            break;
        }
        bFix = !bFix;
    }
    if (bFix)
    {
        *(lpStr + nLen - 1) = CHAR_NULL;
    }
}


////////////////////////////////////////////////////////////////////////////
//
//  IsBackSlash
//
//  Decides whether a character is a '\' or a DBCS trail byte with the same
//  code point value.
//
//  NOTE: For non-Unicode strings only.
//
////////////////////////////////////////////////////////////////////////////

BOOL IsBackSlash(
    LPSTR lpStart,
    LPSTR lpChar)
{
    if (*lpChar == CHAR_BSLASH)
    {
        BOOL bRet = TRUE;

        while (--lpChar >= lpStart)
        {
            if (!IsDBCSLeadByte(*lpChar))
            {
                break;
            }
            bRet = !bRet;
        }
        return (bRet);
    }
    return (FALSE);
}

#endif

