1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.IO;
6 using System.Text;
7 using System.Diagnostics;
8 using System.Text.RegularExpressions;
9 using System.Threading;
10 using System.Globalization;
11 
12 namespace Microsoft.Build.BuildEngine.Shared
13 {
14     /// <summary>
15     /// Functions for matching file names with patterns.
16     /// </summary>
17     /// <owner>JomoF</owner>
18     internal static class FileMatcher
19     {
20         private const string recursiveDirectoryMatch = "**";
21         private const string dotdot = "..";
22         private static readonly string directorySeparator = new string(Path.DirectorySeparatorChar,1);
23         private static readonly string altDirectorySeparator = new string(Path.AltDirectorySeparatorChar,1);
24 
25         private static readonly char[] wildcardCharacters = { '*', '?' };
26         internal static readonly char[] directorySeparatorCharacters = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
27         private static readonly GetFileSystemEntries defaultGetFileSystemEntries = new GetFileSystemEntries(GetAccessibleFileSystemEntries);
28         private static readonly DirectoryExists defaultDirectoryExists = new DirectoryExists(Directory.Exists);
29 
30         /// <summary>
31         /// The type of entity that GetFileSystemEntries should return.
32         /// </summary>
33         internal enum FileSystemEntity
34         {
35             Files,
36             Directories,
37             FilesAndDirectories
38         };
39 
40         /// <summary>
41         /// Delegate defines the GetFileSystemEntries signature that GetLongPathName uses
42         /// to enumerate directories on the file system.
43         /// </summary>
44         /// <param name="entityType">Files, Directories, or Files and Directories</param>
45         /// <param name="path">The path to search.</param>
46         /// <param name="pattern">The file pattern.</param>
47         /// <param name="projectDirectory"></param>
48         /// <param name="stripProjectDirectory"></param>
49         /// <returns>The array of filesystem entries.</returns>
GetFileSystemEntries(FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory)50         internal delegate string[] GetFileSystemEntries(FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory);
51 
52         /// <summary>
53         /// Returns true if the directory exists and is not a file, otherwise false.
54         /// </summary>
55         /// <param name="path">The path to check</param>
56         /// <returns>True if the directory exists.</returns>
DirectoryExists(string path)57         internal delegate bool DirectoryExists(string path);
58 
59 
60         /// <summary>
61         /// Determines whether the given path has any wild card characters.
62         /// </summary>
63         /// <param name="filespec"></param>
64         /// <returns></returns>
HasWildcards(string filespec)65         internal static bool HasWildcards(string filespec)
66         {
67             return -1 != filespec.IndexOfAny(wildcardCharacters);
68         }
69 
70         /// <summary>
71         /// Get the files and\or folders specified by the given path and pattern.
72         /// </summary>
73         /// <param name="entityType">Whether Files, Directories or both.</param>
74         /// <param name="path">The path to search.</param>
75         /// <param name="pattern">The pattern to search.</param>
76         /// <param name="projectDirectory">The directory for the project within which the call is made</param>
77         /// <param name="stripProjectDirectory">If true the project directory should be stripped</param>
78         /// <returns></returns>
GetAccessibleFileSystemEntries(FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory)79         private static string[] GetAccessibleFileSystemEntries(FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory)
80         {
81             string[] files = null;
82             switch (entityType)
83             {
84                 case FileSystemEntity.Files: files = GetAccessibleFiles(path, pattern, projectDirectory, stripProjectDirectory); break;
85                 case FileSystemEntity.Directories: files = GetAccessibleDirectories(path, pattern); break;
86                 case FileSystemEntity.FilesAndDirectories: files = GetAccessibleFilesAndDirectories(path, pattern); break;
87                 default:
88                     ErrorUtilities.VerifyThrow(false, "Unexpected filesystem entity type.");
89                     break;
90             }
91 
92             return files;
93         }
94 
95         /// <summary>
96         /// Returns an array of file system entries matching the specified search criteria. Inaccessible or non-existent file
97         /// system entries are skipped.
98         /// </summary>
99         /// <owner>SumedhK,JomoF</owner>
100         /// <param name="path"></param>
101         /// <param name="pattern"></param>
102         /// <returns>Array of matching file system entries (can be empty).</returns>
GetAccessibleFilesAndDirectories(string path, string pattern)103         private static string[] GetAccessibleFilesAndDirectories(string path, string pattern)
104         {
105             string[] entries = null;
106 
107             if (Directory.Exists(path))
108             {
109                 try
110                 {
111                     entries = Directory.GetFileSystemEntries(path, pattern);
112                 }
113                 // for OS security
114                 catch (UnauthorizedAccessException)
115                 {
116                     // do nothing
117                 }
118                 // for code access security
119                 catch (System.Security.SecurityException)
120                 {
121                     // do nothing
122                 }
123             }
124 
125             if (entries == null)
126             {
127                 entries = new string[0];
128             }
129 
130             return entries;
131         }
132 
133         /// <summary>
134         /// Same as Directory.GetFiles(...) except that files that
135         /// aren't accessible are skipped instead of throwing an exception.
136         ///
137         /// Other exceptions are passed through.
138         /// </summary>
139         /// <param name="path">The path.</param>
140         /// <param name="filespec">The pattern.</param>
141         /// <param name="projectDirectory">The project directory</param>
142         /// <param name="stripProjectDirectory"></param>
143         /// <returns>Files that can be accessed.</returns>
GetAccessibleFiles( string path, string filespec, string projectDirectory, bool stripProjectDirectory )144         private static string[] GetAccessibleFiles
145         (
146             string path,
147             string filespec,     // can be null
148             string projectDirectory,
149             bool   stripProjectDirectory
150         )
151         {
152             try
153             {
154                 // look in current directory if no path specified
155                 string dir = ((path.Length == 0) ? ".\\" : path);
156 
157                 // get all files in specified directory, unless a file-spec has been provided
158                 string[] files = (filespec == null)
159                     ? Directory.GetFiles(dir)
160                     : Directory.GetFiles(dir, filespec);
161 
162                 // If the Item is based on a relative path we need to strip
163                 // the current directory from the front
164                 if (stripProjectDirectory)
165                 {
166                     RemoveProjectDirectory(files, projectDirectory);
167                 }
168                 // Files in the current directory are coming back with a ".\"
169                 // prepended to them.  We need to remove this; it breaks the
170                 // IDE, which expects just the filename if it is in the current
171                 // directory.  But only do this if the original path requested
172                 // didn't itself contain a ".\".
173                 else if (!path.StartsWith(".\\", StringComparison.Ordinal))
174                 {
175                     RemoveInitialDotSlash(files);
176                 }
177 
178                 return files;
179             }
180             catch (System.Security.SecurityException)
181             {
182                 // For code access security.
183                 return new string[0];
184             }
185             catch (System.UnauthorizedAccessException)
186             {
187                 // For OS security.
188                 return new string[0];
189             }
190         }
191 
192         /// <summary>
193         /// Same as Directory.GetDirectories(...) except that files that
194         /// aren't accessible are skipped instead of throwing an exception.
195         ///
196         /// Other exceptions are passed through.
197         /// </summary>
198         /// <param name="path">The path.</param>
199         /// <param name="pattern">Pattern to match</param>
200         /// <returns>Accessible directories.</returns>
GetAccessibleDirectories( string path, string pattern )201         private static string[] GetAccessibleDirectories
202         (
203             string path,
204             string pattern
205         )
206         {
207             try
208             {
209                 string[] directories = null;
210 
211                 if (pattern == null)
212                 {
213                     directories = Directory.GetDirectories((path.Length == 0) ? ".\\" : path);
214                 }
215                 else
216                 {
217                     directories = Directory.GetDirectories((path.Length == 0) ? ".\\" : path, pattern);
218                 }
219 
220                 // Subdirectories in the current directory are coming back with a ".\"
221                 // prepended to them.  We need to remove this; it breaks the
222                 // IDE, which expects just the filename if it is in the current
223                 // directory.  But only do this if the original path requested
224                 // didn't itself contain a ".\".
225                 if (!path.StartsWith(".\\", StringComparison.Ordinal))
226                 {
227                     RemoveInitialDotSlash(directories);
228                 }
229 
230                 return directories;
231             }
232             catch (System.Security.SecurityException)
233             {
234                 // For code access security.
235                 return new string[0];
236             }
237             catch (System.UnauthorizedAccessException)
238             {
239                 // For OS security.
240                 return new string[0];
241             }
242         }
243 
244         /// <summary>
245         /// Given a path name, get its long version.
246         /// </summary>
247         /// <param name="path">The short path.</param>
248         /// <param name="getFileSystemEntries">Delegate.</param>
249         /// <returns>The long path.</returns>
GetLongPathName( string path, GetFileSystemEntries getFileSystemEntries )250         internal static string GetLongPathName
251         (
252             string path,
253             GetFileSystemEntries getFileSystemEntries
254         )
255         {
256 
257             if (path.IndexOf("~", StringComparison.Ordinal) == -1)
258             {
259                 // A path with no '~' must not be a short name.
260                 return path;
261             }
262 
263             ErrorUtilities.VerifyThrow(!HasWildcards(path),
264                 "GetLongPathName does not handle wildcards and was passed '{0}'.", path);
265 
266             string[] parts = path.Split(directorySeparatorCharacters);
267             string pathRoot;
268             int startingElement=0;
269 
270             bool isUnc = path.StartsWith(directorySeparator + directorySeparator, StringComparison.Ordinal);
271             if (isUnc)
272             {
273                 pathRoot = directorySeparator + directorySeparator;
274                 pathRoot += parts[2];
275                 pathRoot += directorySeparator;
276                 pathRoot += parts[3];
277                 pathRoot += directorySeparator;
278                 startingElement = 4;
279             }
280             else
281             {
282                 // Is it relative?
283                 if (path.Length>2 && path[1] == ':')
284                 {
285                     // Not relative
286                     pathRoot = parts[0] + directorySeparator;
287                     startingElement = 1;
288                 }
289                 else
290                 {
291                     // Relative
292                     pathRoot = String.Empty;
293                     startingElement = 0;
294                 }
295             }
296 
297             // Build up an array of parts. These elements may be "" if there are
298             // extra slashes.
299             string[] longParts = new string[parts.Length - startingElement];
300 
301             string longPath = pathRoot;
302             for (int i = startingElement; i < parts.Length; ++i)
303             {
304                 // If there is a zero-length part, then that means there was an extra slash.
305                 if (parts[i].Length == 0)
306                 {
307                     longParts[i-startingElement] = String.Empty;
308                 }
309                 else
310                 {
311 
312                     if (parts[i].IndexOf("~", StringComparison.Ordinal) == -1)
313                     {
314                         // If there's no ~, don't hit the disk.
315                         longParts[i - startingElement] = parts[i];
316                         longPath = Path.Combine(longPath, parts[i]);
317                     }
318                     else
319                     {
320                         // getFileSystemEntries(...) returns an empty array if longPath doesn't exist.
321                         string[] entries = getFileSystemEntries(FileSystemEntity.FilesAndDirectories, longPath, parts[i], null, false);
322 
323                         if (0 == entries.Length)
324                         {
325                             // The next part doesn't exist. Therefore, no more of the path will exist.
326                             // Just return the rest.
327                             for (int j = i; j<parts.Length; ++j)
328                             {
329                                 longParts[j - startingElement] = parts[j];
330                             }
331                             break;
332                         }
333 
334                         // Since we know there are no wild cards, this should be length one.
335                         ErrorUtilities.VerifyThrow(entries.Length == 1,
336                             "Unexpected number of entries ({3}) found when enumerating '{0}' under '{1}'. Original path was '{2}'",
337                             parts[i], longPath, path, entries.Length);
338 
339                         // Entries[0] contains the full path.
340                         longPath = entries[0];
341 
342                         // We just want the trailing node.
343                         longParts[i - startingElement] = Path.GetFileName(longPath);
344                     }
345                 }
346             }
347 
348             return pathRoot + String.Join (directorySeparator, longParts);
349         }
350 
351         /// <summary>
352         /// Given a filespec, split it into left-most 'fixed' dir part, middle 'wildcard' dir part, and filename part.
353         /// The filename part may have wildcard characters in it.
354         /// </summary>
355         /// <param name="filespec">The filespec to be decomposed.</param>
356         /// <param name="fixedDirectoryPart">Receives the fixed directory part.</param>
357         /// <param name="wildcardDirectoryPart">The wildcard directory part.</param>
358         /// <param name="filenamePart">The filename part.</param>
359         /// <param name="getFileSystemEntries">Delegate.</param>
SplitFileSpec( string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, GetFileSystemEntries getFileSystemEntries )360         internal static void SplitFileSpec
361         (
362             string filespec,
363             out string fixedDirectoryPart,
364             out string wildcardDirectoryPart,
365             out string filenamePart,
366             GetFileSystemEntries getFileSystemEntries
367         )
368         {
369             PreprocessFileSpecForSplitting
370             (
371                 filespec,
372                 out fixedDirectoryPart,
373                 out wildcardDirectoryPart,
374                 out filenamePart
375             );
376 
377             /*
378              * Handle the special case in which filenamePart is '**'.
379              * In this case, filenamePart becomes '*.*' and the '**' is appended
380              * to the end of the wildcardDirectory part.
381              * This is so that later regular expression matching can accurately
382              * pull out the different parts (fixed, wildcard, filename) of given
383              * file specs.
384              */
385             if (recursiveDirectoryMatch == filenamePart)
386             {
387                 wildcardDirectoryPart += recursiveDirectoryMatch;
388                 wildcardDirectoryPart += directorySeparator;
389                 filenamePart = "*.*";
390             }
391 
392             fixedDirectoryPart = FileMatcher.GetLongPathName(fixedDirectoryPart, getFileSystemEntries);
393         }
394 
395         /// <summary>
396         /// Do most of the grunt work of splitting the filespec into parts.
397         /// Does not handle post-processing common to the different matching
398         /// paths.
399         /// </summary>
400         /// <param name="filespec">The filespec to be decomposed.</param>
401         /// <param name="fixedDirectoryPart">Receives the fixed directory part.</param>
402         /// <param name="wildcardDirectoryPart">The wildcard directory part.</param>
403         /// <param name="filenamePart">The filename part.</param>
PreprocessFileSpecForSplitting( string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart )404         private static void PreprocessFileSpecForSplitting
405         (
406             string filespec,
407             out string fixedDirectoryPart,
408             out string wildcardDirectoryPart,
409             out string filenamePart
410         )
411         {
412             int indexOfLastDirectorySeparator = filespec.LastIndexOfAny(directorySeparatorCharacters);
413             if (-1 == indexOfLastDirectorySeparator)
414             {
415                 /*
416                  * No dir separator found. This is either this form,
417                  *
418                  *      Source.cs
419                  *      *.cs
420                  *
421                  *  or this form,
422                  *
423                  *     **
424                  */
425                 fixedDirectoryPart = String.Empty;
426                 wildcardDirectoryPart = String.Empty;
427                 filenamePart = filespec;
428                 return;
429             }
430 
431             int indexOfFirstWildcard = filespec.IndexOfAny(wildcardCharacters);
432             if
433             (
434                 -1 == indexOfFirstWildcard
435                 || indexOfFirstWildcard > indexOfLastDirectorySeparator
436             )
437             {
438                 /*
439                  * There is at least one dir separator, but either there is no wild card or the
440                  * wildcard is after the dir separator.
441                  *
442                  * The form is one of these:
443                  *
444                  *      dir1\Source.cs
445                  *      dir1\*.cs
446                  *
447                  * Where the trailing spec is meant to be a filename. Or,
448                  *
449                  *      dir1\**
450                  *
451                  * Where the trailing spec is meant to be any file recursively.
452                  */
453 
454                 // We know the fixed director part now.
455                 fixedDirectoryPart = filespec.Substring (0, indexOfLastDirectorySeparator + 1);
456                 wildcardDirectoryPart = String.Empty;
457                 filenamePart = filespec.Substring (indexOfLastDirectorySeparator + 1);
458                 return;
459             }
460 
461             /*
462              * Find the separator right before the first wildcard.
463              */
464             string filespecLeftOfWildcard = filespec.Substring(0, indexOfFirstWildcard);
465             int indexOfSeparatorBeforeWildCard = filespecLeftOfWildcard.LastIndexOfAny(directorySeparatorCharacters);
466             if (-1 == indexOfSeparatorBeforeWildCard)
467             {
468                 /*
469                  * There is no separator before the wildcard, so the form is like this:
470                  *
471                  *      dir?\Source.cs
472                  *
473                  * or this,
474                  *
475                  *      dir?\**
476                  */
477                 fixedDirectoryPart = String.Empty;
478                 wildcardDirectoryPart = filespec.Substring (0, indexOfLastDirectorySeparator + 1);
479                 filenamePart = filespec.Substring (indexOfLastDirectorySeparator + 1);
480                 return;
481             }
482 
483             /*
484              * There is at least one wildcard and one dir separator, split parts out.
485              */
486             fixedDirectoryPart = filespec.Substring(0, indexOfSeparatorBeforeWildCard+1);
487             wildcardDirectoryPart = filespec.Substring(indexOfSeparatorBeforeWildCard+1, indexOfLastDirectorySeparator-indexOfSeparatorBeforeWildCard);
488             filenamePart = filespec.Substring(indexOfLastDirectorySeparator+1);
489         }
490 
491         /// <summary>
492         /// Removes the leading ".\" from all of the paths in the array.
493         /// </summary>
494         /// <param name="paths">Paths to remove .\ from.</param>
RemoveInitialDotSlash( string[] paths )495         private static void RemoveInitialDotSlash
496         (
497             string[] paths
498         )
499         {
500             for (int i=0; i < paths.Length; i++)
501             {
502                 if (paths[i].StartsWith(".\\", StringComparison.Ordinal))
503                 {
504                     paths[i] = paths[i].Substring(2);
505                 }
506             }
507         }
508 
509 
510         /// <summary>
511         /// Checks if the char is a DirectorySeparatorChar or a AltDirectorySeparatorChar
512         /// </summary>
513         /// <param name="c"></param>
514         /// <returns></returns>
IsDirectorySeparator(char c)515         internal static bool IsDirectorySeparator(char c)
516         {
517             return (c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar);
518         }
519         /// <summary>
520         /// Removes the current directory converting the file back to relative path
521         /// </summary>
522         /// <param name="paths">Paths to remove current directory from.</param>
523         /// <param name="projectDirectory"></param>
RemoveProjectDirectory( string[] paths, string projectDirectory )524         internal static void RemoveProjectDirectory
525         (
526             string[] paths,
527             string projectDirectory
528         )
529         {
530             bool directoryLastCharIsSeparator = IsDirectorySeparator(projectDirectory[projectDirectory.Length - 1]);
531              for (int i = 0; i < paths.Length; i++)
532             {
533                 if (paths[i].StartsWith(projectDirectory, StringComparison.Ordinal))
534                 {
535                     // If the project directory did not end in a slash we need to check to see if the next char in the path is a slash
536                     if (!directoryLastCharIsSeparator)
537                     {
538                         //If the next char after the project directory is not a slash, skip this path
539                         if (paths[i].Length <= projectDirectory.Length || !IsDirectorySeparator(paths[i][projectDirectory.Length]))
540                         {
541                             continue;
542                         }
543                         paths[i] = paths[i].Substring(projectDirectory.Length + 1);
544                     }
545                     else
546                     {
547                         paths[i] = paths[i].Substring(projectDirectory.Length);
548                     }
549                 }
550             }
551         }
552 
553         /// <summary>
554         /// Get all files that match either the file-spec or the regular expression.
555         /// </summary>
556         /// <param name="listOfFiles">List of files that gets populated.</param>
557         /// <param name="baseDirectory">The path to enumerate</param>
558         /// <param name="remainingWildcardDirectory">The remaining, wildcard part of the directory.</param>
559         /// <param name="filespec">The filespec.</param>
560         /// <param name="extensionLengthToEnforce"></param>
561         /// <param name="regexFileMatch">Wild-card matching.</param>
562         /// <param name="needsRecursion">If true, then recursion is required.</param>
563         /// <param name="projectDirectory"></param>
564         /// <param name="stripProjectDirectory"></param>
565         /// <param name="getFileSystemEntries">Delegate.</param>
GetFilesRecursive( System.Collections.IList listOfFiles, string baseDirectory, string remainingWildcardDirectory, string filespec, int extensionLengthToEnforce, Regex regexFileMatch, bool needsRecursion, string projectDirectory, bool stripProjectDirectory, GetFileSystemEntries getFileSystemEntries )566         private static void GetFilesRecursive
567         (
568             System.Collections.IList listOfFiles,
569             string baseDirectory,
570             string remainingWildcardDirectory,
571             string filespec,                // can be null
572             int extensionLengthToEnforce,   // only relevant when filespec is not null
573             Regex regexFileMatch,           // can be null
574             bool needsRecursion,
575             string projectDirectory,
576             bool   stripProjectDirectory,
577             GetFileSystemEntries getFileSystemEntries
578         )
579         {
580             Debug.Assert((filespec == null) || (regexFileMatch == null),
581                 "File-spec overrides the regular expression -- pass null for file-spec if you want to use the regular expression.");
582 
583             ErrorUtilities.VerifyThrow((filespec != null) || (regexFileMatch != null),
584                 "Need either a file-spec or a regular expression to match files.");
585 
586             ErrorUtilities.VerifyThrow(remainingWildcardDirectory!=null, "Expected non-null remaning wildcard directory.");
587 
588             /*
589              * Get the matching files.
590              */
591             bool considerFiles = false;
592 
593             // Only consider files if...
594             if (remainingWildcardDirectory.Length == 0)
595             {
596                 // We've reached the end of the wildcard directory elements.
597                 considerFiles = true;
598             }
599             else if (remainingWildcardDirectory.IndexOf(recursiveDirectoryMatch, StringComparison.Ordinal) == 0)
600             {
601                 // or, we've reached a "**" so everything else is matched recursively.
602                 considerFiles = true;
603             }
604 
605             if (considerFiles)
606             {
607                 string[] files = getFileSystemEntries(FileSystemEntity.Files, baseDirectory, filespec, projectDirectory, stripProjectDirectory);
608                 foreach (string file in files)
609                 {
610                     if ((filespec != null) ||
611                         // if no file-spec provided, match the file to the regular expression
612                         // PERF NOTE: Regex.IsMatch() is an expensive operation, so we avoid it whenever possible
613                         regexFileMatch.IsMatch(file))
614                     {
615                         if ((filespec == null) ||
616                             // if we used a file-spec with a "loosely" defined extension
617                             (extensionLengthToEnforce == 0) ||
618                             // discard all files that do not have extensions of the desired length
619                             (Path.GetExtension(file).Length == extensionLengthToEnforce))
620                         {
621                             listOfFiles.Add((object)file);
622                         }
623                     }
624                 }
625             }
626 
627             /*
628              * Recurse into subdirectories.
629              */
630             if (needsRecursion && remainingWildcardDirectory.Length>0)
631             {
632                 // Find the next directory piece.
633                 string pattern = null;
634 
635                 if (remainingWildcardDirectory != recursiveDirectoryMatch)
636                 {
637                     int indexOfNextSlash = remainingWildcardDirectory.IndexOfAny(directorySeparatorCharacters);
638                     ErrorUtilities.VerifyThrow(indexOfNextSlash != -1, "Slash should be guaranteed.");
639 
640                     // Peel off the leftmost directory piece. So for example, if remainingWildcardDirectory
641                     // contains:
642                     //
643                     //        ?emp\foo\**\bar
644                     //
645                     // then put '?emp' into pattern. Then put the remaining part,
646                     //
647                     //        foo\**\bar
648                     //
649                     // back into remainingWildcardDirectory.
650                     // This is a performance optimization. We don't want to enumerate everything if we
651                     // don't have to.
652                     pattern = remainingWildcardDirectory.Substring(0, indexOfNextSlash);
653                     remainingWildcardDirectory = remainingWildcardDirectory.Substring(indexOfNextSlash + 1);
654 
655                     // If pattern turned into **, then there's no choice but to enumerate everything.
656                     if (pattern == recursiveDirectoryMatch)
657                     {
658                         pattern = null;
659                         remainingWildcardDirectory = recursiveDirectoryMatch;
660                     }
661                 }
662 
663                 // We never want to strip the project directory from the leaves, because the current
664                 // process directory maybe different
665                 string[] subdirs = getFileSystemEntries(FileSystemEntity.Directories, baseDirectory, pattern, null, false);
666                 foreach (string subdir in subdirs)
667                 {
668                     GetFilesRecursive(listOfFiles, subdir, remainingWildcardDirectory, filespec, extensionLengthToEnforce, regexFileMatch, true, projectDirectory, stripProjectDirectory, getFileSystemEntries);
669                 }
670             }
671         }
672 
673         /// <summary>
674         /// Given a file spec, create a regular expression that will match that
675         /// file spec.
676         ///
677         /// PERF WARNING: this method is called in performance-critical
678         /// scenarios, so keep it fast and cheap
679         /// </summary>
680         /// <param name="fixedDirectoryPart">The fixed directory part.</param>
681         /// <param name="wildcardDirectoryPart">The wildcard directory part.</param>
682         /// <param name="filenamePart">The filename part.</param>
683         /// <param name="isLegalFileSpec">Receives whether this pattern is legal or not.</param>
684         /// <returns>The regular expression string.</returns>
RegularExpressionFromFileSpec( string fixedDirectoryPart, string wildcardDirectoryPart, string filenamePart, out bool isLegalFileSpec )685         private static string RegularExpressionFromFileSpec
686         (
687             string fixedDirectoryPart,
688             string wildcardDirectoryPart,
689             string filenamePart,
690             out bool isLegalFileSpec
691         )
692         {
693             isLegalFileSpec = true;
694 
695             /*
696              * The code below uses tags in the form <:tag:> to encode special information
697              * while building the regular expression.
698              *
699              * This format was chosen because it's not a legal form for filespecs. If the
700              * filespec comes in with either "<:" or ":>", return isLegalFileSpec=false to
701              * prevent intrusion into the special processing.
702              */
703             if ((fixedDirectoryPart.IndexOf("<:", StringComparison.Ordinal) != -1) ||
704                 (fixedDirectoryPart.IndexOf(":>", StringComparison.Ordinal) != -1) ||
705                 (wildcardDirectoryPart.IndexOf("<:", StringComparison.Ordinal) != -1) ||
706                 (wildcardDirectoryPart.IndexOf(":>", StringComparison.Ordinal) != -1) ||
707                 (filenamePart.IndexOf("<:", StringComparison.Ordinal) != -1) ||
708                 (filenamePart.IndexOf(":>", StringComparison.Ordinal) != -1))
709             {
710                 isLegalFileSpec = false;
711                 return String.Empty;
712             }
713 
714             /*
715              * Its not legal for there to be a ".." after a wildcard.
716              */
717             if (wildcardDirectoryPart.Contains(dotdot))
718             {
719                 isLegalFileSpec = false;
720                 return String.Empty;
721             }
722 
723             /*
724              * Trailing dots in file names have to be treated specially.
725              * We want:
726              *
727              *     *. to match foo
728              *
729              * but 'foo' doesn't have a trailing '.' so we need to handle this while still being careful
730              * not to match 'foo.txt'
731              */
732             if (filenamePart.EndsWith(".", StringComparison.Ordinal))
733             {
734                 filenamePart = filenamePart.Replace("*", "<:anythingbutdot:>");
735                 filenamePart = filenamePart.Replace("?", "<:anysinglecharacterbutdot:>");
736                 filenamePart = filenamePart.Substring(0, filenamePart.Length - 1);
737             }
738 
739             /*
740              * Now, build up the starting filespec but put tags in to identify where the fixedDirectory,
741              * wildcardDirectory and filenamePart are. Also tag the beginning of the line and the end of
742              * the line, so that we can identify patterns by whether they're on one end or the other.
743              */
744             StringBuilder matchFileExpression = new StringBuilder();
745             matchFileExpression.Append("<:bol:>");
746             matchFileExpression.Append("<:fixeddir:>").Append(fixedDirectoryPart).Append("<:endfixeddir:>");
747             matchFileExpression.Append("<:wildcarddir:>").Append(wildcardDirectoryPart).Append("<:endwildcarddir:>");
748             matchFileExpression.Append("<:filename:>").Append(filenamePart).Append("<:endfilename:>");
749             matchFileExpression.Append("<:eol:>");
750 
751             /*
752              *  Call out our special matching characters.
753              */
754             matchFileExpression.Replace(directorySeparator, "<:dirseparator:>");
755             matchFileExpression.Replace(altDirectorySeparator, "<:dirseparator:>");
756 
757             /*
758              * Capture the leading \\ in UNC paths, so that the doubled slash isn't
759              * reduced in a later step.
760              */
761             matchFileExpression.Replace("<:fixeddir:><:dirseparator:><:dirseparator:>", "<:fixeddir:><:uncslashslash:>");
762 
763             /*
764              * Iteratively reduce four cases involving directory separators
765              *
766              *  (1) <:dirseparator:>.<:dirseparator:> -> <:dirseparator:>
767              *        This is an identity, so for example, these two are equivalent,
768              *
769              *            dir1\.\dir2 == dir1\dir2
770              *
771              *    (2) <:dirseparator:><:dirseparator:> -> <:dirseparator:>
772              *      Double directory separators are treated as a single directory separator,
773              *      so, for example, this is an identity:
774              *
775              *          f:\dir1\\dir2 == f:\dir1\dir2
776              *
777              *      The single exemption is for UNC path names, like this:
778              *
779              *          \\server\share != \server\share
780              *
781              *      This case is handled by the <:uncslashslash:> which was substituted in
782              *      a prior step.
783              *
784              *  (3) <:fixeddir:>.<:dirseparator:>.<:dirseparator:> -> <:fixeddir:>.<:dirseparator:>
785              *      A ".\" at the beginning of a line is equivalent to nothing, so:
786              *
787              *          .\.\dir1\file.txt == .\dir1\file.txt
788              *
789              *  (4) <:dirseparator:>.<:eol:> -> <:eol:>
790              *      A "\." at the end of a line is equivalent to nothing, so:
791              *
792              *          dir1\dir2\. == dir1\dir2             *
793              */
794             int sizeBefore;
795             do
796             {
797                 sizeBefore = matchFileExpression.Length;
798 
799                 // NOTE: all these replacements will necessarily reduce the expression length i.e. length will either reduce or
800                 // stay the same through this loop
801                 matchFileExpression.Replace("<:dirseparator:>.<:dirseparator:>", "<:dirseparator:>");
802                 matchFileExpression.Replace("<:dirseparator:><:dirseparator:>", "<:dirseparator:>");
803                 matchFileExpression.Replace("<:fixeddir:>.<:dirseparator:>.<:dirseparator:>", "<:fixeddir:>.<:dirseparator:>");
804                 matchFileExpression.Replace("<:dirseparator:>.<:endfilename:>", "<:endfilename:>");
805                 matchFileExpression.Replace("<:filename:>.<:endfilename:>", "<:filename:><:endfilename:>");
806 
807                 ErrorUtilities.VerifyThrow(matchFileExpression.Length <= sizeBefore,
808                     "Expression reductions cannot increase the length of the expression.");
809 
810             } while (matchFileExpression.Length < sizeBefore);
811 
812             /*
813              * Collapse **\** into **.
814              */
815             do
816             {
817                 sizeBefore = matchFileExpression.Length;
818                 matchFileExpression.Replace(recursiveDirectoryMatch + "<:dirseparator:>" + recursiveDirectoryMatch, recursiveDirectoryMatch);
819 
820                 ErrorUtilities.VerifyThrow(matchFileExpression.Length <= sizeBefore,
821                     "Expression reductions cannot increase the length of the expression.");
822 
823             } while (matchFileExpression.Length < sizeBefore);
824 
825             /*
826              * Call out legal recursion operators:
827              *
828              *        fixed-directory + **\
829              *        \**\
830              *        **\**
831              *
832              */
833             do
834             {
835                 sizeBefore = matchFileExpression.Length;
836                 matchFileExpression.Replace("<:dirseparator:>" + recursiveDirectoryMatch + "<:dirseparator:>", "<:middledirs:>");
837                 matchFileExpression.Replace("<:wildcarddir:>" + recursiveDirectoryMatch + "<:dirseparator:>", "<:wildcarddir:><:leftdirs:>");
838 
839                 ErrorUtilities.VerifyThrow(matchFileExpression.Length <= sizeBefore,
840                     "Expression reductions cannot increase the length of the expression.");
841 
842             } while (matchFileExpression.Length < sizeBefore);
843 
844 
845             /*
846              * By definition, "**" must appear alone between directory slashes. If there is any remaining "**" then this is not
847              * a valid filespec.
848              */
849             // NOTE: this condition is evaluated left-to-right -- this is important because we want the length BEFORE stripping
850             // any "**"s remaining in the expression
851             if (matchFileExpression.Length > matchFileExpression.Replace(recursiveDirectoryMatch, null).Length)
852             {
853                 isLegalFileSpec = false;
854                 return String.Empty;
855             }
856 
857             /*
858              * Remaining call-outs not involving "**"
859              */
860             matchFileExpression.Replace("*.*", "<:anynonseparator:>");
861             matchFileExpression.Replace("*", "<:anynonseparator:>");
862             matchFileExpression.Replace("?", "<:singlecharacter:>");
863 
864             /*
865              *  Escape all special characters defined for regular expresssions.
866              */
867             matchFileExpression.Replace("\\", "\\\\"); // Must be first.
868             matchFileExpression.Replace("$", "\\$");
869             matchFileExpression.Replace("(", "\\(");
870             matchFileExpression.Replace(")", "\\)");
871             matchFileExpression.Replace("*", "\\*");
872             matchFileExpression.Replace("+", "\\+");
873             matchFileExpression.Replace(".", "\\.");
874             matchFileExpression.Replace("[", "\\[");
875             matchFileExpression.Replace("?", "\\?");
876             matchFileExpression.Replace("^", "\\^");
877             matchFileExpression.Replace("{", "\\{");
878             matchFileExpression.Replace("|", "\\|");
879 
880             /*
881              *  Now, replace call-outs with their regex equivalents.
882              */
883             matchFileExpression.Replace("<:middledirs:>", "((/)|(\\\\)|(/.*/)|(/.*\\\\)|(\\\\.*\\\\)|(\\\\.*/))");
884             matchFileExpression.Replace("<:leftdirs:>", "((.*/)|(.*\\\\)|())");
885             matchFileExpression.Replace("<:rightdirs:>", ".*");
886             matchFileExpression.Replace("<:anything:>", ".*");
887             matchFileExpression.Replace("<:anythingbutdot:>", "[^\\.]*");
888             matchFileExpression.Replace("<:anysinglecharacterbutdot:>", "[^\\.].");
889             matchFileExpression.Replace("<:anynonseparator:>", "[^/\\\\]*");
890             matchFileExpression.Replace("<:singlecharacter:>", ".");
891             matchFileExpression.Replace("<:dirseparator:>", "[/\\\\]+");
892             matchFileExpression.Replace("<:uncslashslash:>", @"\\\\");
893             matchFileExpression.Replace("<:bol:>", "^");
894             matchFileExpression.Replace("<:eol:>", "$");
895             matchFileExpression.Replace("<:fixeddir:>", "(?<FIXEDDIR>");
896             matchFileExpression.Replace("<:endfixeddir:>", ")");
897             matchFileExpression.Replace("<:wildcarddir:>", "(?<WILDCARDDIR>");
898             matchFileExpression.Replace("<:endwildcarddir:>", ")");
899             matchFileExpression.Replace("<:filename:>", "(?<FILENAME>");
900             matchFileExpression.Replace("<:endfilename:>", ")");
901 
902             return matchFileExpression.ToString();
903         }
904 
905 
906 
907         /// <summary>
908         /// Given a filespec, get the information needed for file matching.
909         /// </summary>
910         /// <param name="filespec">The filespec.</param>
911         /// <param name="regexFileMatch">Receives the regular expression.</param>
912         /// <param name="needsRecursion">Receives the flag that is true if recursion is required.</param>
913         /// <param name="isLegalFileSpec">Receives the flag that is true if the filespec is legal.</param>
914         /// <param name="getFileSystemEntries">Delegate.</param>
GetFileSpecInfo( string filespec, out Regex regexFileMatch, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries )915         internal static void GetFileSpecInfo
916         (
917             string filespec,
918             out Regex regexFileMatch,
919             out bool needsRecursion,
920             out bool isLegalFileSpec,
921             GetFileSystemEntries getFileSystemEntries
922 
923         )
924         {
925             string fixedDirectoryPart;
926             string wildcardDirectoryPart;
927             string filenamePart;
928             string matchFileExpression;
929 
930             GetFileSpecInfo(filespec,
931                 out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart,
932                 out matchFileExpression, out needsRecursion, out isLegalFileSpec,
933                 getFileSystemEntries);
934 
935             if (isLegalFileSpec)
936             {
937                 regexFileMatch = new Regex(matchFileExpression, RegexOptions.IgnoreCase);
938             }
939             else
940             {
941                 regexFileMatch = null;
942             }
943         }
944 
945         /// <summary>
946         /// Given a filespec, get the information needed for file matching.
947         /// </summary>
948         /// <param name="filespec">The filespec.</param>
949         /// <param name="fixedDirectoryPart">Receives the fixed directory part.</param>
950         /// <param name="wildcardDirectoryPart">Receives the wildcard directory part.</param>
951         /// <param name="filenamePart">Receives the filename part.</param>
952         /// <param name="matchFileExpression">Receives the regular expression.</param>
953         /// <param name="needsRecursion">Receives the flag that is true if recursion is required.</param>
954         /// <param name="isLegalFileSpec">Receives the flag that is true if the filespec is legal.</param>
955         /// <param name="getFileSystemEntries">Delegate.</param>
GetFileSpecInfo( string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out string matchFileExpression, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries )956         private static void GetFileSpecInfo
957         (
958             string filespec,
959             out string fixedDirectoryPart,
960             out string wildcardDirectoryPart,
961             out string filenamePart,
962             out string matchFileExpression,
963             out bool needsRecursion,
964             out bool isLegalFileSpec,
965             GetFileSystemEntries getFileSystemEntries
966         )
967         {
968             isLegalFileSpec = true;
969             needsRecursion = false;
970             fixedDirectoryPart = String.Empty;
971             wildcardDirectoryPart = String.Empty;
972             filenamePart = String.Empty;
973             matchFileExpression = null;
974 
975             // bail out if filespec contains illegal characters
976             if (-1 != filespec.IndexOfAny(Path.GetInvalidPathChars()))
977             {
978                 isLegalFileSpec = false;
979                 return;
980             }
981 
982             /*
983              * Check for patterns in the filespec that are explicitly illegal.
984              *
985              * Any path with "..." in it is illegal.
986              */
987             if (-1 != filespec.IndexOf("...", StringComparison.Ordinal))
988             {
989                 isLegalFileSpec = false;
990                 return;
991             }
992 
993             /*
994              * If there is a ':' anywhere but the second character, this is an illegal pattern.
995              * Catches this case among others,
996              *
997              *        http://www.website.com
998              *
999              */
1000             int rightmostColon = filespec.LastIndexOf(":", StringComparison.Ordinal);
1001 
1002             if
1003             (
1004                 -1 != rightmostColon
1005                 && 1 != rightmostColon
1006             )
1007             {
1008                 isLegalFileSpec = false;
1009                 return;
1010             }
1011 
1012             /*
1013              * Now break up the filespec into constituent parts--fixed, wildcard and filename.
1014              */
1015             SplitFileSpec(filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, getFileSystemEntries);
1016 
1017             /*
1018              *  Get a regular expression for matching files that will be found.
1019              */
1020             matchFileExpression = RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart, out isLegalFileSpec);
1021 
1022             /*
1023              * Was the filespec valid? If not, then just return now.
1024              */
1025             if (!isLegalFileSpec)
1026             {
1027                 return;
1028             }
1029 
1030             /*
1031              * Determine whether recursion will be required.
1032              */
1033             needsRecursion = (wildcardDirectoryPart.Length != 0);
1034         }
1035 
1036         /// <summary>
1037         /// The results of a match between a filespec and a file name.
1038         /// </summary>
1039         internal sealed class Result
1040         {
1041             /// <summary>
1042             /// Default constructor.
1043             /// </summary>
Result()1044             internal Result()
1045             {
1046                 // do nothing
1047             }
1048 
1049             internal bool isLegalFileSpec = false;
1050             internal bool isMatch = false;
1051             internal bool isFileSpecRecursive = false;
1052             internal string fixedDirectoryPart = String.Empty;
1053             internal string wildcardDirectoryPart = String.Empty;
1054             internal string filenamePart = String.Empty;
1055         }
1056 
1057         /// <summary>
1058         /// Given a pattern (filespec) and a candidate filename (fileToMatch)
1059         /// return matching information.
1060         /// </summary>
1061         /// <param name="filespec">The filespec.</param>
1062         /// <param name="fileToMatch">The candidate to match against.</param>
1063         /// <returns>The result class.</returns>
FileMatch( string filespec, string fileToMatch )1064         internal static Result FileMatch
1065         (
1066             string filespec,
1067             string fileToMatch
1068         )
1069         {
1070             return FileMatch(filespec, fileToMatch, defaultGetFileSystemEntries);
1071         }
1072 
1073         /// <summary>
1074         /// Given a pattern (filespec) and a candidate filename (fileToMatch)
1075         /// return matching information.
1076         /// </summary>
1077         /// <param name="filespec">The filespec.</param>
1078         /// <param name="fileToMatch">The candidate to match against.</param>
1079         /// <param name="getFileSystemEntries">Delegate.</param>
1080         /// <returns>The result class.</returns>
FileMatch( string filespec, string fileToMatch, GetFileSystemEntries getFileSystemEntries )1081         internal static Result FileMatch
1082         (
1083             string filespec,
1084             string fileToMatch,
1085             GetFileSystemEntries getFileSystemEntries
1086         )
1087         {
1088             Result matchResult = new Result();
1089 
1090             fileToMatch = GetLongPathName(fileToMatch, getFileSystemEntries);
1091 
1092             Regex regexFileMatch;
1093             GetFileSpecInfo
1094             (
1095                 filespec,
1096                 out regexFileMatch,
1097                 out matchResult.isFileSpecRecursive,
1098                 out matchResult.isLegalFileSpec,
1099                 getFileSystemEntries
1100             );
1101 
1102             if (matchResult.isLegalFileSpec)
1103             {
1104                 Match match = regexFileMatch.Match(fileToMatch);
1105                 matchResult.isMatch = match.Success;
1106 
1107                 if (matchResult.isMatch)
1108                 {
1109                     matchResult.fixedDirectoryPart = match.Groups["FIXEDDIR"].Value;
1110                     matchResult.wildcardDirectoryPart = match.Groups["WILDCARDDIR"].Value;
1111                     matchResult.filenamePart = match.Groups["FILENAME"].Value;
1112                 }
1113             }
1114 
1115             return matchResult;
1116         }
1117 
1118         /// <summary>
1119         /// Given a filespec, find the files that match.
1120         /// </summary>
1121         /// <param name="filespec">Get files that match the given file spec.</param>
1122         /// <returns>The array of files.</returns>
GetFiles( string projectDirectory, string filespec )1123         internal static string[] GetFiles
1124         (
1125             string projectDirectory,
1126             string filespec
1127         )
1128         {
1129             string[] files = GetFiles(projectDirectory, filespec, defaultGetFileSystemEntries, defaultDirectoryExists);
1130             return files;
1131         }
1132 
1133         /// <summary>
1134         /// Given a filespec, find the files that match.
1135         /// </summary>
1136         /// <param name="filespec">Get files that match the given file spec.</param>
1137         /// <param name="getFileSystemEntries">Get files that match the given file spec.</param>
1138         /// <param name="directoryExists">Determine whether a directory exists.</param>
1139         /// <returns>The array of files.</returns>
GetFiles( string projectDirectory, string filespec, GetFileSystemEntries getFileSystemEntries, DirectoryExists directoryExists )1140         internal static string[] GetFiles
1141         (
1142             string projectDirectory,
1143             string filespec,
1144             GetFileSystemEntries getFileSystemEntries,
1145             DirectoryExists directoryExists
1146         )
1147         {
1148             // For performance. Short-circuit iff there is no wildcard.
1149             // Perf Note: Doing a [Last]IndexOfAny(...) is much faster than compiling a
1150             // regular expression that does the same thing, regardless of whether
1151             // filespec contains one of the characters.
1152             // Choose LastIndexOfAny instead of IndexOfAny because it seems more likely
1153             // that wildcards will tend to be towards the right side.
1154             if (!HasWildcards(filespec))
1155             {
1156                 return new string[] { filespec };
1157             }
1158 
1159             /*
1160              * Even though we return a string[] we work internally with an IList.
1161              * This is because it's cheaper to add items to an IList and this code
1162              * might potentially do a lot of that.
1163              */
1164             System.Collections.ArrayList arrayListOfFiles = new System.Collections.ArrayList();
1165             System.Collections.IList listOfFiles = (System.Collections.IList) arrayListOfFiles;
1166 
1167             /*
1168              * Analyze the file spec and get the information we need to do the matching.
1169              */
1170             string fixedDirectoryPart;
1171             string wildcardDirectoryPart;
1172             string filenamePart;
1173             string matchFileExpression;
1174             bool needsRecursion;
1175             bool isLegalFileSpec;
1176             GetFileSpecInfo
1177             (
1178                 filespec,
1179                 out fixedDirectoryPart,
1180                 out wildcardDirectoryPart,
1181                 out filenamePart,
1182                 out matchFileExpression,
1183                 out needsRecursion,
1184                 out isLegalFileSpec,
1185                 getFileSystemEntries
1186             );
1187 
1188             /*
1189              * If the filespec is invalid, then just return now.
1190              */
1191             if (!isLegalFileSpec)
1192             {
1193                 return new string[] { filespec };
1194             }
1195 
1196             // The projectDirectory is not null only if we are running the evaluation from
1197             // inside the engine (i.e. not from a task)
1198             bool stripProjectDirectory = false;
1199             if (projectDirectory != null)
1200             {
1201                 if (fixedDirectoryPart != null)
1202                 {
1203                     string oldFixedDirectoryPart = fixedDirectoryPart;
1204                     try
1205                     {
1206                         fixedDirectoryPart = Path.Combine(projectDirectory, fixedDirectoryPart);
1207                     }
1208                     catch (ArgumentException)
1209                     {
1210                         return new string[0];
1211                     }
1212 
1213                     stripProjectDirectory = !String.Equals(fixedDirectoryPart, oldFixedDirectoryPart, StringComparison.OrdinalIgnoreCase);
1214                 }
1215                 else
1216                 {
1217                     fixedDirectoryPart = projectDirectory;
1218                     stripProjectDirectory = true;
1219                 }
1220             }
1221 
1222             /*
1223              * If the fixed directory part doesn't exist, then this means no files should be
1224              * returned.
1225              */
1226             if (fixedDirectoryPart.Length > 0 && !directoryExists(fixedDirectoryPart))
1227             {
1228                 return new string[0];
1229             }
1230 
1231             // determine if we need to use the regular expression to match the files
1232             // PERF NOTE: Constructing a Regex object is expensive, so we avoid it whenever possible
1233             bool matchWithRegex =
1234                 // if we have a directory specification that uses wildcards, and
1235                 (wildcardDirectoryPart.Length > 0) &&
1236                 // the specification is not a simple "**"
1237                 (wildcardDirectoryPart != (recursiveDirectoryMatch + directorySeparator));
1238                 // then we need to use the regular expression
1239 
1240             // if we're not using the regular expression, get the file pattern extension
1241             string extensionPart = matchWithRegex
1242                 ? null
1243                 : Path.GetExtension(filenamePart);
1244 
1245             // check if the file pattern would cause Windows to match more loosely on the extension
1246             // NOTE: Windows matches loosely in two cases (in the absence of the * wildcard in the extension):
1247             // 1) if the extension ends with the ? wildcard, it matches files with shorter extensions also e.g. "file.tx?" would
1248             //    match both "file.txt" and "file.tx"
1249             // 2) if the extension is three characters, and the filename contains the * wildcard, it matches files with longer
1250             //    extensions that start with the same three characters e.g. "*.htm" would match both "file.htm" and "file.html"
1251             bool needToEnforceExtensionLength =
1252                     (extensionPart != null) &&
1253                     (extensionPart.IndexOf('*') == -1)
1254                 &&
1255                     (extensionPart.EndsWith("?", StringComparison.Ordinal)
1256                 ||
1257                     ((extensionPart.Length == (3 + 1 /* +1 for the period */)) &&
1258                     (filenamePart.IndexOf('*') != -1)));
1259 
1260             /*
1261              * Now get the files that match, starting at the lowest fixed directory.
1262              */
1263             GetFilesRecursive(listOfFiles, fixedDirectoryPart, wildcardDirectoryPart,
1264                 // if using the regular expression, ignore the file pattern
1265                 (matchWithRegex ? null : filenamePart), (needToEnforceExtensionLength ? extensionPart.Length : 0),
1266                 // if using the file pattern, ignore the regular expression
1267                 (matchWithRegex ? new Regex(matchFileExpression, RegexOptions.IgnoreCase) : null),
1268                 needsRecursion, projectDirectory, stripProjectDirectory, getFileSystemEntries);
1269 
1270             /*
1271              * Build the return array.
1272              */
1273             string[] files = (string[])arrayListOfFiles.ToArray(typeof(string));
1274             return files;
1275         }
1276     }
1277 }
1278