From 08bccbff9313b5ecaf379924e9b48c1e5539b7ad Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Thu, 23 Jun 2022 13:00:44 +0200 Subject: [PATCH 1/3] Allow all ImageSharpMiddleware to be configurable Add missing settings to the imagine settings, and add a new interface to be able to override / extend the methods --- .../Configuration/Models/ImagingSettings.cs | 8 +- .../UmbracoBuilder.Uniques.cs | 15 +++- .../Imaging/AdditionalImagingOptions.cs | 73 +++++++++++++++++ .../Imaging/IAdditionalImagingOptions.cs | 16 ++++ .../Umbraco.Infrastructure.csproj | 1 + .../ConfigureImageSharpMiddlewareOptions.cs | 81 ++++++++----------- .../UmbracoBuilder.ImageSharp.cs | 2 + 7 files changed, 144 insertions(+), 52 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Imaging/AdditionalImagingOptions.cs create mode 100644 src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs diff --git a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs index fde303343c9c..ae9c1e1fee43 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs @@ -9,14 +9,18 @@ namespace Umbraco.Cms.Core.Configuration.Models [UmbracoOptions(Constants.Configuration.ConfigImaging)] public class ImagingSettings { + public bool? UseInvariantParsingCulture { get; set; } + + public byte[] HMACSecretKey { get; set; } + /// /// Gets or sets a value for imaging cache settings. /// - public ImagingCacheSettings Cache { get; set; } = new ImagingCacheSettings(); + public ImagingCacheSettings Cache { get; set; } = new (); /// /// Gets or sets a value for imaging resize settings. /// - public ImagingResizeSettings Resize { get; set; } = new ImagingResizeSettings(); + public ImagingResizeSettings Resize { get; set; } = new (); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs index e3839e152b9a..717d05bbef0e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Dictionary; @@ -6,6 +5,7 @@ using Umbraco.Cms.Core.Logging.Viewer; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Imaging; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.DependencyInjection @@ -40,6 +40,19 @@ public static IUmbracoBuilder SetDefaultViewContentProvider(this IUmbracoBuil return builder; } + /// + /// Sets the default view content provider + /// + /// The type of the provider. + /// The builder. + /// + public static IUmbracoBuilder SetDefaultAdditionalImagingOptions(this IUmbracoBuilder builder) + where T : class, IAdditionalImagingOptions + { + builder.Services.AddUnique(); + return builder; + } + /// /// Sets the culture dictionary factory. /// diff --git a/src/Umbraco.Infrastructure/Imaging/AdditionalImagingOptions.cs b/src/Umbraco.Infrastructure/Imaging/AdditionalImagingOptions.cs new file mode 100644 index 000000000000..77f7a1a9d1a2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Imaging/AdditionalImagingOptions.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Extensions.Options; +using Microsoft.IO; +using Microsoft.Net.Http.Headers; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.Middleware; +using SixLabors.ImageSharp.Web.Processors; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Infrastructure.Imaging; + +public class AdditionalImagingOptions : IAdditionalImagingOptions +{ + private readonly ImagingSettings _imagingSettings; + + public AdditionalImagingOptions(IOptions imagingSettings) => _imagingSettings = imagingSettings.Value; + + public RecyclableMemoryStreamManager MemoryStreamManager(ImageSharpMiddlewareOptions options) => options.MemoryStreamManager; + + public Task OnParseCommandsAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context) + { + if (context.Commands.Count == 0) + { + return Task.CompletedTask; + } + + // Use configurable maximum width and height + var width = context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), + context.Culture); + if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } + + var height = context.Parser.ParseValue( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), + context.Culture); + if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } + + return Task.CompletedTask; + } + + public Task OnPrepareResponseAsync(ImageSharpMiddlewareOptions options, HttpContext context) + { + // Change Cache-Control header when cache buster value is present + if (context.Request.Query.ContainsKey("rnd") || context.Request.Query.ContainsKey("v")) + { + ResponseHeaders headers = context.Response.GetTypedHeaders(); + + CacheControlHeaderValue cacheControl = + headers.CacheControl ?? new CacheControlHeaderValue { Public = true }; + cacheControl.MustRevalidate = false; // ImageSharp enables this by default + cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); + + headers.CacheControl = cacheControl; + } + + return Task.CompletedTask; + } + + + public Task OnComputeHMACAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context, byte[] hmac) => options.OnComputeHMACAsync(context, hmac); + + public Task OnBeforeSaveAsync(ImageSharpMiddlewareOptions options, FormattedImage image) => options.OnBeforeSaveAsync(image); + + public Task OnProcessedAsync(ImageSharpMiddlewareOptions options, ImageProcessingContext context) => options.OnProcessedAsync(context); +} diff --git a/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs b/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs new file mode 100644 index 000000000000..59031874e8d0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.IO; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Middleware; + +namespace Umbraco.Cms.Infrastructure.Imaging; + +public interface IAdditionalImagingOptions +{ + RecyclableMemoryStreamManager MemoryStreamManager(ImageSharpMiddlewareOptions options); + public Task OnParseCommandsAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context); + public Task OnPrepareResponseAsync(ImageSharpMiddlewareOptions options, HttpContext context); + Task OnComputeHMACAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context, byte[] hmac); + Task OnBeforeSaveAsync(ImageSharpMiddlewareOptions options, FormattedImage image); + Task OnProcessedAsync(ImageSharpMiddlewareOptions options, ImageProcessingContext context); +} diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index adde7948ef6b..f36d6bec51a9 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -49,6 +49,7 @@ + diff --git a/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs index 2b372992ccdd..da27df14da7d 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs @@ -1,12 +1,9 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Middleware; -using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Infrastructure.Imaging; namespace Umbraco.Cms.Web.Common.DependencyInjection; @@ -17,6 +14,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection; public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions { private readonly Configuration _configuration; + private readonly IAdditionalImagingOptions _additionalImagingOptions; private readonly ImagingSettings _imagingSettings; /// @@ -24,10 +22,30 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions /// The ImageSharp configuration. /// The Umbraco imaging settings. - public ConfigureImageSharpMiddlewareOptions(Configuration configuration, IOptions imagingSettings) + /// Additional options for context related methods. + public ConfigureImageSharpMiddlewareOptions( + Configuration configuration, + IOptions imagingSettings, + IAdditionalImagingOptions additionalImageOptions) { _configuration = configuration; _imagingSettings = imagingSettings.Value; + _additionalImagingOptions = additionalImageOptions; + } + + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + /// The Umbraco imaging settings. + [Obsolete("Use ctor with all params")] + public ConfigureImageSharpMiddlewareOptions( + Configuration configuration, + IOptions imagingSettings) + { + _configuration = configuration; + _imagingSettings = imagingSettings.Value; + _additionalImagingOptions = StaticServiceProvider.Instance.GetRequiredService(); } /// @@ -39,49 +57,14 @@ public void Configure(ImageSharpMiddlewareOptions options) options.CacheMaxAge = _imagingSettings.Cache.CacheMaxAge; options.CacheHashLength = _imagingSettings.Cache.CacheHashLength; - // Use configurable maximum width and height - options.OnParseCommandsAsync = context => - { - if (context.Commands.Count == 0) - { - return Task.CompletedTask; - } - - var width = context.Parser.ParseValue( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), - context.Culture); - if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) - { - context.Commands.Remove(ResizeWebProcessor.Width); - } - - var height = context.Parser.ParseValue( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), - context.Culture); - if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) - { - context.Commands.Remove(ResizeWebProcessor.Height); - } - - return Task.CompletedTask; - }; - - // Change Cache-Control header when cache buster value is present - options.OnPrepareResponseAsync = context => - { - if (context.Request.Query.ContainsKey("rnd") || context.Request.Query.ContainsKey("v")) - { - ResponseHeaders headers = context.Response.GetTypedHeaders(); - - CacheControlHeaderValue cacheControl = - headers.CacheControl ?? new CacheControlHeaderValue { Public = true }; - cacheControl.MustRevalidate = false; // ImageSharp enables this by default - cacheControl.Extensions.Add(new NameValueHeaderValue("immutable")); - - headers.CacheControl = cacheControl; - } + options.HMACSecretKey = _imagingSettings.HMACSecretKey.Length != 0 ? _imagingSettings.HMACSecretKey : options.HMACSecretKey; + options.UseInvariantParsingCulture = _imagingSettings.UseInvariantParsingCulture.GetValueOrDefault(options.UseInvariantParsingCulture); - return Task.CompletedTask; - }; + options.MemoryStreamManager = _additionalImagingOptions.MemoryStreamManager(options); + options.OnComputeHMACAsync = (context, hmac) => _additionalImagingOptions.OnComputeHMACAsync(options, context, hmac); + options.OnParseCommandsAsync = context => _additionalImagingOptions.OnParseCommandsAsync(options, context); + options.OnBeforeSaveAsync = image => _additionalImagingOptions.OnBeforeSaveAsync(options, image); + options.OnProcessedAsync = context => _additionalImagingOptions.OnProcessedAsync(options, context); + options.OnPrepareResponseAsync = context => _additionalImagingOptions.OnPrepareResponseAsync(options, context); } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index e5e642f4a3f7..02c840b49cb7 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Infrastructure.Imaging; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.ImageProcessors; using Umbraco.Cms.Web.Common.Media; @@ -31,6 +32,7 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build .AddProcessor(); // Configure middleware + builder.Services.AddSingleton(); builder.Services .AddTransient, ConfigureImageSharpMiddlewareOptions>(); From a6fd5219bb071d8cf6467884ab579ef0e2af6104 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Thu, 23 Jun 2022 13:05:00 +0200 Subject: [PATCH 2/3] Nullable type Allow hmac to be null --- src/Umbraco.Core/Configuration/Models/ImagingSettings.cs | 2 +- .../DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs index ae9c1e1fee43..0526c156c982 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs @@ -11,7 +11,7 @@ public class ImagingSettings { public bool? UseInvariantParsingCulture { get; set; } - public byte[] HMACSecretKey { get; set; } + public byte[]? HMACSecretKey { get; set; } /// /// Gets or sets a value for imaging cache settings. diff --git a/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs index da27df14da7d..65120f8399fd 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ConfigureImageSharpMiddlewareOptions.cs @@ -57,7 +57,7 @@ public void Configure(ImageSharpMiddlewareOptions options) options.CacheMaxAge = _imagingSettings.Cache.CacheMaxAge; options.CacheHashLength = _imagingSettings.Cache.CacheHashLength; - options.HMACSecretKey = _imagingSettings.HMACSecretKey.Length != 0 ? _imagingSettings.HMACSecretKey : options.HMACSecretKey; + options.HMACSecretKey = _imagingSettings.HMACSecretKey?.Length != 0 ? _imagingSettings.HMACSecretKey : options.HMACSecretKey; options.UseInvariantParsingCulture = _imagingSettings.UseInvariantParsingCulture.GetValueOrDefault(options.UseInvariantParsingCulture); options.MemoryStreamManager = _additionalImagingOptions.MemoryStreamManager(options); From cde052d361befd06a33ebba76cf27f0b7b66259a Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Thu, 23 Jun 2022 14:35:07 +0200 Subject: [PATCH 3/3] Remove access modifier Remove access modifier mistakenly added to interface --- .../Imaging/IAdditionalImagingOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs b/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs index 59031874e8d0..2d53975b03f2 100644 --- a/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs +++ b/src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs @@ -8,8 +8,8 @@ namespace Umbraco.Cms.Infrastructure.Imaging; public interface IAdditionalImagingOptions { RecyclableMemoryStreamManager MemoryStreamManager(ImageSharpMiddlewareOptions options); - public Task OnParseCommandsAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context); - public Task OnPrepareResponseAsync(ImageSharpMiddlewareOptions options, HttpContext context); + Task OnParseCommandsAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context); + Task OnPrepareResponseAsync(ImageSharpMiddlewareOptions options, HttpContext context); Task OnComputeHMACAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context, byte[] hmac); Task OnBeforeSaveAsync(ImageSharpMiddlewareOptions options, FormattedImage image); Task OnProcessedAsync(ImageSharpMiddlewareOptions options, ImageProcessingContext context);