/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Globalization; using System.Text.RegularExpressions; using dnlib.DotNet; namespace dnSpy.Contracts.Search { /// /// factory /// static class SearchComparerFactory { /// /// Creates a /// /// Search text /// true if case sensitive /// true to match whole words /// true to match any word /// public static ISearchComparer Create(string searchText, bool caseSensitive, bool matchWholeWords, bool matchAnyWords) { var regex = TryCreateRegEx(searchText, caseSensitive); if (regex is not null) return new RegExSearchComparer(regex); var searchTerms = searchText.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (matchAnyWords) return new OrSearchComparer(searchTerms, caseSensitive, matchWholeWords); return new AndSearchComparer(searchTerms, caseSensitive, matchWholeWords); } /// /// Creates a that compares literals /// /// Search text /// true if case sensitive /// true to match whole words /// true to match any word /// public static ISearchComparer CreateLiteral(string searchText, bool caseSensitive, bool matchWholeWords, bool matchAnyWords) { var s = searchText.Trim(); var val64 = TryParseInt64(s); if (val64 is not null) return new IntegerLiteralSearchComparer(val64.Value); var uval64 = TryParseUInt64(s); if (uval64 is not null) return new IntegerLiteralSearchComparer(unchecked((long)uval64.Value)); if (double.TryParse(s, out double dbl)) return new DoubleLiteralSearchComparer(dbl); if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"') s = s.Substring(1, s.Length - 2); else { var regex = TryCreateRegEx(s, caseSensitive); if (regex is not null) return new RegExStringLiteralSearchComparer(regex); } return new StringLiteralSearchComparer(s, caseSensitive, matchWholeWords); } static Regex? TryCreateRegEx(string s, bool caseSensitive) { s = s.Trim(); if (s.Length > 2 && s[0] == '/' && s[s.Length - 1] == '/') { var regexOpts = RegexOptions.Compiled; if (!caseSensitive) regexOpts |= RegexOptions.IgnoreCase; try { return new Regex(s.Substring(1, s.Length - 2), regexOpts); } catch (ArgumentException) { } } return null; } static long? TryParseInt64(string s) { bool isSigned = s.StartsWith("-", StringComparison.OrdinalIgnoreCase); if (isSigned) s = s.Substring(1); if (s != s.Trim()) return null; ulong val; if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { s = s.Substring(2); if (s != s.Trim()) return null; if (!ulong.TryParse(s, NumberStyles.HexNumber, null, out val)) return null; } else { if (!ulong.TryParse(s, out val)) return null; } if (isSigned) { if (val > (ulong)long.MaxValue + 1) return null; return unchecked(-(long)val); } else { if (val > long.MaxValue) return null; return (long)val; } } static ulong? TryParseUInt64(string s) { ulong val; if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { s = s.Substring(2); if (s != s.Trim()) return null; if (!ulong.TryParse(s, NumberStyles.HexNumber, null, out val)) return null; } else { if (!ulong.TryParse(s, out val)) return null; } return val; } } sealed class RegExStringLiteralSearchComparer : ISearchComparer { readonly Regex regex; public RegExStringLiteralSearchComparer(Regex regex) => this.regex = regex ?? throw new ArgumentNullException(nameof(regex)); public bool IsMatch(string? text, object? obj) { if (obj is IHasConstant hc && hc.Constant is not null) obj = hc.Constant.Value; return obj is string s && regex.IsMatch(s); } } sealed class StringLiteralSearchComparer : ISearchComparer { readonly string str; readonly StringComparison stringComparison; readonly bool matchWholeString; public StringLiteralSearchComparer(string s, bool caseSensitive = false, bool matchWholeString = false) { str = s ?? throw new ArgumentNullException(nameof(s)); stringComparison = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase; this.matchWholeString = matchWholeString; } public bool IsMatch(string? text, object? obj) { if (obj is IHasConstant hc && hc.Constant is not null) obj = hc.Constant.Value; if (!(obj is string s)) return false; if (matchWholeString) return s.Equals(str, stringComparison); return s.IndexOf(str, stringComparison) >= 0; } } sealed class IntegerLiteralSearchComparer : ISearchComparer { readonly long searchValue; public IntegerLiteralSearchComparer(long value) => searchValue = value; public bool IsMatch(string? text, object? obj) { if (obj is IHasConstant hc && hc.Constant is not null) obj = hc.Constant.Value; if (obj is null) return false; switch (Type.GetTypeCode(obj.GetType())) { case TypeCode.Char: return searchValue == (char)obj; case TypeCode.SByte: return searchValue == (sbyte)obj; case TypeCode.Byte: return searchValue == (byte)obj; case TypeCode.Int16: return searchValue == (short)obj; case TypeCode.UInt16: return searchValue == (ushort)obj; case TypeCode.Int32: return searchValue == (int)obj; case TypeCode.UInt32: return searchValue == (uint)obj; case TypeCode.Int64: return searchValue == (long)obj; case TypeCode.UInt64: return searchValue == unchecked((long)(ulong)obj); case TypeCode.Single: return searchValue == (float)obj; case TypeCode.Double: return searchValue == (double)obj; case TypeCode.Decimal: return searchValue == (decimal)obj; case TypeCode.DateTime: return new DateTime(searchValue) == (DateTime)obj; } return false; } } sealed class DoubleLiteralSearchComparer : ISearchComparer { readonly double searchValue; public DoubleLiteralSearchComparer(double value) => searchValue = value; public bool IsMatch(string? text, object? obj) { if (obj is IHasConstant hc && hc.Constant is not null) obj = hc.Constant.Value; if (obj is null) return false; switch (Type.GetTypeCode(obj.GetType())) { case TypeCode.Char: return searchValue == (char)obj; case TypeCode.SByte: return searchValue == (sbyte)obj; case TypeCode.Byte: return searchValue == (byte)obj; case TypeCode.Int16: return searchValue == (short)obj; case TypeCode.UInt16: return searchValue == (ushort)obj; case TypeCode.Int32: return searchValue == (int)obj; case TypeCode.UInt32: return searchValue == (uint)obj; case TypeCode.Int64: return searchValue == (long)obj; case TypeCode.UInt64: return searchValue == (ulong)obj; case TypeCode.Single: return searchValue == (float)obj; case TypeCode.Double: return searchValue == (double)obj; } return false; } } sealed class RegExSearchComparer : ISearchComparer { readonly Regex regex; public RegExSearchComparer(Regex regex) => this.regex = regex ?? throw new ArgumentNullException(nameof(regex)); public bool IsMatch(string? text, object? obj) { if (text is null) return false; return regex.IsMatch(text); } } sealed class AndSearchComparer : ISearchComparer { readonly string[] searchTerms; readonly StringComparison stringComparison; readonly bool matchWholeWords; public AndSearchComparer(string[] searchTerms, bool caseSensitive = false, bool matchWholeWords = false) : this(searchTerms, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase, matchWholeWords) { } public AndSearchComparer(string[] searchTerms, StringComparison stringComparison, bool matchWholeWords = false) { this.searchTerms = searchTerms; this.stringComparison = stringComparison; this.matchWholeWords = matchWholeWords; } public bool IsMatch(string? text, object? obj) { if (text is null) return false; foreach (var searchTerm in searchTerms) { if (matchWholeWords) { if (!text.Equals(searchTerm, stringComparison)) return false; } else { if (text.IndexOf(searchTerm, stringComparison) < 0) return false; } } return true; } } sealed class OrSearchComparer : ISearchComparer { readonly string[] searchTerms; readonly StringComparison stringComparison; readonly bool matchWholeWords; public OrSearchComparer(string[] searchTerms, bool caseSensitive = false, bool matchWholeWords = false) : this(searchTerms, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase, matchWholeWords) { } public OrSearchComparer(string[] searchTerms, StringComparison stringComparison, bool matchWholeWords = false) { this.searchTerms = searchTerms; this.stringComparison = stringComparison; this.matchWholeWords = matchWholeWords; } public bool IsMatch(string? text, object? obj) { if (text is null) return false; foreach (var searchTerm in searchTerms) { if (matchWholeWords) { if (text.Equals(searchTerm, stringComparison)) return true; } else { if (text.IndexOf(searchTerm, stringComparison) >= 0) return true; } } return false; } } }