diff --git a/src/Lidarr.Api.V1/Artist/ArtistModule.cs b/src/Lidarr.Api.V1/Artist/ArtistModule.cs index 3b9267d46..af3ef571d 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistModule.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistModule.cs @@ -45,6 +45,7 @@ namespace Lidarr.Api.V1.Artist ArtistPathValidator artistPathValidator, ArtistExistsValidator artistExistsValidator, ArtistAncestorValidator artistAncestorValidator, + SystemFolderValidator systemFolderValidator, ProfileExistsValidator profileExistsValidator, LanguageProfileExistsValidator languageProfileExistsValidator, MetadataProfileExistsValidator metadataProfileExistsValidator @@ -71,6 +72,7 @@ namespace Lidarr.Api.V1.Artist .SetValidator(rootFolderValidator) .SetValidator(artistPathValidator) .SetValidator(artistAncestorValidator) + .SetValidator(systemFolderValidator) .When(s => !s.Path.IsNullOrWhiteSpace()); SharedValidator.RuleFor(s => s.QualityProfileId).SetValidator(profileExistsValidator); diff --git a/src/Lidarr.Api.V1/RootFolders/RootFolderModule.cs b/src/Lidarr.Api.V1/RootFolders/RootFolderModule.cs index 6ba44bc63..0ee85e2b6 100644 --- a/src/Lidarr.Api.V1/RootFolders/RootFolderModule.cs +++ b/src/Lidarr.Api.V1/RootFolders/RootFolderModule.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using FluentValidation; using NzbDrone.Core.RootFolders; using NzbDrone.Core.Validation.Paths; @@ -15,7 +15,11 @@ namespace Lidarr.Api.V1.RootFolders IBroadcastSignalRMessage signalRBroadcaster, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator, - MappedNetworkDriveValidator mappedNetworkDriveValidator) + MappedNetworkDriveValidator mappedNetworkDriveValidator, + StartupFolderValidator startupFolderValidator, + SystemFolderValidator systemFolderValidator, + FolderWritableValidator folderWritableValidator + ) : base(signalRBroadcaster) { _rootFolderService = rootFolderService; @@ -30,7 +34,10 @@ namespace Lidarr.Api.V1.RootFolders .IsValidPath() .SetValidator(rootFolderValidator) .SetValidator(mappedNetworkDriveValidator) - .SetValidator(pathExistsValidator); + .SetValidator(startupFolderValidator) + .SetValidator(pathExistsValidator) + .SetValidator(systemFolderValidator) + .SetValidator(folderWritableValidator); } private RootFolderResource GetRootFolder(int id) @@ -55,4 +62,4 @@ namespace Lidarr.Api.V1.RootFolders _rootFolderService.Remove(id); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 0722ecfcf..4702e530c 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -347,6 +347,7 @@ + Always diff --git a/src/NzbDrone.Core.Test/ValidationTests/SystemFolderValidatorFixture.cs b/src/NzbDrone.Core.Test/ValidationTests/SystemFolderValidatorFixture.cs new file mode 100644 index 000000000..89bd34078 --- /dev/null +++ b/src/NzbDrone.Core.Test/ValidationTests/SystemFolderValidatorFixture.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Music; +using NzbDrone.Core.Validation.Paths; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ValidationTests +{ + public class SystemFolderValidatorFixture : CoreTest + { + private TestValidator _validator; + + [SetUp] + public void Setup() + { + _validator = new TestValidator + { + v => v.RuleFor(s => s.Path).SetValidator(Subject) + }; + } + + [Test] + public void should_not_be_valid_if_set_to_windows_folder() + { + WindowsOnly(); + + var artist = Builder.CreateNew() + .With(s => s.Path = Environment.GetFolderPath(Environment.SpecialFolder.Windows)) + .Build(); + + _validator.Validate(artist).IsValid.Should().BeFalse(); + } + + [Test] + public void should_not_be_valid_if_child_of_windows_folder() + { + WindowsOnly(); + + var artist = Builder.CreateNew() + .With(s => s.Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Test")) + .Build(); + + _validator.Validate(artist).IsValid.Should().BeFalse(); + } + + [Test] + public void should_not_be_valid_if_set_to_bin_folder() + { + MonoOnly(); + + var artist = Builder.CreateNew() + .With(s => s.Path = "/bin") + .Build(); + + _validator.Validate(artist).IsValid.Should().BeFalse(); + } + + [Test] + public void should_not_be_valid_if_child_of_bin_folder() + { + MonoOnly(); + + var artist = Builder.CreateNew() + .With(s => s.Path = "/bin/test") + .Build(); + + _validator.Validate(artist).IsValid.Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs index 053e51026..bd9763c3f 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs @@ -21,16 +21,19 @@ namespace NzbDrone.Core.MediaFiles private readonly IDiskProvider _diskProvider; private readonly IRecycleBinProvider _recycleBinProvider; private readonly IMediaFileService _mediaFileService; + private readonly IArtistService _artistService; private readonly Logger _logger; public MediaFileDeletionService(IDiskProvider diskProvider, IRecycleBinProvider recycleBinProvider, IMediaFileService mediaFileService, + IArtistService artistService, Logger logger) { _diskProvider = diskProvider; _recycleBinProvider = recycleBinProvider; _mediaFileService = mediaFileService; + _artistService = artistService; _logger = logger; } @@ -76,6 +79,25 @@ namespace NzbDrone.Core.MediaFiles { if (message.DeleteFiles) { + var artist = message.Artist; + var allArtists = _artistService.GetAllArtists(); + + foreach (var s in allArtists) + { + if (s.Id == artist.Id) continue; + + if (artist.Path.IsParentPath(s.Path)) + { + _logger.Error("Artist path: '{0}' is a parent of another artist, not deleting files.", artist.Path); + return; + } + + if (artist.Path.PathEquals(s.Path)) + { + _logger.Error("Artist path: '{0}' is the same as another artist, not deleting files.", artist.Path); + return; + } + } if (_diskProvider.FolderExists(message.Artist.Path)) { _recycleBinProvider.DeleteFolder(message.Artist.Path); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 7d23eeb97..96cdc27a1 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1092,6 +1092,7 @@ + diff --git a/src/NzbDrone.Core/Validation/Paths/ArtistAncestorValidator.cs b/src/NzbDrone.Core/Validation/Paths/ArtistAncestorValidator.cs index 6ba36edfe..d6de1df78 100644 --- a/src/NzbDrone.Core/Validation/Paths/ArtistAncestorValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/ArtistAncestorValidator.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.Validation.Paths private readonly IArtistService _artistService; public ArtistAncestorValidator(IArtistService artistService) - : base("Path is an ancestor of an existing path") + : base("Path is an ancestor of an existing artist") { _artistService = artistService; } diff --git a/src/NzbDrone.Core/Validation/Paths/SystemFolderValidator.cs b/src/NzbDrone.Core/Validation/Paths/SystemFolderValidator.cs new file mode 100644 index 000000000..a7af763cb --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/SystemFolderValidator.cs @@ -0,0 +1,92 @@ +using System; +using FluentValidation.Validators; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Core.Validation.Paths +{ + public class SystemFolderValidator : PropertyValidator + { + public SystemFolderValidator() + : base("Is {relationship} system folder {systemFolder}") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + var folder = context.PropertyValue.ToString(); + + if (OsInfo.IsWindows) + { + var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + context.MessageFormatter.AppendArgument("systemFolder", windowsFolder); + + if (windowsFolder.PathEquals(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "set to"); + + return false; + } + + if (windowsFolder.IsParentPath(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "child of"); + + return false; + } + } + else if (OsInfo.IsOsx) + { + var systemFolder = "/System"; + context.MessageFormatter.AppendArgument("systemFolder", systemFolder); + + if (systemFolder.PathEquals(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "child of"); + + return false; + } + + if (systemFolder.IsParentPath(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "child of"); + + return false; + } + } + else + { + var folders = new[] + { + "/bin", + "/boot", + "/lib", + "/sbin", + "/srv", + "/proc" + }; + + foreach (var f in folders) + { + context.MessageFormatter.AppendArgument("systemFolder", f); + + if (f.PathEquals(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "child of"); + + return false; + } + + if (f.IsParentPath(folder)) + { + context.MessageFormatter.AppendArgument("relationship", "child of"); + + return false; + } + } + } + + return true; + } + } +}