[corlib] Use Directory.GetFiles/GetDirectories implementation from referencesource...
[mono.git] / mcs / class / referencesource / mscorlib / system / io / filesystemenumerable.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 /*============================================================
7 **
8 ** Class:  FileSystemEnumerable
9 ** 
10 ** <OWNER>[....]</OWNER>
11 **
12 **
13 ** Purpose: Enumerates files and dirs
14 **
15 ===========================================================*/
16
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Security;
21 using System.Security.Permissions;
22 using Microsoft.Win32;
23 using Microsoft.Win32.SafeHandles;
24 using System.Text;
25 using System.Runtime.InteropServices;
26 using System.Globalization;
27 using System.Runtime.Versioning;
28 using System.Diagnostics.Contracts;
29 using System.Threading;
30
31 namespace System.IO
32 {
33
34     // Overview:
35     // The key methods instantiate FileSystemEnumerableIterators. These compose the iterator with search result
36     // handlers that instantiate the FileInfo, DirectoryInfo, String, etc. The handlers then perform any
37     // additional required permission demands. 
38     internal static class FileSystemEnumerableFactory
39     {
40         internal static IEnumerable<String> CreateFileNameIterator(String path, String originalUserPath, String searchPattern,
41                                                                     bool includeFiles, bool includeDirs, SearchOption searchOption, bool checkHost)
42         {
43             Contract.Requires(path != null);
44             Contract.Requires(originalUserPath != null);
45             Contract.Requires(searchPattern != null);
46
47             SearchResultHandler<String> handler = new StringResultHandler(includeFiles, includeDirs);
48             return new FileSystemEnumerableIterator<String>(path, originalUserPath, searchPattern, searchOption, handler, checkHost);
49         }
50
51         internal static IEnumerable<FileInfo> CreateFileInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
52         {
53             Contract.Requires(path != null);
54             Contract.Requires(originalUserPath != null);
55             Contract.Requires(searchPattern != null);
56
57             SearchResultHandler<FileInfo> handler = new FileInfoResultHandler();
58             return new FileSystemEnumerableIterator<FileInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
59         }
60
61         internal static IEnumerable<DirectoryInfo> CreateDirectoryInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
62         {
63
64             Contract.Requires(path != null);
65             Contract.Requires(originalUserPath != null);
66             Contract.Requires(searchPattern != null);
67
68             SearchResultHandler<DirectoryInfo> handler = new DirectoryInfoResultHandler();
69             return new FileSystemEnumerableIterator<DirectoryInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
70         }
71
72         internal static IEnumerable<FileSystemInfo> CreateFileSystemInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption)
73         {
74             Contract.Requires(path != null);
75             Contract.Requires(originalUserPath != null);
76             Contract.Requires(searchPattern != null);
77
78             SearchResultHandler<FileSystemInfo> handler = new FileSystemInfoResultHandler();
79             return new FileSystemEnumerableIterator<FileSystemInfo>(path, originalUserPath, searchPattern, searchOption, handler, true);
80         }
81     }
82
83     // Abstract Iterator, borrowed from Linq. Used in anticipation of need for similar enumerables
84     // in the future
85     abstract internal class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource>
86     {
87         int threadId;
88         internal int state;
89         internal TSource current;
90
91         public Iterator()
92         {
93             threadId = Thread.CurrentThread.ManagedThreadId;
94         }
95
96         public TSource Current
97         {
98             get { return current; }
99         }
100
101         protected abstract Iterator<TSource> Clone();
102
103         public void Dispose()
104         {
105             Dispose(true);
106             GC.SuppressFinalize(this);
107         }
108
109         protected virtual void Dispose(bool disposing)
110         {
111             current = default(TSource);
112             state = -1;
113         }
114
115         public IEnumerator<TSource> GetEnumerator()
116         {
117             if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0)
118             {
119                 state = 1;
120                 return this;
121             }
122
123             Iterator<TSource> duplicate = Clone();
124             duplicate.state = 1;
125             return duplicate;
126         }
127
128         public abstract bool MoveNext();
129
130         object IEnumerator.Current
131         {
132             get { return Current; }
133         }
134
135         IEnumerator IEnumerable.GetEnumerator()
136         {
137             return GetEnumerator();
138         }
139
140         void IEnumerator.Reset()
141         {
142             throw new NotSupportedException();
143         }
144
145     }
146
147     // Overview:
148     // Enumerates file system entries matching the search parameters. For recursive searches this
149     // searches through all the sub dirs and executes the search criteria against every dir.
150     // 
151     // Generic implementation:
152     // FileSystemEnumerableIterator is generic. When it gets a WIN32_FIND_DATA, it calls the 
153     // result handler to create an instance of the generic type. 
154     // 
155     // Usage:
156     // Use FileSystemEnumerableFactory to obtain FSEnumerables that can enumerate file system 
157     // entries as String path names, FileInfos, DirectoryInfos, or FileSystemInfos.
158     // 
159     // Security:
160     // For all the dirs/files returned, demands path discovery permission for their parent folders
161     internal class FileSystemEnumerableIterator<TSource> : Iterator<TSource>
162     {
163
164         private const int STATE_INIT = 1;
165         private const int STATE_SEARCH_NEXT_DIR = 2;
166         private const int STATE_FIND_NEXT_FILE = 3;
167         private const int STATE_FINISH = 4;
168
169         private SearchResultHandler<TSource> _resultHandler;
170         private List<Directory.SearchData> searchStack;
171         private Directory.SearchData searchData;
172         private String searchCriteria;
173         [System.Security.SecurityCritical]
174         SafeFindHandle _hnd = null;
175         bool needsParentPathDiscoveryDemand;
176
177         // empty means we know in advance that we won't find any search results, which can happen if:
178         // 1. we don't have a search pattern
179         // 2. we're enumerating only the top directory and found no matches during the first call
180         // This flag allows us to return early for these cases. We can't know this in advance for
181         // SearchOption.AllDirectories because we do a "*" search for subdirs and then use the
182         // searchPattern at each directory level.
183         bool empty;
184
185         private String userPath;
186         private SearchOption searchOption;
187         private String fullPath;
188         private String normalizedSearchPath;
189         private int oldMode;
190         private bool _checkHost;
191
192         [System.Security.SecuritySafeCritical]
193         internal FileSystemEnumerableIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
194         {
195             Contract.Requires(path != null);
196             Contract.Requires(originalUserPath != null);
197             Contract.Requires(searchPattern != null);
198             Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
199             Contract.Requires(resultHandler != null);
200
201 #if !MONO  // TODO: check if we need this on Windows
202             oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
203 #endif
204
205             searchStack = new List<Directory.SearchData>();
206
207             String normalizedSearchPattern = NormalizeSearchPattern(searchPattern);
208
209             if (normalizedSearchPattern.Length == 0)
210             {
211                 empty = true;
212             }
213             else
214             {
215                 _resultHandler = resultHandler;
216                 this.searchOption = searchOption;
217
218                 fullPath = Path.GetFullPathInternal(path);
219                 String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern);
220                 normalizedSearchPath = Path.GetDirectoryName(fullSearchString);
221
222                 // permission demands
223                 String[] demandPaths = new String[2];
224 #if MONO
225                 // TODO: we don't call FileIOPermission.HasIllegalCharacters on Mono since CAS is disabled
226 #else
227                 // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
228 #endif
229                 demandPaths[0] = Directory.GetDemandDir(fullPath, true);
230                 // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
231                 // Do a demand on the combined path so that we can fail early in case of deny
232                 demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
233                 _checkHost = checkHost;
234 #if FEATURE_MONO_CAS
235 #if FEATURE_CORECLR
236                 if (checkHost)
237                 {
238                     FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]);
239                     state1.EnsureState();
240                     FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]);
241                     state2.EnsureState();
242                 }
243 #else
244                 FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false);
245 #endif
246 #endif
247
248                 // normalize search criteria
249                 searchCriteria = GetNormalizedSearchCriteria(fullSearchString, normalizedSearchPath);
250
251                 // fix up user path
252                 String searchPatternDirName = Path.GetDirectoryName(normalizedSearchPattern);
253                 String userPathTemp = originalUserPath;
254                 if (searchPatternDirName != null && searchPatternDirName.Length != 0)
255                 {
256                     userPathTemp = Path.Combine(userPathTemp, searchPatternDirName);
257                 }
258                 this.userPath = userPathTemp;
259
260                 searchData = new Directory.SearchData(normalizedSearchPath, this.userPath, searchOption);
261
262                 CommonInit();
263             }
264
265         }
266
267         [System.Security.SecurityCritical]
268         private void CommonInit()
269         {
270             Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized");
271
272             // Execute searchCriteria against the current directory
273             String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
274
275             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
276
277             // Open a Find handle
278 #if MONO
279             int error;
280             _hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
281 #else
282             _hnd = Win32Native.FindFirstFile(searchPath, data);
283 #endif
284
285             if (_hnd.IsInvalid)
286             {
287 #if MONO
288                 int hr = error;
289 #else
290                 int hr = Marshal.GetLastWin32Error();
291 #endif
292                 if (hr != Win32Native.ERROR_FILE_NOT_FOUND && hr != Win32Native.ERROR_NO_MORE_FILES)
293                 {
294                     HandleError(hr, searchData.fullPath);
295                 }
296                 else
297                 {
298                     // flag this as empty only if we're searching just top directory
299                     // Used in fast path for top directory only
300                     empty = searchData.searchOption == SearchOption.TopDirectoryOnly;
301                 }
302             }
303             // fast path for TopDirectoryOnly. If we have a result, go ahead and set it to 
304             // current. If empty, dispose handle.
305             if (searchData.searchOption == SearchOption.TopDirectoryOnly)
306             {
307                 if (empty)
308                 {
309                     _hnd.Dispose();
310                 }
311                 else
312                 {
313                     SearchResult searchResult = CreateSearchResult(searchData, data);
314                     if (_resultHandler.IsResultIncluded(searchResult))
315                     {
316                         current = _resultHandler.CreateObject(searchResult);
317                     }
318                 }
319             }
320             // for AllDirectories, we first recurse into dirs, so cleanup and add searchData 
321             // to the stack
322             else
323             {
324                 _hnd.Dispose();
325                 searchStack.Add(searchData);
326             }
327         }
328
329         [System.Security.SecuritySafeCritical]
330         private FileSystemEnumerableIterator(String fullPath, String normalizedSearchPath, String searchCriteria, String userPath, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
331         {
332             this.fullPath = fullPath;
333             this.normalizedSearchPath = normalizedSearchPath;
334             this.searchCriteria = searchCriteria;
335             this._resultHandler = resultHandler;
336             this.userPath = userPath;
337             this.searchOption = searchOption;
338             this._checkHost = checkHost;
339
340             searchStack = new List<Directory.SearchData>();
341
342             if (searchCriteria != null)
343             {
344                 // permission demands
345                 String[] demandPaths = new String[2];
346                 // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
347                 demandPaths[0] = Directory.GetDemandDir(fullPath, true);
348                 // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
349                 // Do a demand on the combined path so that we can fail early in case of deny
350                 demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
351 #if FEATURE_MONO_CAS
352 #if FEATURE_CORECLR
353                 if (checkHost) 
354                 {
355                     FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]);
356                     state1.EnsureState();
357                     FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]);
358                     state2.EnsureState();
359                 }
360 #else
361                 FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false);
362 #endif
363 #endif
364                 searchData = new Directory.SearchData(normalizedSearchPath, userPath, searchOption);
365                 CommonInit();
366             }
367             else
368             {
369                 empty = true;
370             }
371         }
372
373         protected override Iterator<TSource> Clone()
374         {
375             return new FileSystemEnumerableIterator<TSource>(fullPath, normalizedSearchPath, searchCriteria, userPath, searchOption, _resultHandler, _checkHost);
376         }
377
378         [System.Security.SecuritySafeCritical]
379         protected override void Dispose(bool disposing)
380         {
381             try
382             {
383                 if (_hnd != null)
384                 {
385                     _hnd.Dispose();
386                 }
387             }
388             finally
389             {
390 #if !MONO  // TODO: check if we need this on Windows
391                 Win32Native.SetErrorMode(oldMode);
392 #endif
393                 base.Dispose(disposing);
394             }
395         }
396
397         [System.Security.SecuritySafeCritical]
398         public override bool MoveNext()
399         {
400             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
401             switch (state)
402             {
403                 case STATE_INIT:
404                     {
405                         if (empty)
406                         {
407                             state = STATE_FINISH;
408                             goto case STATE_FINISH;
409                         }
410                         if (searchData.searchOption == SearchOption.TopDirectoryOnly)
411                         {
412                             state = STATE_FIND_NEXT_FILE;
413                             if (current != null)
414                             {
415                                 return true;
416                             }
417                             else
418                             {
419                                 goto case STATE_FIND_NEXT_FILE;
420                             }
421                         }
422                         else
423                         {
424                             state = STATE_SEARCH_NEXT_DIR;
425                             goto case STATE_SEARCH_NEXT_DIR;
426                         }
427                     }
428                 case STATE_SEARCH_NEXT_DIR:
429                     {
430                         Contract.Assert(searchData.searchOption != SearchOption.TopDirectoryOnly, "should not reach this code path if searchOption == TopDirectoryOnly");
431                         // Traverse directory structure. We need to get '*'
432                         while (searchStack.Count > 0)
433                         {
434                             searchData = searchStack[0];
435                             Contract.Assert((searchData.fullPath != null), "fullpath can't be null!");
436                             searchStack.RemoveAt(0);
437
438                             // Traverse the subdirs
439                             AddSearchableDirsToStack(searchData);
440
441                             // Execute searchCriteria against the current directory
442                             String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
443
444                             // Open a Find handle
445 #if MONO
446                             int error;
447                             _hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
448 #else
449                             _hnd = Win32Native.FindFirstFile(searchPath, data);
450 #endif
451                             if (_hnd.IsInvalid)
452                             {
453 #if MONO
454                                 int hr = error;
455 #else
456                                 int hr = Marshal.GetLastWin32Error();
457 #endif
458                                 if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
459                                     continue;
460
461                                 _hnd.Dispose();
462                                 HandleError(hr, searchData.fullPath);
463                             }
464
465                             state = STATE_FIND_NEXT_FILE;
466                             needsParentPathDiscoveryDemand = true;
467                             SearchResult searchResult = CreateSearchResult(searchData, data);
468                             if (_resultHandler.IsResultIncluded(searchResult))
469                             {
470                                 if (needsParentPathDiscoveryDemand)
471                                 {
472                                     DoDemand(searchData.fullPath);
473                                     needsParentPathDiscoveryDemand = false;
474                                 }
475                                 current = _resultHandler.CreateObject(searchResult);
476                                 return true;
477                             }
478                             else
479                             {
480                                 goto case STATE_FIND_NEXT_FILE;
481                             }
482                         }
483                         state = STATE_FINISH;
484                         goto case STATE_FINISH;
485                     }
486                 case STATE_FIND_NEXT_FILE:
487                     {
488                         if (searchData != null && _hnd != null)
489                         {
490                             // Keep asking for more matching files/dirs, add it to the list
491 #if MONO
492                             int error;
493                             while (MonoIO.FindNextFile (_hnd.DangerousGetHandle(), out data.cFileName, out data.dwFileAttributes, out error))
494 #else
495                             while (Win32Native.FindNextFile(_hnd, data))
496 #endif
497                             {
498                                 SearchResult searchResult = CreateSearchResult(searchData, data);
499                                 if (_resultHandler.IsResultIncluded(searchResult))
500                                 {
501                                     if (needsParentPathDiscoveryDemand)
502                                     {
503                                         DoDemand(searchData.fullPath);
504                                         needsParentPathDiscoveryDemand = false;
505                                     }
506                                     current = _resultHandler.CreateObject(searchResult);
507                                     return true;
508                                 }
509                             }
510
511 #if MONO
512                             int hr = error;
513 #else
514                             // Make sure we quit with a sensible error.
515                             int hr = Marshal.GetLastWin32Error();
516 #endif
517
518                             if (_hnd != null)
519                                 _hnd.Dispose();
520
521                             // ERROR_FILE_NOT_FOUND is valid here because if the top level
522                             // dir doen't contain any subdirs and matching files then 
523                             // we will get here with this errorcode from the searchStack walk
524                             if ((hr != 0) && (hr != Win32Native.ERROR_NO_MORE_FILES)
525                                 && (hr != Win32Native.ERROR_FILE_NOT_FOUND))
526                             {
527                                 HandleError(hr, searchData.fullPath);
528                             }
529                         }
530                         if (searchData.searchOption == SearchOption.TopDirectoryOnly)
531                         {
532                             state = STATE_FINISH;
533                             goto case STATE_FINISH;
534                         }
535                         else
536                         {
537                             state = STATE_SEARCH_NEXT_DIR;
538                             goto case STATE_SEARCH_NEXT_DIR;
539                         }
540                     }
541                 case STATE_FINISH:
542                     {
543                         Dispose();
544                         break;
545                     }
546             }
547             return false;
548         }
549
550         [System.Security.SecurityCritical]
551         private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Win32Native.WIN32_FIND_DATA findData)
552         {
553             String userPathFinal = Path.InternalCombine(localSearchData.userPath, findData.cFileName);
554             String fullPathFinal = Path.InternalCombine(localSearchData.fullPath, findData.cFileName);
555             return new SearchResult(fullPathFinal, userPathFinal, findData);
556         }
557
558         [System.Security.SecurityCritical]
559         private void HandleError(int hr, String path)
560         {
561             Dispose();
562             __Error.WinIOError(hr, path);
563         }
564
565         [System.Security.SecurityCritical]  // auto-generated
566         private void AddSearchableDirsToStack(Directory.SearchData localSearchData)
567         {
568             Contract.Requires(localSearchData != null);
569
570             String searchPath = Path.InternalCombine(localSearchData.fullPath, "*");
571             SafeFindHandle hnd = null;
572             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
573             try
574             {
575                 // Get all files and dirs
576 #if MONO
577                 int error;
578                 hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
579 #else
580                 hnd = Win32Native.FindFirstFile(searchPath, data);
581 #endif
582
583                 if (hnd.IsInvalid)
584                 {
585 #if MONO
586                     int hr = error;
587 #else
588                     int hr = Marshal.GetLastWin32Error();
589 #endif
590
591                     // This could happen if the dir doesn't contain any files.
592                     // Continue with the recursive search though, eventually
593                     // searchStack will become empty
594                     if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
595                         return;
596
597                     HandleError(hr, localSearchData.fullPath);
598                 }
599
600                 // Add subdirs to searchStack. Exempt ReparsePoints as appropriate
601                 int incr = 0;
602                 do
603                 {
604                     if (FileSystemEnumerableHelpers.IsDir(data))
605                     {
606                         String tempFullPath = Path.InternalCombine(localSearchData.fullPath, data.cFileName);
607                         String tempUserPath = Path.InternalCombine(localSearchData.userPath, data.cFileName);
608
609                         SearchOption option = localSearchData.searchOption;
610
611 #if EXCLUDE_REPARSEPOINTS
612                         // Traverse reparse points depending on the searchoption specified
613                         if ((searchDataSubDir.searchOption == SearchOption.AllDirectories) && (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_REPARSE_POINT)))
614                             option = SearchOption.TopDirectoryOnly; 
615 #endif
616                         // Setup search data for the sub directory and push it into the stack
617                         Directory.SearchData searchDataSubDir = new Directory.SearchData(tempFullPath, tempUserPath, option);
618
619                         searchStack.Insert(incr++, searchDataSubDir);
620                     }
621 #if MONO
622                 } while (MonoIO.FindNextFile (hnd.DangerousGetHandle(), out data.cFileName, out data.dwFileAttributes, out error));
623 #else
624                 } while (Win32Native.FindNextFile(hnd, data));
625 #endif
626                 // We don't care about errors here
627             }
628             finally
629             {
630                 if (hnd != null)
631                     hnd.Dispose();
632             }
633         }
634
635         [System.Security.SecurityCritical]
636         internal void DoDemand(String fullPathToDemand)
637         {
638 #if FEATURE_MONO_CAS
639 #if FEATURE_CORECLR
640             if(_checkHost) {
641                 String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
642                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandDir);
643                 state.EnsureState();
644             }
645 #else
646             String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
647             FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandDir, false, false);
648 #endif
649 #endif
650         }
651
652         private static String NormalizeSearchPattern(String searchPattern)
653         {
654             Contract.Requires(searchPattern != null);
655
656             // Win32 normalization trims only U+0020. 
657             String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars);
658
659             // Make this corner case more useful, like dir
660             if (tempSearchPattern.Equals("."))
661             {
662                 tempSearchPattern = "*";
663             }
664
665             Path.CheckSearchPattern(tempSearchPattern);
666             return tempSearchPattern;
667         }
668
669         private static String GetNormalizedSearchCriteria(String fullSearchString, String fullPathMod)
670         {
671             Contract.Requires(fullSearchString != null);
672             Contract.Requires(fullPathMod != null);
673             Contract.Requires(fullSearchString.Length >= fullPathMod.Length);
674
675             String searchCriteria = null;
676             char lastChar = fullPathMod[fullPathMod.Length - 1];
677             if (Path.IsDirectorySeparator(lastChar))
678             {
679                 // Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\
680                 searchCriteria = fullSearchString.Substring(fullPathMod.Length);
681             }
682             else
683             {
684                 Contract.Assert(fullSearchString.Length > fullPathMod.Length);
685                 searchCriteria = fullSearchString.Substring(fullPathMod.Length + 1);
686             }
687             return searchCriteria;
688         }
689
690         private static String GetFullSearchString(String fullPath, String searchPattern)
691         {
692             Contract.Requires(fullPath != null);
693             Contract.Requires(searchPattern != null);
694
695             String tempStr = Path.InternalCombine(fullPath, searchPattern);
696
697             // If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception
698             char lastChar = tempStr[tempStr.Length - 1];
699             if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar)
700             {
701                 tempStr = tempStr + '*';
702             }
703
704             return tempStr;
705         }
706     }
707
708     internal abstract class SearchResultHandler<TSource>
709     {
710
711         [System.Security.SecurityCritical]
712         internal abstract bool IsResultIncluded(SearchResult result);
713
714         [System.Security.SecurityCritical]
715         internal abstract TSource CreateObject(SearchResult result);
716
717     }
718
719     internal class StringResultHandler : SearchResultHandler<String>
720     {
721         private bool _includeFiles;
722         private bool _includeDirs;
723
724         internal StringResultHandler(bool includeFiles, bool includeDirs)
725         {
726             _includeFiles = includeFiles;
727             _includeDirs = includeDirs;
728         }
729
730         [System.Security.SecurityCritical]
731         internal override bool IsResultIncluded(SearchResult result)
732         {
733             bool includeFile = _includeFiles && FileSystemEnumerableHelpers.IsFile(result.FindData);
734             bool includeDir = _includeDirs && FileSystemEnumerableHelpers.IsDir(result.FindData);
735             Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!");
736             return (includeFile || includeDir);
737         }
738
739         [System.Security.SecurityCritical]
740         internal override String CreateObject(SearchResult result)
741         {
742             return result.UserPath;
743         }
744     }
745
746     internal class FileInfoResultHandler : SearchResultHandler<FileInfo>
747     {
748         [System.Security.SecurityCritical]
749         internal override bool IsResultIncluded(SearchResult result)
750         {
751             return FileSystemEnumerableHelpers.IsFile(result.FindData);
752         }
753
754         [System.Security.SecurityCritical]
755         internal override FileInfo CreateObject(SearchResult result)
756         {
757             String name = result.FullPath;
758 #if FEATURE_MONO_CAS
759 #if FEATURE_CORECLR
760             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name);
761             state.EnsureState();
762 #else
763             FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, name, false, false);
764 #endif
765 #endif
766             FileInfo fi = new FileInfo(name, false);
767             fi.InitializeFrom(result.FindData);
768             return fi;
769         }
770     }
771
772     internal class DirectoryInfoResultHandler : SearchResultHandler<DirectoryInfo>
773     {
774         [System.Security.SecurityCritical]
775         internal override bool IsResultIncluded(SearchResult result)
776         {
777             return FileSystemEnumerableHelpers.IsDir(result.FindData);
778         }
779
780         [System.Security.SecurityCritical]
781         internal override DirectoryInfo CreateObject(SearchResult result)
782         {
783             String name = result.FullPath;
784             String permissionName = name + "\\.";
785
786 #if FEATURE_MONO_CAS
787 #if FEATURE_CORECLR
788             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName);
789             state.EnsureState();
790 #else
791             FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, permissionName, false, false);
792 #endif
793 #endif
794             DirectoryInfo di = new DirectoryInfo(name, false);
795             di.InitializeFrom(result.FindData);
796             return di;
797         }
798     }
799
800     internal class FileSystemInfoResultHandler : SearchResultHandler<FileSystemInfo>
801     {
802
803         [System.Security.SecurityCritical]
804         internal override bool IsResultIncluded(SearchResult result)
805         {
806             bool includeFile = FileSystemEnumerableHelpers.IsFile(result.FindData);
807             bool includeDir = FileSystemEnumerableHelpers.IsDir(result.FindData);
808             Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!");
809
810             return (includeDir || includeFile);
811         }
812
813         [System.Security.SecurityCritical]
814         internal override FileSystemInfo CreateObject(SearchResult result)
815         {
816             bool isFile = FileSystemEnumerableHelpers.IsFile(result.FindData);
817             bool isDir = FileSystemEnumerableHelpers.IsDir(result.FindData);
818
819             if (isDir)
820             {
821                 String name = result.FullPath;
822                 String permissionName = name + "\\.";
823
824 #if FEATURE_MONO_CAS
825 #if FEATURE_CORECLR
826                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName);
827                 state.EnsureState();
828 #else
829                 FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, permissionName, false, false);
830 #endif
831 #endif
832                 DirectoryInfo di = new DirectoryInfo(name, false);
833                 di.InitializeFrom(result.FindData);
834                 return di;
835             }
836             else
837             {
838                 Contract.Assert(isFile);
839                 String name = result.FullPath;
840
841 #if FEATURE_MONO_CAS
842 #if FEATURE_CORECLR
843                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name);
844                 state.EnsureState();
845 #else
846                 FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, name, false, false);
847 #endif
848 #endif
849                 FileInfo fi = new FileInfo(name, false);
850                 fi.InitializeFrom(result.FindData);
851                 return fi;
852             }
853         }
854
855     }
856
857     internal sealed class SearchResult
858     {
859         private String fullPath;     // fully-qualifed path
860         private String userPath;     // user-specified path
861         [System.Security.SecurityCritical]
862         private Win32Native.WIN32_FIND_DATA findData;
863
864         [System.Security.SecurityCritical]
865         internal SearchResult(String fullPath, String userPath, Win32Native.WIN32_FIND_DATA findData)
866         {
867             Contract.Requires(fullPath != null);
868             Contract.Requires(userPath != null);
869
870             this.fullPath = fullPath;
871             this.userPath = userPath;
872             this.findData = findData;
873         }
874
875         internal String FullPath
876         {
877             get { return fullPath; }
878         }
879
880         internal String UserPath
881         {
882             get { return userPath; }
883         }
884
885         internal Win32Native.WIN32_FIND_DATA FindData
886         {
887             [System.Security.SecurityCritical]
888             get { return findData; }
889         }
890
891     }
892
893     internal static class FileSystemEnumerableHelpers
894     {
895         [System.Security.SecurityCritical]  // auto-generated
896         internal static bool IsDir(Win32Native.WIN32_FIND_DATA data)
897         {
898             // Don't add "." nor ".."
899 #if MONO
900             return (0 != (data.dwFileAttributes & (int)Win32Native.FILE_ATTRIBUTE_DIRECTORY))
901 #else
902             return (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY))
903 #endif
904                                                 && !data.cFileName.Equals(".") && !data.cFileName.Equals("..");
905         }
906
907         [System.Security.SecurityCritical]  // auto-generated
908         internal static bool IsFile(Win32Native.WIN32_FIND_DATA data)
909         {
910 #if MONO
911             return 0 == (data.dwFileAttributes & (int)Win32Native.FILE_ATTRIBUTE_DIRECTORY);
912 #else
913             return 0 == (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY);
914 #endif
915         }
916
917     }
918 }
919