using System; using System.Collections.Generic; using System.IO; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Text.RegularExpressions; using Microsoft.Win32.SafeHandles; using Wox.UpdateFeedGenerator.Win32; namespace Wox.UpdateFeedGenerator { namespace Win32 { /// /// Structure that maps to WIN32_FIND_DATA /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal sealed class FindData { public int fileAttributes; public int creationTime_lowDateTime; public int creationTime_highDateTime; public int lastAccessTime_lowDateTime; public int lastAccessTime_highDateTime; public int lastWriteTime_lowDateTime; public int lastWriteTime_highDateTime; public int nFileSizeHigh; public int nFileSizeLow; public int dwReserved0; public int dwReserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public String fileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public String alternateFileName; } /// /// SafeHandle class for holding find handles /// internal sealed class SafeFindHandle : SafeHandleMinusOneIsInvalid { /// /// Constructor /// public SafeFindHandle() : base(true) {} /// /// Release the find handle /// /// true if the handle was released [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] protected override bool ReleaseHandle() { return SafeNativeMethods.FindClose(handle); } } /// /// Wrapper for P/Invoke methods used by FileSystemEnumerator /// [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] internal static class SafeNativeMethods { [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] public static extern SafeFindHandle FindFirstFile(String fileName, [In, Out] FindData findFileData); [DllImport("kernel32", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData); [DllImport("kernel32", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FindClose(IntPtr hFindFile); } } /// /// File system enumerator. This class provides an easy to use, efficient mechanism for searching a list of /// directories for files matching a list of file specifications. The search is done incrementally as matches /// are consumed, so the overhead before processing the first match is always kept to a minimum. /// public sealed class FileSystemEnumerator : IDisposable { /// /// Information that's kept in our stack for simulated recursion /// private struct SearchInfo { /// /// Find handle returned by FindFirstFile /// public readonly SafeFindHandle Handle; /// /// Path that was searched to yield the find handle. /// public readonly string Path; /// /// Constructor /// /// Find handle returned by FindFirstFile. /// Path corresponding to find handle. public SearchInfo(SafeFindHandle h, string p) { Handle = h; Path = p; } } /// /// Stack of open scopes. This is a member (instead of a local variable) /// to allow Dispose to close any open find handles if the object is disposed /// before the enumeration is completed. /// private readonly Stack m_scopes; /// /// Array of paths to be searched. /// private readonly string[] m_paths; /// /// Array of regular expressions that will detect matching files. /// private readonly List m_fileSpecs; /// /// If true, sub-directories are searched. /// private readonly bool m_includeSubDirs; #region IDisposable implementation /// /// IDisposable.Dispose /// public void Dispose() { while (m_scopes.Count > 0) { SearchInfo si = m_scopes.Pop(); si.Handle.Close(); } } #endregion /// /// Constructor. /// /// Semicolon- or comma-delimitted list of paths to search. /// Semicolon- or comma-delimitted list of wildcard filespecs to match. /// If true, subdirectories are searched. public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool includeSubDirs) { m_scopes = new Stack(); // check for nulls if (null == pathsToSearch) throw new ArgumentNullException("pathsToSearch"); if (null == fileTypesToMatch) throw new ArgumentNullException("fileTypesToMatch"); // make sure spec doesn't contain invalid characters if (fileTypesToMatch.IndexOfAny(new[] { ':', '<', '>', '/', '\\' }) >= 0) throw new ArgumentException("invalid cahracters in wildcard pattern", "fileTypesToMatch"); m_includeSubDirs = includeSubDirs; m_paths = pathsToSearch.Split(new[] { ';', ',' }); string[] specs = fileTypesToMatch.Split(new[] { ';', ',' }); m_fileSpecs = new List(specs.Length); foreach (string spec in specs) { // trim whitespace off file spec and convert Win32 wildcards to regular expressions string pattern = spec.Trim().Replace(".", @"\.").Replace("*", @".*").Replace("?", @".?"); m_fileSpecs.Add(new Regex("^" + pattern + "$", RegexOptions.IgnoreCase)); } } /// /// Get an enumerator that returns all of the files that match the wildcards that /// are in any of the directories to be searched. /// /// An IEnumerable that returns all matching files one by one. /// /// The enumerator that is returned finds files using a lazy algorithm that /// searches directories incrementally as matches are consumed. /// public IEnumerable Matches() { foreach (string rootPath in m_paths) { string path = rootPath.Trim(); // we "recurse" into a new directory by jumping to this spot top: // check security - ensure that caller has rights to read this directory new FileIOPermission(FileIOPermissionAccess.PathDiscovery, Path.Combine(path, ".")).Demand(); // now that security is checked, go read the directory FindData findData = new FindData(); SafeFindHandle handle = SafeNativeMethods.FindFirstFile(Path.Combine(path, "*"), findData); m_scopes.Push(new SearchInfo(handle, path)); bool restart = false; // we "return" from a sub-directory by jumping to this spot restart: // ReSharper disable InvertIf if (!handle.IsInvalid) { // ReSharper restore InvertIf do { // if we restarted the loop (unwound a recursion), fetch the next match if (restart) { restart = false; continue; } // don't match . or .. if (findData.fileName.Equals(@".") || findData.fileName.Equals(@"..")) continue; if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0) { if (m_includeSubDirs) { // it's a directory - recurse into it path = Path.Combine(path, findData.fileName); goto top; } } else { // it's a file, see if any of the filespecs matches it foreach (Regex fileSpec in m_fileSpecs) { // if this spec matches, return this file's info if (fileSpec.IsMatch(findData.fileName)) yield return new FileInfo(Path.Combine(path, findData.fileName)); } } } while (SafeNativeMethods.FindNextFile(handle, findData)); // close this find handle handle.Close(); // unwind the stack - are we still in a recursion? m_scopes.Pop(); if (m_scopes.Count > 0) { SearchInfo si = m_scopes.Peek(); handle = si.Handle; path = si.Path; restart = true; goto restart; } } } } } }