Compare commits

..

No commits in common. "develop" and "v2.3.5.5318" have entirely different histories.

33 changed files with 122 additions and 281 deletions

View file

@ -1,26 +0,0 @@
name: Close issues without labels
on:
issues:
types:
- opened
- reopened
jobs:
close-issue:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: |
.github
- name: Close issue if no labels found
if: join(github.event.issue.labels) == ''
run: |
gh issue comment ${{ github.event.issue.number }} --body ":wave: @${{ github.event.issue.user.login }}, this issue was closed automatically because it was created without following an issue template. Please update the issue following the correct template for this issue. Once updated please reply to this issue so we can review and re-open. In the future, use the [issue templates](https://github.com/${{ github.repository }}/issues/new/choose) instead of creating your own."
gh issue close ${{ github.event.issue.number }} --reason "not planned"
env:
GH_TOKEN: ${{ github.token }}

View file

@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.3.7' majorVersion: '2.3.5'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)' prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)' buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View file

@ -133,12 +133,6 @@ module.exports = (env) => {
{ {
source: 'frontend/src/Content/robots.txt', source: 'frontend/src/Content/robots.txt',
destination: path.join(distFolder, 'Content/robots.txt') destination: path.join(distFolder, 'Content/robots.txt')
},
// manifest.json and browserconfig.xml
{
source: 'frontend/src/Content/*.(json|xml)',
destination: path.join(distFolder, 'Content')
} }
] ]
} }

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/Content/Images/Icons/mstile-150x150.png"/>
<TileColor>#00ccff</TileColor>
</tile>
</msapplication>
</browserconfig>

View file

@ -0,0 +1,19 @@
{
"name": "Prowlarr",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "standalone"
}

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="__URL_BASE__/Content/Images/Icons/mstile-150x150.png" />
<TileColor>
#00ccff
</TileColor>
</tile>
</msapplication>
</browserconfig>

View file

@ -1,19 +0,0 @@
{
"name": "__INSTANCE_NAME__",
"icons": [
{
"src": "__URL_BASE__/Content/Images/Icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "__URL_BASE__/Content/Images/Icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "__URL_BASE__/",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "standalone"
}

View file

@ -33,7 +33,7 @@
sizes="16x16" sizes="16x16"
href="/Content/Images/Icons/favicon-16x16.png" href="/Content/Images/Icons/favicon-16x16.png"
/> />
<link rel="manifest" href="/Content/manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
<link <link
rel="mask-icon" rel="mask-icon"
href="/Content/Images/Icons/safari-pinned-tab.svg" href="/Content/Images/Icons/safari-pinned-tab.svg"
@ -47,7 +47,7 @@
/> />
<meta <meta
name="msapplication-config" name="msapplication-config"
content="/Content/browserconfig.xml" content="/Content/Images/Icons/browserconfig.xml"
/> />
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css"> <link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">

View file

@ -11,11 +11,8 @@
<!-- Android/Apple Phone --> <!-- Android/Apple Phone -->
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
name="apple-mobile-web-app-status-bar-style" <meta name="format-detection" content="telephone=no">
content="black-translucent"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="description" content="Prowlarr" /> <meta name="description" content="Prowlarr" />
@ -36,11 +33,7 @@
sizes="16x16" sizes="16x16"
href="/Content/Images/Icons/favicon-16x16.png" href="/Content/Images/Icons/favicon-16x16.png"
/> />
<link <link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
rel="manifest"
href="/Content/manifest.json"
crossorigin="use-credentials"
/>
<link <link
rel="mask-icon" rel="mask-icon"
href="/Content/Images/Icons/safari-pinned-tab.svg" href="/Content/Images/Icons/safari-pinned-tab.svg"
@ -52,7 +45,10 @@
href="/favicon.ico" href="/favicon.ico"
data-no-hash data-no-hash
/> />
<meta name="msapplication-config" content="/Content/browserconfig.xml" /> <meta
name="msapplication-config"
content="/Content/Images/Icons/browserconfig.xml"
/>
<link rel="stylesheet" type="text/css" href="/Content/styles.css" /> <link rel="stylesheet" type="text/css" href="/Content/styles.css" />
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css" /> <link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css" />
@ -63,7 +59,7 @@
body { body {
background-color: var(--pageBackground); background-color: var(--pageBackground);
color: var(--textColor); color: var(--textColor);
font-family: 'Roboto', 'open sans', 'Helvetica Neue', Helvetica, Arial, font-family: "Roboto", "open sans", "Helvetica Neue", Helvetica, Arial,
sans-serif; sans-serif;
} }
@ -213,7 +209,9 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="sign-in">SIGN IN TO CONTINUE</div> <div class="sign-in">
SIGN IN TO CONTINUE
</div>
<form <form
role="form" role="form"
@ -232,8 +230,8 @@
pattern=".{1,}" pattern=".{1,}"
required required
title="User name is required" title="User name is required"
autofocus="true" autoFocus="true"
autocapitalize="false" autoCapitalize="false"
/> />
</div> </div>
@ -284,16 +282,16 @@
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
var yearSpan = document.getElementById('year'); var yearSpan = document.getElementById("year");
yearSpan.innerHTML = '2010-' + new Date().getFullYear(); yearSpan.innerHTML = "2010-" + new Date().getFullYear();
var copyDiv = document.getElementById('copy'); var copyDiv = document.getElementById("copy");
copyDiv.classList.remove('hidden'); copyDiv.classList.remove("hidden");
if (window.location.search.indexOf('loginFailed=true') > -1) { if (window.location.search.indexOf("loginFailed=true") > -1) {
var loginFailedDiv = document.getElementById('login-failed'); var loginFailedDiv = document.getElementById("login-failed");
loginFailedDiv.classList.remove('hidden'); loginFailedDiv.classList.remove("hidden");
} }
var light = { var light = {
@ -313,7 +311,7 @@
primaryHoverBorderColor: '#3483e7', primaryHoverBorderColor: '#3483e7',
failedColor: '#f05050', failedColor: '#f05050',
forgotPasswordColor: '#909fa7', forgotPasswordColor: '#909fa7',
forgotPasswordAltColor: '#748690', forgotPasswordAltColor: '#748690'
}; };
var dark = { var dark = {
@ -333,16 +331,21 @@
primaryHoverBorderColor: '#3483e7', primaryHoverBorderColor: '#3483e7',
failedColor: '#f05050', failedColor: '#f05050',
forgotPasswordColor: '#737d83', forgotPasswordColor: '#737d83',
forgotPasswordAltColor: '#546067', forgotPasswordAltColor: '#546067'
}; };
var theme = '_THEME_'; var theme = "_THEME_";
var defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches; var defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var finalTheme = var finalTheme = theme === 'dark' || (theme === 'auto' && defaultDark) ?
theme === 'dark' || (theme === 'auto' && defaultDark) ? dark : light; dark :
light;
Object.entries(finalTheme).forEach(([key, value]) => { Object.entries(finalTheme).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--${key}`, value); document.documentElement.style.setProperty(
`--${key}`,
value
);
}); });
</script> </script>
</html> </html>

View file

@ -120,7 +120,7 @@ private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCr
baseUrl += "&apikey=" + Settings.ApiKey; baseUrl += "&apikey=" + Settings.ApiKey;
} }
if (searchCriteria.Limit is > 0) if (searchCriteria.Limit.HasValue)
{ {
parameters.Add("limit", searchCriteria.Limit.ToString()); parameters.Add("limit", searchCriteria.Limit.ToString());
} }

View file

@ -263,7 +263,7 @@ private IEnumerable<IndexerRequest> GetPagedRequests(SearchCriteriaBase searchCr
searchUrl += "&apikey=" + Settings.ApiKey; searchUrl += "&apikey=" + Settings.ApiKey;
} }
if (searchCriteria.Limit is > 0) if (searchCriteria.Limit.HasValue)
{ {
parameters.Set("limit", searchCriteria.Limit.ToString()); parameters.Set("limit", searchCriteria.Limit.ToString());
} }

View file

@ -19,7 +19,6 @@
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
[Obsolete("Migrated to YAML for Torznab API")]
public class SceneTime : TorrentIndexerBase<SceneTimeSettings> public class SceneTime : TorrentIndexerBase<SceneTimeSettings>
{ {
public override string Name => "SceneTime"; public override string Name => "SceneTime";

View file

@ -23,7 +23,6 @@
namespace NzbDrone.Core.Indexers.Definitions namespace NzbDrone.Core.Indexers.Definitions
{ {
[Obsolete("Site does not allow automation")]
public class ZonaQ : TorrentIndexerBase<UserPassTorrentBaseSettings> public class ZonaQ : TorrentIndexerBase<UserPassTorrentBaseSettings>
{ {
public override string Name => "ZonaQ"; public override string Name => "ZonaQ";

View file

@ -6,7 +6,7 @@
<PackageReference Include="AngleSharp.Xml" Version="1.0.0" /> <PackageReference Include="AngleSharp.Xml" Version="1.0.0" />
<PackageReference Include="Dapper" Version="2.1.66" /> <PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Diacritical.Net" Version="1.0.4" /> <PackageReference Include="Diacritical.Net" Version="1.0.4" />
<PackageReference Include="MailKit" Version="4.16.0" /> <PackageReference Include="MailKit" Version="4.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.16" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.16" /> <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.16" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />

View file

@ -14,7 +14,7 @@ public SearchResource()
public string Type { get; set; } public string Type { get; set; }
public List<int> IndexerIds { get; set; } public List<int> IndexerIds { get; set; }
public List<int> Categories { get; set; } public List<int> Categories { get; set; }
public int? Limit { get; set; } public int Limit { get; set; }
public int? Offset { get; set; } public int Offset { get; set; }
} }
} }

View file

@ -15,13 +15,11 @@ public BackupFileMapper(IBackupService backupService, IDiskProvider diskProvider
_backupService = backupService; _backupService = backupService;
} }
protected override string FolderPath => _backupService.GetBackupFolder().TrimEnd(Path.DirectorySeparatorChar); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
return Path.Combine(FolderPath, path); return Path.Combine(_backupService.GetBackupFolder(), path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)

View file

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -6,29 +6,29 @@
namespace Prowlarr.Http.Frontend.Mappers namespace Prowlarr.Http.Frontend.Mappers
{ {
public class BrowserConfig : UrlBaseReplacementResourceMapperBase public class BrowserConfig : StaticResourceMapperBase
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger) public BrowserConfig(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, configFileProvider, logger) : base(diskProvider, logger)
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string FilePath => Path.Combine(FolderPath, "Content", "browserconfig.xml");
protected override string MapPath(string resourceUrl)
{ {
return FilePath; var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "xml");
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
return resourceUrl.StartsWith("/Content/browserconfig"); return resourceUrl.StartsWith("/content/images/icons/browserconfig");
} }
} }
} }

View file

@ -30,12 +30,6 @@ public string AddCacheBreakerToPath(string resourceUrl)
var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl)); var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl));
var pathToFile = mapper.Map(resourceUrl); var pathToFile = mapper.Map(resourceUrl);
if (pathToFile == null)
{
return resourceUrl;
}
var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64(); var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64();
return resourceUrl + "?h=" + hash.Trim('='); return resourceUrl + "?h=" + hash.Trim('=');

View file

@ -18,9 +18,7 @@ public FaviconMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, I
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var fileName = "favicon.ico"; var fileName = "favicon.ico";
@ -31,7 +29,7 @@ protected override string MapPath(string resourceUrl)
var path = Path.Combine("Content", "Images", "Icons", fileName); var path = Path.Combine("Content", "Images", "Icons", fileName);
return Path.Combine(FolderPath, path); return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)

View file

@ -4,7 +4,6 @@
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace Prowlarr.Http.Frontend.Mappers namespace Prowlarr.Http.Frontend.Mappers
{ {
@ -14,22 +13,19 @@ public abstract class HtmlMapperBase : StaticResourceMapperBase
private readonly Lazy<ICacheBreakerProvider> _cacheBreakProviderFactory; private readonly Lazy<ICacheBreakerProvider> _cacheBreakProviderFactory;
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private string _urlBase;
private string _generatedContent; private string _generatedContent;
protected HtmlMapperBase(IDiskProvider diskProvider, protected HtmlMapperBase(IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger) Logger logger)
: base(diskProvider, logger) : base(diskProvider, logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_cacheBreakProviderFactory = cacheBreakProviderFactory; _cacheBreakProviderFactory = cacheBreakProviderFactory;
_urlBase = configFileProvider.UrlBase;
} }
protected abstract string HtmlPath { get; } protected string HtmlPath;
protected string UrlBase;
protected override Stream GetContentStream(string filePath) protected override Stream GetContentStream(string filePath)
{ {
@ -66,10 +62,10 @@ protected virtual string GetHtmlText()
url = cacheBreakProvider.AddCacheBreakerToPath(match.Groups["path"].Value); 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; _generatedContent = text;

View file

@ -7,6 +7,6 @@ public interface IMapHttpRequestsToDisk
{ {
string Map(string resourceUrl); string Map(string resourceUrl);
bool CanHandle(string resourceUrl); bool CanHandle(string resourceUrl);
Task<IActionResult> GetResponse(string resourceUrl); Task<FileStreamResult> GetResponse(string resourceUrl);
} }
} }

View file

@ -9,7 +9,6 @@ namespace Prowlarr.Http.Frontend.Mappers
{ {
public class IndexHtmlMapper : HtmlMapperBase public class IndexHtmlMapper : HtmlMapperBase
{ {
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
@ -17,16 +16,15 @@ public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger) Logger logger)
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger) : base(diskProvider, cacheBreakProviderFactory, logger)
{ {
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, "index.html");
UrlBase = configFileProvider.UrlBase;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string HtmlPath => Path.Combine(FolderPath, "index.html");
protected override string MapPath(string resourceUrl)
{ {
return HtmlPath; return HtmlPath;
} }

View file

@ -16,14 +16,12 @@ public LogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, L
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string FolderPath => _appFolderInfo.GetLogFolder(); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path); path = Path.GetFileName(path);
return Path.Combine(FolderPath, path); return Path.Combine(_appFolderInfo.GetLogFolder(), path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)

View file

@ -9,7 +9,6 @@ namespace Prowlarr.Http.Frontend.Mappers
{ {
public class LoginHtmlMapper : HtmlMapperBase public class LoginHtmlMapper : HtmlMapperBase
{ {
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
public LoginHtmlMapper(IAppFolderInfo appFolderInfo, public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
@ -17,16 +16,14 @@ public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
Logger logger) Logger logger)
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger) : base(diskProvider, cacheBreakProviderFactory, logger)
{ {
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
HtmlPath = Path.Combine(appFolderInfo.StartUpFolder, configFileProvider.UiFolder, "login.html");
UrlBase = configFileProvider.UrlBase;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string HtmlPath => Path.Combine(FolderPath, "login.html");
protected override string MapPath(string resourceUrl)
{ {
return HtmlPath; return HtmlPath;
} }

View file

@ -1,4 +1,4 @@
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -6,47 +6,29 @@
namespace Prowlarr.Http.Frontend.Mappers namespace Prowlarr.Http.Frontend.Mappers
{ {
public class ManifestMapper : UrlBaseReplacementResourceMapperBase public class ManifestMapper : StaticResourceMapperBase
{ {
private readonly IAppFolderInfo _appFolderInfo; private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private string _generatedContent;
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger) public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, configFileProvider, logger) : base(diskProvider, logger)
{ {
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string FilePath => Path.Combine(FolderPath, "Content", "manifest.json");
protected override string MapPath(string resourceUrl)
{ {
return FilePath; var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "json");
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
return resourceUrl.StartsWith("/Content/manifest"); return resourceUrl.StartsWith("/Content/Images/Icons/manifest");
}
protected override string GetFileText()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var text = base.GetFileText();
text = text.Replace("__INSTANCE_NAME__", _configFileProvider.InstanceName);
_generatedContent = text;
return _generatedContent;
} }
} }
} }

View file

@ -22,9 +22,7 @@ public MediaCoverMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider
_diskProvider = diskProvider; _diskProvider = diskProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover"); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);

View file

@ -18,13 +18,11 @@ public RobotsTxtMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider,
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = Path.Combine("Content", "robots.txt"); var path = Path.Combine("Content", "robots.txt");
return Path.Combine(FolderPath, path); return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)

View file

@ -18,22 +18,20 @@ public StaticResourceMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProv
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
} }
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(FolderPath, path); return Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
resourceUrl = resourceUrl.ToLowerInvariant(); resourceUrl = resourceUrl.ToLowerInvariant();
if (resourceUrl.StartsWith("/content/manifest") || if (resourceUrl.StartsWith("/content/images/icons/manifest") ||
resourceUrl.StartsWith("/content/browserconfig")) resourceUrl.StartsWith("/content/images/icons/browserconfig"))
{ {
return false; return false;
} }

View file

@ -27,28 +27,14 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase; _caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
} }
protected abstract string FolderPath { get; } public abstract string Map(string resourceUrl);
protected abstract string MapPath(string resourceUrl);
public abstract bool CanHandle(string resourceUrl); public abstract bool CanHandle(string resourceUrl);
public string Map(string resourceUrl) public Task<FileStreamResult> GetResponse(string resourceUrl)
{
var filePath = Path.GetFullPath(MapPath(resourceUrl));
var parentPath = Path.GetFullPath(FolderPath) + Path.DirectorySeparatorChar;
return filePath.StartsWith(parentPath) ? filePath : null;
}
public Task<IActionResult> GetResponse(string resourceUrl)
{ {
var filePath = Map(resourceUrl); var filePath = Map(resourceUrl);
if (filePath == null)
{
return Task.FromResult<IActionResult>(null);
}
if (_diskProvider.FileExists(filePath, _caseSensitive)) if (_diskProvider.FileExists(filePath, _caseSensitive))
{ {
if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType)) if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType))
@ -56,7 +42,7 @@ public Task<IActionResult> GetResponse(string resourceUrl)
contentType = "application/octet-stream"; contentType = "application/octet-stream";
} }
return Task.FromResult<IActionResult>(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType) return Task.FromResult(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
{ {
Encoding = contentType == "text/plain" ? Encoding.UTF8 : null Encoding = contentType == "text/plain" ? Encoding.UTF8 : null
})); }));
@ -64,7 +50,7 @@ public Task<IActionResult> GetResponse(string resourceUrl)
_logger.Warn("File {0} not found", filePath); _logger.Warn("File {0} not found", filePath);
return Task.FromResult<IActionResult>(null); return Task.FromResult<FileStreamResult>(null);
} }
protected virtual Stream GetContentStream(string filePath) protected virtual Stream GetContentStream(string filePath)

View file

@ -16,14 +16,12 @@ public UpdateLogFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvi
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
} }
protected override string FolderPath => _appFolderInfo.GetUpdateLogFolder(); public override string Map(string resourceUrl)
protected override string MapPath(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = Path.GetFileName(path); path = Path.GetFileName(path);
return Path.Combine(FolderPath, path); return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path);
} }
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)

View file

@ -1,58 +0,0 @@
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace Prowlarr.Http.Frontend.Mappers
{
public abstract class UrlBaseReplacementResourceMapperBase : StaticResourceMapperBase
{
private readonly IDiskProvider _diskProvider;
private readonly string _urlBase;
private string _generatedContent;
public UrlBaseReplacementResourceMapperBase(IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, logger)
{
_diskProvider = diskProvider;
_urlBase = configFileProvider.UrlBase;
}
protected abstract string FilePath { get; }
protected override string MapPath(string resourceUrl)
{
return FilePath;
}
protected override Stream GetContentStream(string filePath)
{
var text = GetFileText();
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(text);
writer.Flush();
stream.Position = 0;
return stream;
}
protected virtual string GetFileText()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var text = _diskProvider.ReadAllText(FilePath);
text = text.Replace("__URL_BASE__", _urlBase);
_generatedContent = text;
return _generatedContent;
}
}
}

View file

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
@ -17,7 +16,6 @@ public class StaticResourceController : Controller
{ {
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers; private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
private readonly Logger _logger; private readonly Logger _logger;
private static readonly Regex InvalidPathRegex = new(@"([\/\\]|%2f|%5c)\.\.|\.\.([\/\\]|%2f|%5c)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public StaticResourceController(IEnumerable<IMapHttpRequestsToDisk> requestMappers, public StaticResourceController(IEnumerable<IMapHttpRequestsToDisk> requestMappers,
Logger logger) Logger logger)
@ -52,11 +50,6 @@ private async Task<IActionResult> MapResource(string path)
{ {
path = "/" + (path ?? ""); path = "/" + (path ?? "");
if (InvalidPathRegex.IsMatch(path))
{
return NotFound();
}
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
if (mapper != null) if (mapper != null)
@ -65,7 +58,7 @@ private async Task<IActionResult> MapResource(string path)
if (result != null) if (result != null)
{ {
if ((result as FileResult)?.ContentType == "text/html") if (result.ContentType == "text/html")
{ {
Response.Headers.DisableCache(); Response.Headers.DisableCache();
} }

View file

@ -2289,9 +2289,9 @@ camelcase@^5.3.1:
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663, caniuse-lite@^1.0.30001716: caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663, caniuse-lite@^1.0.30001716:
version "1.0.30001782" version "1.0.30001718"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz"
integrity sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw== integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
chalk@^2.4.1, chalk@^2.4.2: chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2" version "2.4.2"