/**********************************************************************/
/**                       Microsoft Windows NT                       **/
/**                Copyright(c) Microsoft Corp., 1993                **/
/**********************************************************************/

/*
    virtual.cxx

    This module contains the virtual I/O package.

    Under Win32, the "current directory" is an attribute of a process,
    not a thread.  This causes some grief for the FTPD service, since
    it is impersonating users on the server side.  The users must
    "think" they can change current directory at will.  We'll provide
    this behaviour in this package.

    Functions exported by this module:

        VirtualCreateFile
        VirtualCreateUniqueFile
        Virtual_fopen

        VirtualDeleteFile
        VirtualRenameFile
        VirtualChDir
        VirtualRmDir
        VirtualMkDir


    FILE HISTORY:
        KeithMo     09-Mar-1993 Created.

        MuraliK     28-Mar-1995 Enabled FILE_FLAG_OVERLAPPED in OpenFile()
        MuraliK     28-Apr-1995 modified to use new canonicalization
                    11-May-1995 made parameters to be const unless otherwise
                                required.
                    12-May-1995 eliminated the old log file access

*/


#include "ftpdp.hxx"


//
//  Private prototypes.
//

VOID
VirtualpSanitizePath(
    CHAR * pszPath
    );



//
//  Public functions.
//



/*******************************************************************

    NAME:       VirtualCreateFile

    SYNOPSIS:   Creates a new (or overwrites an existing) file.

    ENTRY:      pUserData - The user initiating the request.

                phFile - Will receive the file handle.  Will be
                    INVALID_HANDLE_VALUE if an error occurs.

                pszFile - The name of the new file.

                fAppend - If TRUE, and pszFile already exists, then
                    append to the existing file.  Otherwise, create
                    a new file.  Note that FALSE will ALWAYS create
                    a new file, potentially overwriting an existing
                    file.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     09-Mar-1993 Created.
        MuraliK     28-Apr-1995 modified to use new canonicalization

********************************************************************/
APIERR
VirtualCreateFile(
    USER_DATA * pUserData,
    HANDLE    * phFile,
    LPSTR       pszFile,
    BOOL        fAppend
    )
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    APIERR err;
    CHAR   szCanonPath[MAX_PATH];
    DWORD  cbSize = MAX_PATH;

    TCP_ASSERT( pUserData != NULL );
    TCP_ASSERT( phFile != NULL );
    TCP_ASSERT( pszFile != NULL );

    err = pUserData->VirtualCanonicalize(szCanonPath,
                                         &cbSize,
                                         pszFile,
                                         AccessTypeCreate );

    if( err == NO_ERROR ) {

        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "creating %s\n", szCanonPath ));
        }

        if ( pUserData->ImpersonateUser()) {

            hFile = CreateFile( szCanonPath,
                               GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ,
                               NULL,
                               fAppend ? OPEN_ALWAYS : CREATE_ALWAYS,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL );

            pUserData->RevertToSelf();

        }

        if( hFile == INVALID_HANDLE_VALUE ) {

            err = GetLastError();
        }

        if( fAppend && ( err == NO_ERROR ) ) {

            if( SetFilePointer( hFile,
                                0,
                                NULL,
                                FILE_END )
               == (DWORD)-1L ) {

                err = GetLastError();

                CloseHandle( hFile );
                hFile = INVALID_HANDLE_VALUE;
            }
        }
    }

    if ( err != NO_ERROR) {

        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "cannot create %s, error %lu\n",
                        szCanonPath,
                        err ));
        }
    }

    *phFile = hFile;

    return err;

}   // VirtualCreateFile


