Compare commits

...

7 Commits

Author SHA1 Message Date
Bogdan
3c4efa0226 Update browserlist db 2026-03-30 14:01:13 +01:00
Mark McDowall
50d31d0c5e Fixed: Downloading backups when path contains a trailing slash
(cherry picked from commit 31c7647eacb3c3a50e55550880287e00302a9881)
2026-03-30 14:01:13 +01:00
Mark McDowall
f48c9f9f88 Improve HTTP file mappers
(cherry picked from commit f30207c3d130c1a37f29e214101c8ec9613d18ee)
2026-03-30 14:01:13 +01:00
Mark McDowall
1ba2f26649 New: Use instance name in PWA manifest
(cherry picked from commit 1fcfb88d2aa0126c4b3c878c8e310311ea57d04d)
2026-03-30 14:01:13 +01:00
Mark McDowall
c880b6c09c Fixed: PWA Manifest with URL base
(cherry picked from commit aedcd046fc4fc621dae4b231cc80d4b269a69177)
2026-03-30 14:01:13 +01:00
Bogdan
6fca0d0b6c Sync static resource mapper with upstream 2026-03-30 14:01:13 +01:00
Mark McDowall
9907342055 Close issues that don't follow issue templates
(cherry picked from commit c8f15ae1989cfb7d83cc580409a972e9fdd44a7c)
2026-03-29 11:47:37 +01:00
26 changed files with 273 additions and 116 deletions

View 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 }}

View File

@@ -133,6 +133,12 @@ module.exports = (env) => {
{
source: 'frontend/src/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

@@ -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>

View File

@@ -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"
}

View 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>

View 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"
}

View File

@@ -33,7 +33,7 @@
sizes="16x16"
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
rel="mask-icon"
href="/Content/Images/Icons/safari-pinned-tab.svg"
@@ -47,7 +47,7 @@
/>
<meta
name="msapplication-config"
content="/Content/Images/Icons/browserconfig.xml"
content="/Content/browserconfig.xml"
/>
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">

View File

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

View File

@@ -15,11 +15,13 @@ namespace Prowlarr.Http.Frontend.Mappers
_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);
return Path.Combine(_backupService.GetBackupFolder(), path);
return Path.Combine(FolderPath, path);
}
public override bool CanHandle(string resourceUrl)

View File

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

View File

@@ -30,6 +30,12 @@ namespace Prowlarr.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('=');

View File

@@ -18,7 +18,9 @@ namespace Prowlarr.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 Prowlarr.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)

View File

@@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace Prowlarr.Http.Frontend.Mappers
{
@@ -13,19 +14,22 @@ namespace Prowlarr.Http.Frontend.Mappers
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 string _urlBase;
private string _generatedContent;
protected HtmlMapperBase(IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> 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 Prowlarr.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;

View File

@@ -7,6 +7,6 @@ namespace Prowlarr.Http.Frontend.Mappers
{
string Map(string resourceUrl);
bool CanHandle(string resourceUrl);
Task<FileStreamResult> GetResponse(string resourceUrl);
Task<IActionResult> GetResponse(string resourceUrl);
}
}

View File

@@ -9,6 +9,7 @@ namespace Prowlarr.Http.Frontend.Mappers
{
public class IndexHtmlMapper : HtmlMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
@@ -16,15 +17,16 @@ namespace Prowlarr.Http.Frontend.Mappers
IConfigFileProvider configFileProvider,
Lazy<ICacheBreakerProvider> 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;
}

View File

@@ -16,12 +16,14 @@ namespace Prowlarr.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)

View File

@@ -9,6 +9,7 @@ namespace Prowlarr.Http.Frontend.Mappers
{
public class LoginHtmlMapper : HtmlMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
@@ -16,14 +17,16 @@ namespace Prowlarr.Http.Frontend.Mappers
Lazy<ICacheBreakerProvider> 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;
}

View File

@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@@ -6,29 +6,47 @@ using NzbDrone.Core.Configuration;
namespace Prowlarr.Http.Frontend.Mappers
{
public class ManifestMapper : StaticResourceMapperBase
public class ManifestMapper : UrlBaseReplacementResourceMapperBase
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IConfigFileProvider _configFileProvider;
private string _generatedContent;
public ManifestMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
: base(diskProvider, logger)
: base(diskProvider, configFileProvider, logger)
{
_appFolderInfo = appFolderInfo;
_configFileProvider = configFileProvider;
}
public override string Map(string resourceUrl)
{
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
protected override string FolderPath => Path.Combine(_appFolderInfo.StartUpFolder, _configFileProvider.UiFolder);
protected override string FilePath => Path.Combine(FolderPath, "Content", "manifest.json");
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)
{
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;
}
}
}

View File

@@ -22,7 +22,9 @@ namespace Prowlarr.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);

View File

@@ -18,11 +18,13 @@ namespace Prowlarr.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)

View File

@@ -18,20 +18,22 @@ namespace Prowlarr.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)
{
resourceUrl = resourceUrl.ToLowerInvariant();
if (resourceUrl.StartsWith("/content/images/icons/manifest") ||
resourceUrl.StartsWith("/content/images/icons/browserconfig"))
if (resourceUrl.StartsWith("/content/manifest") ||
resourceUrl.StartsWith("/content/browserconfig"))
{
return false;
}

View File

@@ -27,14 +27,28 @@ namespace Prowlarr.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 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);
if (filePath == null)
{
return Task.FromResult<IActionResult>(null);
}
if (_diskProvider.FileExists(filePath, _caseSensitive))
{
if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType))
@@ -42,7 +56,7 @@ namespace Prowlarr.Http.Frontend.Mappers
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
}));
@@ -50,7 +64,7 @@ namespace Prowlarr.Http.Frontend.Mappers
_logger.Warn("File {0} not found", filePath);
return Task.FromResult<FileStreamResult>(null);
return Task.FromResult<IActionResult>(null);
}
protected virtual Stream GetContentStream(string filePath)

View File

@@ -16,12 +16,14 @@ namespace Prowlarr.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)

View File

@@ -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;
}
}
}

View File

@@ -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 Prowlarr.Http.Frontend
{
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
private readonly Logger _logger;
private static readonly Regex InvalidPathRegex = new(@"([\/\\]|%2f|%5c)\.\.|\.\.([\/\\]|%2f|%5c)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public StaticResourceController(IEnumerable<IMapHttpRequestsToDisk> requestMappers,
Logger logger)
@@ -50,6 +52,11 @@ namespace Prowlarr.Http.Frontend
{
path = "/" + (path ?? "");
if (InvalidPathRegex.IsMatch(path))
{
return NotFound();
}
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
if (mapper != null)
@@ -58,7 +65,7 @@ namespace Prowlarr.Http.Frontend
if (result != null)
{
if (result.ContentType == "text/html")
if ((result as FileResult)?.ContentType == "text/html")
{
Response.Headers.DisableCache();
}

View File

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