diff --git a/src/Lidarr.Http/Extensions/RequestExtensions.cs b/src/Lidarr.Http/Extensions/RequestExtensions.cs index 59c6aef80..5ada6f5d7 100644 --- a/src/Lidarr.Http/Extensions/RequestExtensions.cs +++ b/src/Lidarr.Http/Extensions/RequestExtensions.cs @@ -193,6 +193,7 @@ namespace Lidarr.Http.Extensions public static void DisableCache(this IHeaderDictionary headers) { + headers.Remove("Last-Modified"); headers["Cache-Control"] = "no-cache, no-store"; headers["Expires"] = "-1"; headers["Pragma"] = "no-cache"; diff --git a/src/Lidarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs b/src/Lidarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs index 0edecbd4c..79cfd8336 100644 --- a/src/Lidarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs +++ b/src/Lidarr.Http/Frontend/Mappers/IMapHttpRequestsToDisk.cs @@ -6,6 +6,6 @@ namespace Lidarr.Http.Frontend.Mappers { string Map(string resourceUrl); bool CanHandle(string resourceUrl); - IActionResult GetResponse(string resourceUrl); + FileStreamResult GetResponse(string resourceUrl); } } diff --git a/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs b/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs index 949c9a2d3..9c518aee3 100644 --- a/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs +++ b/src/Lidarr.Http/Frontend/Mappers/StaticResourceMapperBase.cs @@ -28,7 +28,7 @@ namespace Lidarr.Http.Frontend.Mappers public abstract bool CanHandle(string resourceUrl); - public virtual IActionResult GetResponse(string resourceUrl) + public FileStreamResult GetResponse(string resourceUrl) { var filePath = Map(resourceUrl); diff --git a/src/Lidarr.Http/Frontend/StaticResourceController.cs b/src/Lidarr.Http/Frontend/StaticResourceController.cs index 5709f7e06..48f6855d3 100644 --- a/src/Lidarr.Http/Frontend/StaticResourceController.cs +++ b/src/Lidarr.Http/Frontend/StaticResourceController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Lidarr.Http.Extensions; using Lidarr.Http.Frontend.Mappers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; @@ -36,6 +37,7 @@ namespace Lidarr.Http.Frontend [HttpGet("login")] public IActionResult LoginPage() { + Response.Headers.DisableCache(); return PhysicalFile(_loginPath, "text/html"); } @@ -62,7 +64,19 @@ namespace Lidarr.Http.Frontend if (mapper != null) { - return mapper.GetResponse(path) ?? NotFound(); + var result = mapper.GetResponse(path); + + if (result != null) + { + if (result.ContentType == "text/html") + { + Response.Headers.DisableCache(); + } + + return result; + } + + return NotFound(); } _logger.Warn("Couldn't find handler for {0}", path); diff --git a/src/Lidarr.Http/Middleware/CacheHeaderMiddleware.cs b/src/Lidarr.Http/Middleware/CacheHeaderMiddleware.cs index 2a754cdf8..d8ac38b26 100644 --- a/src/Lidarr.Http/Middleware/CacheHeaderMiddleware.cs +++ b/src/Lidarr.Http/Middleware/CacheHeaderMiddleware.cs @@ -19,7 +19,7 @@ namespace Lidarr.Http.Middleware { if (context.Request.Method != "OPTIONS") { - if (_cacheableSpecification.IsCacheable(context)) + if (_cacheableSpecification.IsCacheable(context.Request)) { context.Response.Headers.EnableCache(); } diff --git a/src/Lidarr.Http/Middleware/CacheableSpecification.cs b/src/Lidarr.Http/Middleware/CacheableSpecification.cs index edcb846f1..c7a58891d 100644 --- a/src/Lidarr.Http/Middleware/CacheableSpecification.cs +++ b/src/Lidarr.Http/Middleware/CacheableSpecification.cs @@ -7,26 +7,26 @@ namespace Lidarr.Http.Middleware { public interface ICacheableSpecification { - bool IsCacheable(HttpContext context); + bool IsCacheable(HttpRequest request); } public class CacheableSpecification : ICacheableSpecification { - public bool IsCacheable(HttpContext context) + public bool IsCacheable(HttpRequest request) { if (!RuntimeInfo.IsProduction) { return false; } - if (context.Request.Query.ContainsKey("h")) + if (request.Query.ContainsKey("h")) { return true; } - if (context.Request.Path.StartsWithSegments("/api", StringComparison.CurrentCultureIgnoreCase)) + if (request.Path.StartsWithSegments("/api", StringComparison.CurrentCultureIgnoreCase)) { - if (context.Request.Path.ToString().ContainsIgnoreCase("/MediaCover")) + if (request.Path.ToString().ContainsIgnoreCase("/MediaCover")) { return true; } @@ -34,40 +34,32 @@ namespace Lidarr.Http.Middleware return false; } - if (context.Request.Path.StartsWithSegments("/signalr", StringComparison.CurrentCultureIgnoreCase)) + if (request.Path.StartsWithSegments("/signalr", StringComparison.CurrentCultureIgnoreCase)) { return false; } - if (context.Request.Path.Value?.EndsWith("/index.js") ?? false) + if (request.Path.Value?.EndsWith("/index.js") ?? false) { return false; } - if (context.Request.Path.Value?.EndsWith("/initialize.js") ?? false) + if (request.Path.Value?.EndsWith("/initialize.js") ?? false) { return false; } - if (context.Request.Path.StartsWithSegments("/feed", StringComparison.CurrentCultureIgnoreCase)) + if (request.Path.StartsWithSegments("/feed", StringComparison.CurrentCultureIgnoreCase)) { return false; } - if (context.Request.Path.StartsWithSegments("/log", StringComparison.CurrentCultureIgnoreCase) && - (context.Request.Path.Value?.EndsWith(".txt", StringComparison.CurrentCultureIgnoreCase) ?? false)) + if (request.Path.StartsWithSegments("/log", StringComparison.CurrentCultureIgnoreCase) && + (request.Path.Value?.EndsWith(".txt", StringComparison.CurrentCultureIgnoreCase) ?? false)) { return false; } - if (context.Response != null) - { - if (context.Response.ContentType?.Contains("text/html") ?? false || context.Response.StatusCode >= 400) - { - return false; - } - } - return true; } } diff --git a/src/Lidarr.Http/Middleware/IfModifiedMiddleware.cs b/src/Lidarr.Http/Middleware/IfModifiedMiddleware.cs index 4aef568ee..04d8b8013 100644 --- a/src/Lidarr.Http/Middleware/IfModifiedMiddleware.cs +++ b/src/Lidarr.Http/Middleware/IfModifiedMiddleware.cs @@ -22,7 +22,7 @@ namespace Lidarr.Http.Middleware public async Task InvokeAsync(HttpContext context) { - if (_cacheableSpecification.IsCacheable(context) && context.Request.Headers["IfModifiedSince"].Any()) + if (_cacheableSpecification.IsCacheable(context.Request) && context.Request.Headers["IfModifiedSince"].Any()) { context.Response.StatusCode = 304; context.Response.Headers.EnableCache(); diff --git a/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs b/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs index 8bd1a26fc..ed732aee8 100644 --- a/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs +++ b/src/NzbDrone.Integration.Test/IndexHtmlFixture.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Linq; +using System.Net; using FluentAssertions; using NUnit.Framework; @@ -13,5 +14,19 @@ namespace NzbDrone.Integration.Test var text = new WebClient().DownloadString(RootUrl); text.Should().NotBeNullOrWhiteSpace(); } + + [Test] + public void index_should_not_be_cached() + { + var client = new WebClient(); + _ = client.DownloadString(RootUrl); + + var headers = client.ResponseHeaders; + + headers.Get("Cache-Control").Split(',').Select(x => x.Trim()) + .Should().BeEquivalentTo("no-store, no-cache".Split(',').Select(x => x.Trim())); + headers.Get("Pragma").Should().Be("no-cache"); + headers.Get("Expires").Should().Be("-1"); + } } }