Skip to content
Draft
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
7 changes: 5 additions & 2 deletions src/dsc/PowerToys.Settings.DSC.Schema.Generator/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@

namespace PowerToys.Settings.DSC.Schema;

internal sealed class Common
internal sealed partial class Common
{
private static string[] TypeParts(string name)
{
return Regex.Split(name.ToLower(CultureInfo.CurrentCulture), @"(?<!^)(?=[A-Z])|\.");
return CamelCaseSplitRegex().Split(name.ToLower(CultureInfo.CurrentCulture));
}

[GeneratedRegex(@"(?<!^)(?=[A-Z])|\.")]
private static partial Regex CamelCaseSplitRegex();

internal static bool InferIsBool(Type propertyInfo)
{
return TypeParts(propertyInfo.Name).Any(word => word.Equals("Bool", StringComparison.OrdinalIgnoreCase) || word.Equals("Boolean", StringComparison.OrdinalIgnoreCase));
Expand Down
49 changes: 30 additions & 19 deletions src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@

namespace AdvancedPaste.Helpers
{
internal static class JsonHelper
internal static partial class JsonHelper
{
// Ini parts regex
private static readonly Regex IniSectionNameRegex = new Regex(@"^\[(.+)\]");
private static readonly Regex IniValueLineRegex = new Regex(@"(.+?)\s*=\s*(.*)");

// List of supported CSV delimiters and Regex to detect separator property
// List of supported CSV delimiters
private static readonly char[] CsvDelimArry = [',', ';', '\t'];
private static readonly Regex CsvSepIdentifierRegex = new Regex(@"^sep=(.)$", RegexOptions.IgnoreCase);

// CSV: Split on every occurrence of the delimiter except if it is enclosed by " and ignore two " as escaped "
private static readonly string CsvDelimSepRegexStr = @"(?=(?:[^""]*""[^""]*"")*(?![^""]*""))";

// Ini parts regex
[GeneratedRegex(@"^\[(.+)\]")]
private static partial Regex IniSectionNameRegex();

[GeneratedRegex(@"(.+?)\s*=\s*(.*)")]
private static partial Regex IniValueLineRegex();

// Regex to detect separator property
[GeneratedRegex(@"^sep=(.)$", RegexOptions.IgnoreCase)]
private static partial Regex CsvSepIdentifierRegex();

// CSV: Regex to remove/replace quotation marks
private static readonly Regex CsvRemoveSingleQuotationMarksRegex = new Regex(@"^""(?!"")|(?<!"")""$|^""""$");
private static readonly Regex CsvRemoveStartAndEndQuotationMarksRegex = new Regex(@"^""(?=(""{2})+)|(?<=(""{2})+)""$");
private static readonly Regex CsvReplaceDoubleQuotationMarksRegex = new Regex(@"""{2}");
[GeneratedRegex(@"^""(?!"")|(?<!"")""$|^""""$")]
private static partial Regex CsvRemoveSingleQuotationMarksRegex();

[GeneratedRegex(@"^""(?=(""{2})+)|(?<=(""{2})+)""$")]
private static partial Regex CsvRemoveStartAndEndQuotationMarksRegex();

[GeneratedRegex(@"""{2}")]
private static partial Regex CsvReplaceDoubleQuotationMarksRegex();

private static bool IsJson(string text)
{
Expand Down Expand Up @@ -98,14 +109,14 @@ internal static async Task<string> ToJsonFromXmlOrCsvAsync(DataPackageView clipb
// Validate content as ini
// (First line is a section name and second line is a section name or a key-value-pair.
// For the second line we check both, in case the first ini section is empty.)
if (lines.Length >= 2 && IniSectionNameRegex.IsMatch(lines[0]) &&
(IniSectionNameRegex.IsMatch(lines[1]) || IniValueLineRegex.IsMatch(lines[1])))
if (lines.Length >= 2 && IniSectionNameRegex().IsMatch(lines[0]) &&
(IniSectionNameRegex().IsMatch(lines[1]) || IniValueLineRegex().IsMatch(lines[1])))
{
// Parse and convert Ini
foreach (string line in lines)
{
Match lineSectionNameCheck = IniSectionNameRegex.Match(line);
Match lineKeyValuePairCheck = IniValueLineRegex.Match(line);
Match lineSectionNameCheck = IniSectionNameRegex().Match(line);
Match lineKeyValuePairCheck = IniValueLineRegex().Match(line);

if (lineSectionNameCheck.Success)
{
Expand Down Expand Up @@ -163,7 +174,7 @@ internal static async Task<string> ToJsonFromXmlOrCsvAsync(DataPackageView clipb
foreach (var line in lines)
{
// If line is separator property line, then skip it
if (CsvSepIdentifierRegex.IsMatch(line))
if (CsvSepIdentifierRegex().IsMatch(line))
{
continue;
}
Expand Down Expand Up @@ -222,7 +233,7 @@ private static void GetCsvDelimiter(in string[] csvLines, out char delimiter, ou
if (csvLines.Length > 1)
{
// Try to select the delimiter based on the separator property.
Match matchChar = CsvSepIdentifierRegex.Match(csvLines[0]);
Match matchChar = CsvSepIdentifierRegex().Match(csvLines[0]);
if (matchChar.Success)
{
// We can do matchChar[0] as the match only returns one character.
Expand Down Expand Up @@ -273,15 +284,15 @@ private static void GetCsvDelimiter(in string[] csvLines, out char delimiter, ou
private static string ReplaceQuotationMarksInCsvData(string str)
{
// Remove first and last single quotation mark (enclosing quotation marks) and remove quotation marks of an empty data set ("").
str = CsvRemoveSingleQuotationMarksRegex.Replace(str, string.Empty);
str = CsvRemoveSingleQuotationMarksRegex().Replace(str, string.Empty);

// Remove first quotation mark if followed by pairs of quotation marks
// and remove last quotation mark if precede by pairs of quotation marks.
// (Removes enclosing quotation marks around the cell data for data like /"""abc"""/.)
str = CsvRemoveStartAndEndQuotationMarksRegex.Replace(str, string.Empty);
str = CsvRemoveStartAndEndQuotationMarksRegex().Replace(str, string.Empty);

// Replace pairs of two quotation marks with a single quotation mark. (Escaped quotation marks.)
str = CsvReplaceDoubleQuotationMarksRegex.Replace(str, "\"");
str = CsvReplaceDoubleQuotationMarksRegex().Replace(str, "\"");

return str;
}
Expand Down
17 changes: 13 additions & 4 deletions src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@

namespace AdvancedPaste.Helpers
{
internal static class MarkdownHelper
internal static partial class MarkdownHelper
{
[GeneratedRegex(@"<!--StartFragment-->|<!--EndFragment-->")]
private static partial Regex FragmentCommentsRegex();

[GeneratedRegex(@"\s{2,}")]
private static partial Regex ExcessiveWhitespaceRegex();

[GeneratedRegex(@"[\r\n]+")]
private static partial Regex LineBreaksRegex();

public static async Task<string> ToMarkdownAsync(DataPackageView clipboardData)
{
Logger.LogTrace();
Expand All @@ -31,7 +40,7 @@ private static string CleanHtml(string html)
Logger.LogTrace();

// Remove the "StartFragment" and "EndFragment" comments
html = Regex.Replace(html, @"<!--StartFragment-->|<!--EndFragment-->", string.Empty);
html = FragmentCommentsRegex().Replace(html, string.Empty);

HtmlDocument document = new HtmlDocument();
document.LoadHtml(html);
Expand Down Expand Up @@ -95,8 +104,8 @@ private static void CleanUpWhitespace(HtmlNode node)
// Clean up line breaks and excessive whitespace
if (node.NodeType == HtmlNodeType.Text)
{
node.InnerHtml = Regex.Replace(node.InnerHtml, @"\s{2,}", " ");
node.InnerHtml = Regex.Replace(node.InnerHtml, @"[\r\n]+", string.Empty);
node.InnerHtml = ExcessiveWhitespaceRegex().Replace(node.InnerHtml, " ");
node.InnerHtml = LineBreaksRegex().Replace(node.InnerHtml, string.Empty);
}
else
{
Expand Down
14 changes: 9 additions & 5 deletions src/modules/Hosts/HostsUILib/Helpers/ValidationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace HostsUILib.Helpers
{
public static class ValidationHelper
public static partial class ValidationHelper
{
/// <summary>
/// Determines whether the address is a valid IPv4
Expand All @@ -19,8 +19,7 @@ public static bool ValidIPv4(string address)
return false;
}

var regex = new Regex("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
return regex.IsMatch(address);
return IPv4Regex().IsMatch(address);
}

/// <summary>
Expand All @@ -33,8 +32,7 @@ public static bool ValidIPv6(string address)
return false;
}

var regex = new Regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$");
return regex.IsMatch(address);
return IPv6Regex().IsMatch(address);
}

/// <summary>
Expand Down Expand Up @@ -64,5 +62,11 @@ public static bool ValidHosts(string hosts, bool validateHostsLength)

return true;
}

[GeneratedRegex("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")]
private static partial Regex IPv4Regex();

[GeneratedRegex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$")]
private static partial Regex IPv6Regex();
}
}
12 changes: 9 additions & 3 deletions src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace ScreenRuler.UITests
{
public static class TestHelper
public static partial class TestHelper
{
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };

Expand Down Expand Up @@ -330,8 +330,8 @@ public static bool ValidateSpacingClipboardContent(string clipboardText, string

return spacingType switch
{
"Spacing" => Regex.IsMatch(clipboardText, @"\d+\s*[�x×]\s*\d+"),
"Horizontal Spacing" or "Vertical Spacing" => Regex.IsMatch(clipboardText, @"^\d+$"),
"Spacing" => SpacingPatternRegex().IsMatch(clipboardText),
"Horizontal Spacing" or "Vertical Spacing" => DigitsOnlyRegex().IsMatch(clipboardText),
_ => false,
};
}
Expand Down Expand Up @@ -462,5 +462,11 @@ private static void ValidateClipboardResults(string testName)
containsValidPattern,
$"{testName}: Clipboard should contain valid spacing measurement, but contained: '{clipboardText}'");
}

[GeneratedRegex(@"\d+\s*[�x×]\s*\d+")]
private static partial Regex SpacingPatternRegex();

[GeneratedRegex(@"^\d+$")]
private static partial Regex DigitsOnlyRegex();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private void LinkButtonClick(object sender, System.EventArgs e)
{
if (GetSecureKey() != SecurityCodeField.Text)
{
Encryption.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
Encryption.MyKey = WhitespaceRegex().Replace(SecurityCodeField.Text, string.Empty);
SecurityCode = Encryption.MyKey;
}

Expand All @@ -113,5 +113,8 @@ private void CollapseHelpButtonClick(object sender, System.EventArgs e)
HelpLabel.Hide();
CollapseHelpButton.Hide();
}

[GeneratedRegex(@"\s+")]
private static partial Regex WhitespaceRegex();
}
}
5 changes: 4 additions & 1 deletion src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private void ButtonOK_Click(object sender, EventArgs e)
{
buttonOK.Enabled = false;

if (!UpdateKey(Regex.Replace(textBoxEnc.Text, @"\s+", string.Empty)))
if (!UpdateKey(WhitespaceRegex().Replace(textBoxEnc.Text, string.Empty)))
{
buttonOK.Enabled = true;
return;
Expand Down Expand Up @@ -1233,5 +1233,8 @@ private void PaintMyLogo()
g2.Dispose();
}
#endif

[GeneratedRegex(@"\s+")]
private static partial Regex WhitespaceRegex();
}
}
9 changes: 5 additions & 4 deletions src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace PowerOCR.Helpers
{
internal static class OcrExtensions
internal static partial class OcrExtensions
{
public static void GetTextFromOcrLine(this OcrLine ocrLine, bool isSpaceJoiningOCRLang, StringBuilder text)
{
Expand All @@ -40,13 +40,11 @@ public static void GetTextFromOcrLine(this OcrLine ocrLine, bool isSpaceJoiningO
bool isFirstWord = true;
bool isPrevWordSpaceJoining = false;

Regex regexSpaceJoiningWord = new(@"(^[\p{L}-[\p{Lo}]]|\p{Nd}$)|.{2,}");

foreach (OcrWord ocrWord in ocrLine.Words)
{
string wordString = ocrWord.Text;

bool isThisWordSpaceJoining = regexSpaceJoiningWord.IsMatch(wordString);
bool isThisWordSpaceJoining = SpaceJoiningWordRegex().IsMatch(wordString);

if (isFirstWord || (!isThisWordSpaceJoining && !isPrevWordSpaceJoining))
{
Expand Down Expand Up @@ -104,5 +102,8 @@ internal static async Task<OcrResult> GetOcrResultFromImageAsync(Bitmap bmp, Lan
OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(language);
return await ocrEngine.RecognizeAsync(softwareBmp);
}

[GeneratedRegex(@"(^[\p{L}-[\p{Lo}]]|\p{Nd}$)|.{2,}")]
private static partial Regex SpaceJoiningWordRegex();
}
}
8 changes: 5 additions & 3 deletions src/modules/PowerOCR/PowerOCR/Helpers/StringHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace PowerOCR.Helpers;

internal static class StringHelpers
internal static partial class StringHelpers
{
public static string MakeStringSingleLine(this string textToEdit)
{
Expand All @@ -25,8 +25,7 @@ public static string MakeStringSingleLine(this string textToEdit)
workingString.Replace('\n', ' ');
workingString.Replace('\r', ' ');

Regex regex = new("[ ]{2,}");
string temp = regex.Replace(workingString.ToString(), " ");
string temp = MultipleSpacesRegex().Replace(workingString.ToString(), " ");
workingString.Clear();
workingString.Append(temp);
if (workingString[0] == ' ')
Expand All @@ -41,4 +40,7 @@ public static string MakeStringSingleLine(this string textToEdit)

return workingString.ToString();
}

[GeneratedRegex("[ ]{2,}")]
private static partial Regex MultipleSpacesRegex();
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ public bool IsPackagedApp
else
{
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_(:?x64|arm64)__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
Match match = packagedAppPathRegex.Match(appPath);
Match match = PackagedAppPathRegex().Match(appPath);
_isPackagedApp = match.Success;
if (match.Success)
{
Expand All @@ -213,5 +212,8 @@ public void Dispose()
{
GC.SuppressFinalize(this);
}

[GeneratedRegex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_(:?x64|arm64)__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
private static partial Regex PackagedAppPathRegex();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Microsoft.CmdPal.Common.Services.Sanitizer;

internal sealed class FilenameMaskRuleProvider : ISanitizationRuleProvider
internal sealed partial class FilenameMaskRuleProvider : ISanitizationRuleProvider
{
private static readonly FrozenSet<string> CommonFileStemExclusions = new[]
{
Expand All @@ -30,14 +30,7 @@ internal sealed class FilenameMaskRuleProvider : ISanitizationRuleProvider

public IEnumerable<SanitizationRule> GetRules()
{
const string pattern = """
(?<full>
(?: [A-Za-z]: )? (?: [\\/][^\\/:*?""<>|\s]+ )+ # drive-rooted or UNC-like
| [^\\/:*?""<>|\s]+ (?: [\\/][^\\/:*?""<>|\s]+ )+ # relative with at least one sep
)
""";

var rx = new Regex(pattern, SanitizerDefaults.DefaultOptions | RegexOptions.IgnorePatternWhitespace, TimeSpan.FromMilliseconds(SanitizerDefaults.DefaultMatchTimeoutMs));
var rx = FilePathRegex();
yield return new SanitizationRule(rx, MatchEvaluator, "Mask filename in any path");
yield break;

Expand Down Expand Up @@ -138,4 +131,12 @@ private static bool IsVersionSegment(string file)

return hasDot;
}

[GeneratedRegex("""
(?<full>
(?: [A-Za-z]: )? (?: [\\/][^\\/:*?""<>|\s]+ )+ # drive-rooted or UNC-like
| [^\\/:*?""<>|\s]+ (?: [\\/][^\\/:*?""<>|\s]+ )+ # relative with at least one sep
)
""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace)]
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FilePathRegex no longer applies the sanitizer regex match timeout (previously TimeSpan.FromMilliseconds(SanitizerDefaults.DefaultMatchTimeoutMs)). Since this rule runs on potentially untrusted/large input, dropping the timeout can reintroduce Regex DoS risks and change operational behavior. Please include SanitizerDefaults.DefaultMatchTimeoutMs in the [GeneratedRegex] attribute (and consider using SanitizerDefaults.DefaultOptions | RegexOptions.IgnorePatternWhitespace to stay consistent with the other sanitizer rule providers).

Suggested change
""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace)]
""", SanitizerDefaults.DefaultOptions | RegexOptions.IgnorePatternWhitespace, SanitizerDefaults.DefaultMatchTimeoutMs)]

Copilot uses AI. Check for mistakes.
private static partial Regex FilePathRegex();
}
Loading
Loading