1
0
Fork 0
mirror of https://github.com/Sonarr/Sonarr synced 2024-12-21 23:33:00 +00:00

New: Option to treat downloads with non-media extensions as failed

Closes #7369
This commit is contained in:
Mark McDowall 2024-11-13 20:13:00 -08:00
parent 8c67a3bdee
commit 776143cc81
25 changed files with 229 additions and 51 deletions

View file

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Torznab;
using NzbDrone.Core.Parser.Model;
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Test.IndexerTests
[Test]
public void should_not_return_config_for_non_existent_indexer()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(It.IsAny<int>()))
.Throws(new ModelNotFoundException(typeof(IndexerDefinition), 0));
Mocker.GetMock<ICachedIndexerSettingsProvider>()
.Setup(v => v.GetSettings(It.IsAny<int>()))
.Returns<CachedIndexerSettings>(null);
var result = Subject.GetSeedConfiguration(new RemoteEpisode
{
@ -38,11 +38,12 @@ namespace NzbDrone.Core.Test.IndexerTests
var settings = new TorznabSettings();
settings.SeedCriteria.SeasonPackSeedTime = 10;
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(It.IsAny<int>()))
.Returns(new IndexerDefinition
Mocker.GetMock<ICachedIndexerSettingsProvider>()
.Setup(v => v.GetSettings(It.IsAny<int>()))
.Returns(new CachedIndexerSettings
{
Settings = settings
FailDownloads = new HashSet<FailDownloads> { FailDownloads.Executables },
SeedCriteriaSettings = settings.SeedCriteria
});
var result = Subject.GetSeedConfiguration(new RemoteEpisode

View file

@ -15,5 +15,6 @@ namespace NzbDrone.Core.Test.IndexerTests
public string BaseUrl { get; set; }
public IEnumerable<int> MultiLanguages { get; set; }
public IEnumerable<int> FailDownloads { get; set; }
}
}

View file

@ -35,6 +35,7 @@ namespace NzbDrone.Core.Download
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
private readonly IEpisodeService _episodeService;
private readonly IMediaFileService _mediaFileService;
private readonly IRejectedImportService _rejectedImportService;
private readonly Logger _logger;
public CompletedDownloadService(IEventAggregator eventAggregator,
@ -46,6 +47,7 @@ namespace NzbDrone.Core.Download
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
IEpisodeService episodeService,
IMediaFileService mediaFileService,
IRejectedImportService rejectedImportService,
Logger logger)
{
_eventAggregator = eventAggregator;
@ -57,6 +59,7 @@ namespace NzbDrone.Core.Download
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
_episodeService = episodeService;
_mediaFileService = mediaFileService;
_rejectedImportService = rejectedImportService;
_logger = logger;
}
@ -165,10 +168,8 @@ namespace NzbDrone.Core.Download
{
var firstResult = importResults.First();
if (firstResult.Result == ImportResultType.Rejected && firstResult.ImportDecision.LocalEpisode == null)
if (_rejectedImportService.Process(trackedDownload, firstResult))
{
trackedDownload.Warn(new TrackedDownloadStatusMessage(firstResult.Errors.First(), new List<string>()));
return;
}
}

View file

@ -55,14 +55,18 @@ namespace NzbDrone.Core.Download
{
try
{
// Process completed items followed by failed, this allows failed imports to have
// their state changed and be processed immediately instead of the next execution.
if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
{
_completedDownloadService.Import(trackedDownload);
}
if (trackedDownload.State == TrackedDownloadState.FailedPending)
{
_failedDownloadService.ProcessFailed(trackedDownload);
}
else if (enableCompletedDownloadHandling && trackedDownload.State == TrackedDownloadState.ImportPending)
{
_completedDownloadService.Import(trackedDownload);
}
}
catch (Exception e)
{

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.MediaFiles.EpisodeImport;
namespace NzbDrone.Core.Download;
public interface IRejectedImportService
{
bool Process(TrackedDownload trackedDownload, ImportResult importResult);
}
public class RejectedImportService : IRejectedImportService
{
private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider;
public RejectedImportService(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider)
{
_cachedIndexerSettingsProvider = cachedIndexerSettingsProvider;
}
public bool Process(TrackedDownload trackedDownload, ImportResult importResult)
{
if (importResult.Result != ImportResultType.Rejected || importResult.ImportDecision.LocalEpisode != null)
{
return false;
}
var indexerSettings = _cachedIndexerSettingsProvider.GetSettings(trackedDownload.RemoteEpisode.Release.IndexerId);
var rejectionReason = importResult.ImportDecision.Rejections.FirstOrDefault()?.Reason;
if (rejectionReason == ImportRejectionReason.DangerousFile &&
indexerSettings.FailDownloads.Contains(FailDownloads.PotentiallyDangerous))
{
trackedDownload.Fail();
}
else if (rejectionReason == ImportRejectionReason.ExecutableFile &&
indexerSettings.FailDownloads.Contains(FailDownloads.Executables))
{
trackedDownload.Fail();
}
else
{
trackedDownload.Warn(new TrackedDownloadStatusMessage(importResult.Errors.First(), new List<string>()));
}
return true;
}
}

View file

@ -35,6 +35,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
Status = TrackedDownloadStatus.Warning;
StatusMessages = statusMessages;
}
public void Fail()
{
Status = TrackedDownloadStatus.Error;
State = TrackedDownloadState.FailedPending;
}
}
public enum TrackedDownloadState

View file

@ -166,6 +166,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
trackedDownload.RemoteEpisode.Release.IndexerFlags = flags;
}
if (downloadHistory != null)
{
trackedDownload.RemoteEpisode.Release.IndexerId = downloadHistory.IndexerId;
}
}
}

