/**********************************************************************/ /** 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. Also handles moving the file pointer and truncating the file in the case of a REST command sequence. 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+1]; DWORD cbSize = sizeof(szCanonPath); LARGE_INTEGER liOffset, liZero; CHAR szVirtualPath[MAX_PATH+1]; DWORD ccbVirtualPath = sizeof(szVirtualPath); DBG_ASSERT( pUserData != NULL ); DBG_ASSERT( phFile != NULL ); DBG_ASSERT( pszFile != NULL ); liZero.QuadPart = 0; liOffset.QuadPart = pUserData->QueryCurrentOffset(); // We'll want to do pretty much the same thing whether we're // actually appending or just starting at an offset due to a REST // command, so combine them here. fAppend = fAppend || (liOffset.QuadPart != 0); err = pUserData->VirtualCanonicalize(szCanonPath, &cbSize, pszFile, AccessTypeCreate, NULL, szVirtualPath, &ccbVirtualPath); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "creating %s\n", szCanonPath )); } // store the virtual file name for logging pUserData->SetVirtualFileName( szVirtualPath ); if ( pUserData->ImpersonateUser()) { WCHAR awchPath[MAX_PATH+8+1]; if (TsMakeWidePath( szCanonPath, awchPath, MAX_PATH+8+1)) { hFile = CreateFileW( awchPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, fAppend ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); } // // Disallow usage of short names // pUserData->RevertToSelf(); if ( hFile != INVALID_HANDLE_VALUE ) { if ( GetFileType( hFile ) != FILE_TYPE_DISK ) { DBG_REQUIRE( CloseHandle( hFile ) ); SetLastError( ERROR_ACCESS_DENIED ); hFile = INVALID_HANDLE_VALUE; } else if ( strchr( szCanonPath, '~' )) { BOOL fShort; DWORD err; err = CheckIfShortFileName( (UCHAR *) szCanonPath, pUserData->QueryImpersonationToken(), &fShort ); if ( !err && fShort ) { err = ERROR_FILE_NOT_FOUND; } if ( err ) { DBG_REQUIRE( CloseHandle( hFile )); hFile = INVALID_HANDLE_VALUE; SetLastError( err ); } } } } if( hFile == INVALID_HANDLE_VALUE ) { err = GetLastError(); } if( fAppend && ( err == NO_ERROR ) ) { if (liOffset.QuadPart == 0) { // This is a real append, not a restart sequence. // set Offset to the file size if( !SetFilePointerEx( hFile, liZero, &liOffset, FILE_END ) ) { err = GetLastError(); } } else { // We're in part of a restart sequence. Set the file pointer // to the offset, and truncate the file there. if ( !SetFilePointerEx( hFile, liOffset, NULL, FILE_BEGIN) || !SetEndOfFile( hFile) ) { err = GetLastError(); } } if (err != NO_ERROR ) { CloseHandle( hFile ); hFile = INVALID_HANDLE_VALUE; } } } if ( err != NO_ERROR) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot create %s, error %lu\n", szCanonPath, err )); } } if ( err == NO_ERROR) { pUserData->SetCurrentOffset( liOffset.QuadPart ); } *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; CHAR szCanon[MAX_PATH+1]; DWORD cbSize = sizeof(szCanon); CHAR szVirtualPath[MAX_PATH+1]; DWORD ccbVirtualPath = sizeof(szVirtualPath); DBG_ASSERT( pUserData != NULL ); DBG_ASSERT( phFile != NULL ); DBG_ASSERT( pszTmpFile != NULL ); // // Obtain the virtual to real path conversion. // err = pUserData->VirtualCanonicalize(szCanon, &cbSize, "", // current directory AccessTypeCreate, NULL, szVirtualPath, &ccbVirtualPath); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "creating unique file %s\n", pszTmpFile )); } // store the virtual file name for logging pUserData->SetVirtualFileName( szVirtualPath ); if ( pUserData->ImpersonateUser()) { if ( GetTempFileName(szCanon, "FTPD", 0, pszTmpFile ) != 0 ) { WCHAR awchPath[MAX_PATH+8+1]; if (TsMakeWidePath( pszTmpFile, awchPath, MAX_PATH+8+1)) { hFile = CreateFileW( awchPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); } } pUserData->RevertToSelf(); } if( hFile == INVALID_HANDLE_VALUE ) { err = GetLastError(); } } if( err != NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot create unique file, error %lu\n", err )); } } if ( err == NO_ERROR) { pUserData->SetCurrentOffset( 0 ); } *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+1]; DWORD cbSize = sizeof(szCanonPath); DBG_ASSERT( pUserData != NULL ); DBG_ASSERT( pszFile != NULL ); DBG_ASSERT( pszMode != NULL ); err = pUserData->VirtualCanonicalize(szCanonPath, &cbSize, pszFile, *pszMode == 'r' ? AccessTypeRead : AccessTypeWrite ); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( 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 ) { DBGPRINTF(( 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; CHAR szCanonPath[MAX_PATH+1]; DWORD cbSize = sizeof(szCanonPath); DWORD dwAccessMask = 0; DBG_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()) { WCHAR awchPath[MAX_PATH+8+1]; if (TsMakeWidePath( szCanonPath, awchPath, MAX_PATH+8+1)) { hFile = CreateFileW( awchPath, 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 ) { DBGPRINTF(( DBG_CONTEXT, "deleting %s\n", szCanonPath )); } if ( pUserData->ImpersonateUser()) { if( !DeleteFile( szCanonPath ) ) { err = GetLastError(); IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot delete %s, error %lu\n", szCanonPath, err )); } } pUserData->RevertToSelf(); } else { err = GetLastError(); } } else { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), 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; CHAR szCanonExisting[MAX_PATH+1]; DWORD cbSize = sizeof(szCanonExisting); DBG_ASSERT( pUserData != NULL ); err = pUserData->VirtualCanonicalize(szCanonExisting, &cbSize, pszExisting, AccessTypeDelete ); if( err == NO_ERROR ) { CHAR szCanonNew[MAX_PATH+1]; cbSize = sizeof(szCanonNew); err = pUserData->VirtualCanonicalize(szCanonNew, &cbSize, pszNew, AccessTypeCreate ); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "renaming %s to %s\n", szCanonExisting, szCanonNew )); } if ( pUserData->ImpersonateUser()) { if( !MoveFileEx( szCanonExisting, szCanonNew, pUserData->QueryInstance()->AllowReplaceOnRename() ? MOVEFILE_REPLACE_EXISTING : 0 ) ){ err = GetLastError(); IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot rename %s to %s, error %lu\n", szCanonExisting, szCanonNew, err )); } } pUserData->RevertToSelf(); } else { err = GetLastError(); } } else { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), pszExisting, err )); } } } else { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), 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+1]; DWORD cbVirtSize; APIERR err = NO_ERROR; DWORD cbSize; CHAR szCanonDir[MAX_PATH+1]; DBG_ASSERT( pUserData != NULL ); if (pszDir == NULL || *pszDir == '\0') { // // Nothing new specified. // return ( NO_ERROR); } // // Canonicalize the new path. // cbSize = sizeof(szCanonDir); cbVirtSize = sizeof(rgchVirtual); err = pUserData->VirtualCanonicalize(szCanonDir, &cbSize, pszDir, AccessTypeRead, NULL, rgchVirtual, &cbVirtSize); if ( err == ERROR_ACCESS_DENIED) { // // this maybe a write only virtual root directory. // Let us try again to find we have Write access atleast // cbSize = sizeof(szCanonDir); cbVirtSize = sizeof(rgchVirtual); err = pUserData->VirtualCanonicalize(szCanonDir, &cbSize, pszDir, AccessTypeWrite, NULL, rgchVirtual, &cbVirtSize); } if( err != NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), 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; err = OpenPathForAccess( &CurrentDirHandle, szCanonDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, pUserData->QueryImpersonationToken() ); if( err == ERROR_ACCESS_DENIED ) { err = OpenPathForAccess( &CurrentDirHandle, szCanonDir, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, pUserData->QueryImpersonationToken() ); } 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 ) { DBGPRINTF(( DBG_CONTEXT, "closing dir handle %08lX for %s\n", pUserData->CurrentDirHandle, pUserData->QueryCurrentDirectory().QueryStr())); } CloseHandle( pUserData->CurrentDirHandle ); } pUserData->CurrentDirHandle = CurrentDirHandle; IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "opened directory %s, handle = %08lX\n", szCanonDir, CurrentDirHandle )); } // update the current directory pUserData->SetCurrentDirectory( rgchVirtual ); } } } pUserData->RevertToSelf(); } else { // Impersonation failed err = GetLastError(); } IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( 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; CHAR szCanonDir[MAX_PATH+1]; DWORD cbSize = sizeof(szCanonDir); err = pUserData->VirtualCanonicalize(szCanonDir, &cbSize, pszDir, AccessTypeDelete ); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "rmdir %s\n", szCanonDir )); } if ( pUserData->ImpersonateUser()) { if( !RemoveDirectory( szCanonDir ) ) { err = GetLastError(); IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot rmdir %s, error %lu\n", szCanonDir, err )); } } pUserData->RevertToSelf(); } else { err = GetLastError(); } } else { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), 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; CHAR szCanonDir[MAX_PATH+1]; DWORD cbSize = sizeof(szCanonDir); DBG_ASSERT( pUserData != NULL ); err = pUserData->VirtualCanonicalize(szCanonDir, &cbSize, pszDir, AccessTypeCreate ); if( err == NO_ERROR ) { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "mkdir %s\n", szCanonDir )); } if ( pUserData->ImpersonateUser()) { if( !CreateDirectory( szCanonDir, NULL ) ) { err = GetLastError(); IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot mkdir %s, error %lu\n", szCanonDir, err )); } } pUserData->RevertToSelf(); } else { err = GetLastError(); } } else { IF_DEBUG( VIRTUAL_IO ) { DBGPRINTF(( DBG_CONTEXT, "cannot canonicalize %s - %s, error %lu\n", pUserData->QueryCurrentDirectory().QueryStr(), 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; BOOL fDBCS = FALSE; // // Ensure we got a valid symbolic path (something starting "/" // DBG_ASSERT( pszPath != NULL ); // DBG_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 '\\' : // // fDBCS is always false for non-DBCS system // if ( fDBCS ) { Class = 3; break; } 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--; DBG_ASSERT( *pszDest == '/' ); *pszDest = '\0'; pszDest = strrchr( pszPath, '/') + 1; } *pszDest = '\0'; break; default : DBG_ASSERT( !"Invalid action code in state table!" ); State = 4; *pszDest++ = '\0'; break; } State &= ~ACTION_MASK; if ( !fDBCS ) { if ( IsDBCSLeadByte( ch ) ) { fDBCS = TRUE; } } else { fDBCS = FALSE; } } // // Remove any trailing slash. // pszDest -= 2; if( ( strlen( pszPath ) > 3 ) && ( *pszDest == '/' ) ) { *pszDest = '\0'; } // // If the final path elements consists solely of dots and spaces, remove them. // while( strlen( pszPath ) > 3 ) { pszDest = strrchr( pszPath, '/'); DBG_ASSERT( pszDest != NULL ); pszHead = pszDest; pszDest++; while( ch = *pszDest++ ) { if( (ch != '.') && (ch != ' ') ) { break; } } if( ch == '\0' ) { // // this is probably dead code left over from // when we used physical paths. // if( pszHead == ( pszPath + 2 ) ) { pszHead++; } // end of dead code // // Don't remove the first '/' // if ( pszHead == pszPath ) { pszHead++; } *pszHead = '\0'; } else { break; } } } // VirtualpSanitizePath