/*******************************************************************

    NAME:       VirtualCreateUniqueFile

    SYNOPSIS:   Creates a new unique (temporary) file in the current
                    virtual directory.

    ENTRY:      pUserData - The user initiating the request.

                phFile - Will receive the file handle.  Will be
                    INVALID_HANDLE_VALUE if an error occurs.

                pszTmpFile - Will receive the name of the temporary
                    file.  This buffer MUST be at least MAX_PATH
                    characters long.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     16-Mar-1993 Created.
        MuraliK     28-Apr-1995 modified to use new canonicalization

********************************************************************/
APIERR
VirtualCreateUniqueFile(
    USER_DATA * pUserData,
    HANDLE    * phFile,
    LPSTR       pszTmpFile
    )
{
    HANDLE      hFile = INVALID_HANDLE_VALUE;
    APIERR      err   = NO_ERROR;
    DWORD       cbSize = MAX_PATH;
    CHAR        szCanon[MAX_PATH];

    TCP_ASSERT( pUserData != NULL );
    TCP_ASSERT( phFile != NULL );
    TCP_ASSERT( pszTmpFile != NULL );

    //
    // Obtain the virtual to real path conversion.
    //

    err = pUserData->VirtualCanonicalize(szCanon,
                                         &cbSize,
                                         "", // current directory
                                         AccessTypeCreate);


    if( err == NO_ERROR )
      {
          IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "creating unique file %s\n", pszTmpFile ));
        }

        if ( pUserData->ImpersonateUser()) {

            //
            // generate a temporary file name and try to create this file.
            //

            if ( GetTempFileName(szCanon,
                                 "FTPD",
                                 0, pszTmpFile ) != 0
                ) {

                hFile = CreateFile( pszTmpFile,
                                   GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ,
                                   NULL,
                                   CREATE_ALWAYS,
                                   FILE_ATTRIBUTE_NORMAL,
                                   NULL );
            }
            
            pUserData->RevertToSelf();
                
        }
          
        if( hFile == INVALID_HANDLE_VALUE )
        {
            err = GetLastError();
        }
    }

    if( err != 0 )
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "cannot create unique file, error %lu\n",
                        err ));
        }
    }

    *phFile = hFile;

    return err;

}   // VirtualCreateUniqueFile()



/*******************************************************************

    NAME:       Virtual_fopen

    SYNOPSIS:   Opens an file stream.

    ENTRY:      pUserData - The user initiating the request.

                pszFile - The name of the file to open.

                pszMode - The type of access required.

    RETURNS:    FILE * - The open file stream, NULL if file cannot
                    be opened.

    NOTES:      Since this is only used for accessing the ~FTPSVC~.CKM
                    annotation files, we don't log file accesses here.

    HISTORY:
        KeithMo     07-May-1993 Created.
        MuraliK     28-Apr-1995 modified to use new canonicalization

********************************************************************/
FILE *
Virtual_fopen(
    USER_DATA * pUserData,
    LPSTR       pszFile,
    LPSTR       pszMode
    )
{
    FILE   * pfile = NULL;
    APIERR   err;
    CHAR     szCanonPath[MAX_PATH];
    DWORD    cbSize = MAX_PATH;

    TCP_ASSERT( pUserData != NULL );
    TCP_ASSERT( pszFile != NULL );
    TCP_ASSERT( pszMode != NULL );

    err = pUserData->VirtualCanonicalize(szCanonPath,
                                         &cbSize,
                                         pszFile,
                                         *pszMode == 'r'
                                         ? AccessTypeRead
                                         : AccessTypeWrite );

    if( err == NO_ERROR )
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "opening %s\n", szCanonPath ));
        }

        if ( pUserData->ImpersonateUser()) {

            pfile = fopen( szCanonPath, pszMode );

            pUserData->RevertToSelf();
        }

        if( pfile == NULL )
        {
            err = ERROR_FILE_NOT_FOUND; // best guess
        }
    }

    IF_DEBUG( VIRTUAL_IO )
    {
        if( err != NO_ERROR )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "cannot open %s, error %lu\n",
                        pszFile,
                        err ));
        }
    }

    return pfile;

}   // Virtual_fopen




