Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Umbraco.Core/Configuration/Models/ImagingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

/// <summary>
/// Gets or sets a value for imaging cache settings.
/// </summary>
public ImagingCacheSettings Cache { get; set; } = new ImagingCacheSettings();
public ImagingCacheSettings Cache { get; set; } = new ();

/// <summary>
/// Gets or sets a value for imaging resize settings.
/// </summary>
public ImagingResizeSettings Resize { get; set; } = new ImagingResizeSettings();
public ImagingResizeSettings Resize { get; set; } = new ();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Dictionary;
using Umbraco.Cms.Core.IO;
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
Expand Down Expand Up @@ -40,6 +40,19 @@ public static IUmbracoBuilder SetDefaultViewContentProvider<T>(this IUmbracoBuil
return builder;
}

/// <summary>
/// Sets the default view content provider
/// </summary>
/// <typeparam name="T">The type of the provider.</typeparam>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static IUmbracoBuilder SetDefaultAdditionalImagingOptions<T>(this IUmbracoBuilder builder)
where T : class, IAdditionalImagingOptions
{
builder.Services.AddUnique<IAdditionalImagingOptions, T>();
return builder;
}

/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
Expand Down
73 changes: 73 additions & 0 deletions src/Umbraco.Infrastructure/Imaging/AdditionalImagingOptions.cs
Original file line number Diff line number Diff line change
@@ -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 = 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<int>(
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
context.Culture);
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
{
context.Commands.Remove(ResizeWebProcessor.Width);
}

var height = context.Parser.ParseValue<int>(
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<string> 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);
}
16 changes: 16 additions & 0 deletions src/Umbraco.Infrastructure/Imaging/IAdditionalImagingOptions.cs
Original file line number Diff line number Diff line change
@@ -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);
Task OnParseCommandsAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context);
Task OnPrepareResponseAsync(ImageSharpMiddlewareOptions options, HttpContext context);
Task<string> OnComputeHMACAsync(ImageSharpMiddlewareOptions options, ImageCommandContext context, byte[] hmac);
Task OnBeforeSaveAsync(ImageSharpMiddlewareOptions options, FormattedImage image);
Task OnProcessedAsync(ImageSharpMiddlewareOptions options, ImageProcessingContext context);
}
1 change: 1 addition & 0 deletions src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
<PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" /> <!-- Explicit updated this nested dependency due to this https://github.com/dotnet/announcements/issues/178-->
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -17,17 +14,38 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection;
public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<ImageSharpMiddlewareOptions>
{
private readonly Configuration _configuration;
private readonly IAdditionalImagingOptions _additionalImagingOptions;
private readonly ImagingSettings _imagingSettings;

/// <summary>
/// Initializes a new instance of the <see cref="ConfigureImageSharpMiddlewareOptions" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
/// <param name="imagingSettings">The Umbraco imaging settings.</param>
public ConfigureImageSharpMiddlewareOptions(Configuration configuration, IOptions<ImagingSettings> imagingSettings)
/// <param name="additionalImageOptions">Additional options for context related methods.</param>
public ConfigureImageSharpMiddlewareOptions(
Configuration configuration,
IOptions<ImagingSettings> imagingSettings,
IAdditionalImagingOptions additionalImageOptions)
{
_configuration = configuration;
_imagingSettings = imagingSettings.Value;
_additionalImagingOptions = additionalImageOptions;
}

/// <summary>
/// Initializes a new instance of the <see cref="ConfigureImageSharpMiddlewareOptions" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
/// <param name="imagingSettings">The Umbraco imaging settings.</param>
[Obsolete("Use ctor with all params")]
public ConfigureImageSharpMiddlewareOptions(
Configuration configuration,
IOptions<ImagingSettings> imagingSettings)
{
_configuration = configuration;
_imagingSettings = imagingSettings.Value;
_additionalImagingOptions = StaticServiceProvider.Instance.GetRequiredService<IAdditionalImagingOptions>();
}

/// <inheritdoc />
Expand All @@ -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<int>(
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
context.Culture);
if (width <= 0 || width > _imagingSettings.Resize.MaxWidth)
{
context.Commands.Remove(ResizeWebProcessor.Width);
}

var height = context.Parser.ParseValue<int>(
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +32,7 @@ public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder build
.AddProcessor<CropWebProcessor>();

// Configure middleware
builder.Services.AddSingleton<IAdditionalImagingOptions, AdditionalImagingOptions>();
builder.Services
.AddTransient<IConfigureOptions<ImageSharpMiddlewareOptions>, ConfigureImageSharpMiddlewareOptions>();

Expand Down