windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/ftp/server51/virtual.cxx

1353 lines
35 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/**********************************************************************/
/** 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];
DWORD cbSize = MAX_PATH;
DWORD dwOffset;
DBG_ASSERT( pUserData != NULL );
DBG_ASSERT( phFile != NULL );
DBG_ASSERT( pszFile != NULL );
dwOffset = 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 || (dwOffset != 0);
err = pUserData->VirtualCanonicalize(szCanonPath,
&cbSize,
pszFile,
AccessTypeCreate );
if( err == NO_ERROR ) {
IF_DEBUG( VIRTUAL_IO )
{
DBGPRINTF(( 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 );
//
// 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 (dwOffset == 0) {
// This is a real append, not a restart sequence.
if( SetFilePointer( hFile,
0,
NULL,
FILE_END )
== (DWORD)-1L ) {
err = GetLastError();
}
} else {
// We're in part of a restart sequence. Set the file pointer
// to the offset, and truncate the file there.
if (SetFilePointer( hFile,
dwOffset,
NULL,
FILE_BEGIN)
== (DWORD)-1L &&
(dwOffset != (DWORD)-1L) ) {
err = GetLastError();
}
else {
if (SetEndOfFile( hFile ) == 0) {
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 ));
}
}
*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];
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);
if( err == NO_ERROR )
{
IF_DEBUG( VIRTUAL_IO ) {
DBGPRINTF(( DBG_CONTEXT,
"creating unique file %s\n", pszTmpFile ));
}
if ( pUserData->ImpersonateUser()) {
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 )
{
DBGPRINTF(( 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;
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;
DWORD cbSize = MAX_PATH;
CHAR szCanonPath[MAX_PATH];
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()) {
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 )
{
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(),
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];
DBG_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 ) {
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(),
pszExisting,
err ));
}
}
} else {
IF_DEBUG( VIRTUAL_IO ) {
DBGPRINTF(( 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;
CHAR szCanonDir[MAX_PATH];
DBG_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.
DBGPRINTF((DBG_CONTEXT, "Long Virtual Path %s---%s\n",
pszNewDir, pszDir));
return ( ERROR_PATH_NOT_FOUND);
}
}
DBG_ASSERT( IS_PATH_SEP(*pszDir));
VirtualpSanitizePath( pszDir);
//
// Canonicalize the new path.
// Canonicalize() automatically updates the current directory if need be
//
cbSize = sizeof(szCanonDir);
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
//
cbSize = sizeof(szCanonDir);
err = pUserData->VirtualCanonicalize(szCanonDir,
&cbSize,
pszDir,
AccessTypeWrite );
}
if( err != NO_ERROR )
{
IF_DEBUG( VIRTUAL_IO )
{
DBGPRINTF(( 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;
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()));
}
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( pszDir );
}
}
}
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;
DWORD cbSize = MAX_PATH;
CHAR szCanonDir[MAX_PATH];
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(),
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];
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(),
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, remove them.
//
while( strlen( pszPath ) > 3 )
{
pszDest = strrchr( pszPath, '/');
DBG_ASSERT( pszDest != NULL );
pszHead = pszDest;
pszDest++;
while( ch = *pszDest++ )
{
if( 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