perf: precompile redaction and UI regexes (#286)
Precompiles reusable regexes in redaction/UI helpers and keeps QuickSend-specific overlap out of this PR.\n\nValidation: local ARM64 build passed; Shared tests 1296 passed / 20 skipped; Tray tests 466 passed; remote CI green.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
ad120cdf70
commit
6e8a9d72ad
@ -17,6 +17,10 @@ public sealed partial class SchemaConfigEditor : UserControl
|
||||
private JsonElement _config;
|
||||
private readonly Dictionary<string, object?> _changes = new();
|
||||
|
||||
private static readonly Regex CamelCaseSplitPattern = new(
|
||||
"([a-z])([A-Z])",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly SolidColorBrush SecondaryBrush =
|
||||
new(ColorHelper.FromArgb(255, 140, 150, 170));
|
||||
|
||||
@ -378,7 +382,7 @@ public sealed partial class SchemaConfigEditor : UserControl
|
||||
|
||||
private static string GetLabel(string path, string name)
|
||||
{
|
||||
var result = Regex.Replace(name, "([a-z])([A-Z])", "$1 $2");
|
||||
var result = CamelCaseSplitPattern.Replace(name, "$1 $2");
|
||||
result = result.Replace("_", " ").Replace(".", " \u203A ");
|
||||
// Title-case the first character
|
||||
if (result.Length > 0)
|
||||
|
||||
@ -11,6 +11,48 @@ namespace OpenClawTray.Helpers;
|
||||
|
||||
internal static class CommandCenterTextHelper
|
||||
{
|
||||
// Pre-compiled patterns used in RedactSupportPath / RedactSupportValue.
|
||||
// Compiled once at startup; reused on every diagnostic / support-text build.
|
||||
private static readonly Regex PathWindowsUserPattern = new(
|
||||
@"\b[A-Za-z]:\\Users\\[^\\]+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex PathUnixUserPattern = new(
|
||||
@"/Users/[^/]+",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueUrlHostPattern = new(
|
||||
@"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueIpPattern = new(
|
||||
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueEmailPattern = new(
|
||||
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueUserAtHostPattern = new(
|
||||
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueHostAfterToPattern = new(
|
||||
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueLeadingHostPattern = new(
|
||||
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
internal static string BuildSupportContext(GatewayCommandCenterState state)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
@ -346,19 +388,9 @@ internal static class CommandCenterTextHelper
|
||||
}
|
||||
}
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b[A-Za-z]:\\Users\\[^\\]+",
|
||||
"%USERPROFILE%",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = PathWindowsUserPattern.Replace(redacted, "%USERPROFILE%");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"/Users/[^/]+",
|
||||
"$HOME",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = PathUnixUserPattern.Replace(redacted, "$HOME");
|
||||
|
||||
return redacted;
|
||||
}
|
||||
@ -368,47 +400,19 @@ internal static class CommandCenterTextHelper
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return "unknown";
|
||||
|
||||
var redacted = Regex.Replace(
|
||||
var redacted = ValueUrlHostPattern.Replace(
|
||||
value,
|
||||
@"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)",
|
||||
match => match.Value.Replace(match.Groups[1].Value, "<host>"),
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
match => match.Value.Replace(match.Groups[1].Value, "<host>"));
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
|
||||
"<ip>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueIpPattern.Replace(redacted, "<ip>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
|
||||
"<email>",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueEmailPattern.Replace(redacted, "<email>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
|
||||
"<user>@<host>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueUserAtHostPattern.Replace(redacted, "<user>@<host>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
"<host>",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueHostAfterToPattern.Replace(redacted, "<host>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
"<host>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueLeadingHostPattern.Replace(redacted, "<host>");
|
||||
|
||||
return redacted;
|
||||
}
|
||||
|
||||
@ -18,6 +18,13 @@ namespace OpenClawTray.Onboarding.Pages;
|
||||
/// </summary>
|
||||
public sealed class WizardPage : Component<OnboardingState>
|
||||
{
|
||||
private static readonly Regex UrlInMessagePattern = new(
|
||||
@"(https?://[^\s\)\"",]+)",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex DeviceCodePattern = new(
|
||||
@"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b",
|
||||
RegexOptions.Compiled);
|
||||
public override Element Render()
|
||||
{
|
||||
// Read persisted wizard state from shared OnboardingState
|
||||
@ -523,7 +530,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
if (!string.IsNullOrEmpty(displayMessage))
|
||||
{
|
||||
// URL detection — find https:// URLs in the message
|
||||
var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)");
|
||||
var urlMatch = UrlInMessagePattern.Match(displayMessage);
|
||||
if (urlMatch.Success)
|
||||
{
|
||||
var detectedUrl = urlMatch.Value;
|
||||
@ -545,9 +552,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
// Capture must contain a digit or hyphen (or be all uppercase) to avoid
|
||||
// matching common English words like "below" that follow "code".
|
||||
// Case-sensitive on the value to require the GitHub-style uppercase code.
|
||||
var codeMatch = Regex.Match(
|
||||
displayMessage,
|
||||
@"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b");
|
||||
var codeMatch = DeviceCodePattern.Match(displayMessage);
|
||||
if (codeMatch.Success)
|
||||
{
|
||||
var code = codeMatch.Groups[1].Value;
|
||||
@ -581,7 +586,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(displayMessage))
|
||||
{
|
||||
var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)");
|
||||
var urlMatch = UrlInMessagePattern.Match(displayMessage);
|
||||
if (urlMatch.Success)
|
||||
{
|
||||
try
|
||||
|
||||
Loading…
Reference in New Issue
Block a user