569 lines
14 KiB
C++
569 lines
14 KiB
C++
|
/*++
|
||
|
|
||
|
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
|
||
|
|