View file

@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
BaseUrl = "https://api.broadcasthe.net/";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
@ -48,6 +49,9 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Indexers;
public interface ICachedIndexerSettingsProvider
{
CachedIndexerSettings GetSettings(int indexerId);
}
public class CachedIndexerSettingsProvider : ICachedIndexerSettingsProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
{
private readonly IIndexerFactory _indexerFactory;
private readonly ICached<CachedIndexerSettings> _cache;
public CachedIndexerSettingsProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
{
_indexerFactory = indexerFactory;
_cache = cacheManager.GetRollingCache<CachedIndexerSettings>(GetType(), "settingsByIndexer", TimeSpan.FromHours(1));
}
public CachedIndexerSettings GetSettings(int indexerId)
{
if (indexerId == 0)
{
return null;
}
return _cache.Get(indexerId.ToString(), () => FetchIndexerSettings(indexerId));
}
private CachedIndexerSettings FetchIndexerSettings(int indexerId)
{
var indexer = _indexerFactory.Get(indexerId);
var indexerSettings = indexer.Settings as IIndexerSettings;
if (indexerSettings == null)
{
return null;
}
var settings = new CachedIndexerSettings
{
FailDownloads = indexerSettings.FailDownloads.Select(f => (FailDownloads)f).ToHashSet()
};
if (indexer.Settings is ITorrentIndexerSettings torrentIndexerSettings)
{
settings.SeedCriteriaSettings = torrentIndexerSettings.SeedCriteria;
}
return settings;
}
public void Handle(ProviderUpdatedEvent<IIndexer> message)
{
_cache.Clear();
}
}
public class CachedIndexerSettings
{
public HashSet<FailDownloads> FailDownloads { get; set; }
public SeedCriteriaSettings SeedCriteriaSettings { get; set; }
}

View file

@ -0,0 +1,12 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Indexers;
public enum FailDownloads
{
[FieldOption(Label = "Executables")]
Executables = 0,
[FieldOption(Label = "Potentially Dangerous")]
PotentiallyDangerous = 1
}

View file

@ -24,6 +24,7 @@ namespace NzbDrone.Core.Indexers.Fanzub
{
BaseUrl = "http://fanzub.com/rss/";
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsRssUrl", HelpText = "IndexerSettingsRssUrlHelpText")]
@ -36,6 +37,9 @@ namespace NzbDrone.Core.Indexers.Fanzub
[FieldDefinition(2, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(3, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -38,6 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList
AnimeCategories = Array.Empty<int>();
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "Username", Privacy = PrivacyLevel.UserName)]
@ -67,6 +68,9 @@ namespace NzbDrone.Core.Indexers.FileList
[FieldDefinition(8, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Indexers.HDBits
Codecs = Array.Empty<int>();
Mediums = Array.Empty<int>();
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsApiUrl", Advanced = true, HelpText = "IndexerSettingsApiUrlHelpText")]
@ -64,6 +65,9 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(9, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(10, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -8,5 +8,7 @@ namespace NzbDrone.Core.Indexers
string BaseUrl { get; set; }
IEnumerable<int> MultiLanguages { get; set; }
IEnumerable<int> FailDownloads { get; set; }
}
}

View file

@ -34,6 +34,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
{
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerIPTorrentsSettingsFeedUrl", HelpText = "IndexerIPTorrentsSettingsFeedUrlHelpText")]
@ -51,6 +52,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents
[FieldDefinition(4, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -59,6 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab
Categories = new[] { 5030, 5040 };
AnimeCategories = Enumerable.Empty<int>();
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "URL")]
@ -86,6 +87,9 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(8, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
// Field 8 is used by TorznabSettings MinimumSeeders
// If you need to add another field here, update TorznabSettings as well and this comment

View file

@ -30,6 +30,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
AdditionalParameters = "&cats=1_0&filter=1";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
@ -53,6 +54,9 @@ namespace NzbDrone.Core.Indexers.Nyaa
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -1,10 +1,6 @@
using System;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Indexers
{
@ -14,15 +10,13 @@ namespace NzbDrone.Core.Indexers
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
}
public class SeedConfigProvider : ISeedConfigProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
public class SeedConfigProvider : ISeedConfigProvider
{
private readonly IIndexerFactory _indexerFactory;
private readonly ICached<SeedCriteriaSettings> _cache;
private readonly ICachedIndexerSettingsProvider _cachedIndexerSettingsProvider;
public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
public SeedConfigProvider(ICachedIndexerSettingsProvider cachedIndexerSettingsProvider)
{
_indexerFactory = indexerFactory;
_cache = cacheManager.GetRollingCache<SeedCriteriaSettings>(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1));
_cachedIndexerSettingsProvider = cachedIndexerSettingsProvider;
}
public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode)
@ -47,7 +41,8 @@ namespace NzbDrone.Core.Indexers
return null;
}
var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId));
var settings = _cachedIndexerSettingsProvider.GetSettings(indexerId);
var seedCriteria = settings?.SeedCriteriaSettings;
if (seedCriteria == null)
{
@ -67,25 +62,5 @@ namespace NzbDrone.Core.Indexers
return seedConfig;
}
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
{
try
{
var indexer = _indexerFactory.Get(indexerId);
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
return torrentIndexerSettings?.SeedCriteria;
}
catch (ModelNotFoundException)
{
return null;
}
}
public void Handle(ProviderUpdatedEvent<IIndexer> message)
{
_cache.Clear();
}
}
}

View file

