1 //------------------------------------------------------------------------------
2 // <copyright file="Wildcard.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 /*
8  * Wildcard
9  *
10  * wildcard wrappers for Regex
11  *
12  * (1) Wildcard does straight string wildcarding with no path separator awareness
13  * (2) WildcardUrl recognizes that forward / slashes are special and can't match * or ?
14  * (3) WildcardDos recognizes that backward \ and : are special and can't match * or ?
15  *
16  * Copyright (c) 1999, Microsoft Corporation
17  */
18 namespace System.Web.Util {
19     using System.Runtime.Serialization.Formatters;
20     using System.Text.RegularExpressions;
21 
22     /*
23      * Wildcard
24      *
25      * Wildcard patterns have three metacharacters:
26      *
27      * A ? is equivalent to .
28      * A * is equivalent to .*
29      * A , is equivalent to |
30      *
31      * Note that by each alternative is surrounded by \A...\z to anchor
32      * at the edges of the string.
33      */
34     internal class Wildcard {
35 #if NOT_USED
Wildcard(String pattern)36         internal /*public*/ Wildcard(String pattern) : this (pattern, false) {
37         }
38 #endif
39 
Wildcard(String pattern, bool caseInsensitive)40         internal /*public*/ Wildcard(String pattern, bool caseInsensitive) {
41             _pattern = pattern;
42             _caseInsensitive = caseInsensitive;
43         }
44 
45         internal String _pattern;
46         internal bool _caseInsensitive;
47         internal Regex _regex;
48 
49         protected static Regex metaRegex = new Regex("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]");
50         protected static Regex questRegex = new Regex("\\?");
51         protected static Regex starRegex = new Regex("\\*");
52         protected static Regex commaRegex = new Regex(",");
53         protected static Regex slashRegex = new Regex("(?=/)");
54         protected static Regex backslashRegex = new Regex("(?=[\\\\:])");
55 
56         /*
57          * IsMatch returns true if the input is an exact match for the
58          * wildcard pattern.
59          */
IsMatch(String input)60         internal /*public*/ bool IsMatch(String input) {
61             EnsureRegex();
62 
63             bool result =  _regex.IsMatch(input);
64 
65             return result;
66         }
67 #if DONT_COMPILE
68         internal /*public*/ String Pattern {
69             get {
70                 return _pattern;
71             }
72         }
73 #endif
74         /*
75          * Builds the matching regex when needed
76          */
EnsureRegex()77         protected void EnsureRegex() {
78             // threadsafe without protection because of gc
79 
80             if (_regex != null)
81                 return;
82 
83             _regex = RegexFromWildcard(_pattern, _caseInsensitive);
84         }
85 
86         /*
87          * Basic wildcard -> Regex conversion, no slashes
88          */
RegexFromWildcard(String pattern, bool caseInsensitive)89         protected virtual Regex RegexFromWildcard(String pattern, bool caseInsensitive) {
90             RegexOptions options = RegexOptions.None;
91 
92             // match right-to-left (for speed) if the pattern starts with a *
93 
94             if (pattern.Length > 0 && pattern[0] == '*')
95                 options = RegexOptions.RightToLeft | RegexOptions.Singleline;
96             else
97                 options = RegexOptions.Singleline;
98 
99             // case insensitivity
100 
101             if (caseInsensitive)
102                 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
103 
104             // Remove regex metacharacters
105 
106             pattern = metaRegex.Replace(pattern, "\\$0");
107 
108             // Replace wildcard metacharacters with regex codes
109 
110             pattern = questRegex.Replace(pattern, ".");
111             pattern = starRegex.Replace(pattern, ".*");
112             pattern = commaRegex.Replace(pattern, "\\z|\\A");
113 
114             // anchor the pattern at beginning and end, and return the regex
115 
116             return new Regex("\\A" + pattern + "\\z", options);
117         }
118     }
119 
120     abstract internal class WildcardPath : Wildcard {
121 #if NOT_USED
WildcardPath(String pattern)122         internal /*public*/ WildcardPath(String pattern) : base(pattern) {
123         }
124 
125         private Regex[][] _dirs;
126 #endif
127 
WildcardPath(String pattern, bool caseInsensitive)128         internal /*public*/ WildcardPath(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) {
129         }
130 
131         private Regex _suffix;
132 
133         /*
134          * IsSuffix returns true if a suffix of the input is an exact
135          * match for the wildcard pattern.
136          */
IsSuffix(String input)137         internal /*public*/ bool IsSuffix(String input) {
138             EnsureSuffix();
139             return _suffix.IsMatch(input);
140         }
141 
142 #if NOT_USED
143         /*
144          * AllowPrefix returns true if the input is an exact match for
145          * a prefix-directory of the wildcard pattern (i.e., if it
146          * is possible to match the wildcard pattern by adding
147          * more subdirectories or a filename at the end of the path).
148          */
AllowPrefix(String prefix)149         internal /*public*/ bool AllowPrefix(String prefix) {
150             String[] dirs = SplitDirs(prefix);
151 
152             EnsureDirs();
153 
154             for (int i = 0; i < _dirs.Length; i++) {
155                 // pattern is shorter than prefix: reject
156                 if (_dirs[i].Length < dirs.Length)
157                     goto NextAlt;
158 
159                 for (int j = 0; j < dirs.Length; j++) {
160                     // the jth directory doesn't match; path is not a prefix
161                     if (!_dirs[i][j].IsMatch(dirs[j]))
162                         goto NextAlt;
163                 }
164 
165                 // one alternative passed: we pass.
166 
167                 return true;
168 
169                 NextAlt:
170                 ;
171             }
172 
173             return false;
174         }
175 
176         /*
177          * Builds the matching regex array when needed
178          */
EnsureDirs()179         protected void EnsureDirs() {
180             // threadsafe without protection because of gc
181 
182             if (_dirs != null)
183                 return;
184 
185             _dirs = DirsFromWildcard(_pattern);
186         }
187 #endif
188 
189         /*
190          * Builds the matching regex when needed
191          */
EnsureSuffix()192         protected void EnsureSuffix() {
193             // threadsafe without protection because of gc
194 
195             if (_suffix != null)
196                 return;
197 
198             _suffix = SuffixFromWildcard(_pattern, _caseInsensitive);
199         }
200 
201 
202         /*
203          * Specialize for forward-slash and backward-slash cases
204          */
SuffixFromWildcard(String pattern, bool caseInsensitive)205         protected abstract Regex SuffixFromWildcard(String pattern, bool caseInsensitive);
DirsFromWildcard(String pattern)206         protected abstract Regex[][] DirsFromWildcard(String pattern);
SplitDirs(String input)207         protected abstract String[] SplitDirs(String input);
208     }
209 
210     /*
211      * WildcardUrl
212      *
213      * The twist is that * and ? cannot match forward slashes,
214      * and we can do an exact suffix match that starts after
215      * any /, and we can also do a prefix prune.
216      */
217     internal class WildcardUrl : WildcardPath {
218 #if NOT_USED
WildcardUrl(String pattern)219         internal /*public*/ WildcardUrl(String pattern) : base(pattern) {
220         }
221 #endif
WildcardUrl(String pattern, bool caseInsensitive)222         internal /*public*/ WildcardUrl(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) {
223         }
224 
SplitDirs(String input)225         protected override String[] SplitDirs(String input) {
226             return slashRegex.Split(input);
227         }
228 
RegexFromWildcard(String pattern, bool caseInsensitive)229         protected override Regex RegexFromWildcard(String pattern, bool caseInsensitive) {
230             RegexOptions options;
231 
232             // match right-to-left (for speed) if the pattern starts with a *
233 
234             if (pattern.Length > 0 && pattern[0] == '*')
235                 options = RegexOptions.RightToLeft;
236             else
237                 options = RegexOptions.None;
238 
239             // case insensitivity
240 
241             if (caseInsensitive)
242                 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
243 
244             // Remove regex metacharacters
245 
246             pattern = metaRegex.Replace(pattern, "\\$0");
247 
248             // Replace wildcard metacharacters with regex codes
249 
250             pattern = questRegex.Replace(pattern, "[^/]");
251             pattern = starRegex.Replace(pattern, "[^/]*");
252             pattern = commaRegex.Replace(pattern, "\\z|\\A");
253 
254             // anchor the pattern at beginning and end, and return the regex
255 
256             return new Regex("\\A" + pattern + "\\z", options);
257         }
258 
SuffixFromWildcard(String pattern, bool caseInsensitive)259         protected override Regex SuffixFromWildcard(String pattern, bool caseInsensitive) {
260             RegexOptions options;
261 
262             // match right-to-left (for speed)
263 
264             options = RegexOptions.RightToLeft;
265 
266             // case insensitivity
267 
268             if (caseInsensitive)
269                 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
270 
271             // Remove regex metacharacters
272 
273             pattern = metaRegex.Replace(pattern, "\\$0");
274 
275             // Replace wildcard metacharacters with regex codes
276 
277             pattern = questRegex.Replace(pattern, "[^/]");
278             pattern = starRegex.Replace(pattern, "[^/]*");
279             pattern = commaRegex.Replace(pattern, "\\z|(?:\\A|(?<=/))");
280 
281             // anchor the pattern at beginning and end, and return the regex
282 
283             return new Regex("(?:\\A|(?<=/))" + pattern + "\\z", options);
284         }
285 
DirsFromWildcard(String pattern)286         protected override Regex[][] DirsFromWildcard(String pattern) {
287             String[] alts = commaRegex.Split(pattern);
288             Regex[][] dirs = new Regex[alts.Length][];
289 
290             for (int i = 0; i < alts.Length; i++) {
291                 String[] dirpats = slashRegex.Split(alts[i]);
292 
293                 Regex[] dirregex = new Regex[dirpats.Length];
294 
295                 if (alts.Length == 1 && dirpats.Length == 1) {
296                     // common case: no commas, no slashes: dir regex is same as top regex.
297 
298                     EnsureRegex();
299                     dirregex[0] = _regex;
300                 }
301                 else {
302                     for (int j = 0; j < dirpats.Length; j++) {
303                         dirregex[j] = RegexFromWildcard(dirpats[j], _caseInsensitive);
304                     }
305                 }
306 
307                 dirs[i] = dirregex;
308             }
309 
310             return dirs;
311         }
312     }
313 }
314