mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-31 18:54:31 -04:00
Compare commits
7 Commits
v2.3.5.531
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c4efa0226 | ||
|
|
50d31d0c5e | ||
|
|
f48c9f9f88 | ||
|
|
1ba2f26649 | ||
|
|
c880b6c09c | ||
|
|
6fca0d0b6c | ||
|
|
9907342055 |
26
.github/workflows/close_invalid_issues.yml
vendored
Normal file
26
.github/workflows/close_invalid_issues.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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 }}
|
||||||
@@ -133,6 +133,12 @@ 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')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<browserconfig>
|
|
||||||
<msapplication>
|
|
||||||
<tile>
|
|
||||||
<square150x150logo src="/Content/Images/Icons/mstile-150x150.png"/>
|
|
||||||
<TileColor>#00ccff</TileColor>
|
|
||||||
</tile>
|
|
||||||
</msapplication>
|
|
||||||
</browserconfig>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
11
frontend/src/Content/browserconfig.xml
Normal file
11
frontend/src/Content/browserconfig.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?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>
|
||||||
19
frontend/src/Content/manifest.json
Normal file
19
frontend/src/Content/manifest.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "__INSTANCE_NAME__",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "__URL_BASE__/",
|
||||||
|
"theme_color": "#3a3f51",
|
||||||
|
"background_color": "#3a3f51",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
@@ -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/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
<link 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"
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="msapplication-config"
|
name="msapplication-config"
|
||||||
content="/Content/Images/Icons/browserconfig.xml"
|
content="/Content/browserconfig.xml"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||||
|
|||||||
@@ -11,8 +11,11 @@
|
|||||||
<!-- 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 name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta
|
||||||
<meta name="format-detection" content="telephone=no">
|
name="apple-mobile-web-app-status-bar-style"
|
||||||
|
content="black-translucent"
|
||||||
|
/>
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
|
||||||
<meta name="description" content="Prowlarr" />
|
<meta name="description" content="Prowlarr" />
|
||||||
|
|
||||||
@@ -33,7 +36,11 @@
|
|||||||
sizes="16x16"
|
sizes="16x16"
|
||||||
href="/Content/Images/Icons/favicon-16x16.png"
|
href="/Content/Images/Icons/favicon-16x16.png"
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="/Content/Images/Icons/manifest.json" crossorigin="use-credentials" />
|
<link
|
||||||
|
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"
|
||||||
@@ -45,10 +52,7 @@
|
|||||||
href="/favicon.ico"
|
href="/favicon.ico"
|
||||||
data-no-hash
|
data-no-hash
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta name="msapplication-config" content="/Content/browserconfig.xml" />
|
||||||
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" />
|
||||||
@@ -59,7 +63,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,9 +213,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="sign-in">
|
<div class="sign-in">SIGN IN TO CONTINUE</div>
|
||||||
SIGN IN TO CONTINUE
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form
|
<form
|
||||||
role="form"
|
role="form"
|
||||||
@@ -230,8 +232,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>
|
||||||
|
|
||||||
@@ -282,16 +284,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 = {
|
||||||
@@ -311,7 +313,7 @@
|
|||||||
primaryHoverBorderColor: '#3483e7',
|
primaryHoverBorderColor: '#3483e7',
|
||||||
failedColor: '#f05050',
|
failedColor: '#f05050',
|
||||||
forgotPasswordColor: '#909fa7',
|
forgotPasswordColor: '#909fa7',
|
||||||
forgotPasswordAltColor: '#748690'
|
forgotPasswordAltColor: '#748690',
|
||||||
};
|
};
|
||||||
|
|
||||||
var dark = {
|
var dark = {
|
||||||
@@ -331,21 +333,16 @@
|
|||||||
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 = theme === 'dark' || (theme === 'auto' && defaultDark) ?
|
var finalTheme =
|
||||||
dark :
|
theme === 'dark' || (theme === 'auto' && defaultDark) ? dark : light;
|
||||||
light;
|
|
||||||
|
|
||||||
Object.entries(finalTheme).forEach(([key, value]) => {
|
Object.entries(finalTheme).forEach(([key, value]) => {
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(`--${key}`, value);
|
||||||
`--${key}`,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_backupService = backupService;
|
_backupService = backupService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Map(string resourceUrl)
|
protected override string FolderPath => _backupService.GetBackupFolder().TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
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(_backupService.GetBackupFolder(), path);
|
return Path.Combine(FolderPath, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
|
|||||||
@@ -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 @@ using NzbDrone.Core.Configuration;
|
|||||||
|
|
||||||
namespace Prowlarr.Http.Frontend.Mappers
|
namespace Prowlarr.Http.Frontend.Mappers
|
||||||
{
|
{
|
||||||
public class BrowserConfig : StaticResourceMapperBase
|
public class BrowserConfig : UrlBaseReplacementResourceMapperBase
|
||||||
{
|
{
|
||||||
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, logger)
|
: base(diskProvider, configFileProvider, logger)
|
||||||
{
|
{
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
_configFileProvider = configFileProvider;
|
_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");
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
|
||||||
path = path.Trim(Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "xml");
|
protected override string MapPath(string resourceUrl)
|
||||||
|
{
|
||||||
|
return FilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
{
|
{
|
||||||
return resourceUrl.StartsWith("/content/images/icons/browserconfig");
|
return resourceUrl.StartsWith("/Content/browserconfig");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
|
|
||||||
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('=');
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_configFileProvider = configFileProvider;
|
_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";
|
var fileName = "favicon.ico";
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
|
|
||||||
var path = Path.Combine("Content", "Images", "Icons", fileName);
|
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)
|
public override bool CanHandle(string resourceUrl)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -13,19 +14,22 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
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 string HtmlPath;
|
protected abstract string HtmlPath { get; }
|
||||||
protected string UrlBase;
|
|
||||||
|
|
||||||
protected override Stream GetContentStream(string filePath)
|
protected override Stream GetContentStream(string filePath)
|
||||||
{
|
{
|
||||||
@@ -62,10 +66,10 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
{
|
{
|
||||||
string Map(string resourceUrl);
|
string Map(string resourceUrl);
|
||||||
bool CanHandle(string resourceUrl);
|
bool CanHandle(string resourceUrl);
|
||||||
Task<FileStreamResult> GetResponse(string resourceUrl);
|
Task<IActionResult> GetResponse(string resourceUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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,
|
||||||
@@ -16,15 +17,16 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
IConfigFileProvider configFileProvider,
|
IConfigFileProvider configFileProvider,
|
||||||
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(diskProvider, cacheBreakProviderFactory, logger)
|
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
|
||||||
{
|
{
|
||||||
|
_appFolderInfo = appFolderInfo;
|
||||||
_configFileProvider = configFileProvider;
|
_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;
|
return HtmlPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_appFolderInfo = appFolderInfo;
|
_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);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = Path.GetFileName(path);
|
path = Path.GetFileName(path);
|
||||||
|
|
||||||
return Path.Combine(_appFolderInfo.GetLogFolder(), path);
|
return Path.Combine(FolderPath, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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,
|
||||||
@@ -16,14 +17,16 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||||
IConfigFileProvider configFileProvider,
|
IConfigFileProvider configFileProvider,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(diskProvider, cacheBreakProviderFactory, logger)
|
: base(diskProvider, configFileProvider, cacheBreakProviderFactory, logger)
|
||||||
{
|
{
|
||||||
|
_appFolderInfo = appFolderInfo;
|
||||||
_configFileProvider = configFileProvider;
|
_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;
|
return HtmlPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,47 @@ using NzbDrone.Core.Configuration;
|
|||||||
|
|
||||||
namespace Prowlarr.Http.Frontend.Mappers
|
namespace Prowlarr.Http.Frontend.Mappers
|
||||||
{
|
{
|
||||||
public class ManifestMapper : StaticResourceMapperBase
|
public class ManifestMapper : UrlBaseReplacementResourceMapperBase
|
||||||
{
|
{
|
||||||
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, logger)
|
: base(diskProvider, configFileProvider, logger)
|
||||||
{
|
{
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
_configFileProvider = configFileProvider;
|
_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", "manifest.json");
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
|
||||||
path = path.Trim(Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
return Path.ChangeExtension(Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder, path), "json");
|
protected override string MapPath(string resourceUrl)
|
||||||
|
{
|
||||||
|
return FilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
{
|
{
|
||||||
return resourceUrl.StartsWith("/Content/Images/Icons/manifest");
|
return resourceUrl.StartsWith("/Content/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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_diskProvider = diskProvider;
|
_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);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = path.Trim(Path.DirectorySeparatorChar);
|
path = path.Trim(Path.DirectorySeparatorChar);
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_configFileProvider = configFileProvider;
|
_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");
|
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)
|
public override bool CanHandle(string resourceUrl)
|
||||||
|
|||||||
@@ -18,20 +18,22 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_configFileProvider = configFileProvider;
|
_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);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = path.Trim(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)
|
public override bool CanHandle(string resourceUrl)
|
||||||
{
|
{
|
||||||
resourceUrl = resourceUrl.ToLowerInvariant();
|
resourceUrl = resourceUrl.ToLowerInvariant();
|
||||||
|
|
||||||
if (resourceUrl.StartsWith("/content/images/icons/manifest") ||
|
if (resourceUrl.StartsWith("/content/manifest") ||
|
||||||
resourceUrl.StartsWith("/content/images/icons/browserconfig"))
|
resourceUrl.StartsWith("/content/browserconfig"))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,28 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
|
_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 abstract bool CanHandle(string resourceUrl);
|
||||||
|
|
||||||
public Task<FileStreamResult> GetResponse(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<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))
|
||||||
@@ -42,7 +56,7 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
contentType = "application/octet-stream";
|
contentType = "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
|
return Task.FromResult<IActionResult>(new FileStreamResult(GetContentStream(filePath), new MediaTypeHeaderValue(contentType)
|
||||||
{
|
{
|
||||||
Encoding = contentType == "text/plain" ? Encoding.UTF8 : null
|
Encoding = contentType == "text/plain" ? Encoding.UTF8 : null
|
||||||
}));
|
}));
|
||||||
@@ -50,7 +64,7 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
|
|
||||||
_logger.Warn("File {0} not found", filePath);
|
_logger.Warn("File {0} not found", filePath);
|
||||||
|
|
||||||
return Task.FromResult<FileStreamResult>(null);
|
return Task.FromResult<IActionResult>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Stream GetContentStream(string filePath)
|
protected virtual Stream GetContentStream(string filePath)
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ namespace Prowlarr.Http.Frontend.Mappers
|
|||||||
_appFolderInfo = appFolderInfo;
|
_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);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = Path.GetFileName(path);
|
path = Path.GetFileName(path);
|
||||||
|
|
||||||
return Path.Combine(_appFolderInfo.GetUpdateLogFolder(), path);
|
return Path.Combine(FolderPath, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanHandle(string resourceUrl)
|
public override bool CanHandle(string resourceUrl)
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -16,6 +17,7 @@ namespace Prowlarr.Http.Frontend
|
|||||||
{
|
{
|
||||||
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)
|
||||||
@@ -50,6 +52,11 @@ namespace Prowlarr.Http.Frontend
|
|||||||
{
|
{
|
||||||
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)
|
||||||
@@ -58,7 +65,7 @@ namespace Prowlarr.Http.Frontend
|
|||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
if (result.ContentType == "text/html")
|
if ((result as FileResult)?.ContentType == "text/html")
|
||||||
{
|
{
|
||||||
Response.Headers.DisableCache();
|
Response.Headers.DisableCache();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.30001718"
|
version "1.0.30001782"
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz"
|
||||||
integrity sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==
|
integrity sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==
|
||||||
|
|
||||||
chalk@^2.4.1, chalk@^2.4.2:
|
chalk@^2.4.1, chalk@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user