/*******************************************************************

    NAME:       VirtualDeleteFile

    SYNOPSIS:   Deletes an existing file.

    ENTRY:      pUserData - The user initiating the request.

                pszFile - The name of the file.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     09-Mar-1993 Created.

********************************************************************/
APIERR
VirtualDeleteFile(
    USER_DATA * pUserData,
    LPSTR       pszFile
    )
{
    APIERR err;
    DWORD  cbSize = MAX_PATH;
    CHAR   szCanonPath[MAX_PATH];
    DWORD  dwAccessMask = 0;

    TCP_ASSERT( pUserData != NULL );

    //
    //  We'll canonicalize the path, asking for *read* access.  If
    //  the path canonicalizes correctly, we'll then try to open the
    //  file to ensure it exists.  Only then will we check for delete
    //  access to the path.  This mumbo-jumbo is necessary to get the
    //  proper error codes if someone trys to delete a nonexistent
    //  file on a read-only volume.
    //

    err = pUserData->VirtualCanonicalize(szCanonPath,
                                         &cbSize,
                                         pszFile,
                                         AccessTypeRead,
                                         &dwAccessMask);

    if( err == NO_ERROR )
    {
        HANDLE hFile = INVALID_HANDLE_VALUE;

        if ( pUserData->ImpersonateUser()) {

            hFile = CreateFile( szCanonPath,
                               GENERIC_READ,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL );

            pUserData->RevertToSelf();
        }

        if( hFile == INVALID_HANDLE_VALUE )
        {
            err = GetLastError();
        }
        else
        {
            //
            //  The file DOES exist.  Close the handle, then check
            //  to ensure we really have delete access.
            //

            CloseHandle( hFile );

            if( !PathAccessCheck( AccessTypeDelete,
                                 dwAccessMask,
                                 TEST_UF(pUserData, READ_ACCESS),
                                 TEST_UF(pUserData, WRITE_ACCESS)
                                 )
               ) {

                err = ERROR_ACCESS_DENIED;
            }
        }
    }

    if( err == NO_ERROR )
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "deleting %s\n", szCanonPath ));
        }

        if ( pUserData->ImpersonateUser()) {

            if( !DeleteFile( szCanonPath ) ) {

                err = GetLastError();

                IF_DEBUG( VIRTUAL_IO ) {

                    TCP_PRINT(( DBG_CONTEXT,
                               "cannot delete %s, error %lu\n",
                               szCanonPath,
                               err ));
                }
            }

            pUserData->RevertToSelf();
        } else {

            err = GetLastError();
        }
    }
    else
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "cannot canonicalize %s - %s, error %lu\n",
                        pUserData->QueryCurrentDirectory(),
                        pszFile,
                        err ));
        }
    }

    return err;

}   // VirtualDeleteFile()



/*******************************************************************

    NAME:       VirtualRenameFile

    SYNOPSIS:   Renames an existing file or directory.

    ENTRY:      pUserData - The user initiating the request.

                pszExisting - The name of an existing file or directory.

                pszNew - The new name for the file or directory.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     10-Mar-1993 Created.

********************************************************************/
APIERR
VirtualRenameFile(
    USER_DATA * pUserData,
    LPSTR       pszExisting,
    LPSTR       pszNew
    )
{
    APIERR err;
    DWORD  cbSize = MAX_PATH;
    CHAR   szCanonExisting[MAX_PATH];

    TCP_ASSERT( pUserData != NULL );

    err = pUserData->VirtualCanonicalize(szCanonExisting,
                                         &cbSize,
                                         pszExisting,
                                         AccessTypeDelete );

    if( err == NO_ERROR ) {

        CHAR   szCanonNew[MAX_PATH];
        cbSize = MAX_PATH;

        err = pUserData->VirtualCanonicalize(szCanonNew,
                                             &cbSize,
                                             pszNew,
                                             AccessTypeCreate );

        if( err == NO_ERROR ) {

            IF_DEBUG( VIRTUAL_IO ) {

                TCP_PRINT(( DBG_CONTEXT,
                           "renaming %s to %s\n",
                           szCanonExisting,
                           szCanonNew ));
            }

            if ( pUserData->ImpersonateUser()) {

                if( !MoveFileEx( szCanonExisting,
                                szCanonNew,
                                MOVEFILE_REPLACE_EXISTING )
                   ){

                    err = GetLastError();

                    IF_DEBUG( VIRTUAL_IO ) {

                        TCP_PRINT(( DBG_CONTEXT,
                                   "cannot rename %s to %s, error %lu\n",
                                   szCanonExisting,
                                   szCanonNew,
                                   err ));
                    }
                }

                pUserData->RevertToSelf();

            } else {
                err = GetLastError();
            }

        } else  {

            IF_DEBUG( VIRTUAL_IO ) {

                TCP_PRINT(( DBG_CONTEXT,
                           "cannot canonicalize %s - %s, error %lu\n",
                           pUserData->QueryCurrentDirectory(),
                           pszExisting,
                           err ));
            }
        }
    } else {

        IF_DEBUG( VIRTUAL_IO ) {

            TCP_PRINT(( DBG_CONTEXT,
                        "cannot canonicalize %s - %s, error %lu\n",
                       pUserData->QueryCurrentDirectory(),
                       pszExisting,
                       err ));
        }
    }

    return err;

}   // VirtualRenameFile()




