//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * Wildcard * * wildcard wrappers for Regex * * (1) Wildcard does straight string wildcarding with no path separator awareness * (2) WildcardUrl recognizes that forward / slashes are special and can't match * or ? * (3) WildcardDos recognizes that backward \ and : are special and can't match * or ? * * Copyright (c) 1999, Microsoft Corporation */ namespace System.Web.Util { using System.Runtime.Serialization.Formatters; using System.Text.RegularExpressions; /* * Wildcard * * Wildcard patterns have three metacharacters: * * A ? is equivalent to . * A * is equivalent to .* * A , is equivalent to | * * Note that by each alternative is surrounded by \A...\z to anchor * at the edges of the string. */ internal class Wildcard { #if NOT_USED internal /*public*/ Wildcard(String pattern) : this (pattern, false) { } #endif internal /*public*/ Wildcard(String pattern, bool caseInsensitive) { _pattern = pattern; _caseInsensitive = caseInsensitive; } internal String _pattern; internal bool _caseInsensitive; internal Regex _regex; protected static Regex metaRegex = new Regex("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]"); protected static Regex questRegex = new Regex("\\?"); protected static Regex starRegex = new Regex("\\*"); protected static Regex commaRegex = new Regex(","); protected static Regex slashRegex = new Regex("(?=/)"); protected static Regex backslashRegex = new Regex("(?=[\\\\:])"); /* * IsMatch returns true if the input is an exact match for the * wildcard pattern. */ internal /*public*/ bool IsMatch(String input) { EnsureRegex(); bool result = _regex.IsMatch(input); return result; } #if DONT_COMPILE internal /*public*/ String Pattern { get { return _pattern; } } #endif /* * Builds the matching regex when needed */ protected void EnsureRegex() { // threadsafe without protection because of gc if (_regex != null) return; _regex = RegexFromWildcard(_pattern, _caseInsensitive); } /* * Basic wildcard -> Regex conversion, no slashes */ protected virtual Regex RegexFromWildcard(String pattern, bool caseInsensitive) { RegexOptions options = RegexOptions.None; // match right-to-left (for speed) if the pattern starts with a * if (pattern.Length > 0 && pattern[0] == '*') options = RegexOptions.RightToLeft | RegexOptions.Singleline; else options = RegexOptions.Singleline; // case insensitivity if (caseInsensitive) options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; // Remove regex metacharacters pattern = metaRegex.Replace(pattern, "\\$0"); // Replace wildcard metacharacters with regex codes pattern = questRegex.Replace(pattern, "."); pattern = starRegex.Replace(pattern, ".*"); pattern = commaRegex.Replace(pattern, "\\z|\\A"); // anchor the pattern at beginning and end, and return the regex return new Regex("\\A" + pattern + "\\z", options); } } abstract internal class WildcardPath : Wildcard { #if NOT_USED internal /*public*/ WildcardPath(String pattern) : base(pattern) { } private Regex[][] _dirs; #endif internal /*public*/ WildcardPath(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) { } private Regex _suffix; /* * IsSuffix returns true if a suffix of the input is an exact * match for the wildcard pattern. */ internal /*public*/ bool IsSuffix(String input) { EnsureSuffix(); return _suffix.IsMatch(input); } #if NOT_USED /* * AllowPrefix returns true if the input is an exact match for * a prefix-directory of the wildcard pattern (i.e., if it * is possible to match the wildcard pattern by adding * more subdirectories or a filename at the end of the path). */ internal /*public*/ bool AllowPrefix(String prefix) { String[] dirs = SplitDirs(prefix); EnsureDirs(); for (int i = 0; i < _dirs.Length; i++) { // pattern is shorter than prefix: reject if (_dirs[i].Length < dirs.Length) goto NextAlt; for (int j = 0; j < dirs.Length; j++) { // the jth directory doesn't match; path is not a prefix if (!_dirs[i][j].IsMatch(dirs[j])) goto NextAlt; } // one alternative passed: we pass. return true; NextAlt: ; } return false; } /* * Builds the matching regex array when needed */ protected void EnsureDirs() { // threadsafe without protection because of gc if (_dirs != null) return; _dirs = DirsFromWildcard(_pattern); } #endif /* * Builds the matching regex when needed */ protected void EnsureSuffix() { // threadsafe without protection because of gc if (_suffix != null) return; _suffix = SuffixFromWildcard(_pattern, _caseInsensitive); } /* * Specialize for forward-slash and backward-slash cases */ protected abstract Regex SuffixFromWildcard(String pattern, bool caseInsensitive); protected abstract Regex[][] DirsFromWildcard(String pattern); protected abstract String[] SplitDirs(String input); } /* * WildcardUrl * * The twist is that * and ? cannot match forward slashes, * and we can do an exact suffix match that starts after * any /, and we can also do a prefix prune. */ internal class WildcardUrl : WildcardPath { #if NOT_USED internal /*public*/ WildcardUrl(String pattern) : base(pattern) { } #endif internal /*public*/ WildcardUrl(String pattern, bool caseInsensitive) : base(pattern, caseInsensitive) { } protected override String[] SplitDirs(String input) { return slashRegex.Split(input); } protected override Regex RegexFromWildcard(String pattern, bool caseInsensitive) { RegexOptions options; // match right-to-left (for speed) if the pattern starts with a * if (pattern.Length > 0 && pattern[0] == '*') options = RegexOptions.RightToLeft; else options = RegexOptions.None; // case insensitivity if (caseInsensitive) options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; // Remove regex metacharacters pattern = metaRegex.Replace(pattern, "\\$0"); // Replace wildcard metacharacters with regex codes pattern = questRegex.Replace(pattern, "[^/]"); pattern = starRegex.Replace(pattern, "[^/]*"); pattern = commaRegex.Replace(pattern, "\\z|\\A"); // anchor the pattern at beginning and end, and return the regex return new Regex("\\A" + pattern + "\\z", options); } protected override Regex SuffixFromWildcard(String pattern, bool caseInsensitive) { RegexOptions options; // match right-to-left (for speed) options = RegexOptions.RightToLeft; // case insensitivity if (caseInsensitive) options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; // Remove regex metacharacters pattern = metaRegex.Replace(pattern, "\\$0"); // Replace wildcard metacharacters with regex codes pattern = questRegex.Replace(pattern, "[^/]"); pattern = starRegex.Replace(pattern, "[^/]*"); pattern = commaRegex.Replace(pattern, "\\z|(?:\\A|(?<=/))"); // anchor the pattern at beginning and end, and return the regex return new Regex("(?:\\A|(?<=/))" + pattern + "\\z", options); } protected override Regex[][] DirsFromWildcard(String pattern) { String[] alts = commaRegex.Split(pattern); Regex[][] dirs = new Regex[alts.Length][]; for (int i = 0; i < alts.Length; i++) { String[] dirpats = slashRegex.Split(alts[i]); Regex[] dirregex = new Regex[dirpats.Length]; if (alts.Length == 1 && dirpats.Length == 1) { // common case: no commas, no slashes: dir regex is same as top regex. EnsureRegex(); dirregex[0] = _regex; } else { for (int j = 0; j < dirpats.Length; j++) { dirregex[j] = RegexFromWildcard(dirpats[j], _caseInsensitive); } } dirs[i] = dirregex; } return dirs; } } }