diff --git a/src/Radarr.Http/Frontend/Mappers/BackupFileMapper.cs b/src/Radarr.Http/Frontend/Mappers/BackupFileMapper.cs index adc08c319f..612b3515b6 100644 --- a/src/Radarr.Http/Frontend/Mappers/BackupFileMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/BackupFileMapper.cs @@ -15,11 +15,13 @@ namespace Radarr.Http.Frontend.Mappers _backupService = backupService; } - public override string Map(string resourceUrl) + protected override string FolderPath => _backupService.GetBackupFolder(); + + protected override string MapPath(string resourceUrl) { var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar); - return Path.Combine(_backupService.GetBackupFolder(), path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/BrowserConfig.cs b/src/Radarr.Http/Frontend/Mappers/BrowserConfig.cs index 6d910940f3..c8412823df 100644 --- a/src/Radarr.Http/Frontend/Mappers/BrowserConfig.cs +++ b/src/Radarr.Http/Frontend/Mappers/BrowserConfig.cs @@ -8,13 +8,20 @@ namespace Radarr.Http.Frontend.Mappers { public class BrowserConfig : UrlBaseReplacementResourceMapperBase { + private readonly IAppFolderInfo _appFolderInfo; + private readonly IConfigFileProvider _configFileProvider; + public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger) : base(diskProvider, configFileProvider, logger) { - FilePath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "Content", "browserconfig.xml"); + _appFolderInfo = appFolderInfo; + _configFileProvider = configFileProvider; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + protected override string FilePath => Path.Combine(FolderPath, "Content", "browserconfig.xml"); + + protected override string MapPath(string resourceUrl) { return FilePath; } diff --git a/src/Radarr.Http/Frontend/Mappers/CacheBreakerProvider.cs b/src/Radarr.Http/Frontend/Mappers/CacheBreakerProvider.cs index 1ae8d8cfa6..bb82d7cf77 100644 --- a/src/Radarr.Http/Frontend/Mappers/CacheBreakerProvider.cs +++ b/src/Radarr.Http/Frontend/Mappers/CacheBreakerProvider.cs @@ -37,6 +37,12 @@ namespace Radarr.Http.Frontend.Mappers var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl)); var pathToFile = mapper.Map(resourceUrl); + + if (pathToFile == null) + { + return resourceUrl; + } + var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64(); return resourceUrl + "?h=" + hash.Trim('='); diff --git a/src/Radarr.Http/Frontend/Mappers/FaviconMapper.cs b/src/Radarr.Http/Frontend/Mappers/FaviconMapper.cs index 65efb29035..93226714ec 100644 --- a/src/Radarr.Http/Frontend/Mappers/FaviconMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/FaviconMapper.cs @@ -18,7 +18,9 @@ namespace Radarr.Http.Frontend.Mappers _configFileProvider = configFileProvider; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + + protected override string MapPath(string resourceUrl) { var fileName = "favicon.ico"; @@ -29,7 +31,7 @@ namespace Radarr.Http.Frontend.Mappers var path = Path.Combine("Content", "Images", "Icons", fileName); - return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/HtmlMapperBase.cs b/src/Radarr.Http/Frontend/Mappers/HtmlMapperBase.cs index 0e2e0bf438..ea9979b2d1 100644 --- a/src/Radarr.Http/Frontend/Mappers/HtmlMapperBase.cs +++ b/src/Radarr.Http/Frontend/Mappers/HtmlMapperBase.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.Configuration; namespace Radarr.Http.Frontend.Mappers { @@ -13,19 +14,22 @@ namespace Radarr.Http.Frontend.Mappers private readonly Lazy _cacheBreakProviderFactory; private static readonly Regex ReplaceRegex = new Regex(@"(?:(?href|src)=\"")(?.*?(?css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private string _urlBase; private string _generatedContent; protected HtmlMapperBase(IDiskProvider diskProvider, + IConfigFileProvider configFileProvider, Lazy cacheBreakProviderFactory, Logger logger) : base(diskProvider, logger) { _diskProvider = diskProvider; _cacheBreakProviderFactory = cacheBreakProviderFactory; + + _urlBase = configFileProvider.UrlBase; } - protected string HtmlPath; - protected string UrlBase; + protected abstract string HtmlPath { get; } protected override Stream GetContentStream(string filePath) { @@ -62,10 +66,10 @@ namespace Radarr.Http.Frontend.Mappers url = cacheBreakProvider.AddCacheBreakerToPath(match.Groups["path"].Value); } - return $"{match.Groups["attribute"].Value}=\"{UrlBase}{url}\""; + return $"{match.Groups["attribute"].Value}=\"{_urlBase}{url}\""; }); - text = text.Replace("__URL_BASE__", UrlBase); + text = text.Replace("__URL_BASE__", _urlBase); _generatedContent = text; diff --git a/src/Radarr.Http/Frontend/Mappers/IndexHtmlMapper.cs b/src/Radarr.Http/Frontend/Mappers/IndexHtmlMapper.cs index 39ae369169..7ca351ecfc 100644 --- a/src/Radarr.Http/Frontend/Mappers/IndexHtmlMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/IndexHtmlMapper.cs @@ -9,6 +9,7 @@ namespace Radarr.Http.Frontend.Mappers { public class IndexHtmlMapper : HtmlMapperBase { + private readonly IAppFolderInfo _appFolderInfo; private readonly IConfigFileProvider _configFileProvider; public IndexHtmlMapper(IAppFolderInfo appFolderInfo, @@ -16,15 +17,16 @@ namespace Radarr.Http.Frontend.Mappers IConfigFileProvider configFileProvider, Lazy cacheBreakProviderFactory, Logger logger) - : base(diskProvider, cacheBreakProviderFactory, logger) + : base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger) { + _appFolderInfo = appFolderInfo; _configFileProvider = configFileProvider; - - HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, "index.html"); - UrlBase = configFileProvider.UrlBase; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + protected override string HtmlPath => Path.Combine(FolderPath, "index.html"); + + protected override string MapPath(string resourceUrl) { return HtmlPath; } diff --git a/src/Radarr.Http/Frontend/Mappers/LogFileMapper.cs b/src/Radarr.Http/Frontend/Mappers/LogFileMapper.cs index f1cb5925dc..40e0c9dc74 100644 --- a/src/Radarr.Http/Frontend/Mappers/LogFileMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/LogFileMapper.cs @@ -16,12 +16,14 @@ namespace Radarr.Http.Frontend.Mappers _appFolderInfo = appFolderInfo; } - public override string Map(string resourceUrl) + protected override string FolderPath => _appFolderInfo.GetLogFolder(); + + protected override string MapPath(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = Path.GetFileName(path); - return Path.Combine(_appFolderInfo.GetLogFolder(), path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/LoginHtmlMapper.cs b/src/Radarr.Http/Frontend/Mappers/LoginHtmlMapper.cs index 43956941ac..4af77e795c 100644 --- a/src/Radarr.Http/Frontend/Mappers/LoginHtmlMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/LoginHtmlMapper.cs @@ -9,6 +9,7 @@ namespace Radarr.Http.Frontend.Mappers { public class LoginHtmlMapper : HtmlMapperBase { + private readonly IAppFolderInfo _appFolderInfo; private readonly IConfigFileProvider _configFileProvider; public LoginHtmlMapper(IAppFolderInfo appFolderInfo, @@ -16,14 +17,16 @@ namespace Radarr.Http.Frontend.Mappers Lazy cacheBreakProviderFactory, IConfigFileProvider configFileProvider, Logger logger) - : base(diskProvider, cacheBreakProviderFactory, logger) + : base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger) { + _appFolderInfo = appFolderInfo; _configFileProvider = configFileProvider; - HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html"); - UrlBase = configFileProvider.UrlBase; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + protected override string HtmlPath => Path.Combine(FolderPath, "login.html"); + + protected override string MapPath(string resourceUrl) { return HtmlPath; } diff --git a/src/Radarr.Http/Frontend/Mappers/ManifestMapper.cs b/src/Radarr.Http/Frontend/Mappers/ManifestMapper.cs index 71f889aaf2..7689811afa 100644 --- a/src/Radarr.Http/Frontend/Mappers/ManifestMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/ManifestMapper.cs @@ -8,6 +8,7 @@ namespace Radarr.Http.Frontend.Mappers { public class ManifestMapper : UrlBaseReplacementResourceMapperBase { + private readonly IAppFolderInfo _appFolderInfo; private readonly IConfigFileProvider _configFileProvider; private string _generatedContent; @@ -15,11 +16,14 @@ namespace Radarr.Http.Frontend.Mappers public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger) : base(diskProvider, configFileProvider, logger) { + _appFolderInfo = appFolderInfo; _configFileProvider = configFileProvider; - FilePath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "Content", "manifest.json"); } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + protected override string FilePath => Path.Combine(FolderPath, "Content", "manifest.json"); + + protected override string MapPath(string resourceUrl) { return FilePath; } diff --git a/src/Radarr.Http/Frontend/Mappers/MediaCoverMapper.cs b/src/Radarr.Http/Frontend/Mappers/MediaCoverMapper.cs index 8522edf625..2deea9daca 100644 --- a/src/Radarr.Http/Frontend/Mappers/MediaCoverMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/MediaCoverMapper.cs @@ -22,7 +22,9 @@ namespace Radarr.Http.Frontend.Mappers _diskProvider = diskProvider; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover"); + + protected override string MapPath(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar); diff --git a/src/Radarr.Http/Frontend/Mappers/RobotsTxtMapper.cs b/src/Radarr.Http/Frontend/Mappers/RobotsTxtMapper.cs index 06fa10407b..a9dedfcc30 100644 --- a/src/Radarr.Http/Frontend/Mappers/RobotsTxtMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/RobotsTxtMapper.cs @@ -18,11 +18,13 @@ namespace Radarr.Http.Frontend.Mappers _configFileProvider = configFileProvider; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + + protected override string MapPath(string resourceUrl) { var path = Path.Combine("Content", "robots.txt"); - return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/StaticResourceMapper.cs b/src/Radarr.Http/Frontend/Mappers/StaticResourceMapper.cs index b9dcd34d9b..b3f74bd93f 100644 --- a/src/Radarr.Http/Frontend/Mappers/StaticResourceMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/StaticResourceMapper.cs @@ -18,12 +18,14 @@ namespace Radarr.Http.Frontend.Mappers _configFileProvider = configFileProvider; } - public override string Map(string resourceUrl) + protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); + + protected override string MapPath(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar); - return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs b/src/Radarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs index 1f946468b0..b343a83ddb 100644 --- a/src/Radarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs +++ b/src/Radarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs @@ -27,14 +27,28 @@ namespace Radarr.Http.Frontend.Mappers _caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase; } - public abstract string Map(string resourceUrl); + protected abstract string FolderPath { get; } + protected abstract string MapPath(string resourceUrl); public abstract bool CanHandle(string resourceUrl); + public string Map(string resourceUrl) + { + var filePath = Path.GetFullPath(MapPath(resourceUrl)); + var parentPath = Path.GetFullPath(FolderPath) + Path.DirectorySeparatorChar; + + return filePath.StartsWith(parentPath) ? filePath : null; + } + public Task GetResponse(string resourceUrl) { var filePath = Map(resourceUrl); + if (filePath == null) + { + return Task.FromResult(null); + } + if (_diskProvider.FileExists(filePath, _caseSensitive)) { if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType)) diff --git a/src/Radarr.Http/Frontend/Mappers/UpdateLogFileMapper.cs b/src/Radarr.Http/Frontend/Mappers/UpdateLogFileMapper.cs index b6d549009c..203eda053d 100644 --- a/src/Radarr.Http/Frontend/Mappers/UpdateLogFileMapper.cs +++ b/src/Radarr.Http/Frontend/Mappers/UpdateLogFileMapper.cs @@ -16,12 +16,14 @@ namespace Radarr.Http.Frontend.Mappers _appFolderInfo = appFolderInfo; } - public override string Map(string resourceUrl) + protected override string FolderPath => _appFolderInfo.GetUpdateLogFolder(); + + protected override string MapPath(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = Path.GetFileName(path); - return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path); + return Path.Combine(FolderPath, path); } public override bool CanHandle(string resourceUrl) diff --git a/src/Radarr.Http/Frontend/Mappers/UrlBaseReplacementResourceMapperBase.cs b/src/Radarr.Http/Frontend/Mappers/UrlBaseReplacementResourceMapperBase.cs index d8697c24a4..084a70d92b 100644 --- a/src/Radarr.Http/Frontend/Mappers/UrlBaseReplacementResourceMapperBase.cs +++ b/src/Radarr.Http/Frontend/Mappers/UrlBaseReplacementResourceMapperBase.cs @@ -20,9 +20,9 @@ namespace Radarr.Http.Frontend.Mappers _urlBase = configFileProvider.UrlBase; } - protected string FilePath; + protected abstract string FilePath { get; } - public override string Map(string resourceUrl) + protected override string MapPath(string resourceUrl) { return FilePath; } diff --git a/src/Radarr.Http/Frontend/StaticResourceController.cs b/src/Radarr.Http/Frontend/StaticResourceController.cs index f4158e40f4..4b3575d3f4 100644 --- a/src/Radarr.Http/Frontend/StaticResourceController.cs +++ b/src/Radarr.Http/Frontend/StaticResourceController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; @@ -16,6 +17,7 @@ namespace Radarr.Http.Frontend { private readonly IEnumerable _requestMappers; private readonly Logger _logger; + private static readonly Regex InvalidPathRegex = new (@"([\/\\]|%2f|%5c)\.\.|\.\.([\/\\]|%2f|%5c)", RegexOptions.IgnoreCase | RegexOptions.Compiled); public StaticResourceController(IEnumerable requestMappers, Logger logger) @@ -50,6 +52,11 @@ namespace Radarr.Http.Frontend { path = "/" + (path ?? ""); + if (InvalidPathRegex.IsMatch(path)) + { + return NotFound(); + } + var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); if (mapper != null)