APIERR
VirtualChDir(
    USER_DATA * pUserData,
    LPSTR       pszDir
    )
/*++
  This function sets the current directory to newly specified directory.

  Arguments:
     pUserData -- the user initiating the request
     pszDir  -- pointer to null-terminated buffer containing the
                   new directory name.
  Returns:
     APIERR  -- NO_ERROR if successful, otherwise a Win32 error code.

  History:
     MuraliK   28-Apr-1995  Modified to use symbolic roots.
--*/
{
    CHAR     rgchVirtual[MAX_PATH];
    APIERR   err = NO_ERROR;
    DWORD    cbSize = MAX_PATH;
    CHAR     szCanonDir[MAX_PATH];

    TCP_ASSERT( pUserData != NULL );

    if (pszDir == NULL || *pszDir == '\0') {

        //
        // Nothing new specified.
        //

        return ( NO_ERROR);
    }


    //
    // Form the complete virtual path for the given path.
    //  We have to form a compelete virtual path and sanitize it, because:
    //     if this new directory is valid, we have to store the
    //      sanitized path in the CurrentDirectory member of USER_DATA.
    //

    if ( !IS_PATH_SEP( *pszDir)) {

        const CHAR * pszNewDir = pUserData->QueryCurrentDirectory();

        //
        // This is a relative path. append it to currrent directory
        //

        if ( strlen(pszNewDir) + strlen(pszDir) + 2 <= MAX_PATH) {

            // copy the current directory
            wsprintfA( rgchVirtual, "%s/%s",
                      pszNewDir, pszDir);
            pszDir = rgchVirtual;

        } else {

            // long path --> is not supported.
            TCP_PRINT((DBG_CONTEXT, "Long Virtual Path %s---%s\n",
                       pszNewDir, pszDir));

            return ( ERROR_PATH_NOT_FOUND);
        }

    }

    TCP_ASSERT( IS_PATH_SEP(*pszDir));
    VirtualpSanitizePath( pszDir);


    //
    //  Canonicalize the new path.
    //  Canonicalize() automatically updates the current directory if need be
    //

    err = pUserData->VirtualCanonicalize(szCanonDir,
                                         &cbSize,
                                         pszDir,
                                         AccessTypeRead );

    if ( err == ERROR_ACCESS_DENIED) {

        //
        // this maybe a write only virtual root directory.
        // Let us try again to find we have Write access atleast
        //

        err = pUserData->VirtualCanonicalize(szCanonDir,
                                             &cbSize,
                                             pszDir,
                                             AccessTypeWrite );
    }

    if( err != NO_ERROR )
    {

        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                       "cannot canonicalize %s - %s, error %lu\n",
                       pUserData->QueryCurrentDirectory(),
                       pszDir,
                       err ));
        }

        return err;
    }

    //
    //  Try to open the directory and get a handle for the same.
    //

    // This is possibly a new place to change directory to.

    if ( pUserData->ImpersonateUser()) {

        HANDLE CurrentDirHandle = INVALID_HANDLE_VALUE;

#ifdef CHICAGO

        LPTS_OPEN_FILE_INFO pGetFile = NULL;

        pGetFile = TsCreateFile( g_pTsvcInfo->GetTsvcCache(),
                                 szCanonDir,
                                 NULL,
                                 TS_CACHING_DESIRED );

        if (pGetFile) {

            if (pGetFile->QueryAttributes() & FILE_ATTRIBUTE_DIRECTORY) {

                //
                //  Directory successfully opened.  Save the handle
                //  in the per-user data. This handle is maintained to
                //  prevent accidental deletion of the directory.
                //

                IF_DEBUG( VIRTUAL_IO ) {

                    TCP_PRINT(( DBG_CONTEXT,
                               "opened directory %s, handle = %08lX\n",
                               szCanonDir,
                               CurrentDirHandle ));
                }

                // update the current directory
                strcpy( pUserData->CurrentDirectory, pszDir);

            } else {

                //
                // this file is not a directory.
                // Do not change directory. But return error.
                //

                err = ERROR_DIRECTORY;

                TCP_REQUIRE( TsCloseHandle( g_pTsvcInfo->GetTsvcCache(),
                                            pGetFile ));
                pGetFile = NULL;

            }

        }

#else
        err = OpenPathForAccess( &CurrentDirHandle,
                                szCanonDir,
                                FILE_SHARE_READ | FILE_SHARE_WRITE
                                );

        if( err == NO_ERROR ) {

            BY_HANDLE_FILE_INFORMATION fileInfo;
            BOOL fRet;

            fRet = GetFileInformationByHandle( CurrentDirHandle,
                                              &fileInfo);

            if ( !fRet) {

                err = GetLastError();

                // Error in getting the file information.
                // close handle and return.

                CloseHandle( CurrentDirHandle);

            } else {

                if ( (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    != FILE_ATTRIBUTE_DIRECTORY) {

                    //
                    // this file is not a directory.
                    // Do not change directory. But return error.
                    //

                    err = ERROR_DIRECTORY;
                    CloseHandle( CurrentDirHandle);
                } else {

                    //
                    //  Directory successfully opened.  Save the handle
                    //  in the per-user data. This handle is maintained to
                    //  prevent accidental deletion of the directory.
                    //

                    if( pUserData->CurrentDirHandle != INVALID_HANDLE_VALUE ) {

                        IF_DEBUG( VIRTUAL_IO ) {

                            TCP_PRINT(( DBG_CONTEXT,
                                       "closing dir handle %08lX for %s\n",
                                       pUserData->CurrentDirHandle,
                                       pUserData->QueryCurrentDirectory()));
                        }

                        CloseHandle( pUserData->CurrentDirHandle );
                    }

                    pUserData->CurrentDirHandle = CurrentDirHandle;

                    IF_DEBUG( VIRTUAL_IO ) {

                        TCP_PRINT(( DBG_CONTEXT,
                                   "opened directory %s, handle = %08lX\n",
                                   szCanonDir,
                                   CurrentDirHandle ));
                    }

                    // update the current directory
                    strcpy( pUserData->CurrentDirectory, pszDir);

                }
            }
        }

#endif

        pUserData->RevertToSelf();

    } else {

        // Impersonation failed
        err = GetLastError();
    }

    IF_DEBUG( VIRTUAL_IO ) {

        TCP_PRINT(( DBG_CONTEXT,
                   "chdir to %s  returns error = %d\n", szCanonDir, err ));
    }

    return ( err);

}   // VirtualChDir()