@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss
AllowZeroSize = false;
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsRssUrl")]
@ -51,6 +52,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(7, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -28,6 +28,7 @@ namespace NzbDrone.Core.Indexers.Torrentleech
BaseUrl = "http://rss.torrentleech.org";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
MultiLanguages = Array.Empty<int>();
FailDownloads = Array.Empty<int>();
}
[FieldDefinition(0, Label = "IndexerSettingsWebsiteUrl")]
@ -48,6 +49,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech
[FieldDefinition(5, Type = FieldType.Select, SelectOptions = typeof(RealLanguageFieldConverter), Label = "IndexerSettingsMultiLanguageRelease", HelpText = "IndexerSettingsMultiLanguageReleaseHelpText", Advanced = true)]
public IEnumerable<int> MultiLanguages { get; set; }
[FieldDefinition(6, Type = FieldType.Select, SelectOptions = typeof(FailDownloads), Label = "IndexerSettingsFailDownloads", HelpText = "IndexerSettingsFailDownloadsHelpText", Advanced = true)]
public IEnumerable<int> FailDownloads { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View file

@ -52,13 +52,13 @@ namespace NzbDrone.Core.Indexers.Torznab
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(8, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)]
[FieldDefinition(9, Type = FieldType.Number, Label = "IndexerSettingsMinimumSeeders", HelpText = "IndexerSettingsMinimumSeedersHelpText", Advanced = true)]
public int MinimumSeeders { get; set; }
[FieldDefinition(9)]
[FieldDefinition(10)]
public SeedCriteriaSettings SeedCriteria { get; set; } = new ();
[FieldDefinition(10, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
[FieldDefinition(11, Type = FieldType.Checkbox, Label = "IndexerSettingsRejectBlocklistedTorrentHashes", HelpText = "IndexerSettingsRejectBlocklistedTorrentHashesHelpText", Advanced = true)]
public bool RejectBlocklistedTorrentHashesWhileGrabbing { get; set; }
public override NzbDroneValidationResult Validate()

View file

@ -998,6 +998,8 @@
"IndexerSettingsCategoriesHelpText": "Drop down list, leave blank to disable standard/daily shows",
"IndexerSettingsCookie": "Cookie",
"IndexerSettingsCookieHelpText": "If your site requires a login cookie to access the rss, you'll have to retrieve it via a browser.",
"IndexerSettingsFailDownloads": "Fail Downloads",
"IndexerSettingsFailDownloadsHelpText": "While processing completed downloads {appName} will treat selected errors preventing importing as failed downloads.",
"IndexerSettingsMinimumSeeders": "Minimum Seeders",
"IndexerSettingsMinimumSeedersHelpText": "Minimum number of seeders required.",
"IndexerSettingsMultiLanguageRelease": "Multi Languages",

View file

@ -318,6 +318,11 @@ namespace NzbDrone.Core.MediaFiles
{
var files = _diskProvider.GetFiles(folder, true);
if (files.Any(file => FileExtensions.DangerousExtensions.Contains(Path.GetExtension(file))))
{
return RejectionResult(ImportRejectionReason.DangerousFile, "Caution: Found potentially dangerous file");
}
if (files.Any(file => FileExtensions.ExecutableExtensions.Contains(Path.GetExtension(file))))
{
return RejectionResult(ImportRejectionReason.ExecutableFile, "Caution: Found executable file");

View file

@ -5,6 +5,7 @@ public enum ImportRejectionReason
Unknown,
FileLocked,
UnknownSeries,
DangerousFile,
ExecutableFile,
ArchiveFile,
SeriesFolder,

View file

@ -18,19 +18,27 @@ namespace NzbDrone.Core.MediaFiles
".tb2",
".tbz2",
".tgz",
".zip",
".zip"
};
private static List<string> _dangerousExtensions = new List<string>
{
".lnk",
".ps1",
".vbs",
".zipx"
};
private static List<string> _executableExtensions = new List<string>
{
".exe",
".bat",
".cmd",
".exe",
".sh"
};
public static HashSet<string> ArchiveExtensions => new HashSet<string>(_archiveExtensions, StringComparer.OrdinalIgnoreCase);
public static HashSet<string> DangerousExtensions => new HashSet<string>(_dangerousExtensions, StringComparer.OrdinalIgnoreCase);
public static HashSet<string> ExecutableExtensions => new HashSet<string>(_executableExtensions, StringComparer.OrdinalIgnoreCase);
}
}