From 139dcf5f44837fed321b4e5a59ecce740c93fabe Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 2 Feb 2026 17:36:34 +0100 Subject: [PATCH 1/7] Update protocol version to V2 --- .../Business/Versions/ProtocolVersion.cs | 7 ++++--- .../Business/Versions/ProtocolVersionTests.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs b/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs index 146f1421..7959b628 100644 --- a/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs +++ b/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs @@ -3,10 +3,11 @@ namespace ByteSync.Common.Business.Versions; public static class ProtocolVersion { public const int V1 = 1; + public const int V2 = 2; - public const int CURRENT = V1; + public const int CURRENT = V2; - public const int MIN_SUPPORTED = V1; + public const int MIN_SUPPORTED = V2; public static bool IsCompatible(int otherVersion) { @@ -17,4 +18,4 @@ public static bool IsCompatible(int otherVersion) return otherVersion == CURRENT; } -} \ No newline at end of file +} diff --git a/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs b/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs index cbd6e1d0..334df5f3 100644 --- a/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs +++ b/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs @@ -8,15 +8,15 @@ namespace TestingCommon.Business.Versions; public class ProtocolVersionTests { [Test] - public void Current_ShouldBeV1() + public void Current_ShouldBeV2() { - ProtocolVersion.CURRENT.Should().Be(ProtocolVersion.V1); + ProtocolVersion.CURRENT.Should().Be(ProtocolVersion.V2); } [Test] - public void MinSupported_ShouldBeV1() + public void MinSupported_ShouldBeV2() { - ProtocolVersion.MIN_SUPPORTED.Should().Be(ProtocolVersion.V1); + ProtocolVersion.MIN_SUPPORTED.Should().Be(ProtocolVersion.V2); } [Test] @@ -28,11 +28,11 @@ public void IsCompatible_WithCurrentVersion_ShouldReturnTrue() } [Test] - public void IsCompatible_WithV1_ShouldReturnTrue() + public void IsCompatible_WithV1_ShouldReturnFalse() { var result = ProtocolVersion.IsCompatible(ProtocolVersion.V1); - result.Should().BeTrue(); + result.Should().BeFalse(); } [Test] @@ -46,7 +46,7 @@ public void IsCompatible_WithZero_ShouldReturnFalse() [Test] public void IsCompatible_WithDifferentVersion_ShouldReturnFalse() { - var result = ProtocolVersion.IsCompatible(2); + var result = ProtocolVersion.IsCompatible(3); result.Should().BeFalse(); } @@ -58,4 +58,4 @@ public void IsCompatible_WithNegativeVersion_ShouldReturnFalse() result.Should().BeFalse(); } -} \ No newline at end of file +} From 9edcd58ba2a866f89e1d2926f451495fb0a57b51 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 2 Feb 2026 17:37:32 +0100 Subject: [PATCH 2/7] Add protected paths utility and tests --- src/ByteSync.Common/Helpers/ProtectedPaths.cs | 71 +++++++++++++++++++ .../Helpers/ProtectedPathsTests.cs | 56 +++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/ByteSync.Common/Helpers/ProtectedPaths.cs create mode 100644 tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs diff --git a/src/ByteSync.Common/Helpers/ProtectedPaths.cs b/src/ByteSync.Common/Helpers/ProtectedPaths.cs new file mode 100644 index 00000000..6f21f2c6 --- /dev/null +++ b/src/ByteSync.Common/Helpers/ProtectedPaths.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using ByteSync.Common.Business.Misc; + +namespace ByteSync.Common.Helpers; + +public static class ProtectedPaths +{ + private static readonly string[] ProtectedRoots = + [ + "/dev", + "/proc", + "/sys", + "/run", + "/var/run", + "/private/var/run" + ]; + + public static bool TryGetProtectedRoot(string path, OSPlatforms osPlatform, out string protectedRoot) + { + protectedRoot = string.Empty; + + if (osPlatform == OSPlatforms.Windows) + { + return false; + } + + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } + + var normalizedPath = Normalize(path); + var comparison = osPlatform == OSPlatforms.Windows + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal; + + foreach (var root in ProtectedRoots) + { + var normalizedRoot = Normalize(root); + + if (IsSameOrSubPath(normalizedPath, normalizedRoot, comparison)) + { + protectedRoot = root; + + return true; + } + } + + return false; + } + + private static string Normalize(string path) + { + var fullPath = Path.GetFullPath(path); + + return Path.TrimEndingDirectorySeparator(fullPath); + } + + private static bool IsSameOrSubPath(string path, string root, StringComparison comparison) + { + if (path.Equals(root, comparison)) + { + return true; + } + + var rootWithSeparator = root + Path.DirectorySeparatorChar; + + return path.StartsWith(rootWithSeparator, comparison); + } +} diff --git a/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs b/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs new file mode 100644 index 00000000..87f2b22a --- /dev/null +++ b/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs @@ -0,0 +1,56 @@ +using ByteSync.Common.Business.Misc; +using ByteSync.Common.Helpers; +using FluentAssertions; +using NUnit.Framework; + +namespace TestingCommon.Helpers; + +[TestFixture] +public class ProtectedPathsTests +{ + [TestCase("/dev", "/dev")] + [TestCase("/dev/sda", "/dev")] + [TestCase("/proc", "/proc")] + [TestCase("/proc/1", "/proc")] + [TestCase("/sys", "/sys")] + [TestCase("/sys/kernel", "/sys")] + [TestCase("/run", "/run")] + [TestCase("/run/user/1000", "/run")] + [TestCase("/var/run", "/var/run")] + [TestCase("/var/run/daemon", "/var/run")] + public void TryGetProtectedRoot_Linux_ReturnsTrue(string path, string expectedRoot) + { + var result = ProtectedPaths.TryGetProtectedRoot(path, OSPlatforms.Linux, out var root); + + result.Should().BeTrue(); + root.Should().Be(expectedRoot); + } + + [TestCase("/private/var/run", "/private/var/run")] + [TestCase("/private/var/run/daemon", "/private/var/run")] + public void TryGetProtectedRoot_MacOs_ReturnsTrue(string path, string expectedRoot) + { + var result = ProtectedPaths.TryGetProtectedRoot(path, OSPlatforms.MacOs, out var root); + + result.Should().BeTrue(); + root.Should().Be(expectedRoot); + } + + [Test] + public void TryGetProtectedRoot_Linux_WithNonProtectedPath_ReturnsFalse() + { + var result = ProtectedPaths.TryGetProtectedRoot("/home/user", OSPlatforms.Linux, out var root); + + result.Should().BeFalse(); + root.Should().BeEmpty(); + } + + [Test] + public void TryGetProtectedRoot_Windows_ReturnsFalse() + { + var result = ProtectedPaths.TryGetProtectedRoot("C:\\Windows", OSPlatforms.Windows, out var root); + + result.Should().BeFalse(); + root.Should().BeEmpty(); + } +} From 2d5284d43bdbd341aa735fa82c0138ce4b7bbcb0 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 2 Feb 2026 17:41:55 +0100 Subject: [PATCH 3/7] Block protected paths when adding data sources --- .../Assets/Resources/Resources.Designer.cs | 12 +++++ .../Assets/Resources/Resources.fr.resx | 6 +++ .../Assets/Resources/Resources.resx | 6 +++ .../Factories/InventoryBuilderFactory.cs | 18 +++++-- .../Services/Inventories/DataSourceChecker.cs | 33 +++++++++++-- .../Services/Inventories/InventoryBuilder.cs | 12 ++++- .../Factories/TestInventoryBuilderFactory.cs | 3 +- .../Inventories/DataSourceCheckerTests.cs | 47 +++++++++++++++++-- 8 files changed, 126 insertions(+), 11 deletions(-) diff --git a/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs b/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs index 04b90231..376f683f 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs +++ b/src/ByteSync.Client/Assets/Resources/Resources.Designer.cs @@ -2304,6 +2304,18 @@ public static string DataSourceChecker_SubPathError_Message { } } + public static string DataSourceChecker_ProtectedPathError_Title { + get { + return ResourceManager.GetString("DataSourceChecker_ProtectedPathError_Title", resourceCulture); + } + } + + public static string DataSourceChecker_ProtectedPathError_Message { + get { + return ResourceManager.GetString("DataSourceChecker_ProtectedPathError_Message", resourceCulture); + } + } + public static string SessionQuitChecker_CanNotRestart_Title { get { return ResourceManager.GetString("SessionQuitChecker_CanNotRestart_Title", resourceCulture); diff --git a/src/ByteSync.Client/Assets/Resources/Resources.fr.resx b/src/ByteSync.Client/Assets/Resources/Resources.fr.resx index 18a47968..1f53c5f1 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.fr.resx +++ b/src/ByteSync.Client/Assets/Resources/Resources.fr.resx @@ -897,6 +897,12 @@ Vous devez sélectionner au minimum 2 Nœuds de Données. Il n'est pas possible d'ajouter cet élément pour la raison suivante : Soit cet élément, soit un parent, soit un descendant est déjà présent. + + Impossible d'ajouter la source + + + Le chemin sélectionné "{0}" correspond à un emplacement système protégé et ne peut pas être inventorié ni synchronisé. + Fichiers et répertoires diff --git a/src/ByteSync.Client/Assets/Resources/Resources.resx b/src/ByteSync.Client/Assets/Resources/Resources.resx index b4f0199e..395492ca 100644 --- a/src/ByteSync.Client/Assets/Resources/Resources.resx +++ b/src/ByteSync.Client/Assets/Resources/Resources.resx @@ -921,6 +921,12 @@ You must select at least 2 Data Nodes. It is not possible to add this element for the following reason: Either this element, or a parent, or a descendant is already present. + + Unable to add the data source + + + The selected path "{0}" points to a protected system location and cannot be inventoried or synchronized. + Files and directories diff --git a/src/ByteSync.Client/Factories/InventoryBuilderFactory.cs b/src/ByteSync.Client/Factories/InventoryBuilderFactory.cs index 7144a913..daa517ac 100644 --- a/src/ByteSync.Client/Factories/InventoryBuilderFactory.cs +++ b/src/ByteSync.Client/Factories/InventoryBuilderFactory.cs @@ -10,16 +10,19 @@ using ByteSync.Interfaces.Factories; using ByteSync.Interfaces.Repositories; using ByteSync.Interfaces.Services.Sessions; +using Microsoft.Extensions.Logging; namespace ByteSync.Factories; public class InventoryBuilderFactory : IInventoryBuilderFactory { private readonly IComponentContext _context; + private readonly ILogger _logger; - public InventoryBuilderFactory(IComponentContext context) + public InventoryBuilderFactory(IComponentContext context, ILogger logger) { _context = context; + _logger = logger; } public IInventoryBuilder CreateInventoryBuilder(DataNode dataNode) @@ -54,9 +57,18 @@ public IInventoryBuilder CreateInventoryBuilder(DataNode dataNode) foreach (var dataSource in myDataSources) { - inventoryBuilder.AddInventoryPart(dataSource); + try + { + inventoryBuilder.AddInventoryPart(dataSource); + } + catch (Exception ex) + { + _logger.LogWarning(ex, + "InventoryBuilderFactory: Failed to add data source {Path} for DataNode {DataNodeId}", + dataSource.Path, dataNode.Id); + } } return inventoryBuilder; } -} \ No newline at end of file +} diff --git a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs index 2465951e..cc869975 100644 --- a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs +++ b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs @@ -1,22 +1,39 @@ -using ByteSync.Assets.Resources; +using ByteSync.Assets.Resources; using ByteSync.Business.DataSources; using ByteSync.Common.Business.Inventories; +using ByteSync.Common.Helpers; using ByteSync.Interfaces; +using ByteSync.Interfaces.Controls.Applications; using ByteSync.Interfaces.Dialogs; +using Microsoft.Extensions.Logging; namespace ByteSync.Services.Inventories; public class DataSourceChecker : IDataSourceChecker { private readonly IDialogService _dialogService; + private readonly IEnvironmentService _environmentService; + private readonly ILogger _logger; - public DataSourceChecker(IDialogService dialogService) + public DataSourceChecker(IDialogService dialogService, IEnvironmentService environmentService, ILogger logger) { _dialogService = dialogService; + _environmentService = environmentService; + _logger = logger; } public async Task CheckDataSource(DataSource dataSource, IEnumerable existingDataSources) { + if (dataSource.ClientInstanceId == _environmentService.ClientInstanceId + && ProtectedPaths.TryGetProtectedRoot(dataSource.Path, _environmentService.OSPlatform, out var protectedRoot)) + { + _logger.LogWarning("Blocked data source path {Path} because it is under protected root {ProtectedRoot}", + dataSource.Path, protectedRoot); + await ShowProtectedPathError(dataSource.Path); + + return false; + } + if (dataSource.Type == FileSystemTypes.File) { if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && ds.Type == FileSystemTypes.File @@ -51,4 +68,14 @@ private async Task ShowError() messageBoxViewModel.ShowOK = true; await _dialogService.ShowMessageBoxAsync(messageBoxViewModel); } -} \ No newline at end of file + + private async Task ShowProtectedPathError(string path) + { + var messageBoxViewModel = _dialogService.CreateMessageBoxViewModel( + nameof(Resources.DataSourceChecker_ProtectedPathError_Title), + nameof(Resources.DataSourceChecker_ProtectedPathError_Message), + path); + messageBoxViewModel.ShowOK = true; + await _dialogService.ShowMessageBoxAsync(messageBoxViewModel); + } +} diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index 968c0e5c..26b36b3d 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -9,6 +9,7 @@ using ByteSync.Business.Sessions; using ByteSync.Common.Business.Inventories; using ByteSync.Common.Business.Misc; +using ByteSync.Common.Helpers; using ByteSync.Interfaces.Controls.Inventories; using ByteSync.Models.FileSystems; using ByteSync.Models.Inventories; @@ -114,6 +115,15 @@ public InventoryPart AddInventoryPart(string fullName) { InventoryPart inventoryPart; + if (ProtectedPaths.TryGetProtectedRoot(fullName, OSPlatform, out var protectedRoot)) + { + _logger.LogWarning( + "InventoryBuilder.AddInventoryPart: Path {Path} is under protected root {ProtectedRoot} and will be rejected", + fullName, protectedRoot); + throw new InvalidOperationException( + $"Path '{fullName}' is under protected root '{protectedRoot}'"); + } + if (Directory.Exists(fullName)) { inventoryPart = new InventoryPart(Inventory, fullName, FileSystemTypes.Directory); @@ -617,4 +627,4 @@ private void AddFileSystemDescription(InventoryPart inventoryPart, FileSystemDes } } } -} \ No newline at end of file +} diff --git a/tests/ByteSync.Client.IntegrationTests/Factories/TestInventoryBuilderFactory.cs b/tests/ByteSync.Client.IntegrationTests/Factories/TestInventoryBuilderFactory.cs index 259d55d6..69c7078d 100644 --- a/tests/ByteSync.Client.IntegrationTests/Factories/TestInventoryBuilderFactory.cs +++ b/tests/ByteSync.Client.IntegrationTests/Factories/TestInventoryBuilderFactory.cs @@ -63,6 +63,7 @@ private void RegisterClientTypes() _builder.RegisterGeneric(typeof(Mock<>)).SingleInstance(); _builder.Register(_ => new Mock>().Object).As>(); + _builder.Register(_ => new Mock>().Object).As>(); _builder.Register(_ => new Mock>().Object).As>(); _builder.Register(_ => new Mock>().Object).As>(); @@ -260,4 +261,4 @@ public void CreateInventoryBuilder_ShouldResolveSharedDependencies() inventoryBuilder2.Should().NotBeNull(); inventoryBuilder1.Should().NotBeSameAs(inventoryBuilder2); } -} \ No newline at end of file +} diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs index f64fca8e..3f7bcb0f 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs @@ -1,10 +1,13 @@ -using ByteSync.Business; +using ByteSync.Business; using ByteSync.Business.DataSources; using ByteSync.Common.Business.Inventories; +using ByteSync.Common.Business.Misc; +using ByteSync.Interfaces.Controls.Applications; using ByteSync.Interfaces.Dialogs; using ByteSync.Services.Inventories; using ByteSync.ViewModels.Misc; using FluentAssertions; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -13,6 +16,7 @@ namespace ByteSync.Client.UnitTests.Services.Inventories; public class DataSourceCheckerTests { private Mock _mockDialogService; + private Mock _mockEnvironmentService; private List _existingDataSources; private DataSourceChecker _dataSourceChecker; @@ -29,7 +33,12 @@ public void Setup() .Setup(x => x.CreateMessageBoxViewModel(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((string title, string message, string[] parameters) => new MessageBoxViewModel { ShowOK = true }); - _dataSourceChecker = new DataSourceChecker(_mockDialogService.Object); + _mockEnvironmentService = new Mock(); + _mockEnvironmentService.SetupGet(x => x.ClientInstanceId).Returns("client1"); + _mockEnvironmentService.SetupGet(x => x.OSPlatform).Returns(OSPlatforms.Linux); + var logger = new Mock>().Object; + + _dataSourceChecker = new DataSourceChecker(_mockDialogService.Object, _mockEnvironmentService.Object, logger); _existingDataSources = new List(); } @@ -160,4 +169,36 @@ public async Task CheckDataSource_Directory_ParentOfExisting_ReturnsFalse() result.Should().BeFalse(); _mockDialogService.Verify(x => x.ShowMessageBoxAsync(It.IsAny()), Times.Once); } -} \ No newline at end of file + + [Test] + public async Task CheckDataSource_ProtectedPath_Local_ReturnsFalse() + { + var dataSource = new DataSource + { + ClientInstanceId = "client1", + Type = FileSystemTypes.Directory, + Path = "/dev" + }; + + var result = await _dataSourceChecker.CheckDataSource(dataSource, _existingDataSources); + + result.Should().BeFalse(); + _mockDialogService.Verify(x => x.ShowMessageBoxAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task CheckDataSource_ProtectedPath_Remote_ReturnsTrue() + { + var dataSource = new DataSource + { + ClientInstanceId = "client2", + Type = FileSystemTypes.Directory, + Path = "/dev" + }; + + var result = await _dataSourceChecker.CheckDataSource(dataSource, _existingDataSources); + + result.Should().BeTrue(); + _mockDialogService.Verify(x => x.ShowMessageBoxAsync(It.IsAny()), Times.Never); + } +} From 578a54bd0a2442ff17e1c58ea226f9c8ba62d141 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 2 Feb 2026 17:48:12 +0100 Subject: [PATCH 4/7] Update protocol version tests for V2 --- .../Services/Communications/PublicKeysTrusterTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ByteSync.Client.UnitTests/Services/Communications/PublicKeysTrusterTests.cs b/tests/ByteSync.Client.UnitTests/Services/Communications/PublicKeysTrusterTests.cs index 6185833e..f8714a10 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Communications/PublicKeysTrusterTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Communications/PublicKeysTrusterTests.cs @@ -78,7 +78,7 @@ public void SetUp() public async Task OnTrustPublicKeyRequestedAsync_WithIncompatibleProtocolVersion_ShouldThrowInvalidOperationException() { var sessionId = "TestSessionId"; - var incompatibleVersion = 2; + var incompatibleVersion = ProtocolVersion.CURRENT - 1; var joinerInstanceId = "JoinerInstanceId"; var myPublicKeyCheckData = new PublicKeyCheckData @@ -180,7 +180,7 @@ await FluentActions.Invoking(async () => await _publicKeysTruster.OnTrustPublicK public async Task TrustAllMembersPublicKeys_WithIncompatibleProtocolVersion_ShouldReturnIncompatibleProtocolVersion() { var sessionId = "TestSessionId"; - var incompatibleVersion = 2; + var incompatibleVersion = ProtocolVersion.CURRENT - 1; _sessionMemberApiClient .Setup(c => c.GetMembersClientInstanceIds(sessionId, It.IsAny())) @@ -427,7 +427,7 @@ public async Task OnPublicKeyCheckDataAskedAsync_WithIncompatibleProtocolVersion { var sessionId = "TestSessionId"; var clientInstanceId = "ClientInstanceId"; - var incompatibleVersion = 2; + var incompatibleVersion = ProtocolVersion.CURRENT - 1; var publicKeyInfo = new PublicKeyInfo { @@ -565,4 +565,4 @@ public async Task TrustAllMembersPublicKeys_WhenProtocolVersionIncompatibleNotif result.Status.Should().Be(JoinSessionStatus.IncompatibleProtocolVersion); } -} \ No newline at end of file +} From ebe9dfeb780468b3cf7cf6f0391095d4e0176331 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 2 Feb 2026 22:35:37 +0100 Subject: [PATCH 5/7] refactor: cleanup --- .../Business/Versions/ProtocolVersion.cs | 2 +- src/ByteSync.Common/Helpers/ProtectedPaths.cs | 11 ++++------- .../Services/Inventories/DataSourceCheckerTests.cs | 12 ++++++------ .../Business/Versions/ProtocolVersionTests.cs | 2 +- .../Helpers/ProtectedPathsTests.cs | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs b/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs index 7959b628..3e8f8608 100644 --- a/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs +++ b/src/ByteSync.Common/Business/Versions/ProtocolVersion.cs @@ -18,4 +18,4 @@ public static bool IsCompatible(int otherVersion) return otherVersion == CURRENT; } -} +} \ No newline at end of file diff --git a/src/ByteSync.Common/Helpers/ProtectedPaths.cs b/src/ByteSync.Common/Helpers/ProtectedPaths.cs index 6f21f2c6..1b706760 100644 --- a/src/ByteSync.Common/Helpers/ProtectedPaths.cs +++ b/src/ByteSync.Common/Helpers/ProtectedPaths.cs @@ -6,7 +6,7 @@ namespace ByteSync.Common.Helpers; public static class ProtectedPaths { - private static readonly string[] ProtectedRoots = + private static readonly string[] _protectedRoots = [ "/dev", "/proc", @@ -31,15 +31,12 @@ public static bool TryGetProtectedRoot(string path, OSPlatforms osPlatform, out } var normalizedPath = Normalize(path); - var comparison = osPlatform == OSPlatforms.Windows - ? StringComparison.OrdinalIgnoreCase - : StringComparison.Ordinal; - foreach (var root in ProtectedRoots) + foreach (var root in _protectedRoots) { var normalizedRoot = Normalize(root); - if (IsSameOrSubPath(normalizedPath, normalizedRoot, comparison)) + if (IsSameOrSubPath(normalizedPath, normalizedRoot, StringComparison.Ordinal)) { protectedRoot = root; @@ -68,4 +65,4 @@ private static bool IsSameOrSubPath(string path, string root, StringComparison c return path.StartsWith(rootWithSeparator, comparison); } -} +} \ No newline at end of file diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs index 3f7bcb0f..07b07708 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/DataSourceCheckerTests.cs @@ -15,11 +15,11 @@ namespace ByteSync.Client.UnitTests.Services.Inventories; public class DataSourceCheckerTests { - private Mock _mockDialogService; - private Mock _mockEnvironmentService; - private List _existingDataSources; + private Mock _mockDialogService = null!; + private Mock _mockEnvironmentService = null!; + private List _existingDataSources = null!; - private DataSourceChecker _dataSourceChecker; + private DataSourceChecker _dataSourceChecker = null!; [SetUp] public void Setup() @@ -31,7 +31,7 @@ public void Setup() .ReturnsAsync(MessageBoxResult.OK); _mockDialogService .Setup(x => x.CreateMessageBoxViewModel(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((string title, string message, string[] parameters) => new MessageBoxViewModel { ShowOK = true }); + .Returns((string _, string _, string[] _) => new MessageBoxViewModel { ShowOK = true }); _mockEnvironmentService = new Mock(); _mockEnvironmentService.SetupGet(x => x.ClientInstanceId).Returns("client1"); @@ -201,4 +201,4 @@ public async Task CheckDataSource_ProtectedPath_Remote_ReturnsTrue() result.Should().BeTrue(); _mockDialogService.Verify(x => x.ShowMessageBoxAsync(It.IsAny()), Times.Never); } -} +} \ No newline at end of file diff --git a/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs b/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs index 334df5f3..fdd5425a 100644 --- a/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs +++ b/tests/ByteSync.Common.Tests/Business/Versions/ProtocolVersionTests.cs @@ -58,4 +58,4 @@ public void IsCompatible_WithNegativeVersion_ShouldReturnFalse() result.Should().BeFalse(); } -} +} \ No newline at end of file diff --git a/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs b/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs index 87f2b22a..ca951268 100644 --- a/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs +++ b/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs @@ -53,4 +53,4 @@ public void TryGetProtectedRoot_Windows_ReturnsFalse() result.Should().BeFalse(); root.Should().BeEmpty(); } -} +} \ No newline at end of file From 714acd884af1303c2bee20b266ed2368c7f7fc5d Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Tue, 3 Feb 2026 10:34:10 +0100 Subject: [PATCH 6/7] Move protected paths to client --- .../Helpers/ProtectedPaths.cs | 4 ++-- .../Services/Inventories/DataSourceChecker.cs | 1 + .../Services/Inventories/InventoryBuilder.cs | 1 + .../Helpers/ProtectedPathsTests.cs | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) rename src/{ByteSync.Common => ByteSync.Client}/Helpers/ProtectedPaths.cs (97%) rename tests/{ByteSync.Common.Tests => ByteSync.Client.UnitTests}/Helpers/ProtectedPathsTests.cs (96%) diff --git a/src/ByteSync.Common/Helpers/ProtectedPaths.cs b/src/ByteSync.Client/Helpers/ProtectedPaths.cs similarity index 97% rename from src/ByteSync.Common/Helpers/ProtectedPaths.cs rename to src/ByteSync.Client/Helpers/ProtectedPaths.cs index 1b706760..000f7938 100644 --- a/src/ByteSync.Common/Helpers/ProtectedPaths.cs +++ b/src/ByteSync.Client/Helpers/ProtectedPaths.cs @@ -2,7 +2,7 @@ using System.IO; using ByteSync.Common.Business.Misc; -namespace ByteSync.Common.Helpers; +namespace ByteSync.Helpers; public static class ProtectedPaths { @@ -65,4 +65,4 @@ private static bool IsSameOrSubPath(string path, string root, StringComparison c return path.StartsWith(rootWithSeparator, comparison); } -} \ No newline at end of file +} diff --git a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs index cc869975..d89d32b4 100644 --- a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs +++ b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs @@ -2,6 +2,7 @@ using ByteSync.Business.DataSources; using ByteSync.Common.Business.Inventories; using ByteSync.Common.Helpers; +using ByteSync.Helpers; using ByteSync.Interfaces; using ByteSync.Interfaces.Controls.Applications; using ByteSync.Interfaces.Dialogs; diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index 26b36b3d..ac0aef76 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -10,6 +10,7 @@ using ByteSync.Common.Business.Inventories; using ByteSync.Common.Business.Misc; using ByteSync.Common.Helpers; +using ByteSync.Helpers; using ByteSync.Interfaces.Controls.Inventories; using ByteSync.Models.FileSystems; using ByteSync.Models.Inventories; diff --git a/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs b/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs similarity index 96% rename from tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs rename to tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs index ca951268..ac1a60e0 100644 --- a/tests/ByteSync.Common.Tests/Helpers/ProtectedPathsTests.cs +++ b/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs @@ -1,9 +1,9 @@ using ByteSync.Common.Business.Misc; -using ByteSync.Common.Helpers; +using ByteSync.Helpers; using FluentAssertions; using NUnit.Framework; -namespace TestingCommon.Helpers; +namespace ByteSync.Client.UnitTests.Helpers; [TestFixture] public class ProtectedPathsTests @@ -53,4 +53,4 @@ public void TryGetProtectedRoot_Windows_ReturnsFalse() result.Should().BeFalse(); root.Should().BeEmpty(); } -} \ No newline at end of file +} From d395386130ba1610143938432cb6a69ddb8ac881 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Tue, 3 Feb 2026 10:40:12 +0100 Subject: [PATCH 7/7] refactor: cleanup --- src/ByteSync.Client/Helpers/ProtectedPaths.cs | 3 +-- .../Services/Inventories/DataSourceChecker.cs | 23 +++++++++---------- .../Services/Inventories/InventoryBuilder.cs | 4 ++-- .../Helpers/ProtectedPathsTests.cs | 2 +- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/ByteSync.Client/Helpers/ProtectedPaths.cs b/src/ByteSync.Client/Helpers/ProtectedPaths.cs index 000f7938..0cbfe427 100644 --- a/src/ByteSync.Client/Helpers/ProtectedPaths.cs +++ b/src/ByteSync.Client/Helpers/ProtectedPaths.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using ByteSync.Common.Business.Misc; @@ -65,4 +64,4 @@ private static bool IsSameOrSubPath(string path, string root, StringComparison c return path.StartsWith(rootWithSeparator, comparison); } -} +} \ No newline at end of file diff --git a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs index d89d32b4..3a4fb9c8 100644 --- a/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs +++ b/src/ByteSync.Client/Services/Inventories/DataSourceChecker.cs @@ -1,12 +1,10 @@ using ByteSync.Assets.Resources; using ByteSync.Business.DataSources; using ByteSync.Common.Business.Inventories; -using ByteSync.Common.Helpers; using ByteSync.Helpers; using ByteSync.Interfaces; using ByteSync.Interfaces.Controls.Applications; using ByteSync.Interfaces.Dialogs; -using Microsoft.Extensions.Logging; namespace ByteSync.Services.Inventories; @@ -38,30 +36,31 @@ public async Task CheckDataSource(DataSource dataSource, IEnumerable ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && ds.Type == FileSystemTypes.File - && ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase))) + && ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase))) { await ShowError(); - + return false; } } else { // We can neither be equal, nor be, nor be a parent of an already selected path - if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && ds.Type == FileSystemTypes.Directory - && (ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase) || - IOUtils.IsSubPathOf(ds.Path, dataSource.Path) || - IOUtils.IsSubPathOf(dataSource.Path, ds.Path)))) + if (existingDataSources.Any(ds => ds.ClientInstanceId.Equals(dataSource.ClientInstanceId) && + ds.Type == FileSystemTypes.Directory + && (ds.Path.Equals(dataSource.Path, StringComparison.InvariantCultureIgnoreCase) || + IOUtils.IsSubPathOf(ds.Path, dataSource.Path) || + IOUtils.IsSubPathOf(dataSource.Path, ds.Path)))) { await ShowError(); - + return false; } } - + return true; } - + private async Task ShowError() { var messageBoxViewModel = _dialogService.CreateMessageBoxViewModel( @@ -79,4 +78,4 @@ private async Task ShowProtectedPathError(string path) messageBoxViewModel.ShowOK = true; await _dialogService.ShowMessageBoxAsync(messageBoxViewModel); } -} +} \ No newline at end of file diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index ac0aef76..46e3511d 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -9,7 +9,6 @@ using ByteSync.Business.Sessions; using ByteSync.Common.Business.Inventories; using ByteSync.Common.Business.Misc; -using ByteSync.Common.Helpers; using ByteSync.Helpers; using ByteSync.Interfaces.Controls.Inventories; using ByteSync.Models.FileSystems; @@ -121,6 +120,7 @@ public InventoryPart AddInventoryPart(string fullName) _logger.LogWarning( "InventoryBuilder.AddInventoryPart: Path {Path} is under protected root {ProtectedRoot} and will be rejected", fullName, protectedRoot); + throw new InvalidOperationException( $"Path '{fullName}' is under protected root '{protectedRoot}'"); } @@ -628,4 +628,4 @@ private void AddFileSystemDescription(InventoryPart inventoryPart, FileSystemDes } } } -} +} \ No newline at end of file diff --git a/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs b/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs index ac1a60e0..dfaa9954 100644 --- a/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs +++ b/tests/ByteSync.Client.UnitTests/Helpers/ProtectedPathsTests.cs @@ -53,4 +53,4 @@ public void TryGetProtectedRoot_Windows_ReturnsFalse() result.Should().BeFalse(); root.Should().BeEmpty(); } -} +} \ No newline at end of file