/*******************************************************************

    NAME:       VirtualRmDir

    SYNOPSIS:   Removes an existing directory.

    ENTRY:      pUserData - The user initiating the request.

                pszDir - The name of the directory to remove.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     09-Mar-1993 Created.

********************************************************************/
APIERR
VirtualRmDir(
    USER_DATA * pUserData,
    LPSTR      pszDir
    )
{
    APIERR err;
    DWORD  cbSize = MAX_PATH;
    CHAR   szCanonDir[MAX_PATH];

    err = pUserData->VirtualCanonicalize(szCanonDir,
                                         &cbSize,
                                         pszDir,
                                         AccessTypeDelete );

    if( err == NO_ERROR )
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "rmdir %s\n", szCanonDir ));
        }

        if ( pUserData->ImpersonateUser()) {

            if( !RemoveDirectory( szCanonDir ) ) {

                err = GetLastError();

                IF_DEBUG( VIRTUAL_IO ) {

                    TCP_PRINT(( DBG_CONTEXT,
                               "cannot rmdir %s, error %lu\n",
                               szCanonDir,
                               err ));
                }
            }
            pUserData->RevertToSelf();
        } else {
            err = GetLastError();
        }
    } else {

        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "cannot canonicalize %s - %s, error %lu\n",
                        pUserData->CurrentDirectory,
                        pszDir,
                        err ));
        }
    }

    return err;

}   // VirtualRmDir



/*******************************************************************

    NAME:       VirtualMkDir

    SYNOPSIS:   Creates a new directory.

    ENTRY:      pUserData - The user initiating the request.

                pszDir - The name of the directory to create.

    RETURNS:    APIERR - NO_ERROR if successful, otherwise a Win32
                    error code.

    HISTORY:
        KeithMo     09-Mar-1993 Created.

********************************************************************/
APIERR
VirtualMkDir(
    USER_DATA * pUserData,
    LPSTR      pszDir
    )
{
    APIERR err;
    DWORD  cbSize = MAX_PATH;
    CHAR   szCanonDir[MAX_PATH];

    TCP_ASSERT( pUserData != NULL );

    err = pUserData->VirtualCanonicalize(szCanonDir,
                                         &cbSize,
                                         pszDir,
                                         AccessTypeCreate );

    if( err == NO_ERROR )
    {
        IF_DEBUG( VIRTUAL_IO )
        {
            TCP_PRINT(( DBG_CONTEXT,
                        "mkdir %s\n", szCanonDir ));
        }

        if ( pUserData->ImpersonateUser()) {

            if( !CreateDirectory( szCanonDir, NULL ) ) {

                err = GetLastError();

                IF_DEBUG( VIRTUAL_IO )
                  {
                      TCP_PRINT(( DBG_CONTEXT,
                                 "cannot mkdir %s, error %lu\n",
                                 szCanonDir,
                                 err ));
                  }
            }
            pUserData->RevertToSelf();
        } else {
            err = GetLastError();
        }
    } else {

        IF_DEBUG( VIRTUAL_IO ) {

            TCP_PRINT(( DBG_CONTEXT,
                       "cannot canonicalize %s - %s, error %lu\n",
                       pUserData->CurrentDirectory,
                       pszDir,
                       err ));
        }
    }

    return err;

}   // VirtualMkDir




//
//  Private constants.
//

#define ACTION_NOTHING              0x00000000
#define ACTION_EMIT_CH              0x00010000
#define ACTION_EMIT_DOT_CH          0x00020000
#define ACTION_EMIT_DOT_DOT_CH      0x00030000
#define ACTION_BACKUP               0x00040000
#define ACTION_MASK                 0xFFFF0000


//
//  Private globals.
//

INT p_StateTable[4][4] =
    {
        {   // state 0
            1 | ACTION_EMIT_CH,             // "\"
            0 | ACTION_EMIT_CH,             // "."
            4 | ACTION_EMIT_CH,             // EOS
            0 | ACTION_EMIT_CH              // other
        },

        {   // state 1
            1 | ACTION_NOTHING,             // "\"
            2 | ACTION_NOTHING,             // "."
            4 | ACTION_EMIT_CH,             // EOS
            0 | ACTION_EMIT_CH              // other
        },

        {   // state 2
            1 | ACTION_NOTHING,             // "\"
            3 | ACTION_NOTHING,             // "."
            4 | ACTION_EMIT_CH,             // EOS
            0 | ACTION_EMIT_DOT_CH          // other
        },

        {   // state 3
            1 | ACTION_BACKUP,              // "\"
            0 | ACTION_EMIT_DOT_DOT_CH,     // "."
            4 | ACTION_BACKUP,              // EOS
            0 | ACTION_EMIT_DOT_DOT_CH      // other
        }
    };



