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