145bbb5424b7ffae80cfb0d158a3c230a822a941
[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>Microsoft</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 #if !MONO        
190         private int oldMode;
191 #endif
192         private bool _checkHost;
193
194         [System.Security.SecuritySafeCritical]
195         internal FileSystemEnumerableIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
196         {
197             Contract.Requires(path != null);
198             Contract.Requires(originalUserPath != null);
199             Contract.Requires(searchPattern != null);
200             Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly);
201             Contract.Requires(resultHandler != null);
202
203 #if !MONO  // TODO: check if we need this on Windows
204             oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
205 #endif
206
207             searchStack = new List<Directory.SearchData>();
208
209             String normalizedSearchPattern = NormalizeSearchPattern(searchPattern);
210
211             if (normalizedSearchPattern.Length == 0)
212             {
213                 empty = true;
214             }
215             else
216             {
217                 _resultHandler = resultHandler;
218                 this.searchOption = searchOption;
219
220                 fullPath = Path.GetFullPathInternal(path);
221                 String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern);
222                 normalizedSearchPath = Path.GetDirectoryName(fullSearchString);
223
224                 // permission demands
225                 String[] demandPaths = new String[2];
226 #if MONO
227                 // TODO: we don't call FileIOPermission.HasIllegalCharacters on Mono since CAS is disabled
228 #else
229                 // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
230 #endif
231                 demandPaths[0] = Directory.GetDemandDir(fullPath, true);
232                 // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
233                 // Do a demand on the combined path so that we can fail early in case of deny
234                 demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
235                 _checkHost = checkHost;
236 #if MONO_FEATURE_CAS
237 #if FEATURE_CORECLR
238                 if (checkHost)
239                 {
240                     FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]);
241                     state1.EnsureState();
242                     FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]);
243                     state2.EnsureState();
244                 }
245 #else
246                 FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false);
247 #endif
248 #endif
249
250                 // normalize search criteria
251                 searchCriteria = GetNormalizedSearchCriteria(fullSearchString, normalizedSearchPath);
252
253                 // fix up user path
254                 String searchPatternDirName = Path.GetDirectoryName(normalizedSearchPattern);
255                 String userPathTemp = originalUserPath;
256                 if (searchPatternDirName != null && searchPatternDirName.Length != 0)
257                 {
258                     userPathTemp = Path.Combine(userPathTemp, searchPatternDirName);
259                 }
260                 this.userPath = userPathTemp;
261
262                 searchData = new Directory.SearchData(normalizedSearchPath, this.userPath, searchOption);
263
264                 CommonInit();
265             }
266
267         }
268
269         [System.Security.SecurityCritical]
270         private void CommonInit()
271         {
272             Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized");
273
274             // Execute searchCriteria against the current directory
275             String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
276
277             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
278
279             // Open a Find handle
280 #if MONO
281             int error;
282             _hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
283 #else
284             _hnd = Win32Native.FindFirstFile(searchPath, data);
285 #endif
286
287             if (_hnd.IsInvalid)
288             {
289 #if MONO
290                 int hr = error;
291 #else
292                 int hr = Marshal.GetLastWin32Error();
293 #endif
294                 if (hr != Win32Native.ERROR_FILE_NOT_FOUND && hr != Win32Native.ERROR_NO_MORE_FILES)
295                 {
296                     HandleError(hr, searchData.fullPath);
297                 }
298                 else
299                 {
300                     // flag this as empty only if we're searching just top directory
301                     // Used in fast path for top directory only
302                     empty = searchData.searchOption == SearchOption.TopDirectoryOnly;
303                 }
304             }
305             // fast path for TopDirectoryOnly. If we have a result, go ahead and set it to 
306             // current. If empty, dispose handle.
307             if (searchData.searchOption == SearchOption.TopDirectoryOnly)
308             {
309                 if (empty)
310                 {
311                     _hnd.Dispose();
312                 }
313                 else
314                 {
315                     SearchResult searchResult = CreateSearchResult(searchData, data);
316                     if (_resultHandler.IsResultIncluded(searchResult))
317                     {
318                         current = _resultHandler.CreateObject(searchResult);
319                     }
320                 }
321             }
322             // for AllDirectories, we first recurse into dirs, so cleanup and add searchData 
323             // to the stack
324             else
325             {
326                 _hnd.Dispose();
327                 searchStack.Add(searchData);
328             }
329         }
330
331         [System.Security.SecuritySafeCritical]
332         private FileSystemEnumerableIterator(String fullPath, String normalizedSearchPath, String searchCriteria, String userPath, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost)
333         {
334             this.fullPath = fullPath;
335             this.normalizedSearchPath = normalizedSearchPath;
336             this.searchCriteria = searchCriteria;
337             this._resultHandler = resultHandler;
338             this.userPath = userPath;
339             this.searchOption = searchOption;
340             this._checkHost = checkHost;
341
342             searchStack = new List<Directory.SearchData>();
343
344             if (searchCriteria != null)
345             {
346                 // permission demands
347                 String[] demandPaths = new String[2];
348                 // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters
349                 demandPaths[0] = Directory.GetDemandDir(fullPath, true);
350                 // For filters like foo\*.cs we need to verify if the directory foo is not denied access.
351                 // Do a demand on the combined path so that we can fail early in case of deny
352                 demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true);
353 #if MONO_FEATURE_CAS
354 #if FEATURE_CORECLR
355                 if (checkHost) 
356                 {
357                     FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]);
358                     state1.EnsureState();
359                     FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]);
360                     state2.EnsureState();
361                 }
362 #else
363                 FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false);
364 #endif
365 #endif
366                 searchData = new Directory.SearchData(normalizedSearchPath, userPath, searchOption);
367                 CommonInit();
368             }
369             else
370             {
371                 empty = true;
372             }
373         }
374
375         protected override Iterator<TSource> Clone()
376         {
377             return new FileSystemEnumerableIterator<TSource>(fullPath, normalizedSearchPath, searchCriteria, userPath, searchOption, _resultHandler, _checkHost);
378         }
379
380         [System.Security.SecuritySafeCritical]
381         protected override void Dispose(bool disposing)
382         {
383             try
384             {
385                 if (_hnd != null)
386                 {
387                     _hnd.Dispose();
388                 }
389             }
390             finally
391             {
392 #if !MONO  // TODO: check if we need this on Windows
393                 Win32Native.SetErrorMode(oldMode);
394 #endif
395                 base.Dispose(disposing);
396             }
397         }
398
399         [System.Security.SecuritySafeCritical]
400         public override bool MoveNext()
401         {
402             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
403             switch (state)
404             {
405                 case STATE_INIT:
406                     {
407                         if (empty)
408                         {
409                             state = STATE_FINISH;
410                             goto case STATE_FINISH;
411                         }
412                         if (searchData.searchOption == SearchOption.TopDirectoryOnly)
413                         {
414                             state = STATE_FIND_NEXT_FILE;
415                             if (current != null)
416                             {
417                                 return true;
418                             }
419                             else
420                             {
421                                 goto case STATE_FIND_NEXT_FILE;
422                             }
423                         }
424                         else
425                         {
426                             state = STATE_SEARCH_NEXT_DIR;
427                             goto case STATE_SEARCH_NEXT_DIR;
428                         }
429                     }
430                 case STATE_SEARCH_NEXT_DIR:
431                     {
432                         Contract.Assert(searchData.searchOption != SearchOption.TopDirectoryOnly, "should not reach this code path if searchOption == TopDirectoryOnly");
433                         // Traverse directory structure. We need to get '*'
434                         while (searchStack.Count > 0)
435                         {
436                             searchData = searchStack[0];
437                             Contract.Assert((searchData.fullPath != null), "fullpath can't be null!");
438                             searchStack.RemoveAt(0);
439
440                             // Traverse the subdirs
441                             AddSearchableDirsToStack(searchData);
442
443                             // Execute searchCriteria against the current directory
444                             String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
445
446                             // Open a Find handle
447 #if MONO
448                             int error;
449                             _hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
450 #else
451                             _hnd = Win32Native.FindFirstFile(searchPath, data);
452 #endif
453                             if (_hnd.IsInvalid)
454                             {
455 #if MONO
456                                 int hr = error;
457 #else
458                                 int hr = Marshal.GetLastWin32Error();
459 #endif
460                                 if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
461                                     continue;
462
463                                 _hnd.Dispose();
464                                 HandleError(hr, searchData.fullPath);
465                             }
466
467                             state = STATE_FIND_NEXT_FILE;
468                             needsParentPathDiscoveryDemand = true;
469                             SearchResult searchResult = CreateSearchResult(searchData, data);
470                             if (_resultHandler.IsResultIncluded(searchResult))
471                             {
472                                 if (needsParentPathDiscoveryDemand)
473                                 {
474                                     DoDemand(searchData.fullPath);
475                                     needsParentPathDiscoveryDemand = false;
476                                 }
477                                 current = _resultHandler.CreateObject(searchResult);
478                                 return true;
479                             }
480                             else
481                             {
482                                 goto case STATE_FIND_NEXT_FILE;
483                             }
484                         }
485                         state = STATE_FINISH;
486                         goto case STATE_FINISH;
487                     }
488                 case STATE_FIND_NEXT_FILE:
489                     {
490                         if (searchData != null && _hnd != null)
491                         {
492                             // Keep asking for more matching files/dirs, add it to the list
493 #if MONO
494                             int error;
495                             while (MonoIO.FindNextFile (_hnd.DangerousGetHandle(), out data.cFileName, out data.dwFileAttributes, out error))
496 #else
497                             while (Win32Native.FindNextFile(_hnd, data))
498 #endif
499                             {
500                                 SearchResult searchResult = CreateSearchResult(searchData, data);
501                                 if (_resultHandler.IsResultIncluded(searchResult))
502                                 {
503                                     if (needsParentPathDiscoveryDemand)
504                                     {
505                                         DoDemand(searchData.fullPath);
506                                         needsParentPathDiscoveryDemand = false;
507                                     }
508                                     current = _resultHandler.CreateObject(searchResult);
509                                     return true;
510                                 }
511                             }
512
513 #if MONO
514                             int hr = error;
515 #else
516                             // Make sure we quit with a sensible error.
517                             int hr = Marshal.GetLastWin32Error();
518 #endif
519
520                             if (_hnd != null)
521                                 _hnd.Dispose();
522
523                             // ERROR_FILE_NOT_FOUND is valid here because if the top level
524                             // dir doen't contain any subdirs and matching files then 
525                             // we will get here with this errorcode from the searchStack walk
526                             if ((hr != 0) && (hr != Win32Native.ERROR_NO_MORE_FILES)
527                                 && (hr != Win32Native.ERROR_FILE_NOT_FOUND))
528                             {
529                                 HandleError(hr, searchData.fullPath);
530                             }
531                         }
532                         if (searchData.searchOption == SearchOption.TopDirectoryOnly)
533                         {
534                             state = STATE_FINISH;
535                             goto case STATE_FINISH;
536                         }
537                         else
538                         {
539                             state = STATE_SEARCH_NEXT_DIR;
540                             goto case STATE_SEARCH_NEXT_DIR;
541                         }
542                     }
543                 case STATE_FINISH:
544                     {
545                         Dispose();
546                         break;
547                     }
548             }
549             return false;
550         }
551
552         [System.Security.SecurityCritical]
553         private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Win32Native.WIN32_FIND_DATA findData)
554         {
555             String userPathFinal = Path.InternalCombine(localSearchData.userPath, findData.cFileName);
556             String fullPathFinal = Path.InternalCombine(localSearchData.fullPath, findData.cFileName);
557             return new SearchResult(fullPathFinal, userPathFinal, findData);
558         }
559
560         [System.Security.SecurityCritical]
561         private void HandleError(int hr, String path)
562         {
563             Dispose();
564             __Error.WinIOError(hr, path);
565         }
566
567         [System.Security.SecurityCritical]  // auto-generated
568         private void AddSearchableDirsToStack(Directory.SearchData localSearchData)
569         {
570             Contract.Requires(localSearchData != null);
571
572             String searchPath = Path.InternalCombine(localSearchData.fullPath, "*");
573             SafeFindHandle hnd = null;
574             Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
575             try
576             {
577                 // Get all files and dirs
578 #if MONO
579                 int error;
580                 hnd = new SafeFindHandle (MonoIO.FindFirstFile (searchPath, out data.cFileName, out data.dwFileAttributes, out error));
581 #else
582                 hnd = Win32Native.FindFirstFile(searchPath, data);
583 #endif
584
585                 if (hnd.IsInvalid)
586                 {
587 #if MONO
588                     int hr = error;
589 #else
590                     int hr = Marshal.GetLastWin32Error();
591 #endif
592
593                     // This could happen if the dir doesn't contain any files.
594                     // Continue with the recursive search though, eventually
595                     // searchStack will become empty
596                     if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND)
597                         return;
598
599                     HandleError(hr, localSearchData.fullPath);
600                 }
601
602                 // Add subdirs to searchStack. Exempt ReparsePoints as appropriate
603                 int incr = 0;
604                 do
605                 {
606                     if (FileSystemEnumerableHelpers.IsDir(data))
607                     {
608                         String tempFullPath = Path.InternalCombine(localSearchData.fullPath, data.cFileName);
609                         String tempUserPath = Path.InternalCombine(localSearchData.userPath, data.cFileName);
610
611                         SearchOption option = localSearchData.searchOption;
612
613 #if EXCLUDE_REPARSEPOINTS
614                         // Traverse reparse points depending on the searchoption specified
615                         if ((searchDataSubDir.searchOption == SearchOption.AllDirectories) && (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_REPARSE_POINT)))
616                             option = SearchOption.TopDirectoryOnly; 
617 #endif
618                         // Setup search data for the sub directory and push it into the stack
619                         Directory.SearchData searchDataSubDir = new Directory.SearchData(tempFullPath, tempUserPath, option);
620
621                         searchStack.Insert(incr++, searchDataSubDir);
622                     }
623 #if MONO
624                 } while (MonoIO.FindNextFile (hnd.DangerousGetHandle(), out data.cFileName, out data.dwFileAttributes, out error));
625 #else
626                 } while (Win32Native.FindNextFile(hnd, data));
627 #endif
628                 // We don't care about errors here
629             }
630             finally
631             {
632                 if (hnd != null)
633                     hnd.Dispose();
634             }
635         }
636
637         [System.Security.SecurityCritical]
638         internal void DoDemand(String fullPathToDemand)
639         {
640 #if MONO_FEATURE_CAS
641 #if FEATURE_CORECLR
642             if(_checkHost) {
643                 String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
644                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandDir);
645                 state.EnsureState();
646             }
647 #else
648             String demandDir = Directory.GetDemandDir(fullPathToDemand, true);
649             FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandDir, false, false);
650 #endif
651 #endif
652         }
653
654         private static String NormalizeSearchPattern(String searchPattern)
655         {
656             Contract.Requires(searchPattern != null);
657
658             // Win32 normalization trims only U+0020. 
659             String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars);
660
661             // Make this corner case more useful, like dir
662             if (tempSearchPattern.Equals("."))
663             {
664                 tempSearchPattern = "*";
665             }
666
667             Path.CheckSearchPattern(tempSearchPattern);
668             return tempSearchPattern;
669         }
670
671         private static String GetNormalizedSearchCriteria(String fullSearchString, String fullPathMod)
672         {
673             Contract.Requires(fullSearchString != null);
674             Contract.Requires(fullPathMod != null);
675             Contract.Requires(fullSearchString.Length >= fullPathMod.Length);
676
677             String searchCriteria = null;
678             char lastChar = fullPathMod[fullPathMod.Length - 1];
679             if (Path.IsDirectorySeparator(lastChar))
680             {
681                 // Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\
682                 searchCriteria = fullSearchString.Substring(fullPathMod.Length);
683             }
684             else
685             {
686                 Contract.Assert(fullSearchString.Length > fullPathMod.Length);
687                 searchCriteria = fullSearchString.Substring(fullPathMod.Length + 1);
688             }
689             return searchCriteria;
690         }
691
692         private static String GetFullSearchString(String fullPath, String searchPattern)
693         {
694             Contract.Requires(fullPath != null);
695             Contract.Requires(searchPattern != null);
696
697             String tempStr = Path.InternalCombine(fullPath, searchPattern);
698
699             // If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception
700             char lastChar = tempStr[tempStr.Length - 1];
701             if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar)
702             {
703                 tempStr = tempStr + '*';
704             }
705
706             return tempStr;
707         }
708     }
709
710     internal abstract class SearchResultHandler<TSource>
711     {
712
713         [System.Security.SecurityCritical]
714         internal abstract bool IsResultIncluded(SearchResult result);
715
716         [System.Security.SecurityCritical]
717         internal abstract TSource CreateObject(SearchResult result);
718
719     }
720
721     internal class StringResultHandler : SearchResultHandler<String>
722     {
723         private bool _includeFiles;
724         private bool _includeDirs;
725
726         internal StringResultHandler(bool includeFiles, bool includeDirs)
727         {
728             _includeFiles = includeFiles;
729             _includeDirs = includeDirs;
730         }
731
732         [System.Security.SecurityCritical]
733         internal override bool IsResultIncluded(SearchResult result)
734         {
735             bool includeFile = _includeFiles && FileSystemEnumerableHelpers.IsFile(result.FindData);
736             bool includeDir = _includeDirs && FileSystemEnumerableHelpers.IsDir(result.FindData);
737             Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!");
738             return (includeFile || includeDir);
739         }
740
741         [System.Security.SecurityCritical]
742         internal override String CreateObject(SearchResult result)
743         {
744             return result.UserPath;
745         }
746     }
747
748     internal class FileInfoResultHandler : SearchResultHandler<FileInfo>
749     {
750         [System.Security.SecurityCritical]
751         internal override bool IsResultIncluded(SearchResult result)
752         {
753             return FileSystemEnumerableHelpers.IsFile(result.FindData);
754         }
755
756         [System.Security.SecurityCritical]
757         internal override FileInfo CreateObject(SearchResult result)
758         {
759             String name = result.FullPath;
760 #if MONO_FEATURE_CAS
761 #if FEATURE_CORECLR
762             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name);
763             state.EnsureState();
764 #else
765             FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, name, false, false);
766 #endif
767 #endif
768             FileInfo fi = new FileInfo(name, false);
769             fi.InitializeFrom(result.FindData);
770             return fi;
771         }
772     }
773
774     internal class DirectoryInfoResultHandler : SearchResultHandler<DirectoryInfo>
775     {
776         [System.Security.SecurityCritical]
777         internal override bool IsResultIncluded(SearchResult result)
778         {
779             return FileSystemEnumerableHelpers.IsDir(result.FindData);
780         }
781
782         [System.Security.SecurityCritical]
783         internal override DirectoryInfo CreateObject(SearchResult result)
784         {
785             String name = result.FullPath;
786             String permissionName = name + "\\.";
787
788 #if MONO_FEATURE_CAS
789 #if FEATURE_CORECLR
790             FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName);
791             state.EnsureState();
792 #else
793             FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, permissionName, false, false);
794 #endif
795 #endif
796             DirectoryInfo di = new DirectoryInfo(name, false);
797             di.InitializeFrom(result.FindData);
798             return di;
799         }
800     }
801
802     internal class FileSystemInfoResultHandler : SearchResultHandler<FileSystemInfo>
803     {
804
805         [System.Security.SecurityCritical]
806         internal override bool IsResultIncluded(SearchResult result)
807         {
808             bool includeFile = FileSystemEnumerableHelpers.IsFile(result.FindData);
809             bool includeDir = FileSystemEnumerableHelpers.IsDir(result.FindData);
810             Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!");
811
812             return (includeDir || includeFile);
813         }
814
815         [System.Security.SecurityCritical]
816         internal override FileSystemInfo CreateObject(SearchResult result)
817         {
818             bool isFile = FileSystemEnumerableHelpers.IsFile(result.FindData);
819             bool isDir = FileSystemEnumerableHelpers.IsDir(result.FindData);
820
821             if (isDir)
822             {
823                 String name = result.FullPath;
824                 String permissionName = name + "\\.";
825
826 #if MONO_FEATURE_CAS
827 #if FEATURE_CORECLR
828                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName);
829                 state.EnsureState();
830 #else
831                 FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, permissionName, false, false);
832 #endif
833 #endif
834                 DirectoryInfo di = new DirectoryInfo(name, false);
835                 di.InitializeFrom(result.FindData);
836                 return di;
837             }
838             else
839             {
840                 Contract.Assert(isFile);
841                 String name = result.FullPath;
842
843 #if MONO_FEATURE_CAS
844 #if FEATURE_CORECLR
845                 FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name);
846                 state.EnsureState();
847 #else
848                 FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, name, false, false);
849 #endif
850 #endif
851                 FileInfo fi = new FileInfo(name, false);
852                 fi.InitializeFrom(result.FindData);
853                 return fi;
854             }
855         }
856
857     }
858
859     internal sealed class SearchResult
860     {
861         private String fullPath;     // fully-qualifed path
862         private String userPath;     // user-specified path
863         [System.Security.SecurityCritical]
864         private Win32Native.WIN32_FIND_DATA findData;
865
866         [System.Security.SecurityCritical]
867         internal SearchResult(String fullPath, String userPath, Win32Native.WIN32_FIND_DATA findData)
868         {
869             Contract.Requires(fullPath != null);
870             Contract.Requires(userPath != null);
871
872             this.fullPath = fullPath;
873             this.userPath = userPath;
874             this.findData = findData;
875         }
876
877         internal String FullPath
878         {
879             get { return fullPath; }
880         }
881
882         internal String UserPath
883         {
884             get { return userPath; }
885         }
886
887         internal Win32Native.WIN32_FIND_DATA FindData
888         {
889             [System.Security.SecurityCritical]
890             get { return findData; }
891         }
892
893     }
894
895     internal static class FileSystemEnumerableHelpers
896     {
897         [System.Security.SecurityCritical]  // auto-generated
898         internal static bool IsDir(Win32Native.WIN32_FIND_DATA data)
899         {
900             // Don't add "." nor ".."
901 #if MONO
902             return (0 != (data.dwFileAttributes & (int)Win32Native.FILE_ATTRIBUTE_DIRECTORY))
903 #else
904             return (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY))
905 #endif
906                                                 && !data.cFileName.Equals(".") && !data.cFileName.Equals("..");
907         }
908
909         [System.Security.SecurityCritical]  // auto-generated
910         internal static bool IsFile(Win32Native.WIN32_FIND_DATA data)
911         {
912 #if MONO
913             return 0 == (data.dwFileAttributes & (int)Win32Native.FILE_ATTRIBUTE_DIRECTORY);
914 #else
915             return 0 == (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY);
916 #endif
917         }
918
919     }
920 }
921