From 7c88f3a694aac1d02441efd3aa2b00c726cc5e5b Mon Sep 17 00:00:00 2001 From: Hirogen Date: Tue, 11 Nov 2025 22:11:42 +0100 Subject: [PATCH 1/4] update .net 10 and nuget --- src/AutoColumnizer/AutoColumnizer.csproj | 2 +- .../ColumnizerLib.UnitTests.csproj | 2 +- src/ColumnizerLib/ColumnizerLib.csproj | 2 +- src/CsvColumnizer/CsvColumnizer.csproj | 2 +- src/DefaultPlugins/DefaultPlugins.csproj | 2 +- src/Directory.Packages.props | 16 +- .../FlashIconHighlighter.csproj | 2 +- .../GlassfishColumnizer.csproj | 2 +- src/JsonColumnizer/JsonColumnizer.csproj | 2 +- .../JsonCompactColumnizer.csproj | 2 +- .../Log4jXmlColumnizer.csproj | 2 +- .../Classes/Filter/FilterParams.cs | 5 +- .../Classes/Highlight/HighlightEntry.cs | 6 +- src/LogExpert.Core/Classes/ParamParser.cs | 16 +- .../Classes/xml/XmlLogReader.cs | 32 ++- src/LogExpert.Core/Helpers/RegexHelper.cs | 127 ++++++++ src/LogExpert.Core/LogExpert.Core.csproj | 2 +- .../LogExpert.Resources.csproj | 2 +- .../Helpers/RegexHelperTests.cs | 271 ++++++++++++++++++ src/LogExpert.Tests/LogExpert.Tests.csproj | 2 +- .../Controls/BufferedDataGridView.cs | 3 + src/LogExpert.UI/Controls/ColorComboBox.cs | 2 + .../Controls/DateTimeDragControl.cs | 5 + src/LogExpert.UI/Controls/KnobControl.cs | 5 + .../Controls/LogWindow/LogWindow.cs | 8 + .../Controls/LogWindow/PatternWindow.cs | 5 + .../Controls/LogWindow/TimeSpreadigControl.cs | 3 + .../Dialogs/BookmarkCommentDlg.cs | 2 + src/LogExpert.UI/Dialogs/BookmarkWindow.cs | 3 + src/LogExpert.UI/Dialogs/ChooseIconDlg.cs | 3 + .../Dialogs/Eminus/EminusConfigDlg.cs | 2 + src/LogExpert.UI/Dialogs/HighlightDialog.cs | 11 +- .../Dialogs/LogTabWindow/LogTabWindow.cs | 3 + .../Dialogs/MultiFileMaskDialog.cs | 3 + src/LogExpert.UI/Dialogs/OpenUriDialog.cs | 2 + .../Dialogs/ParamRequesterDialog.cs | 4 + src/LogExpert.UI/Dialogs/ProjectLoadDlg.cs | 6 +- src/LogExpert.UI/Dialogs/RegexHelperDialog.cs | 5 + src/LogExpert.UI/Dialogs/SearchDialog.cs | 9 +- src/LogExpert.UI/Dialogs/TabRenameDialog.cs | 2 + src/LogExpert.UI/Dialogs/ToolArgsDialog.cs | 2 + src/LogExpert.UI/LogExpert.UI.csproj | 2 +- src/LogExpert/LogExpert.csproj | 2 +- src/LogExpert/Program.cs | 10 +- .../LogExpert.PluginRegistry.csproj | 2 +- .../RegexColumnizer.UnitTests.csproj | 2 +- src/RegexColumnizer/RegexColumnizer.cs | 5 +- src/RegexColumnizer/RegexColumnizer.csproj | 3 +- .../RegexColumnizer.manifest.json | 20 ++ .../RegexColumnizerConfigDialog.cs | 7 +- src/SftpFileSystemx64/LoginDialog.cs | 4 +- .../SftpFileSystemx64.csproj | 2 +- .../SftpFileSystemx86.csproj | 2 +- 53 files changed, 604 insertions(+), 44 deletions(-) create mode 100644 src/LogExpert.Core/Helpers/RegexHelper.cs create mode 100644 src/LogExpert.Tests/Helpers/RegexHelperTests.cs create mode 100644 src/RegexColumnizer/RegexColumnizer.manifest.json diff --git a/src/AutoColumnizer/AutoColumnizer.csproj b/src/AutoColumnizer/AutoColumnizer.csproj index 3f5dd983..cb537385 100644 --- a/src/AutoColumnizer/AutoColumnizer.csproj +++ b/src/AutoColumnizer/AutoColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 AutoColumnizer $(SolutionDir)..\bin\$(Configuration)\plugins diff --git a/src/ColumnizerLib.UnitTests/ColumnizerLib.UnitTests.csproj b/src/ColumnizerLib.UnitTests/ColumnizerLib.UnitTests.csproj index af5aa1ac..96ae6dae 100644 --- a/src/ColumnizerLib.UnitTests/ColumnizerLib.UnitTests.csproj +++ b/src/ColumnizerLib.UnitTests/ColumnizerLib.UnitTests.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 true ColumnizerLib.UnitTests diff --git a/src/ColumnizerLib/ColumnizerLib.csproj b/src/ColumnizerLib/ColumnizerLib.csproj index 960aa76e..5f0376de 100644 --- a/src/ColumnizerLib/ColumnizerLib.csproj +++ b/src/ColumnizerLib/ColumnizerLib.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 $(SolutionDir)..\bin\Docs\ColumnizerLib.xml diff --git a/src/CsvColumnizer/CsvColumnizer.csproj b/src/CsvColumnizer/CsvColumnizer.csproj index 4e54cc43..d08b4c80 100644 --- a/src/CsvColumnizer/CsvColumnizer.csproj +++ b/src/CsvColumnizer/CsvColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true true diff --git a/src/DefaultPlugins/DefaultPlugins.csproj b/src/DefaultPlugins/DefaultPlugins.csproj index 20255a67..3f53e80e 100644 --- a/src/DefaultPlugins/DefaultPlugins.csproj +++ b/src/DefaultPlugins/DefaultPlugins.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 $(SolutionDir)..\bin\$(Configuration)\plugins diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index d357ea89..100bf68d 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -9,18 +9,18 @@ - + - - + + - + - - - - + + + + \ No newline at end of file diff --git a/src/FlashIconHighlighter/FlashIconHighlighter.csproj b/src/FlashIconHighlighter/FlashIconHighlighter.csproj index 50489d9a..af0c5828 100644 --- a/src/FlashIconHighlighter/FlashIconHighlighter.csproj +++ b/src/FlashIconHighlighter/FlashIconHighlighter.csproj @@ -1,7 +1,7 @@  - net8.0-windows + net10.0-windows true true true diff --git a/src/GlassfishColumnizer/GlassfishColumnizer.csproj b/src/GlassfishColumnizer/GlassfishColumnizer.csproj index 23bf3af2..3f0c708b 100644 --- a/src/GlassfishColumnizer/GlassfishColumnizer.csproj +++ b/src/GlassfishColumnizer/GlassfishColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 $(SolutionDir)..\bin\$(Configuration)\plugins GlassfishColumnizer diff --git a/src/JsonColumnizer/JsonColumnizer.csproj b/src/JsonColumnizer/JsonColumnizer.csproj index 0092f405..8a882ff2 100644 --- a/src/JsonColumnizer/JsonColumnizer.csproj +++ b/src/JsonColumnizer/JsonColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 $(SolutionDir)..\bin\$(Configuration)\plugins JsonColumnizer diff --git a/src/JsonCompactColumnizer/JsonCompactColumnizer.csproj b/src/JsonCompactColumnizer/JsonCompactColumnizer.csproj index bd18a3cc..e46da3ba 100644 --- a/src/JsonCompactColumnizer/JsonCompactColumnizer.csproj +++ b/src/JsonCompactColumnizer/JsonCompactColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 JsonColumnizer $(SolutionDir)..\bin\$(Configuration)\plugins diff --git a/src/Log4jXmlColumnizer/Log4jXmlColumnizer.csproj b/src/Log4jXmlColumnizer/Log4jXmlColumnizer.csproj index 3008beb6..02f31fe5 100644 --- a/src/Log4jXmlColumnizer/Log4jXmlColumnizer.csproj +++ b/src/Log4jXmlColumnizer/Log4jXmlColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true true diff --git a/src/LogExpert.Core/Classes/Filter/FilterParams.cs b/src/LogExpert.Core/Classes/Filter/FilterParams.cs index 68376518..e8aaa1af 100644 --- a/src/LogExpert.Core/Classes/Filter/FilterParams.cs +++ b/src/LogExpert.Core/Classes/Filter/FilterParams.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using LogExpert.Core.Classes.JsonConverters; +using LogExpert.Core.Helpers; using Newtonsoft.Json; @@ -130,12 +131,12 @@ public void CreateRegex () { if (SearchText != null) { - Regex = new Regex(SearchText, IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); + Regex = RegexHelper.GetOrCreateCached(SearchText, IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); } if (RangeSearchText != null && IsRangeSearch) { - RangeRex = new Regex(RangeSearchText, IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); + RangeRex = RegexHelper.GetOrCreateCached(RangeSearchText, IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); } } diff --git a/src/LogExpert.Core/Classes/Highlight/HighlightEntry.cs b/src/LogExpert.Core/Classes/Highlight/HighlightEntry.cs index cd0391ee..b4f18867 100644 --- a/src/LogExpert.Core/Classes/Highlight/HighlightEntry.cs +++ b/src/LogExpert.Core/Classes/Highlight/HighlightEntry.cs @@ -1,6 +1,8 @@ using System.Drawing; using System.Text.RegularExpressions; +using LogExpert.Core.Helpers; + using Newtonsoft.Json; namespace LogExpert.Core.Classes.Highlight; @@ -54,10 +56,10 @@ public Regex Regex get { _regex ??= IsRegex - ? new Regex(SearchText, IsCaseSensitive + ? RegexHelper.GetOrCreateCached(SearchText, IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase) - : new Regex(Regex.Escape(SearchText), + : RegexHelper.GetOrCreateCached(System.Text.RegularExpressions.Regex.Escape(SearchText), IsCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase); diff --git a/src/LogExpert.Core/Classes/ParamParser.cs b/src/LogExpert.Core/Classes/ParamParser.cs index 6ca9731f..10ede7c6 100644 --- a/src/LogExpert.Core/Classes/ParamParser.cs +++ b/src/LogExpert.Core/Classes/ParamParser.cs @@ -1,6 +1,8 @@ using System.Text; using System.Text.RegularExpressions; +using LogExpert.Core.Helpers; + namespace LogExpert.Core.Classes; public class ParamParser (string argTemplate) @@ -38,8 +40,18 @@ public string ReplaceParams (ILogLine logLine, int lineNum, string fileName) replace = GetNextGroup(builder, ref sPos); if (reg != null && replace != null) { - var result = Regex.Replace(logLine.FullLine, reg, replace); - builder.Insert(sPos, result); + // Use RegexHelper for safe regex operations with timeout protection + try + { + var regex = RegexHelper.GetOrCreateCached(reg); + var result = regex.Replace(logLine.FullLine, replace); + builder.Insert(sPos, result); + } + catch (RegexMatchTimeoutException) + { + // If regex times out, insert the original pattern as fallback + builder.Insert(sPos, $"{{timeout: {reg}}}"); + } } } while (replace != null); return builder.ToString(); diff --git a/src/LogExpert.Core/Classes/xml/XmlLogReader.cs b/src/LogExpert.Core/Classes/xml/XmlLogReader.cs index 99ed640f..58832449 100644 --- a/src/LogExpert.Core/Classes/xml/XmlLogReader.cs +++ b/src/LogExpert.Core/Classes/xml/XmlLogReader.cs @@ -58,17 +58,37 @@ public override int ReadChar() } public override string ReadLine() + { + // Call async version synchronously for backward compatibility + // This maintains the interface but uses the improved async implementation internally + return ReadLineAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + + /// + /// Reads a complete XML block asynchronously. + /// Replaces Thread.Sleep with Task.Delay for non-blocking waits. + /// + /// Cancellation token for graceful cancellation + /// Complete XML block or null if not available + public async Task ReadLineAsync(CancellationToken cancellationToken = default) { short state = 0; var tagIndex = 0; var blockComplete = false; var eof = false; var tryCounter = 5; + const int delayMs = 100; StringBuilder builder = new(); while (!eof && !blockComplete) { + // Check for cancellation + if (cancellationToken.IsCancellationRequested) + { + return null; + } + var readInt = ReadChar(); if (readInt == -1) { @@ -77,11 +97,21 @@ public override string ReadLine() { if (--tryCounter > 0) { - Thread.Sleep(100); + // Use Task.Delay instead of Thread.Sleep for non-blocking wait + try + { + await Task.Delay(delayMs, cancellationToken); + } + catch (OperationCanceledException) + { + // Gracefully handle cancellation + return null; + } continue; } else { + // Timeout - return partial block if available break; } } diff --git a/src/LogExpert.Core/Helpers/RegexHelper.cs b/src/LogExpert.Core/Helpers/RegexHelper.cs new file mode 100644 index 00000000..13b93cb5 --- /dev/null +++ b/src/LogExpert.Core/Helpers/RegexHelper.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; + +namespace LogExpert.Core.Helpers; + +/// +/// Helper class for creating and managing regex instances with safety features. +/// Provides timeout protection against catastrophic backtracking (DoS attacks) +/// and caching for improved performance. +/// +public static class RegexHelper +{ + /// + /// Default timeout for all regex operations to prevent DoS attacks. + /// This prevents catastrophic backtracking from freezing the application. + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(2); + + private static readonly ConcurrentDictionary _cache = new(); + private const int MaxCacheSize = 100; + + /// + /// Creates a regex with timeout protection. + /// + /// The regular expression pattern. + /// Regex options to use. + /// Optional timeout override. Uses DefaultTimeout if not specified. + /// A Regex instance with timeout protection. + /// Thrown if pattern is null. + /// Thrown if pattern is invalid. + public static Regex CreateSafeRegex( + string pattern, + RegexOptions options = RegexOptions.None, + TimeSpan? timeout = null) + { + ArgumentNullException.ThrowIfNull(pattern); + + return new Regex( + pattern, + options, + timeout ?? DefaultTimeout); + } + + /// + /// Gets or creates a cached regex instance. + /// This improves performance by reusing compiled regex patterns. + /// + /// The regular expression pattern. + /// Regex options to use. + /// A cached Regex instance with timeout protection. + public static Regex GetOrCreateCached( + string pattern, + RegexOptions options = RegexOptions.None) + { + var key = new RegexCacheKey(pattern, options); + + return _cache.GetOrAdd(key, k => + { + // Evict oldest entries if cache is full + if (_cache.Count >= MaxCacheSize) + { + TrimCache(); + } + + return CreateSafeRegex(k.Pattern, k.Options); + }); + } + + /// + /// Validates a regex pattern without executing it. + /// + /// The pattern to validate. + /// Output parameter containing error message if validation fails. + /// True if the pattern is valid, false otherwise. + public static bool IsValidPattern(string pattern, out string? error) + { + if (string.IsNullOrEmpty(pattern)) + { + error = "Pattern cannot be null or empty."; + return false; + } + + try + { + _ = new Regex(pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)); + error = null; + return true; + } + catch (ArgumentException ex) + { + error = ex.Message; + return false; + } + catch (RegexMatchTimeoutException) + { + // Pattern is valid syntactically, but may be complex + error = null; + return true; + } + } + + /// + /// Clears the regex cache. Useful for testing or memory management. + /// + public static void ClearCache() + { + _cache.Clear(); + } + + /// + /// Gets the current cache size. + /// + public static int CacheSize => _cache.Count; + + private static void TrimCache() + { + // Keep most recent 50 entries (half of max) + var toRemove = _cache.Keys.Take(_cache.Count - MaxCacheSize / 2).ToList(); + foreach (var key in toRemove) + { + _cache.TryRemove(key, out _); + } + } + + private record RegexCacheKey(string Pattern, RegexOptions Options); +} diff --git a/src/LogExpert.Core/LogExpert.Core.csproj b/src/LogExpert.Core/LogExpert.Core.csproj index 8b8e1e8f..09862a29 100644 --- a/src/LogExpert.Core/LogExpert.Core.csproj +++ b/src/LogExpert.Core/LogExpert.Core.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 true LogExpert.Core diff --git a/src/LogExpert.Resources/LogExpert.Resources.csproj b/src/LogExpert.Resources/LogExpert.Resources.csproj index 1337b7d9..1af925eb 100644 --- a/src/LogExpert.Resources/LogExpert.Resources.csproj +++ b/src/LogExpert.Resources/LogExpert.Resources.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 True False ..\Solution Items\Key.snk diff --git a/src/LogExpert.Tests/Helpers/RegexHelperTests.cs b/src/LogExpert.Tests/Helpers/RegexHelperTests.cs new file mode 100644 index 00000000..573755d0 --- /dev/null +++ b/src/LogExpert.Tests/Helpers/RegexHelperTests.cs @@ -0,0 +1,271 @@ +using System.Text.RegularExpressions; + +using LogExpert.Core.Helpers; + +using NUnit.Framework; + +namespace LogExpert.Tests.Helpers; + +[TestFixture] +public class RegexHelperTests +{ + [SetUp] + public void Setup() + { + // Clear cache before each test to ensure isolation + RegexHelper.ClearCache(); + } + + [Test] + public void CreateSafeRegex_ShouldHaveDefaultTimeout() + { + // Arrange & Act + var regex = RegexHelper.CreateSafeRegex("test"); + + // Assert + Assert.That(regex.MatchTimeout, Is.EqualTo(RegexHelper.DefaultTimeout)); + } + + [Test] + public void CreateSafeRegex_WithCustomTimeout_ShouldUseCustomTimeout() + { + // Arrange + var customTimeout = TimeSpan.FromSeconds(5); + + // Act + var regex = RegexHelper.CreateSafeRegex("test", RegexOptions.None, customTimeout); + + // Assert + Assert.That(regex.MatchTimeout, Is.EqualTo(customTimeout)); + } + + [Test] + public void CreateSafeRegex_WithNullPattern_ShouldThrowArgumentNullException() + { + // Act & Assert + Assert.Throws(() => RegexHelper.CreateSafeRegex(null!)); + } + + [Test] + public void CreateSafeRegex_ShouldPreventCatastrophicBacktracking() + { + // Arrange + var maliciousPattern = "^(a+)+$"; + var maliciousInput = "aaaaaaaaaaaaaaaaaX"; + var regex = RegexHelper.CreateSafeRegex(maliciousPattern); + + // Act & Assert + Assert.Throws(() => + { + regex.IsMatch(maliciousInput); + }); + } + + [Test] + public void CreateSafeRegex_WithComplexPattern_ShouldTimeout() + { + // Arrange - Another catastrophic backtracking pattern + var pattern = "(x+x+)+y"; + var input = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX"; + var regex = RegexHelper.CreateSafeRegex(pattern); + + // Act & Assert + Assert.Throws(() => + { + regex.IsMatch(input); + }); + } + + [Test] + public void GetOrCreateCached_ShouldReturnSameInstance() + { + // Arrange & Act + var regex1 = RegexHelper.GetOrCreateCached("test"); + var regex2 = RegexHelper.GetOrCreateCached("test"); + + // Assert + Assert.That(regex1, Is.SameAs(regex2)); + } + + [Test] + public void GetOrCreateCached_WithDifferentPatterns_ShouldReturnDifferentInstances() + { + // Arrange & Act + var regex1 = RegexHelper.GetOrCreateCached("test1"); + var regex2 = RegexHelper.GetOrCreateCached("test2"); + + // Assert + Assert.That(regex1, Is.Not.SameAs(regex2)); + } + + [Test] + public void GetOrCreateCached_WithDifferentOptions_ShouldReturnDifferentInstances() + { + // Arrange & Act + var regex1 = RegexHelper.GetOrCreateCached("test", RegexOptions.None); + var regex2 = RegexHelper.GetOrCreateCached("test", RegexOptions.IgnoreCase); + + // Assert + Assert.That(regex1, Is.Not.SameAs(regex2)); + } + + [Test] + public void GetOrCreateCached_ShouldCacheUpToMaxSize() + { + // Arrange - Create more patterns than cache size + var cacheSize = 100; + + // Act - Fill the cache + for (int i = 0; i < cacheSize; i++) + { + RegexHelper.GetOrCreateCached($"pattern{i}"); + } + + // Assert - Cache should be at max size + Assert.That(RegexHelper.CacheSize, Is.EqualTo(cacheSize)); + + // Act - Add more to trigger eviction + RegexHelper.GetOrCreateCached("pattern_overflow"); + + // Assert - Cache should have evicted some entries + Assert.That(RegexHelper.CacheSize, Is.LessThanOrEqualTo(cacheSize)); + } + + [Test] + public void IsValidPattern_WithValidPattern_ShouldReturnTrue() + { + // Arrange + var pattern = @"\d{4}-\d{2}-\d{2}"; + + // Act + var result = RegexHelper.IsValidPattern(pattern, out var error); + + // Assert + Assert.That(result, Is.True); + Assert.That(error, Is.Null); + } + + [Test] + public void IsValidPattern_WithInvalidPattern_ShouldReturnFalse() + { + // Arrange + var pattern = "[invalid"; + + // Act + var result = RegexHelper.IsValidPattern(pattern, out var error); + + // Assert + Assert.That(result, Is.False); + Assert.That(error, Is.Not.Null); + Assert.That(error, Does.Contain("parsing")); + } + + [Test] + public void IsValidPattern_WithNullPattern_ShouldReturnFalse() + { + // Act + var result = RegexHelper.IsValidPattern(null!, out var error); + + // Assert + Assert.That(result, Is.False); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void IsValidPattern_WithEmptyPattern_ShouldReturnFalse() + { + // Act + var result = RegexHelper.IsValidPattern(string.Empty, out var error); + + // Assert + Assert.That(result, Is.False); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void ClearCache_ShouldRemoveAllCachedRegex() + { + // Arrange + RegexHelper.GetOrCreateCached("test1"); + RegexHelper.GetOrCreateCached("test2"); + RegexHelper.GetOrCreateCached("test3"); + Assert.That(RegexHelper.CacheSize, Is.GreaterThan(0)); + + // Act + RegexHelper.ClearCache(); + + // Assert + Assert.That(RegexHelper.CacheSize, Is.EqualTo(0)); + } + + [Test] + public void CachedRegex_ShouldWorkCorrectly() + { + // Arrange + var pattern = @"(\d{4})-(\d{2})-(\d{2})"; + var input = "Date: 2025-11-11"; + var regex = RegexHelper.GetOrCreateCached(pattern); + + // Act + var match = regex.Match(input); + + // Assert + Assert.That(match.Success, Is.True); + Assert.That(match.Groups[1].Value, Is.EqualTo("2025")); + Assert.That(match.Groups[2].Value, Is.EqualTo("11")); + Assert.That(match.Groups[3].Value, Is.EqualTo("11")); + } + + [Test] + public void CachedRegex_WithIgnoreCase_ShouldMatchCaseInsensitively() + { + // Arrange + var pattern = "test"; + var regex = RegexHelper.GetOrCreateCached(pattern, RegexOptions.IgnoreCase); + + // Act + var match1 = regex.IsMatch("TEST"); + var match2 = regex.IsMatch("Test"); + var match3 = regex.IsMatch("test"); + + // Assert + Assert.That(match1, Is.True); + Assert.That(match2, Is.True); + Assert.That(match3, Is.True); + } + + [Test] + public void CachedRegex_ShouldHaveTimeout() + { + // Arrange & Act + var regex = RegexHelper.GetOrCreateCached("test"); + + // Assert + Assert.That(regex.MatchTimeout, Is.EqualTo(RegexHelper.DefaultTimeout)); + } + + [Test] + public void DefaultTimeout_ShouldBeTwoSeconds() + { + // Assert + Assert.That(RegexHelper.DefaultTimeout, Is.EqualTo(TimeSpan.FromSeconds(2))); + } + + [TestCase(@"\d+", "123", true)] + [TestCase(@"\d+", "abc", false)] + [TestCase(@"[A-Z]+", "ABC", true)] + [TestCase(@"[A-Z]+", "abc", false)] + [TestCase(@"^\w+@\w+\.\w+$", "test@example.com", true)] + [TestCase(@"^\w+@\w+\.\w+$", "invalid-email", false)] + public void CreateSafeRegex_CommonPatterns_ShouldWorkCorrectly(string pattern, string input, bool expectedMatch) + { + // Arrange + var regex = RegexHelper.CreateSafeRegex(pattern); + + // Act + var result = regex.IsMatch(input); + + // Assert + Assert.That(result, Is.EqualTo(expectedMatch)); + } +} diff --git a/src/LogExpert.Tests/LogExpert.Tests.csproj b/src/LogExpert.Tests/LogExpert.Tests.csproj index 71fdb0dc..ca38ba90 100644 --- a/src/LogExpert.Tests/LogExpert.Tests.csproj +++ b/src/LogExpert.Tests/LogExpert.Tests.csproj @@ -1,7 +1,7 @@  - net8.0-windows + net10.0-windows true true diff --git a/src/LogExpert.UI/Controls/BufferedDataGridView.cs b/src/LogExpert.UI/Controls/BufferedDataGridView.cs index d2f557da..5d5d4a83 100644 --- a/src/LogExpert.UI/Controls/BufferedDataGridView.cs +++ b/src/LogExpert.UI/Controls/BufferedDataGridView.cs @@ -6,6 +6,7 @@ using LogExpert.UI.Controls; using NLog; +using System.ComponentModel; namespace LogExpert.Dialogs; @@ -61,8 +62,10 @@ public Graphics Buffer } */ + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public ContextMenuStrip EditModeMenuStrip { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool PaintWithOverlays { get; set; } #endregion diff --git a/src/LogExpert.UI/Controls/ColorComboBox.cs b/src/LogExpert.UI/Controls/ColorComboBox.cs index ecafb2a1..af12e836 100644 --- a/src/LogExpert.UI/Controls/ColorComboBox.cs +++ b/src/LogExpert.UI/Controls/ColorComboBox.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Drawing.Drawing2D; using System.Runtime.Versioning; @@ -49,6 +50,7 @@ public ColorComboBox () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color CustomColor { get => _customColor; diff --git a/src/LogExpert.UI/Controls/DateTimeDragControl.cs b/src/LogExpert.UI/Controls/DateTimeDragControl.cs index 497b906e..c0671ad5 100644 --- a/src/LogExpert.UI/Controls/DateTimeDragControl.cs +++ b/src/LogExpert.UI/Controls/DateTimeDragControl.cs @@ -73,16 +73,19 @@ public DateTimeDragControl () /// /// Gets or sets the minimum allowable date and time value. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public DateTime MinDateTime { get; set; } = DateTime.MinValue; /// /// Gets or sets the maximum allowable date and time value. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public DateTime MaxDateTime { get; set; } = DateTime.MaxValue; /// /// Gets or sets the orientation for drag operations. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public DragOrientations DragOrientation { get => _dragOrientation; @@ -96,11 +99,13 @@ public DragOrientations DragOrientation /// /// Gets or sets the color used to highlight an element when the mouse hovers over it. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color HoverColor { get; set; } /// /// Gets or sets the date and time value, adjusted to exclude milliseconds. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public DateTime DateTime { get => _dateTime.Subtract(TimeSpan.FromMilliseconds(_dateTime.Millisecond)); diff --git a/src/LogExpert.UI/Controls/KnobControl.cs b/src/LogExpert.UI/Controls/KnobControl.cs index c816e4ad..5701a9e4 100644 --- a/src/LogExpert.UI/Controls/KnobControl.cs +++ b/src/LogExpert.UI/Controls/KnobControl.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.UI.Controls; @@ -36,10 +37,13 @@ public KnobControl () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MinValue { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MaxValue { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int Value { get => _value; @@ -53,6 +57,7 @@ public int Value public int Range => MaxValue - MinValue; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int DragSensitivity { get; set; } = 3; #endregion diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs index b28cd6d4..f4c23208 100644 --- a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs +++ b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs @@ -318,6 +318,7 @@ public LogWindow (LogTabWindow.LogTabWindow parent, string fileName, bool isTemp #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color BookmarkColor { get; set; } = Color.FromArgb(165, 200, 225); public ILogLineColumnizer CurrentColumnizer @@ -334,6 +335,7 @@ private set } [SupportedOSPlatform("windows")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool ShowBookmarkBubbles { get => _guiStateArgs.ShowBookmarkBubbles; @@ -350,6 +352,7 @@ public bool ShowBookmarkBubbles public string FileName { get; private set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string SessionFileName { get; set; } public bool IsMultiFile @@ -362,8 +365,10 @@ public bool IsMultiFile private readonly IConfigManager ConfigManager; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string TempTitleName { get; set; } = string.Empty; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] internal FilterPipe FilterPipe { get; set; } public string Title => IsTempFile @@ -372,12 +377,15 @@ public bool IsMultiFile public ColumnizerCallback ColumnizerCallbackObject { get; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool ForcePersistenceLoading { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string ForcedPersistenceFileName { get; set; } public Preferences Preferences => _parentLogTabWin.Preferences; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string GivenFileName { get; set; } public TimeSyncList TimeSyncList { get; private set; } diff --git a/src/LogExpert.UI/Controls/LogWindow/PatternWindow.cs b/src/LogExpert.UI/Controls/LogWindow/PatternWindow.cs index 7e784b6d..8a623d8c 100644 --- a/src/LogExpert.UI/Controls/LogWindow/PatternWindow.cs +++ b/src/LogExpert.UI/Controls/LogWindow/PatternWindow.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; using LogExpert.Core.Classes; @@ -38,24 +39,28 @@ public PatternWindow (LogWindow logWindow) #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int Fuzzy { set => fuzzyKnobControl.Value = value; get => fuzzyKnobControl.Value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MaxDiff { set => maxDiffKnobControl.Value = value; get => maxDiffKnobControl.Value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MaxMisses { set => maxMissesKnobControl.Value = value; get => maxMissesKnobControl.Value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int Weight { set => weigthKnobControl.Value = value; diff --git a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs index 15cb8d7e..191a2657 100644 --- a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs +++ b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Globalization; using System.Runtime.Versioning; @@ -55,8 +56,10 @@ public TimeSpreadingControl () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool ReverseAlpha { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] internal TimeSpreadCalculator TimeSpreadCalc { get => _timeSpreadCalc; diff --git a/src/LogExpert.UI/Dialogs/BookmarkCommentDlg.cs b/src/LogExpert.UI/Dialogs/BookmarkCommentDlg.cs index ac8752c6..19ca630d 100644 --- a/src/LogExpert.UI/Dialogs/BookmarkCommentDlg.cs +++ b/src/LogExpert.UI/Dialogs/BookmarkCommentDlg.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.Dialogs; @@ -27,6 +28,7 @@ private void ApplyResources () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string Comment { set => textBoxComment.Text = value; diff --git a/src/LogExpert.UI/Dialogs/BookmarkWindow.cs b/src/LogExpert.UI/Dialogs/BookmarkWindow.cs index 7cfcaaeb..7f2849d6 100644 --- a/src/LogExpert.UI/Dialogs/BookmarkWindow.cs +++ b/src/LogExpert.UI/Dialogs/BookmarkWindow.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; using LogExpert.Core.Config; @@ -53,11 +54,13 @@ private void ApplyResources () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool LineColumnVisible { set => bookmarkDataGridView.Columns[2].Visible = value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool ShowBookmarkCommentColumn { get => checkBoxCommentColumn.Checked; diff --git a/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs b/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs index f8be593b..5b529089 100644 --- a/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs +++ b/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; using LogExpert.UI.Extensions; @@ -37,8 +38,10 @@ private void ApplyResources () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string FileName { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int IconIndex { get; set; } #endregion diff --git a/src/LogExpert.UI/Dialogs/Eminus/EminusConfigDlg.cs b/src/LogExpert.UI/Dialogs/Eminus/EminusConfigDlg.cs index e40ca2d4..e78dc89d 100644 --- a/src/LogExpert.UI/Dialogs/Eminus/EminusConfigDlg.cs +++ b/src/LogExpert.UI/Dialogs/Eminus/EminusConfigDlg.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Globalization; using System.Runtime.Versioning; @@ -47,6 +48,7 @@ private void LoadResources () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public EminusConfig Config { get; set; } #endregion diff --git a/src/LogExpert.UI/Dialogs/HighlightDialog.cs b/src/LogExpert.UI/Dialogs/HighlightDialog.cs index 8d786ee4..3a6ff9ad 100644 --- a/src/LogExpert.UI/Dialogs/HighlightDialog.cs +++ b/src/LogExpert.UI/Dialogs/HighlightDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Globalization; using System.Runtime.Versioning; using System.Security; @@ -5,6 +6,7 @@ using LogExpert.Core.Classes.Highlight; using LogExpert.Core.Entities; +using LogExpert.Core.Helpers; using LogExpert.Core.Interface; using LogExpert.UI.Controls; using LogExpert.UI.Dialogs; @@ -44,6 +46,7 @@ public HighlightDialog (IConfigManager configManager) #region Properties / Indexers + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public List HighlightGroupList { get => _highlightGroupList; @@ -58,8 +61,10 @@ public List HighlightGroupList } } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public IList KeywordActionList { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string PreSelectedGroupName { get; set; } private bool IsDirty => btnApply.Image == _applyButtonImage; @@ -519,7 +524,11 @@ private void CheckRegex () throw new ArgumentException(Resources.HighlightDialog_RegexError); } - _ = Regex.IsMatch(string.Empty, textBoxSearchString.Text); + // Use RegexHelper for safer validation with timeout protection + if (!RegexHelper.IsValidPattern(textBoxSearchString.Text, out var error)) + { + throw new ArgumentException(error ?? Resources.HighlightDialog_RegexError); + } } } diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 1bdad1a6..b75fcf21 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -212,6 +212,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu #region Properties [SupportedOSPlatform("windows")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public LogWindow.LogWindow CurrentLogWindow { get => _currentLogWindow; @@ -229,7 +230,9 @@ public LogWindow.LogWindow CurrentLogWindow // get { return ConfigManager.Settings; } //} + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public ILogExpertProxy LogExpertProxy { get; set; } + public IConfigManager ConfigManager { get; } #endregion diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs index 6bbf4b12..e6e17f2c 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.UI.Dialogs; @@ -38,8 +39,10 @@ public MultiFileMaskDialog(Form parent, string fileName) #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string FileNamePattern { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int MaxDays { get; set; } #endregion diff --git a/src/LogExpert.UI/Dialogs/OpenUriDialog.cs b/src/LogExpert.UI/Dialogs/OpenUriDialog.cs index 06bb7fa8..6c5447a2 100644 --- a/src/LogExpert.UI/Dialogs/OpenUriDialog.cs +++ b/src/LogExpert.UI/Dialogs/OpenUriDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.UI.Dialogs; @@ -28,6 +29,7 @@ public OpenUriDialog() public string Uri => cmbUri.Text; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public IList UriHistory { get; set; } #endregion diff --git a/src/LogExpert.UI/Dialogs/ParamRequesterDialog.cs b/src/LogExpert.UI/Dialogs/ParamRequesterDialog.cs index 263a8a71..ab509557 100644 --- a/src/LogExpert.UI/Dialogs/ParamRequesterDialog.cs +++ b/src/LogExpert.UI/Dialogs/ParamRequesterDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.Dialogs; @@ -23,10 +24,13 @@ public ParamRequesterDialog () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string ParamName { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string ParamValue { get; set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string[] Values { get; set; } #endregion diff --git a/src/LogExpert.UI/Dialogs/ProjectLoadDlg.cs b/src/LogExpert.UI/Dialogs/ProjectLoadDlg.cs index 5d268884..e4123acd 100644 --- a/src/LogExpert.UI/Dialogs/ProjectLoadDlg.cs +++ b/src/LogExpert.UI/Dialogs/ProjectLoadDlg.cs @@ -1,7 +1,8 @@ -using LogExpert.Core.Enums; - +using System.ComponentModel; using System.Runtime.Versioning; +using LogExpert.Core.Enums; + namespace LogExpert.Dialogs; [SupportedOSPlatform("windows")] @@ -25,6 +26,7 @@ public ProjectLoadDlg() #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public ProjectLoadDlgResult ProjectLoadResult { get; set; } = ProjectLoadDlgResult.Cancel; #endregion diff --git a/src/LogExpert.UI/Dialogs/RegexHelperDialog.cs b/src/LogExpert.UI/Dialogs/RegexHelperDialog.cs index ac401d8b..56d68459 100644 --- a/src/LogExpert.UI/Dialogs/RegexHelperDialog.cs +++ b/src/LogExpert.UI/Dialogs/RegexHelperDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; using System.Text.RegularExpressions; @@ -31,6 +32,7 @@ public RegexHelperDialog () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public bool CaseSensitive { get => _caseSensitive; @@ -41,18 +43,21 @@ public bool CaseSensitive } } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string Pattern { get => comboBoxRegex.Text; set => comboBoxRegex.Text = value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public List ExpressionHistoryList { get => _expressionHistoryList; set => _expressionHistoryList = value; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public List TesttextHistoryList { get => _testtextHistoryList; diff --git a/src/LogExpert.UI/Dialogs/SearchDialog.cs b/src/LogExpert.UI/Dialogs/SearchDialog.cs index a12294ed..daadba92 100644 --- a/src/LogExpert.UI/Dialogs/SearchDialog.cs +++ b/src/LogExpert.UI/Dialogs/SearchDialog.cs @@ -1,6 +1,8 @@ +using System.ComponentModel; using System.Runtime.Versioning; using System.Text.RegularExpressions; +using LogExpert.Core.Helpers; using LogExpert.Entities; using LogExpert.UI.Dialogs; @@ -31,6 +33,7 @@ public SearchDialog () #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public SearchParams SearchParams { get; set; } = new(); #endregion @@ -106,7 +109,11 @@ private void OnButtonOkClick (object sender, EventArgs e) throw new ArgumentException("Search text is empty"); } - Regex.IsMatch("", comboBoxSearchFor.Text); + // Use RegexHelper for safer validation with timeout protection + if (!RegexHelper.IsValidPattern(comboBoxSearchFor.Text, out var error)) + { + throw new ArgumentException($"Invalid regex pattern: {error}"); + } } SearchParams.SearchText = comboBoxSearchFor.Text; diff --git a/src/LogExpert.UI/Dialogs/TabRenameDialog.cs b/src/LogExpert.UI/Dialogs/TabRenameDialog.cs index 2f2455db..f77b7e3c 100644 --- a/src/LogExpert.UI/Dialogs/TabRenameDialog.cs +++ b/src/LogExpert.UI/Dialogs/TabRenameDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; namespace LogExpert.UI.Dialogs; @@ -19,6 +20,7 @@ public TabRenameDialog() #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string TabName { get => textBoxTabName.Text; diff --git a/src/LogExpert.UI/Dialogs/ToolArgsDialog.cs b/src/LogExpert.UI/Dialogs/ToolArgsDialog.cs index f8a18284..513d4b1d 100644 --- a/src/LogExpert.UI/Dialogs/ToolArgsDialog.cs +++ b/src/LogExpert.UI/Dialogs/ToolArgsDialog.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Runtime.Versioning; using LogExpert.UI.Controls.LogTabWindow; @@ -32,6 +33,7 @@ public ToolArgsDialog (LogTabWindow logTabWin, Form parent) #region Properties + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string Arg { get; set; } #endregion diff --git a/src/LogExpert.UI/LogExpert.UI.csproj b/src/LogExpert.UI/LogExpert.UI.csproj index 4d401ccd..d3b2a4d1 100644 --- a/src/LogExpert.UI/LogExpert.UI.csproj +++ b/src/LogExpert.UI/LogExpert.UI.csproj @@ -4,7 +4,7 @@ true true true - net8.0-windows + net10.0-windows true diff --git a/src/LogExpert/LogExpert.csproj b/src/LogExpert/LogExpert.csproj index 20ea2108..f63f4f8d 100644 --- a/src/LogExpert/LogExpert.csproj +++ b/src/LogExpert/LogExpert.csproj @@ -9,7 +9,7 @@ Auto $(SolutionDir)..\bin\$(Configuration) WinExe - net8.0-windows + net10.0-windows False diff --git a/src/LogExpert/Program.cs b/src/LogExpert/Program.cs index 0e09d273..063dabe3 100644 --- a/src/LogExpert/Program.cs +++ b/src/LogExpert/Program.cs @@ -44,6 +44,9 @@ internal static class Program [SupportedOSPlatform("windows")] private static void Main (string[] args) { + // Set global regex timeout to prevent DoS attacks from catastrophic backtracking + AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(2)); + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Application.ThreadException += Application_ThreadException; @@ -143,7 +146,12 @@ or ArgumentException _logger.Error($"IpcClientChannel error: {ex}"); errMsg = ex; counter--; - Thread.Sleep(500); + + // Use Task.Delay instead of Thread.Sleep for non-blocking wait + if (counter > 0) + { + Task.Delay(500).Wait(); + } } } diff --git a/src/PluginRegistry/LogExpert.PluginRegistry.csproj b/src/PluginRegistry/LogExpert.PluginRegistry.csproj index cf09bc7b..7ffeb844 100644 --- a/src/PluginRegistry/LogExpert.PluginRegistry.csproj +++ b/src/PluginRegistry/LogExpert.PluginRegistry.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 diff --git a/src/RegexColumnizer.UnitTests/RegexColumnizer.UnitTests.csproj b/src/RegexColumnizer.UnitTests/RegexColumnizer.UnitTests.csproj index be3904ad..5ff20bb2 100644 --- a/src/RegexColumnizer.UnitTests/RegexColumnizer.UnitTests.csproj +++ b/src/RegexColumnizer.UnitTests/RegexColumnizer.UnitTests.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true diff --git a/src/RegexColumnizer/RegexColumnizer.cs b/src/RegexColumnizer/RegexColumnizer.cs index bc66b146..0febb7e5 100644 --- a/src/RegexColumnizer/RegexColumnizer.cs +++ b/src/RegexColumnizer/RegexColumnizer.cs @@ -1,4 +1,5 @@ -using LogExpert; +using LogExpert; +using LogExpert.Core.Helpers; using System; using System.IO; using System.Linq; @@ -188,7 +189,7 @@ public void Init(RegexColumnizerConfig config) try { - Regex = new Regex(Config.Expression, RegexOptions.Compiled); + Regex = RegexHelper.GetOrCreateCached(Config.Expression, RegexOptions.Compiled); var skip = Regex.GetGroupNames().Length == 1 ? 0 : 1; columns = Regex.GetGroupNames().Skip(skip).ToArray(); } diff --git a/src/RegexColumnizer/RegexColumnizer.csproj b/src/RegexColumnizer/RegexColumnizer.csproj index b85e7eac..e13e7b05 100644 --- a/src/RegexColumnizer/RegexColumnizer.csproj +++ b/src/RegexColumnizer/RegexColumnizer.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true true @@ -10,6 +10,7 @@ + \ No newline at end of file diff --git a/src/RegexColumnizer/RegexColumnizer.manifest.json b/src/RegexColumnizer/RegexColumnizer.manifest.json new file mode 100644 index 00000000..8c21171a --- /dev/null +++ b/src/RegexColumnizer/RegexColumnizer.manifest.json @@ -0,0 +1,20 @@ +{ + "name": "RegexColumnizer", + "version": "1.0.0", + "author": "LogExpert Team", + "description": "Highly configurable columnizer using regular expressions to parse log lines into columns", + "apiVersion": "1.0", + "requires": { + "logExpert": ">=1.10.0", + "dotnet": ">=8.0" + }, + "permissions": [ + "filesystem:read", + "config:read", + "config:write" + ], + "dependencies": {}, + "main": "RegexColumnizer.dll", + "url": "https://github.com/LogExperts/LogExpert", + "license": "MIT" +} diff --git a/src/RegexColumnizer/RegexColumnizerConfigDialog.cs b/src/RegexColumnizer/RegexColumnizerConfigDialog.cs index 0e26cdee..fdf94e0b 100644 --- a/src/RegexColumnizer/RegexColumnizerConfigDialog.cs +++ b/src/RegexColumnizer/RegexColumnizerConfigDialog.cs @@ -1,10 +1,13 @@ using System; +using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Forms; +using LogExpert.Core.Helpers; + namespace RegexColumnizer; public partial class RegexColumnizerConfigDialog : Form @@ -19,6 +22,7 @@ public RegexColumnizerConfigDialog() ResumeLayout(); } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public RegexColumnizerConfig Config { get; set; } private void OnBtnOkClick(object sender, EventArgs e) @@ -48,7 +52,8 @@ private bool Check() try { - Regex regex = new(tbExpression.Text); + // Use RegexHelper for safe regex creation with timeout protection + Regex regex = RegexHelper.CreateSafeRegex(tbExpression.Text); var groupNames = regex.GetGroupNames(); var offset = groupNames.Length > 1 ? 1 : 0; diff --git a/src/SftpFileSystemx64/LoginDialog.cs b/src/SftpFileSystemx64/LoginDialog.cs index 7f9581a7..5c9265b0 100644 --- a/src/SftpFileSystemx64/LoginDialog.cs +++ b/src/SftpFileSystemx64/LoginDialog.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; +using System.ComponentModel; namespace SftpFileSystem; @@ -49,6 +50,7 @@ public LoginDialog(string host, IList userNames, bool hidePasswordField) public string Password { get; private set; } + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string Username { get => _username; diff --git a/src/SftpFileSystemx64/SftpFileSystemx64.csproj b/src/SftpFileSystemx64/SftpFileSystemx64.csproj index 2627ca27..9328ff2f 100644 --- a/src/SftpFileSystemx64/SftpFileSystemx64.csproj +++ b/src/SftpFileSystemx64/SftpFileSystemx64.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true SftpFileSystem diff --git a/src/SftpFileSystemx86/SftpFileSystemx86.csproj b/src/SftpFileSystemx86/SftpFileSystemx86.csproj index eb96b544..24a449b1 100644 --- a/src/SftpFileSystemx86/SftpFileSystemx86.csproj +++ b/src/SftpFileSystemx86/SftpFileSystemx86.csproj @@ -1,6 +1,6 @@  - net8.0-windows + net10.0-windows true SftpFileSystem From 1f813c38f93681ce3966a6ccae90bb3dab34e907 Mon Sep 17 00:00:00 2001 From: Hirogen Date: Tue, 11 Nov 2025 22:12:00 +0100 Subject: [PATCH 2/4] missing project --- build/_build.csproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/build/_build.csproj b/build/_build.csproj index d5eadde9..dbc52a56 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 False CS0649;CS0169 @@ -10,15 +10,17 @@ - - + + + + all runtime; build; native; contentfiles; analyzers - + From 606a3b09304865e810d8e89a0832acda8e0bf3ef Mon Sep 17 00:00:00 2001 From: Hirogen Date: Wed, 12 Nov 2025 10:47:00 +0100 Subject: [PATCH 3/4] update global.json --- global.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index 4c686a52..971b5004 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "9.0.301" + "version": "10.0.100", + "rollForward": "latestPatch" } } \ No newline at end of file From 267950537619551cd2e00898a7201321d2ae2848 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 13 Nov 2025 18:52:04 +0100 Subject: [PATCH 4/4] updates resources.designer --- src/LogExpert.Resources/Resources.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 5fd16d87..4880ca24 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace LogExpert { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources {