Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions SysManager/SysManager/Helpers/MarkdownTextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace SysManager.Helpers;
/// Supported syntax: ## headings, **bold**, - / * bullets, `code`,
/// blank-line paragraph breaks. Anything else is rendered as plain text.
/// </summary>
public static class MarkdownTextBlock
public static partial class MarkdownTextBlock
{
public static readonly DependencyProperty MarkdownProperty =
DependencyProperty.RegisterAttached(
Expand Down Expand Up @@ -81,9 +81,8 @@ private static void OnMarkdownChanged(DependencyObject d, DependencyPropertyChan
}
}

// PERF-004: Pre-compiled regex avoids creating a new state machine on every call.
private static readonly Regex InlineFormattingRegex = new(
@"\*\*(.+?)\*\*|`([^`]+)`", RegexOptions.Compiled);
[GeneratedRegex(@"\*\*(.+?)\*\*|`([^`]+)`")]
private static partial Regex InlineFormattingRegex();

// PERF-007: Cache FontFamily to avoid allocating a new instance per code span render.
private static readonly FontFamily CodeFontFamily = new("Consolas");
Expand All @@ -95,7 +94,7 @@ private static void OnMarkdownChanged(DependencyObject d, DependencyPropertyChan
private static void AddFormattedText(TextBlock tb, string text)
{
// Merge bold and code patterns, process left-to-right
var combined = InlineFormattingRegex.Matches(text);
var combined = InlineFormattingRegex().Matches(text);
var pos = 0;

foreach (Match m in combined)
Expand Down
8 changes: 6 additions & 2 deletions SysManager/SysManager/Services/DiskHealthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// License: MIT

using System.Management;
using System.Text.RegularExpressions;
using Serilog;
using SysManager.Models;

Expand All @@ -14,7 +15,7 @@ namespace SysManager.Services;
/// user-friendly verdict ("Healthy / Watch out / Replace soon").
/// No admin required for read-only queries.
/// </summary>
public sealed class DiskHealthService
public sealed partial class DiskHealthService
{
public Task<IReadOnlyList<DiskHealthReport>> CollectAsync(CancellationToken ct = default)
=> Task.Run(() => Collect(), ct);
Expand Down Expand Up @@ -72,7 +73,7 @@ private static void EnrichWithReliability(ManagementScope scope, string objectId
// Defense-in-depth: validate objectId format before WQL interpolation.
// ObjectId from MSFT_PhysicalDisk is typically like {guid}\\PhysicalDisk0.
// Reject anything that doesn't match expected characters.
if (!System.Text.RegularExpressions.Regex.IsMatch(objectId, @"^[\w{}\-\\.:/]+$"))
if (!ObjectIdFormatPattern().IsMatch(objectId))
{
Log.Warning("DiskHealth: unexpected objectId format, skipping reliability query");
return;
Expand Down Expand Up @@ -213,4 +214,7 @@ private static void ApplyVerdict(DiskHealthReport r)
2 => "Unhealthy",
_ => "Unknown"
};

[GeneratedRegex(@"^[\w{}\-\\.:/]+$")]
private static partial Regex ObjectIdFormatPattern();
}
21 changes: 14 additions & 7 deletions SysManager/SysManager/Services/UninstallerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,25 @@ public async Task<int> UninstallAsync(string packageId, CancellationToken ct = d
/// Matches valid winget package IDs: alphanumeric, dots, hyphens,
/// underscores, forward slashes, plus signs, and spaces. Max 256 chars.
/// </summary>
[System.Text.RegularExpressions.GeneratedRegex(@"^[\w.\-/+ ]{1,256}$")]
private static partial System.Text.RegularExpressions.Regex PackageIdPattern();
[GeneratedRegex(@"^[\w.\-/+ ]{1,256}$")]
private static partial Regex PackageIdPattern();

[GeneratedRegex(@"^\s*Name\s+Id\s+Version", RegexOptions.IgnoreCase)]
private static partial Regex ListHeaderPattern();

[GeneratedRegex(@"^\d+\s+packages?\s+", RegexOptions.IgnoreCase)]
private static partial Regex PackageSummaryPattern();

[GeneratedRegex(@"(?<![A-Za-z0-9])/I(?![A-Za-z0-9])", RegexOptions.IgnoreCase)]
private static partial Regex MsiInstallSwitchPattern();

internal static List<InstalledApp> ParseListTable(List<string> lines)
{
var apps = new List<InstalledApp>();

// Find header line: "Name Id Version [Available] Source"
int headerIdx = lines.FindIndex(l =>
Regex.IsMatch(l, @"^\s*Name\s+Id\s+Version", RegexOptions.IgnoreCase));
ListHeaderPattern().IsMatch(l));
if (headerIdx < 0) return apps;

var header = lines[headerIdx];
Expand All @@ -95,7 +104,7 @@ internal static List<InstalledApp> ParseListTable(List<string> lines)
if (string.IsNullOrWhiteSpace(line)) continue;
if (line.StartsWith("--")) continue;
// Stop at summary lines like "123 packages installed"
if (Regex.IsMatch(line, @"^\d+\s+packages?\s+", RegexOptions.IgnoreCase)) break;
if (PackageSummaryPattern().IsMatch(line)) break;
if (line.Length < idxVersion) continue;

string Slice(int start, int end) =>
Expand Down Expand Up @@ -337,9 +346,7 @@ internal static (string Exe, string Args) ParseUninstallCommand(string command)
var args = command[(spaceIdx + 1)..].TrimStart();
// Convert /I (modify) to /X (uninstall) if needed, add /quiet.
// Use regex to match /I only as a standalone switch (not inside GUIDs).
args = System.Text.RegularExpressions.Regex.Replace(
args, @"(?<![A-Za-z0-9])/I(?![A-Za-z0-9])", "/X",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
args = MsiInstallSwitchPattern().Replace(args, "/X");
if (!args.Contains("/quiet", StringComparison.OrdinalIgnoreCase)
&& !args.Contains("/qn", StringComparison.OrdinalIgnoreCase))
args += " /quiet /norestart";
Expand Down
10 changes: 8 additions & 2 deletions SysManager/SysManager/Services/WingetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static List<AppPackage> ParseUpgradeTable(List<string> lines)
var packages = new List<AppPackage>();
// Find the header line containing "Name" and "Id" and "Version" and "Available"
int headerIdx = lines.FindIndex(l =>
Regex.IsMatch(l, @"^\s*Name\s+Id\s+Version\s+Available", RegexOptions.IgnoreCase));
UpgradeHeaderPattern().IsMatch(l));
if (headerIdx < 0) return packages;

var header = lines[headerIdx];
Expand All @@ -66,7 +66,7 @@ private static List<AppPackage> ParseUpgradeTable(List<string> lines)
if (string.IsNullOrWhiteSpace(line)) continue;
if (line.StartsWith("--")) continue;
// Stop at "X upgrades available." summary or similar
if (Regex.IsMatch(line, @"^\d+\s+(upgrades|packages|package)\s+", RegexOptions.IgnoreCase)) break;
if (UpgradeSummaryPattern().IsMatch(line)) break;
if (line.Length < idxAvailable) continue;

string Slice(int start, int end) =>
Expand Down Expand Up @@ -112,6 +112,12 @@ public async Task<int> UpgradeAsync(string packageId, CancellationToken ct = def
[GeneratedRegex(@"^[\w.\-/+\s]{1,256}$")]
private static partial Regex PackageIdPattern();

[GeneratedRegex(@"^\s*Name\s+Id\s+Version\s+Available", RegexOptions.IgnoreCase)]
private static partial Regex UpgradeHeaderPattern();

[GeneratedRegex(@"^\d+\s+(upgrades|packages|package)\s+", RegexOptions.IgnoreCase)]
private static partial Regex UpgradeSummaryPattern();

public async Task<int> UpgradeAllAsync(CancellationToken ct = default)
{
var args = "upgrade --all --silent --accept-source-agreements --accept-package-agreements --disable-interactivity --include-unknown";
Expand Down
6 changes: 5 additions & 1 deletion SysManager/SysManager/ViewModels/SystemHealthViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Serilog;
Expand Down Expand Up @@ -198,7 +199,7 @@ private async Task RunChkdskAsync(string? driveLetter)
}

// SEC-003: validate drive letter format to prevent argument injection
if (!System.Text.RegularExpressions.Regex.IsMatch(driveLetter, @"^[A-Z]:$", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
if (!DriveLetterPattern().IsMatch(driveLetter))
{
StatusMessage = "Invalid drive letter format.";
return;
Expand Down Expand Up @@ -347,6 +348,9 @@ private void RelaunchAsAdmin()
System.Windows.Application.Current?.Shutdown();
}

[GeneratedRegex(@"^[A-Z]:$", RegexOptions.IgnoreCase)]
private static partial Regex DriveLetterPattern();

[RelayCommand]
private void CancelScan() => _cts?.Cancel();
}
Expand Down
Loading