/*******************************************************************

    NAME:       VirtualpSanitizePath

    SYNOPSIS:   Sanitizes a path by removing bogus path elements.

                As expected, "/./" entries are simply removed, and
                "/../" entries are removed along with the previous
                path element.

                To maintain compatibility with URL path semantics
                 additional transformations are required. All backward
                 slashes "\\" are converted to forward slashes. Any
                 repeated forward slashes (such as "///") are mapped to
                 single backslashes.  Also, any trailing path elements
                 consisting solely of dots "/....." are removed.

                Thus, the path "/foo\./bar/../tar\....\......" is
                mapped to "/foo/tar".

                A state table (see the p_StateTable global at the
                beginning of this file) is used to perform most of
                the transformations.  The table's rows are indexed
                by current state, and the columns are indexed by
                the current character's "class" (either slash, dot,
                NULL, or other).  Each entry in the table consists
                of the new state tagged with an action to perform.
                See the ACTION_* constants for the valid action
                codes.

                After the FSA is finished with the path, we make one
                additional pass through it to remove any trailing
                backslash, and to remove any trailing path elements
                consisting solely of dots.

    ENTRY:      pszPath - The path to sanitize.

    HISTORY:
        KeithMo     07-Sep-1994 Created.
        MuraliK     28-Apr-1995 Adopted this for symbolic paths

********************************************************************/
VOID
VirtualpSanitizePath(
    CHAR * pszPath
    )
{
    CHAR * pszSrc;
    CHAR * pszDest;
    CHAR * pszHead;
    CHAR   ch;
    INT    State;
    INT    Class;

    //
    //  Ensure we got a valid symbolic path (something starting "/"
    //

    TCP_ASSERT( pszPath != NULL );
//     TCP_ASSERT( pszPath[0] == '/');

    //
    //  Start our scan at the first "/.
    //

    pszHead = pszSrc = pszDest = pszPath;

    //
    //  State 0 is the initial state.
    //

    State = 0;

    //
    //  Loop until we enter state 4 (the final, accepting state).
    //

    while( State != 4 )
    {
        //
        //  Grab the next character from the path and compute its
        //  character class.  While we're at it, map any forward
        //  slashes to backward slashes.
        //

        ch = *pszSrc++;

        switch( ch )
        {
        case '\\' :
            ch = '/';  // convert it to symbolic URL path separator char.
            /* fall through */

        case '/' :
            Class = 0;
            break;

        case '.' :
            Class = 1;
            break;

        case '\0' :
            Class = 2;
            break;

        default :
            Class = 3;
            break;
        }

        //
        //  Advance to the next state.
        //

        State = p_StateTable[State][Class];

        //
        //  Perform the action associated with the state.
        //

        switch( State & ACTION_MASK )
        {
        case ACTION_EMIT_DOT_DOT_CH :
            *pszDest++ = '.';
            /* fall through */

        case ACTION_EMIT_DOT_CH :
            *pszDest++ = '.';
            /* fall through */

        case ACTION_EMIT_CH :
            *pszDest++ = ch;
            /* fall through */

        case ACTION_NOTHING :
            break;

        case ACTION_BACKUP :
            if( pszDest > ( pszHead + 1 ) )
            {
                pszDest--;
                TCP_ASSERT( *pszDest == '/' );

                *pszDest = '\0';
                pszDest = strrchr( pszPath, '/') + 1;
            }

            *pszDest = '\0';
            break;

        default :
            TCP_ASSERT( !"Invalid action code in state table!" );
            State = 4;
            *pszDest++ = '\0';
            break;
        }

        State &= ~ACTION_MASK;
    }

    //
    //  Remove any trailing slash.
    //

    pszDest -= 2;

    if( ( strlen( pszPath ) > 3 ) && ( *pszDest == '/' ) )
    {
        *pszDest = '\0';
    }

    //
    //  If the final path elements consists solely of dots, remove them.
    //

    while( strlen( pszPath ) > 3 )
    {
        pszDest = strrchr( pszPath, '/');
        TCP_ASSERT( pszDest != NULL );

        pszHead = pszDest;
        pszDest++;

        while( ch = *pszDest++ )
        {
            if( ch != '.' )
            {
                break;
            }
        }

        if( ch == '\0' )
        {
            if( pszHead == ( pszPath + 2 ) )
            {
                pszHead++;
            }

            *pszHead = '\0';
        }
        else
        {
            break;
        }
    }

}   // VirtualpSanitizePath