From de48dc263dcd38032fc5d18287d48894f5385a1b Mon Sep 17 00:00:00 2001 From: Dean Marcussen Date: Wed, 27 Apr 2022 20:59:13 +0100 Subject: [PATCH] Update ImageSharp.Web v2 (#11585) --- src/OrchardCore.Build/Dependencies.props | 2 +- .../Processing/BackwardsCompatibleCacheKey.cs | 33 +++++++++++++++++++ .../Processing/ImageVersionProcessor.cs | 6 ++++ .../MediaImageSharpConfiguration.cs | 6 ++-- .../Processing/MediaResizingFileProvider.cs | 14 ++------ .../Processing/MediaTokenService.cs | 14 +++++--- .../Processing/TokenCommandProcessor.cs | 6 ++++ .../OrchardCore.Media/Startup.cs | 10 +++++- .../Views/MediaProfiles/Create.cshtml | 2 +- .../Views/MediaProfiles/Edit.cshtml | 2 +- .../IMediaTokenService.cs | 3 +- .../MediaTokenServiceBenchmark.cs | 2 +- 12 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Media/Processing/BackwardsCompatibleCacheKey.cs diff --git a/src/OrchardCore.Build/Dependencies.props b/src/OrchardCore.Build/Dependencies.props index b98478cb529..476fcac4337 100644 --- a/src/OrchardCore.Build/Dependencies.props +++ b/src/OrchardCore.Build/Dependencies.props @@ -43,7 +43,7 @@ - + diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/BackwardsCompatibleCacheKey.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/BackwardsCompatibleCacheKey.cs new file mode 100644 index 00000000000..7ab6c0e45e6 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/BackwardsCompatibleCacheKey.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Http; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Caching; +using SixLabors.ImageSharp.Web.Commands; + +namespace OrchardCore.Media.Processing +{ + /// + /// Backwards compatible absolute url cache key. + /// + public class BackwardsCompatibleCacheKey : ICacheKey + { + /// + public string Create(HttpContext context, CommandCollection commands) + { + + var pathBase = context.Request.PathBase; + if (pathBase.HasValue) + { + // Due to bugs with an earlier version of cache calculation the cache key result was + // localhost:44300/agency1//media/portfolio/5.jpg?width=600&height=480&rmode=stretch + // the default ImageSharp absolute cache builder produces the correct value + // localhost:44300/agency1/media/portfolio/5.jpg?width=600&height=480&rmode=stretch + // which causes an entire cache refresh. + // refer https://github.com/SixLabors/ImageSharp.Web/issues/254 + + pathBase = new PathString(pathBase + "//"); + } + + return CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.LowerInvariant, context.Request.Host, pathBase, context.Request.Path, QueryString.Create(commands)); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/ImageVersionProcessor.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/ImageVersionProcessor.cs index de217f4ae98..fb57c4ce388 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Processing/ImageVersionProcessor.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/ImageVersionProcessor.cs @@ -24,5 +24,11 @@ public IEnumerable Commands public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) => image; + + public FormattedImage Process(FormattedImage image, ILogger logger, CommandCollection commands, CommandParser parser, CultureInfo culture) + => image; + + public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) + => false; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaImageSharpConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaImageSharpConfiguration.cs index 857be422d74..e9341fcf037 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaImageSharpConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaImageSharpConfiguration.cs @@ -25,7 +25,7 @@ public void Configure(ImageSharpMiddlewareOptions options) options.Configuration = Configuration.Default; options.BrowserMaxAge = TimeSpan.FromDays(_mediaOptions.MaxBrowserCacheDays); options.CacheMaxAge = TimeSpan.FromDays(_mediaOptions.MaxCacheDays); - options.CachedNameLength = 12; + options.CacheHashLength = 12; options.OnParseCommandsAsync = context => { if (context.Commands.Count == 0) @@ -72,11 +72,11 @@ public void Configure(ImageSharpMiddlewareOptions options) context.Commands.Remove(ResizeWebProcessor.Anchor); // When only a version command is applied pass on this request. - if (context.Commands.Count == 1 && context.Commands.ContainsKey(ImageVersionProcessor.VersionCommand)) + if (context.Commands.Count == 1 && context.Commands.Contains(ImageVersionProcessor.VersionCommand)) { context.Commands.Clear(); } - else if (!context.Commands.ContainsKey(ResizeWebProcessor.Mode)) + else if (!context.Commands.Contains(ResizeWebProcessor.Mode)) { context.Commands[ResizeWebProcessor.Mode] = "max"; } diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaResizingFileProvider.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaResizingFileProvider.cs index fc20132ebb5..b89372fc353 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaResizingFileProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaResizingFileProvider.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Options; using OrchardCore.Routing; using SixLabors.ImageSharp.Web; -using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Providers; using SixLabors.ImageSharp.Web.Resolvers; @@ -25,7 +24,6 @@ public class MediaResizingFileProvider : IImageProvider public MediaResizingFileProvider( IMediaFileProvider mediaFileProvider, - CommandParser commandParser, IOptions imageSharpOptions, IOptions mediaOptions ) @@ -46,14 +44,7 @@ public Func Match /// public bool IsValidRequest(HttpContext context) - { - if (_formatUtilities.GetExtensionFromUri(context.Request.GetDisplayUrl()) == null) - { - return false; - } - - return true; - } + => _formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _); /// public Task GetAsync(HttpContext context) @@ -70,8 +61,7 @@ public Task GetAsync(HttpContext context) } // We don't care about the content type nor cache control max age here. - var metadata = new ImageMetadata(fileInfo.LastModified.UtcDateTime, fileInfo.Length); - return Task.FromResult(new PhysicalFileSystemResolver(fileInfo, metadata)); + return Task.FromResult(new FileProviderImageResolver(fileInfo)); } private bool IsMatch(HttpContext context) diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaTokenService.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaTokenService.cs index fe62f783eb5..f0c52386c7d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaTokenService.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/MediaTokenService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; @@ -114,9 +115,9 @@ private void ParseQuery( } } - public bool TryValidateToken(IDictionary commands, string token) + public bool TryValidateToken(KeyedCollection> commands, string token) { - var queryStringTokenKey = CreateQueryStringTokenKey(commands); + var queryStringTokenKey = CreateCommandCollectionTokenKey(commands); // Store a hash of the valid query string commands. var queryStringToken = GetHash(queryStringTokenKey); @@ -140,10 +141,11 @@ private static string CreateQueryStringTokenKey(Dictionary { builder.Append(pair.Value.ToString()); } + return builder.ToString(); } - private static string CreateQueryStringTokenKey(IDictionary values) + private static string CreateCommandCollectionTokenKey(KeyedCollection> values) { using var builder = ZString.CreateStringBuilder(); builder.Append(TokenCacheKeyPrefix); @@ -151,6 +153,7 @@ private static string CreateQueryStringTokenKey(IDictionary valu { builder.Append(pair.Value); } + return builder.ToString(); } @@ -183,11 +186,11 @@ private string GetHash(string queryStringTokenKey) entry.Value = result = Convert.ToBase64String(hashBytes.Slice(0, hashBytesLength)); } - return (string) result; + return (string)result; } /// - /// Custom version of that takes our pre-built + /// Custom version of that takes our pre-built /// dictionary, uri as ReadOnlySpan<char> and uses ZString. Otherwise same logic. /// private static string AddQueryString( @@ -219,6 +222,7 @@ private static string AddQueryString( } sb.Append(anchorText); + return sb.ToString(); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Processing/TokenCommandProcessor.cs b/src/OrchardCore.Modules/OrchardCore.Media/Processing/TokenCommandProcessor.cs index f9c3c3b1eed..0694a6d2c5a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Processing/TokenCommandProcessor.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Processing/TokenCommandProcessor.cs @@ -24,5 +24,11 @@ public IEnumerable Commands public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) => image; + + public FormattedImage Process(FormattedImage image, ILogger logger, CommandCollection commands, CommandParser parser, CultureInfo culture) + => image; + + public bool RequiresTrueColorPixelFormat(CommandCollection commands, CommandParser parser, CultureInfo culture) + => false; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs index 30015f1e958..6bf60d222f4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/Startup.cs @@ -48,6 +48,7 @@ using OrchardCore.Recipes; using OrchardCore.Security.Permissions; using OrchardCore.Shortcodes; +using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Providers; @@ -132,8 +133,15 @@ public override void ConfigureServices(IServiceCollection services) // Add ImageSharp Configuration first, to override ImageSharp defaults. services.AddTransient, MediaImageSharpConfiguration>(); - services.AddImageSharp() + services + .AddImageSharp() .RemoveProvider() + // For multitenancy we must use an absolute path to prevent leakage across tenants on different hosts. + .SetCacheKey() + .Configure(options => + { + options.CacheFolderDepth = 12; + }) .AddProvider() .AddProcessor() .AddProcessor(); diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Create.cshtml b/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Create.cshtml index 31f840a225e..54de371f608 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Create.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Create.cshtml @@ -92,7 +92,7 @@
- @T["The quality percentage for the processed image. Only supported with the JPG format. A value of 100% means a quality command will not be applied."] + @T["The quality percentage for the processed image. Only supported with the JPG and WebP format. A value of 100% means a quality command will not be applied."]
diff --git a/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Edit.cshtml index 4557e84e44b..4528e4a07d8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Media/Views/MediaProfiles/Edit.cshtml @@ -93,7 +93,7 @@
- @T["The quality percentage for the processed image. Only supported with the JPG format. A value of 100% means a quality command will not be applied."] + @T["The quality percentage for the processed image. Only supported with the JPG or WebP format. A value of 100% means a quality command will not be applied."]
diff --git a/src/OrchardCore/OrchardCore.Media.Abstractions/IMediaTokenService.cs b/src/OrchardCore/OrchardCore.Media.Abstractions/IMediaTokenService.cs index 52302043c42..61ec2efe60b 100644 --- a/src/OrchardCore/OrchardCore.Media.Abstractions/IMediaTokenService.cs +++ b/src/OrchardCore/OrchardCore.Media.Abstractions/IMediaTokenService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; namespace OrchardCore.Media { @@ -9,6 +10,6 @@ namespace OrchardCore.Media public interface IMediaTokenService { string AddTokenToPath(string path); - bool TryValidateToken(IDictionary commands, string token); + bool TryValidateToken(KeyedCollection> commands, string token); } } diff --git a/test/OrchardCore.Benchmarks/MediaTokenServiceBenchmark.cs b/test/OrchardCore.Benchmarks/MediaTokenServiceBenchmark.cs index 6d671072ec8..93e1d2cad44 100644 --- a/test/OrchardCore.Benchmarks/MediaTokenServiceBenchmark.cs +++ b/test/OrchardCore.Benchmarks/MediaTokenServiceBenchmark.cs @@ -33,7 +33,7 @@ static MediaTokenServiceBenchmark() new ResizeWebProcessor(), new FormatWebProcessor(Options.Create(new ImageSharpMiddlewareOptions())), new BackgroundColorWebProcessor(), - new JpegQualityWebProcessor(), + new QualityWebProcessor(), new ImageVersionProcessor(), new TokenCommandProcessor() };