using System.Text; using System.Text.RegularExpressions; namespace XFESystem.Collections; /// A bounded atom replacement fast path for the private Jint runtime. public static class LiteralRegexReplacement { public static string Replace(Regex regex, string input, string replacement, int count) { // Every other regex shape, option, replacement substitution and overload // retains the original engine and its timeout. No JS accessors or // callbacks are moved: Jint has already converted these exact arguments. if (count <= 0 || input.Length > 131_072 || replacement.Length > 4096 || replacement.Contains('$') || (regex.Options & (RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.RightToLeft)) != 0 || !TryLiteral(regex.ToString(), out string literal) || replacement.Length > literal.Length && input.Length + (long)(input.Length / literal.Length) * (replacement.Length - literal.Length) > 1_048_576) return RegexPauseRecovery.Replace(regex, input, replacement, count); int match = input.IndexOf(literal, StringComparison.Ordinal); if (match < 0) return input; var output = new StringBuilder(input.Length); int start = 0, replaced = 0; do { output.Append(input, start, match - start).Append(replacement); start = match + literal.Length; if (++replaced == count) break; match = input.IndexOf(literal, start, StringComparison.Ordinal); } while (match >= 0); return output.Append(input, start, input.Length - start).ToString(); } private static bool TryLiteral(string pattern, out string literal) { literal = string.Empty; if (pattern.Length is < 1 or > 256) return false; Span decoded = stackalloc char[256]; int count = 0; const string meta = @"\.^$|?*+()[]{}"; for (int index = 0; index < pattern.Length; index++) { char value = pattern[index]; if (value == '\\') { if (++index == pattern.Length || !meta.Contains(pattern[index])) return false; value = pattern[index]; } else if (value is < ' ' or > '~' || meta.Contains(value)) return false; decoded[count++] = value; } literal = count == pattern.Length ? pattern : new string(decoded[..count]); return count > 0; } }