|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: FileSystemEnumerable
**
** <OWNER>kimhamil</OWNER>
**
**
** Purpose: Enumerates files and dirs
**
===========================================================*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security;
using System.Security.Permissions;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Runtime.Versioning;
using System.Diagnostics.Contracts;
using System.Threading;
namespace System.IO
{
// Overview:
// The key methods instantiate FileSystemEnumerableIterators. These compose the iterator with search result
// handlers that instantiate the FileInfo, DirectoryInfo, String, etc. The handlers then perform any
// additional required permission demands.
internal static class FileSystemEnumerableFactory
{
internal static IEnumerable<String> CreateFileNameIterator(String path, String originalUserPath, String searchPattern,
bool includeFiles, bool includeDirs, SearchOption searchOption, bool checkHost)
{
Contract.Requires(path != null);
Contract.Requires(originalUserPath != null);
Contract.Requires(searchPattern != null);
SearchResultHandler<String> handler = new StringResultHandler(includeFiles, includeDirs);
return new FileSystemEnumerableIterator<String>(path, originalUserPath, searchPattern, searchOption, handler, checkHost);
}
internal static IEnumerable<FileInfo> CreateFileInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
{
Contract.Requires(path != null);
Contract.Requires(originalUserPath != null);
Contract.Requires(searchPattern != null);
SearchResultHandler<FileInfo> handler = new FileInfoResultHandler();
return new FileSystemEnumerableIterator<FileInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
}
internal static IEnumerable<DirectoryInfo> CreateDirectoryInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
{
Contract.Requires(path != null);
Contract.Requires(originalUserPath != null);
Contract.Requires(searchPattern != null);
SearchResultHandler<DirectoryInfo> handler = new DirectoryInfoResultHandler();
return new FileSystemEnumerableIterator<DirectoryInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
}
internal static IEnumerable<FileSystemInfo> CreateFileSystemInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
{
Contract.Requires(path != null);
Contract.Requires(originalUserPath != null);
Contract.Requires(searchPattern != null);
SearchResultHandler<FileSystemInfo> handler = new FileSystemInfoResultHandler();
return new FileSystemEnumerableIterator<FileSystemInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
}
}
// Abstract Iterator, borrowed from Linq. Used in anticipation of need for similar enumerables
// in the future
abstract internal class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource>
{
int threadId;
internal int state;
internal TSource current;
public Iterator()
{
threadId = Thread.CurrentThread.ManagedThreadId;
}
public TSource Current
{
get { return current; }
}
protected abstract Iterator<TSource> Clone();
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
current = default(TSource);
state = -1;
}
public IEnumerator<TSource> GetEnumerator()
{
if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0)
{
state = 1;
return this;
}
Iterator<TSource> duplicate = Clone();
duplicate.state = 1;
return duplicate;
}
public abstract bool MoveNext();
object IEnumerator.Current
{
get { return Current; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
// Overview:
// Enumerates file system entries matching the search parameters. For recursive searches this
// searches through all the sub dirs and executes the search criteria against every dir.
//
// Generic implementation:
// FileSystemEnumerableIterator is generic. When it gets a WIN32_FIND_DATA, it calls the
// result handler to create an instance of the generic type.
//
// Usage:
// Use FileSystemEnumerableFactory to obtain FSEnumerables that can enumerate file system
// entries as String path names, FileInfos, DirectoryInfos, or FileSystemInfos.
//
// Security:
// For all the dirs/files returned, demands path discovery permission for their parent folders
internal class FileSystemEnumerableIterator<TSource> : Iterator<TSource>
{
private const int STATE_INIT = 1;
private const int STATE_SEARCH_NEXT_DIR = 2;
private const int STATE_FIND_NEXT_FILE = 3;
private const int STATE_FINISH = 4;
private SearchResultHandler<TSource> _resultHandler;
private List<Directory.SearchData> searchStack;
private Directory.SearchData searchData;
private String searchCriteria;
[System.Security.SecurityCritical]
SafeFindHandle _hnd = null;
bool needsParentPathDiscoveryDemand;
// empty means we know in advance that we won�t find any search results, which can happen if:
// 1. we don�t have a search pattern
// 2. we�re enumerating only the top directory and found no matches during the first call
// This flag allows us to return early for these cases. We can�t know this in advance for
// SearchOption.AllDirectories because we do a �*� search for subdirs and then use the
// searchPattern at each directory level.
bool empty;
private String userPath;
private SearchOption searchOption;
private String fullPath;
private String normalizedSearchPath;
private int oldMode;
private bool _checkHost;
[System.Security.SecuritySafeCritical]
internal FileSystemEnumerableIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
{
Contract.Requires(path != null);
Contract.Requires(originalUserPath != null);
Contract.Requires(searchPattern != null);
Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
Contract.Requires(resultHandler != null);
oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
searchStack = new List<Directory.SearchData>();
String normalizedSearchPattern = NormalizeSearchPattern(searchPattern);
if (normalizedSearchPattern.Length == 0)
{
empty = true;
}
else
{
_resultHandler = resultHandler;
this.searchOption = searchOption;
fullPath = Path.GetFullPathInternal(path);
String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern);
normalizedSearchPath = Path.GetDirectoryName(fullSearchString);
if (CodeAccessSecurityEngine.QuickCheckForAllDemands())
{
// Full trust, just need to validate incoming paths
// (we don't need to get the demand directory as it has no impact)
FileIOPermission.EmulateFileIOPermissionChecks(fullPath);
FileIOPermission.EmulateFileIOPermissionChecks(normalizedSearchPath);
}
else
{
// Not full trust, need to check for rights
string[] demandPaths = new string[2];
// Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
demandPaths[0] = Directory.GetDemandDir(fullPath, true);
// For filters like foo\*.cs we need to verify if the directory foo is not denied access.
// Do a demand on the combined path so that we can fail early in case of deny
demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false).Demand();
}
_checkHost = checkHost;
// normalize search criteria
searchCriteria = GetNormalizedSearchCriteria(fullSearchString, normalizedSearchPath);
// fix up user path
String searchPatternDirName = Path.GetDirectoryName(normalizedSearchPattern);
String userPathTemp = originalUserPath;
if (searchPatternDirName != null && searchPatternDirName.Length != 0)
{
userPathTemp = Path.CombineNoChecks(userPathTemp, searchPatternDirName);
}
this.userPath = userPathTemp;
searchData = new Directory.SearchData(normalizedSearchPath, this.userPath, searchOption);
CommonInit();
}
}
[System.Security.SecurityCritical]
private void CommonInit()
{
Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized");
// Execute searchCriteria against the current directory
String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
// Open a Find handle
_hnd = Win32Native.FindFirstFile(searchPath, ref data);
if (_hnd.IsInvalid)
{
int hr = Marshal.GetLastWin32Error();
if (hr != Win32Native.ERROR_FILE_NOT_FOUND && hr != Win32Native.ERROR_NO_MORE_FILES)
{
HandleError(hr, searchData.fullPath);
}
else
{
// flag this as empty only if we're searching just top directory
// Used in fast path for top directory only
empty = searchData.searchOption == SearchOption.TopDirectoryOnly;
}
}
// fast path for TopDirectoryOnly. If we have a result, go ahead and set it to
// current. If empty, dispose handle.
if (searchData.searchOption == SearchOption.TopDirectoryOnly)
{
if (empty)
{
_hnd.Dispose();
}
else
{
if (_resultHandler.IsResultIncluded(ref data))
{
current = _resultHandler.CreateObject(searchData, ref data);
}
}
}
// for AllDirectories, we first recurse into dirs, so cleanup and add searchData
// to the stack
else
{
_hnd.Dispose();
searchStack.Add(searchData);
}
}
[System.Security.SecuritySafeCritical]
private FileSystemEnumerableIterator(String fullPath, String normalizedSearchPath, String searchCriteria, String userPath, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
{
this.fullPath = fullPath;
this.normalizedSearchPath = normalizedSearchPath;
this.searchCriteria = searchCriteria;
this._resultHandler = resultHandler;
this.userPath = userPath;
this.searchOption = searchOption;
this._checkHost = checkHost;
searchStack = new List<Directory.SearchData>();
if (searchCriteria != null)
{
if (CodeAccessSecurityEngine.QuickCheckForAllDemands())
{
// Full trust, just need to validate incoming paths
// (we don't need to get the demand directory as it has no impact)
FileIOPermission.EmulateFileIOPermissionChecks(fullPath);
FileIOPermission.EmulateFileIOPermissionChecks(normalizedSearchPath);
}
else
{
// Not full trust, need to check for rights
string[] demandPaths = new string[2];
// Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
demandPaths[0] = Directory.GetDemandDir(fullPath, true);
// For filters like foo\*.cs we need to verify if the directory foo is not denied access.
// Do a demand on the combined path so that we can fail early in case of deny
demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false).Demand();
}
searchData = new Directory.SearchData(normalizedSearchPath, userPath, searchOption);
CommonInit();
}
else
{
empty = true;
}
}
protected override Iterator<TSource> Clone()
{
return new FileSystemEnumerableIterator<TSource>(fullPath, normalizedSearchPath, searchCriteria, userPath, searchOption, _resultHandler, _checkHost);
}
[System.Security.SecuritySafeCritical]
protected override void Dispose(bool disposing)
{
try
{
if (_hnd != null)
{
_hnd.Dispose();
}
}
finally
{
Win32Native.SetErrorMode(oldMode);
base.Dispose(disposing);
}
}
[System.Security.SecuritySafeCritical]
public override bool MoveNext()
{
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
switch (state)
{
case STATE_INIT:
{
if (empty)
{
state = STATE_FINISH;
goto case STATE_FINISH;
}
if (searchData.searchOption == SearchOption.TopDirectoryOnly)
{
state = STATE_FIND_NEXT_FILE;
if (current != null)
{
return true;
}
else
{
goto case STATE_FIND_NEXT_FILE;
}
}
else
{
state = STATE_SEARCH_NEXT_DIR;
goto case STATE_SEARCH_NEXT_DIR;
}
}
case STATE_SEARCH_NEXT_DIR:
{
Contract.Assert(searchData.searchOption != SearchOption.TopDirectoryOnly, "should not reach this code path if searchOption == TopDirectoryOnly");
// Traverse directory structure. We need to get '*'
while (searchStack.Count > 0)
{
searchData = searchStack[0];
Contract.Assert((searchData.fullPath != null), "fullpath can't be null!");
searchStack.RemoveAt(0);
// Traverse the subdirs
AddSearchableDirsToStack(searchData);
// Execute searchCriteria against the current directory
String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
// Open a Find handle
_hnd = Win32Native.FindFirstFile(searchPath, ref data);
if (_hnd.IsInvalid)
{
int hr = Marshal.GetLastWin32Error();
if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
continue;
_hnd.Dispose();
HandleError(hr, searchData.fullPath);
}
state = STATE_FIND_NEXT_FILE;
needsParentPathDiscoveryDemand = true;
if (_resultHandler.IsResultIncluded(ref data))
{
if (needsParentPathDiscoveryDemand)
{
DoDemand(searchData.fullPath);
needsParentPathDiscoveryDemand = false;
}
current = _resultHandler.CreateObject(searchData, ref data);
return true;
}
else
{
goto case STATE_FIND_NEXT_FILE;
}
}
state = STATE_FINISH;
goto case STATE_FINISH;
}
case STATE_FIND_NEXT_FILE:
{
if (searchData != null && _hnd != null)
{
// Keep asking for more matching files/dirs, add it to the list
while (Win32Native.FindNextFile(_hnd, ref data))
{
if (_resultHandler.IsResultIncluded(ref data))
{
if (needsParentPathDiscoveryDemand)
{
DoDemand(searchData.fullPath);
needsParentPathDiscoveryDemand = false;
}
current = _resultHandler.CreateObject(searchData, ref data);
return true;
}
}
// Make sure we quit with a sensible error.
int hr = Marshal.GetLastWin32Error();
if (_hnd != null)
_hnd.Dispose();
// ERROR_FILE_NOT_FOUND is valid here because if the top level
// dir doen't contain any subdirs and matching files then
// we will get here with this errorcode from the searchStack walk
if ((hr != 0) && (hr != Win32Native.ERROR_NO_MORE_FILES)
&& (hr != Win32Native.ERROR_FILE_NOT_FOUND))
{
HandleError(hr, searchData.fullPath);
}
}
if (searchData.searchOption == SearchOption.TopDirectoryOnly)
{
state = STATE_FINISH;
goto case STATE_FINISH;
}
else
{
state = STATE_SEARCH_NEXT_DIR;
goto case STATE_SEARCH_NEXT_DIR;
}
}
case STATE_FINISH:
{
Dispose();
break;
}
}
return false;
}
[System.Security.SecurityCritical]
private void HandleError(int hr, String path)
{
Dispose();
__Error.WinIOError(hr, path);
}
[System.Security.SecurityCritical] // auto-generated
private void AddSearchableDirsToStack(Directory.SearchData localSearchData)
{
Contract.Requires(localSearchData != null);
String searchPath = Path.InternalCombine(localSearchData.fullPath, "*");
SafeFindHandle hnd = null;
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
try
{
// Get all files and dirs
hnd = Win32Native.FindFirstFile(searchPath, ref data);
if (hnd.IsInvalid)
{
int hr = Marshal.GetLastWin32Error();
// This could happen if the dir doesn't contain any files.
// Continue with the recursive search though, eventually
// searchStack will become empty
if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
return;
HandleError(hr, localSearchData.fullPath);
}
// Add subdirs to searchStack. Exempt ReparsePoints as appropriate
int incr = 0;
do
{
if (data.IsNormalDirectory)
{
string fileName = data.cFileName;
string tempFullPath = Path.CombineNoChecks(localSearchData.fullPath, fileName);
string tempUserPath = Path.CombineNoChecks(localSearchData.userPath, fileName);
SearchOption option = localSearchData.searchOption;
#if EXCLUDE_REPARSEPOINTS
// Traverse reparse points depending on the searchoption specified
if ((searchDataSubDir.searchOption == SearchOption.AllDirectories) && (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_REPARSE_POINT)))
option = SearchOption.TopDirectoryOnly;
#endif
// Setup search data for the sub directory and push it into the stack
Directory.SearchData searchDataSubDir = new Directory.SearchData(tempFullPath, tempUserPath, option);
searchStack.Insert(incr++, searchDataSubDir);
}
} while (Win32Native.FindNextFile(hnd, ref data));
// We don't care about errors here
}
finally
{
if (hnd != null)
hnd.Dispose();
}
}
[System.Security.SecurityCritical]
internal void DoDemand(String fullPathToDemand)
{
#if FEATURE_CORECLR
if(_checkHost) {
String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandDir);
state.EnsureState();
}
#else
String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandDir, false, false);
#endif
}
private static String NormalizeSearchPattern(String searchPattern)
{
Contract.Requires(searchPattern != null);
// Win32 normalization trims only U+0020.
String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars);
// Make this corner case more useful, like dir
if (tempSearchPattern.Equals("."))
{
tempSearchPattern = "*";
}
Path.CheckSearchPattern(tempSearchPattern);
return tempSearchPattern;
}
private static String GetNormalizedSearchCriteria(String fullSearchString, String fullPathMod)
{
Contract.Requires(fullSearchString != null);
Contract.Requires(fullPathMod != null);
Contract.Requires(fullSearchString.Length >= fullPathMod.Length);
String searchCriteria = null;
char lastChar = fullPathMod[fullPathMod.Length - 1];
if (Path.IsDirectorySeparator(lastChar))
{
// Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\
searchCriteria = fullSearchString.Substring(fullPathMod.Length);
}
else
{
Contract.Assert(fullSearchString.Length > fullPathMod.Length);
searchCriteria = fullSearchString.Substring(fullPathMod.Length + 1);
}
return searchCriteria;
}
private static String GetFullSearchString(String fullPath, String searchPattern)
{
Contract.Requires(fullPath != null);
Contract.Requires(searchPattern != null);
String tempStr = Path.InternalCombine(fullPath, searchPattern);
// If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception
char lastChar = tempStr[tempStr.Length - 1];
if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar)
{
tempStr = tempStr + '*';
}
return tempStr;
}
}
internal abstract class SearchResultHandler<TSource>
{
[System.Security.SecurityCritical]
internal abstract bool IsResultIncluded(ref Win32Native.WIN32_FIND_DATA findData);
[System.Security.SecurityCritical]
internal abstract TSource CreateObject(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData);
}
internal class StringResultHandler : SearchResultHandler<string>
{
private bool _includeFiles;
private bool _includeDirs;
internal StringResultHandler(bool includeFiles, bool includeDirs)
{
_includeFiles = includeFiles;
_includeDirs = includeDirs;
}
[System.Security.SecurityCritical]
internal override bool IsResultIncluded(ref Win32Native.WIN32_FIND_DATA findData)
=> (_includeFiles && findData.IsFile) || (_includeDirs && findData.IsNormalDirectory);
[System.Security.SecurityCritical]
internal override string CreateObject(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
=> Path.CombineNoChecks(searchData.userPath, findData.cFileName);
}
internal class FileInfoResultHandler : SearchResultHandler<FileInfo>
{
[System.Security.SecurityCritical]
internal override bool IsResultIncluded(ref Win32Native.WIN32_FIND_DATA findData) => findData.IsFile;
[System.Security.SecurityCritical]
internal override FileInfo CreateObject(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
{
return CreateFileInfo(searchData, ref findData);
}
[System.Security.SecurityCritical]
internal static FileInfo CreateFileInfo(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
{
string fileName = findData.cFileName;
string fullPath = Path.CombineNoChecks(searchData.fullPath, fileName);
if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
{
// There is no need to emulate checks that FileIOPermission does if we aren't in full trust.
// The paths we're getting are already tested and/or coming straight from the OS.
new FileIOPermission(FileIOPermissionAccess.Read, new string[] { fullPath }, false, false).Demand();
}
FileInfo fi = new FileInfo(fullPath, fileName);
fi.InitializeFrom(ref findData);
return fi;
}
}
internal class DirectoryInfoResultHandler : SearchResultHandler<DirectoryInfo>
{
[System.Security.SecurityCritical]
internal override bool IsResultIncluded(ref Win32Native.WIN32_FIND_DATA findData) => findData.IsNormalDirectory;
[System.Security.SecurityCritical]
internal override DirectoryInfo CreateObject(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
{
return CreateDirectoryInfo(searchData, ref findData);
}
[System.Security.SecurityCritical]
internal static DirectoryInfo CreateDirectoryInfo(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
{
string fileName = findData.cFileName;
string fullPath = Path.CombineNoChecks(searchData.fullPath, fileName);
if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
{
// There is no need to emulate checks that FileIOPermission does if we aren't in full trust.
// The paths we're getting are already tested and/or coming straight from the OS.
new FileIOPermission(FileIOPermissionAccess.Read, new string[] { fullPath + "\\." }, false, false).Demand();
}
DirectoryInfo di = new DirectoryInfo(fullPath, fileName);
di.InitializeFrom(ref findData);
return di;
}
}
internal class FileSystemInfoResultHandler : SearchResultHandler<FileSystemInfo>
{
[System.Security.SecurityCritical]
internal override bool IsResultIncluded(ref Win32Native.WIN32_FIND_DATA findData) => findData.IsFile || findData.IsNormalDirectory;
[System.Security.SecurityCritical]
internal override FileSystemInfo CreateObject(Directory.SearchData searchData, ref Win32Native.WIN32_FIND_DATA findData)
{
return findData.IsFile
? (FileSystemInfo)FileInfoResultHandler.CreateFileInfo(searchData, ref findData)
: (FileSystemInfo)DirectoryInfoResultHandler.CreateDirectoryInfo(searchData, ref findData);
}
}
}
|