1 //------------------------------------------------------------------------------
2 // <copyright file="Wildcard.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 * wildcard wrappers for Regex
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 ?
16 * Copyright (c) 1999, Microsoft Corporation
18 namespace System.Web.Util {
19 using System.Runtime.Serialization.Formatters;
20 using System.Text.RegularExpressions;
25 * Wildcard patterns have three metacharacters:
27 * A ? is equivalent to .
28 * A * is equivalent to .*
29 * A , is equivalent to |
31 * Note that by each alternative is surrounded by \A...\z to anchor
32 * at the edges of the string.
34 internal class Wildcard {
36 internal /*public*/ Wildcard(String pattern) : this (pattern, false) {
40 internal /*public*/ Wildcard(String pattern, bool caseInsensitive) {
42 _caseInsensitive = caseInsensitive;
45 internal String _pattern;
46 internal bool _caseInsensitive;
47 internal Regex _regex;
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("(?=[\\\\:])");
57 * IsMatch returns true if the input is an exact match for the
60 internal /*public*/ bool IsMatch(String input) {
63 bool result = _regex.IsMatch(input);
68 internal /*public*/ String Pattern {
75 * Builds the matching regex when needed
77 protected void EnsureRegex() {
78 // threadsafe without protection because of gc
83 _regex = RegexFromWildcard(_pattern, _caseInsensitive);
87 * Basic wildcard -> Regex conversion, no slashes
89 protected virtual Regex RegexFromWildcard(String pattern, bool caseInsensitive) {
90 RegexOptions options = RegexOptions.None;
92 // match right-to-left (for speed) if the pattern starts with a *
94 if (pattern.Length > 0 && pattern[0] == '*')
95 options = RegexOptions.RightToLeft | RegexOptions.Singleline;
97 options = RegexOptions.Singleline;
102 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
104 // Remove regex metacharacters
106 pattern = metaRegex.Replace(pattern, "\\$0");
108 // Replace wildcard metacharacters with regex codes
110 pattern = questRegex.Replace(pattern, ".");
111 pattern = starRegex.Replace(pattern, ".*");
112 pattern = commaRegex.Replace(pattern, "\\z|\\A");
114 // anchor the pattern at beginning and end, and return the regex
116 return new Regex("\\A" + pattern + "\\z", options);
120 abstract internal class WildcardPath : Wildcard {
122 internal /*public*/ WildcardPath(String pattern) : base(pattern) {
125 private Regex[][] _dirs;
128 internal /*public*/ WildcardPath(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) {
131 private Regex _suffix;
134 * IsSuffix returns true if a suffix of the input is an exact
135 * match for the wildcard pattern.
137 internal /*public*/ bool IsSuffix(String input) {
139 return _suffix.IsMatch(input);
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).
149 internal /*public*/ bool AllowPrefix(String prefix) {
150 String[] dirs = SplitDirs(prefix);
154 for (int i = 0; i < _dirs.Length; i++) {
155 // pattern is shorter than prefix: reject
156 if (_dirs[i].Length < dirs.Length)
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]))
165 // one alternative passed: we pass.
177 * Builds the matching regex array when needed
179 protected void EnsureDirs() {
180 // threadsafe without protection because of gc
185 _dirs = DirsFromWildcard(_pattern);
190 * Builds the matching regex when needed
192 protected void EnsureSuffix() {
193 // threadsafe without protection because of gc
198 _suffix = SuffixFromWildcard(_pattern, _caseInsensitive);
203 * Specialize for forward-slash and backward-slash cases
205 protected abstract Regex SuffixFromWildcard(String pattern, bool caseInsensitive);
206 protected abstract Regex[][] DirsFromWildcard(String pattern);
207 protected abstract String[] SplitDirs(String input);
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.
217 internal class WildcardUrl : WildcardPath {
219 internal /*public*/ WildcardUrl(String pattern) : base(pattern) {
222 internal /*public*/ WildcardUrl(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) {
225 protected override String[] SplitDirs(String input) {
226 return slashRegex.Split(input);
229 protected override Regex RegexFromWildcard(String pattern, bool caseInsensitive) {
230 RegexOptions options;
232 // match right-to-left (for speed) if the pattern starts with a *
234 if (pattern.Length > 0 && pattern[0] == '*')
235 options = RegexOptions.RightToLeft;
237 options = RegexOptions.None;
239 // case insensitivity
242 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
244 // Remove regex metacharacters
246 pattern = metaRegex.Replace(pattern, "\\$0");
248 // Replace wildcard metacharacters with regex codes
250 pattern = questRegex.Replace(pattern, "[^/]");
251 pattern = starRegex.Replace(pattern, "[^/]*");
252 pattern = commaRegex.Replace(pattern, "\\z|\\A");
254 // anchor the pattern at beginning and end, and return the regex
256 return new Regex("\\A" + pattern + "\\z", options);
259 protected override Regex SuffixFromWildcard(String pattern, bool caseInsensitive) {
260 RegexOptions options;
262 // match right-to-left (for speed)
264 options = RegexOptions.RightToLeft;
266 // case insensitivity
269 options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
271 // Remove regex metacharacters
273 pattern = metaRegex.Replace(pattern, "\\$0");
275 // Replace wildcard metacharacters with regex codes
277 pattern = questRegex.Replace(pattern, "[^/]");
278 pattern = starRegex.Replace(pattern, "[^/]*");
279 pattern = commaRegex.Replace(pattern, "\\z|(?:\\A|(?<=/))");
281 // anchor the pattern at beginning and end, and return the regex
283 return new Regex("(?:\\A|(?<=/))" + pattern + "\\z", options);
286 protected override Regex[][] DirsFromWildcard(String pattern) {
287 String[] alts = commaRegex.Split(pattern);
288 Regex[][] dirs = new Regex[alts.Length][];
290 for (int i = 0; i < alts.Length; i++) {
291 String[] dirpats = slashRegex.Split(alts[i]);
293 Regex[] dirregex = new Regex[dirpats.Length];
295 if (alts.Length == 1 && dirpats.Length == 1) {
296 // common case: no commas, no slashes: dir regex is same as top regex.
299 dirregex[0] = _regex;
302 for (int j = 0; j < dirpats.Length; j++) {
303 dirregex[j] = RegexFromWildcard(dirpats[j], _caseInsensitive);