|
|
/*++
Copyright (c) 2000-2002 Microsoft Corporation
Module Name:
LimitFindFile.cpp
Abstract:
This shim was originally intended for QuickTime's qt32inst.exe which did a breadth-first search of the directory tree and would overflow the buffer in which it was keeping a list of subdirs yet to visit.
With this shim you can limit the number of files that a single FindFile search will return, you can limit the number of subdirectories (aka the branching factor) returned, you can limit the "depth" to which any FindFile search will locate files, and you can specify whether these limits should be applied to all FindFiles or only fully-qualified FindFiles. You can also request that FindFile return only short filenames.
The shim's arguments are: DEPTH=# BRANCH=# FILES=# SHORTFILENAMES or LONGFILENAMES LIMITRELATIVE or ALLOWRELATIVE
The default behavior is: SHORTFILENAMES DEPTH = 4 ALLOWRELATIVE ... but if any command line is specified, the behavior is only that which is specified on the command line (no default behavior).
An example command line: COMMAND_LINE="FILES=100 LIMITRELATIVE" Which would limit every FindFile search to returning 100 or fewer files (but still returning any and all subdirectories).
Note: Depth is a bit tricky. The method used is to count backslashes, so limiting depth to zero will allow no files to be found ("C:\autorun.bat" has 1 backslash).
History:
08/24/2000 t-adams Created 03/14/2002 mnikkel Changed InitializeCriticalSection to InitializeCriticalSectionAndSpinCount changed to use strsafe.h --*/
#include "precomp.h"
IMPLEMENT_SHIM_BEGIN(LimitFindFile) #include "ShimHookMacro.h"
APIHOOK_ENUM_BEGIN APIHOOK_ENUM_ENTRY(FindFirstFileA) APIHOOK_ENUM_ENTRY(FindNextFileA) APIHOOK_ENUM_ENTRY(FindClose) APIHOOK_ENUM_END
// Linked list of FindFileHandles
struct FFNode { FFNode *next; HANDLE hFF; DWORD dwBranches; DWORD dwFiles; }; FFNode *g_FFList = NULL;
// Default behaviors - overridden by Commandline
BOOL g_bUseShortNames = TRUE; BOOL g_bLimitDepth = TRUE; DWORD g_dwDepthLimit = 4; BOOL g_bLimitRelative = FALSE; BOOL g_bLimitBranch = FALSE; DWORD g_dwBranchLimit = 0; BOOL g_bLimitFiles = FALSE; DWORD g_dwFileLimit = 0;
CRITICAL_SECTION g_MakeThreadSafe;
/*++
Abstract: ApplyLimits applys the recently found file from lpFindFileData to the current node, checks that none of the limits have been violated, and shortens the filename if requested.
It returns TRUE if within limits, FALSE if limits have been exceeded. History:
08/24/2000 t-adams Created
--*/ BOOL ApplyLimits(FFNode *pFFNode, LPWIN32_FIND_DATAA lpFindFileData) { BOOL bRet = TRUE;
// If it's a directory
if ( lpFindFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { pFFNode->dwBranches++; if ( g_bLimitBranch && pFFNode->dwBranches > g_dwBranchLimit ) { bRet = FALSE; goto exit; } } else { // else it's a file
pFFNode->dwFiles++; if ( g_bLimitFiles && pFFNode->dwFiles > g_dwFileLimit ) { bRet = FALSE; goto exit; } }
// Change to short name if requested
if ( g_bUseShortNames && NULL != lpFindFileData->cAlternateFileName[0]) { if (S_OK != StringCchCopyA(lpFindFileData->cFileName, ARRAYSIZE(lpFindFileData->cFileName), lpFindFileData->cAlternateFileName)) bRet = FALSE; }
exit: return bRet; }
/*++
Abstract: CheckDepthLimit checks to see if the depth of the requested search is greater than is allowed. If we are limiting relative paths, then the current directory is prepended to the requested search string.
It returns TRUE if within limits, FALSE if limits have been exceeded. History:
08/24/2000 t-adams Created
--*/ BOOL CheckDepthLimit(const CString & csFileName) { BOOL bRet = TRUE;
CSTRING_TRY { // Check the depth of the requested file
if ( g_bLimitDepth ) { DWORD dwDepth = 0; int nIndex = 0;
for(; nIndex >= 0; dwDepth++) { nIndex = csFileName.Find(L'\\', nIndex); } if ( dwDepth > g_dwDepthLimit ) { bRet = FALSE; } } } CSTRING_CATCH { // Do nothing
}
return bRet; }
/*++
Abstract: This function checks the depth of the requested search (see comments above). If the depth check passes it performs the search, request limit application, and finally returns a successful handle only if within all limits.
History:
08/24/2000 t-adams Created
--*/ HANDLE APIHOOK(FindFirstFileA)( LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData) { HANDLE hRet = INVALID_HANDLE_VALUE; FFNode *pFFNode = NULL; BOOL bRelPath = FALSE;
CString csFileName(lpFileName); // Determine if the path is relative to the CWD:
CString csDrive; csFileName.GetDrivePortion(csDrive); bRelPath = csDrive.IsEmpty();
// If it is a relative path & we're not limiting such, then just do
// the FindFile and get out.
if ( bRelPath) { if (!g_bLimitRelative) { return ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData); }
// We need to expand the directory portion of lpFileName to its full path
CString csPath; CString csFile;
csFileName.GetNotLastPathComponent(csPath); csFileName.GetLastPathComponent(csFile);
csPath.GetFullPathNameW(); csPath.AppendPath(csFile);
csFileName = csPath;
// Check the depth limit
if ( !CheckDepthLimit(csFileName) ) { return INVALID_HANDLE_VALUE; } }
hRet = ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData); if ( INVALID_HANDLE_VALUE == hRet ) { return hRet; }
EnterCriticalSection(&g_MakeThreadSafe);
// Make a new node for this handle
pFFNode = (FFNode *) malloc(sizeof FFNode); if ( !pFFNode ) { // Don't close the find, maybe it could still work for the app.
goto exit; } pFFNode->hFF = hRet; pFFNode->dwBranches = 0; pFFNode->dwFiles = 0;
// Apply our limits until we get a passable find
while( !ApplyLimits(pFFNode, lpFindFileData) ) { // If there are no more files to find, clean up & exit
// else loop back & ApplyLimits again
if ( !FindNextFileA(hRet, lpFindFileData) ) { free(pFFNode); FindClose(hRet); hRet = INVALID_HANDLE_VALUE; goto exit; } }
// We are clear to add this node to the global list
pFFNode->next = g_FFList; g_FFList = pFFNode;
LeaveCriticalSection(&g_MakeThreadSafe);
exit: return hRet; }
/*++
Abstract: This function continues a limited search given the search's handle. History:
08/24/2000 t-adams Created
--*/
BOOL FindNextFileAInternal( HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData) { FFNode *pFFNode = NULL; BOOL bRet = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
if ( !bRet ) { goto exit; }
// Find our node in the global list
pFFNode = g_FFList; while( pFFNode ) { if ( pFFNode->hFF == hFindFile ) { break; } pFFNode = pFFNode->next; }
// We don't keep track of relative-path searches if we're not
// limiting such.
if ( pFFNode == NULL ) { goto exit; }
// Apply our limits until we get a passable find
while( !ApplyLimits(pFFNode, lpFindFileData) ) { // If there are no more files to find return FALSE
// else loop back & ApplyLimits again
if ( !FindNextFileAInternal(hFindFile, lpFindFileData) ) { bRet = FALSE; goto exit; } }
exit: return bRet; }
BOOL APIHOOK(FindNextFileA)( HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData) { // FindNextFileAInternal is called seperately since it may recurse
EnterCriticalSection(&g_MakeThreadSafe);
BOOL bRet = FindNextFileAInternal(hFindFile, lpFindFileData);
LeaveCriticalSection(&g_MakeThreadSafe);
return bRet; }
/*++
Abstract: This function closes a search, cleaning up the structures used in keeping track of the limits.
History:
08/24/2000 t-adams Created
--*/ BOOL APIHOOK(FindClose)( HANDLE hFindFile) {
FFNode *pFFNode, *prev;
BOOL bRet = ORIGINAL_API(FindClose)(hFindFile);
EnterCriticalSection(&g_MakeThreadSafe);
// Find the node that matches the handle
pFFNode = g_FFList; prev = NULL; while( pFFNode ) { if ( pFFNode->hFF == hFindFile ) { // Remove this node from this list
if ( prev ) { prev->next = pFFNode->next; } else { g_FFList = pFFNode->next; }
free(pFFNode); pFFNode = NULL; break; } prev = pFFNode; pFFNode = pFFNode->next; }
LeaveCriticalSection(&g_MakeThreadSafe);
return bRet; }
/*++
Abstract: This function parses the command line. See the top of the file for valid arguments.
History:
08/24/2000 t-adams Created
--*/
VOID ParseCommandLine( LPCSTR lpCommandLine ) { // If there is a command line, reset the default behavior
if (*lpCommandLine != 0) { g_bLimitDepth = FALSE; g_bLimitBranch = FALSE; g_bLimitFiles = FALSE; g_bUseShortNames = FALSE; }
CSTRING_TRY { CStringToken csCommandLine(COMMAND_LINE, L" ,\t;:="); CString csOperator;
// Parse the command line
DWORD *pdwValue = NULL;
while (csCommandLine.GetToken(csOperator)) { if (csOperator.IsEmpty()) { goto Exit; }
// If we're looking for a value
if ( pdwValue ) { *pdwValue = atol(csOperator.GetAnsi()); pdwValue = NULL; } else { // We're expecting a keyword
if ( csOperator.CompareNoCase(L"DEPTH") == 0 ) { g_bLimitDepth = TRUE; pdwValue = &g_dwDepthLimit; } else if ( csOperator.CompareNoCase(L"BRANCH") == 0 ) { g_bLimitBranch = TRUE; pdwValue = &g_dwBranchLimit; } else if ( csOperator.CompareNoCase(L"FILES") == 0 ) { g_bLimitFiles = TRUE; pdwValue = &g_dwFileLimit; } else if ( csOperator.CompareNoCase(L"SHORTFILENAMES") == 0) { g_bUseShortNames = TRUE; // Don't need a value here
} else if ( csOperator.CompareNoCase(L"LONGFILENAMES") == 0) { g_bUseShortNames = FALSE; // Don't need a value here
} else if ( csOperator.CompareNoCase(L"LIMITRELATIVE") == 0) { g_bLimitRelative = TRUE; // Don't need a value here
} else if ( csOperator.CompareNoCase(L"ALLOWRELATIVE") == 0) { g_bLimitRelative = FALSE; // Don't need a value here
} } } } CSTRING_CATCH { // Do nothing
}
Exit: //
// Dump results of command line parse
//
DPFN( eDbgLevelInfo, "===================================\n"); DPFN( eDbgLevelInfo, " Limit FindFile \n"); DPFN( eDbgLevelInfo, "===================================\n"); if ( g_bLimitDepth ) { DPFN( eDbgLevelInfo, " Depth = %d\n", g_dwDepthLimit); } if ( g_bLimitBranch ) { DPFN( eDbgLevelInfo, " Branch = %d\n", g_dwBranchLimit); } if ( g_bLimitFiles ) { DPFN( eDbgLevelInfo, " Files = %d\n", g_dwFileLimit); } if ( g_bLimitRelative ) { DPFN( eDbgLevelInfo, " Limiting Relative Paths.\n"); } else { DPFN( eDbgLevelInfo, " Not Limiting Relative Paths.\n"); } if ( g_bUseShortNames ) { DPFN( eDbgLevelInfo, " Using short file names.\n"); }
DPFN( eDbgLevelInfo, "-----------------------------------\n"); }
BOOL NOTIFY_FUNCTION( DWORD fdwReason) { if (fdwReason == DLL_PROCESS_ATTACH) { ParseCommandLine(COMMAND_LINE);
return InitializeCriticalSectionAndSpinCount(&g_MakeThreadSafe,0x80000000); }
return TRUE; }
/*++
Register hooked functions
--*/
HOOK_BEGIN
APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA) APIHOOK_ENTRY(KERNEL32.DLL, FindNextFileA) APIHOOK_ENTRY(KERNEL32.DLL, FindClose) CALL_NOTIFY_FUNCTION
HOOK_END
IMPLEMENT_SHIM_END
|