Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ff153cd
Update to ImageSharp 2.1.0 and ImageSharp.Web 2.0.0-alpha.0.23
ronaldbarendse Mar 27, 2022
e8d82af
Rename CachedNameLength to CacheHashLength and add CacheFolderDepth s…
ronaldbarendse Mar 27, 2022
5fa02e9
Replace PhysicalFileSystemProvider with WebRootImageProvider
ronaldbarendse Mar 27, 2022
da34c09
Support EXIF-orientation in image dimention extractor
ronaldbarendse Mar 28, 2022
91fbdb2
Remove virtual methods on FileProviderImageProvider
ronaldbarendse Mar 30, 2022
827ae60
Simplify FileInfoImageResolver
ronaldbarendse Mar 30, 2022
8efb526
Update to SixLabors.ImageSharp.Web 2.0.0-alpha.0.25 and remove custom…
ronaldbarendse Apr 4, 2022
d35dfc6
Make CropWebProcessor EXIF orientation-aware
ronaldbarendse Apr 4, 2022
ba491ec
Improve width/height sanitization
ronaldbarendse Apr 4, 2022
5b973a9
Also use 'v' as cache buster value
ronaldbarendse Apr 4, 2022
2cbef09
Add WebP to supported image file types
ronaldbarendse Apr 4, 2022
e7f74dc
Update to SixLabors.ImageSharp.Web 2.0.0-alpha.0.27 and fix test
ronaldbarendse Apr 5, 2022
70e2437
Fix rounding error and add test cases
ronaldbarendse Apr 9, 2022
67b8dbc
Update to newest and stable releases
ronaldbarendse Apr 25, 2022
5b758db
Merge branch 'v10/dev' into v10/feature/imagesharp2
ronaldbarendse Apr 25, 2022
798a3bd
Merge branch 'origin/v10/dev' into v10/feature/imagesharp2
ronaldbarendse Apr 28, 2022
cac5402
Move ImageSharpImageUrlGenerator to Umbraco.Web.Common
ronaldbarendse Apr 28, 2022
3cae534
Use IConfigureOptions to configure ImageSharp options
ronaldbarendse Apr 28, 2022
10eb181
Implement IEquatable on ImageUrlGenerationOptions classes
ronaldbarendse Apr 28, 2022
2de5520
Fix empty/null values in image URL generation and corresponding tests
ronaldbarendse Apr 28, 2022
5898b0c
Use IsSupportedImageFormat extension method
ronaldbarendse Apr 28, 2022
5cb9a9b
Remove unneeded reflection
ronaldbarendse Apr 28, 2022
bd4bb54
Add HMACSecretKey setting and add token when generating image URLs
ronaldbarendse Apr 28, 2022
3dd078a
Ensure backoffice image URLs are generated by the server (and include…
ronaldbarendse Apr 28, 2022
8bd54f2
Abstract HMAC generation to IImageUrlTokenGenerator
ronaldbarendse Apr 29, 2022
8ee6938
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse Apr 29, 2022
e16940e
Change cache buster value to 'v' and use hexadecimal timestamp
ronaldbarendse May 1, 2022
845cadc
Update comments
ronaldbarendse May 1, 2022
c17c58c
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse May 6, 2022
9e7141f
Fix backoffice thumbnail URL generation
ronaldbarendse May 6, 2022
c9401c9
Update grid media thumbnail URL generation
ronaldbarendse May 6, 2022
19c01cf
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse May 10, 2022
11b16c9
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse Jun 29, 2022
d9879ff
Remove breaking changes
ronaldbarendse Jun 30, 2022
577f866
Strip unknown commands from image URL token
ronaldbarendse Jun 30, 2022
c627a90
Remove HMAC whitelisting possibility (not supported by ImageSharp)
ronaldbarendse Jun 30, 2022
e19b2d2
Update to SixLabors.ImageSharp 2.1.3
ronaldbarendse Jun 30, 2022
1748b78
Add comment to internal constructor
ronaldbarendse Jul 5, 2022
69ab5af
Fix to support absolute image URLs
ronaldbarendse Jul 5, 2022
5274346
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse Aug 24, 2022
dd184d0
Update to SixLabors.ImageSharp.Web 2.0.3-alpha.0.3
ronaldbarendse Aug 24, 2022
40f694d
Remove IImageUrlTokenGenerator and use ImageSharpRequestAuthorization…
ronaldbarendse Aug 24, 2022
5f0f351
Move NuGet feed to config file
ronaldbarendse Aug 25, 2022
41712f3
Merge branch 'v10/dev' into v10/feature/imagesharp2-hmacsecretkey
ronaldbarendse Jan 9, 2023
b4296a5
Update to ImageSharp v3
ronaldbarendse Apr 28, 2023
dcf0d19
Merge branch 'v10/feature/imagesharp2-hmacsecretkey' into v12/feature…
ronaldbarendse Apr 28, 2023
b09a96c
Merge branch 'v12/dev' into v12/feature/imagesharp3-hmacsecretkey
ronaldbarendse May 9, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Umbraco.Cms.Imaging.ImageSharp;

/// <summary>
/// Configures the ImageSharp middleware options.
/// Configures the ImageSharp middleware options.
/// </summary>
/// <seealso cref="IConfigureOptions{ImageSharpMiddlewareOptions}" />
public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<ImageSharpMiddlewareOptions>
Expand All @@ -19,7 +19,7 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions<Ima
private readonly ImagingSettings _imagingSettings;

/// <summary>
/// Initializes a new instance of the <see cref="ConfigureImageSharpMiddlewareOptions" /> class.
/// 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>
Expand All @@ -34,29 +34,27 @@ public void Configure(ImageSharpMiddlewareOptions options)
{
options.Configuration = _configuration;

options.HMACSecretKey = _imagingSettings.HMACSecretKey;
options.BrowserMaxAge = _imagingSettings.Cache.BrowserMaxAge;
options.CacheMaxAge = _imagingSettings.Cache.CacheMaxAge;
options.CacheHashLength = _imagingSettings.Cache.CacheHashLength;

// Use configurable maximum width and height
options.OnParseCommandsAsync = context =>
{
if (context.Commands.Count == 0)
if (context.Commands.Count == 0 || _imagingSettings.HMACSecretKey.Length > 0)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can safely skip validating the maximum width/height when using HMAC authentication (the default callback in V2 also removed the hardcoded width/height check).

{
// Nothing to parse or using HMAC authentication
return Task.CompletedTask;
}

var width = context.Parser.ParseValue<int>(
context.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
context.Culture);
int 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);
int height = context.Parser.ParseValue<int>(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture);
if (height <= 0 || height > _imagingSettings.Resize.MaxHeight)
{
context.Commands.Remove(ResizeWebProcessor.Height);
Expand All @@ -72,11 +70,16 @@ public void Configure(ImageSharpMiddlewareOptions options)
{
ResponseHeaders headers = context.Response.GetTypedHeaders();

CacheControlHeaderValue cacheControl =
headers.CacheControl ?? new CacheControlHeaderValue { Public = true };
cacheControl.MustRevalidate = false; // ImageSharp enables this by default
CacheControlHeaderValue cacheControl = headers.CacheControl ?? new CacheControlHeaderValue()
{
Public = true
};

// ImageSharp enables cache revalidation by default, so disable and add immutable directive
cacheControl.MustRevalidate = false;
cacheControl.Extensions.Add(new NameValueHeaderValue("immutable"));

// Set updated value
headers.CacheControl = cacheControl;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Globalization;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using SixLabors.ImageSharp.Web.Processors;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors;
Expand All @@ -10,31 +12,47 @@
namespace Umbraco.Cms.Imaging.ImageSharp.Media;

/// <summary>
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
/// </summary>
/// <seealso cref="IImageUrlGenerator" />
public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator
{
/// <inheritdoc />
public IEnumerable<string> SupportedImageFileTypes { get; }
private readonly RequestAuthorizationUtilities? _requestAuthorizationUtilities;

/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
/// <param name="requestAuthorizationUtilities">Contains helpers that allow authorization of image requests.</param>
public ImageSharpImageUrlGenerator(Configuration configuration, RequestAuthorizationUtilities? requestAuthorizationUtilities)
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(), requestAuthorizationUtilities)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// </summary>
/// <param name="configuration">The ImageSharp configuration.</param>
[Obsolete("Use ctor with all params - This will be removed in Umbraco 13.")]
public ImageSharpImageUrlGenerator(Configuration configuration)
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray())
: this(configuration, StaticServiceProvider.Instance.GetService<RequestAuthorizationUtilities>())
{ }

/// <summary>
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
/// </summary>
/// <param name="supportedImageFileTypes">The supported image file types/extensions.</param>
/// <param name="requestAuthorizationUtilities">Contains helpers that allow authorization of image requests.</param>
/// <remarks>
/// This constructor is only used for testing.
/// This constructor is only used for testing.
/// </remarks>
internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes) =>
internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes, RequestAuthorizationUtilities? requestAuthorizationUtilities = null)
{
SupportedImageFileTypes = supportedImageFileTypes;
_requestAuthorizationUtilities = requestAuthorizationUtilities;
}

/// <inheritdoc />
public IEnumerable<string> SupportedImageFileTypes { get; }

/// <inheritdoc />
public string? GetImageUrl(ImageUrlGenerationOptions? options)
Expand All @@ -47,57 +65,63 @@ internal ImageSharpImageUrlGenerator(IEnumerable<string> supportedImageFileTypes
var queryString = new Dictionary<string, string?>();
Dictionary<string, StringValues> furtherOptions = QueryHelpers.ParseQuery(options.FurtherOptions);

if (options.Crop is not null)
if (options.Crop is CropCoordinates crop)
{
CropCoordinates? crop = options.Crop;
queryString.Add(
CropWebProcessor.Coordinates,
FormattableString.Invariant($"{crop.Left},{crop.Top},{crop.Right},{crop.Bottom}"));
queryString.Add(CropWebProcessor.Coordinates, FormattableString.Invariant($"{crop.Left},{crop.Top},{crop.Right},{crop.Bottom}"));
}

if (options.FocalPoint is not null)
if (options.FocalPoint is FocalPointPosition focalPoint)
{
queryString.Add(ResizeWebProcessor.Xy, FormattableString.Invariant($"{options.FocalPoint.Left},{options.FocalPoint.Top}"));
queryString.Add(ResizeWebProcessor.Xy, FormattableString.Invariant($"{focalPoint.Left},{focalPoint.Top}"));
}

if (options.ImageCropMode is not null)
if (options.ImageCropMode is ImageCropMode imageCropMode)
{
queryString.Add(ResizeWebProcessor.Mode, options.ImageCropMode.ToString()?.ToLowerInvariant());
queryString.Add(ResizeWebProcessor.Mode, imageCropMode.ToString().ToLowerInvariant());
}

if (options.ImageCropAnchor is not null)
if (options.ImageCropAnchor is ImageCropAnchor imageCropAnchor)
{
queryString.Add(ResizeWebProcessor.Anchor, options.ImageCropAnchor.ToString()?.ToLowerInvariant());
queryString.Add(ResizeWebProcessor.Anchor, imageCropAnchor.ToString().ToLowerInvariant());
}

if (options.Width is not null)
if (options.Width is int width)
{
queryString.Add(ResizeWebProcessor.Width, options.Width?.ToString(CultureInfo.InvariantCulture));
queryString.Add(ResizeWebProcessor.Width, width.ToString(CultureInfo.InvariantCulture));
}

if (options.Height is not null)
if (options.Height is int height)
{
queryString.Add(ResizeWebProcessor.Height, options.Height?.ToString(CultureInfo.InvariantCulture));
queryString.Add(ResizeWebProcessor.Height, height.ToString(CultureInfo.InvariantCulture));
}

if (furtherOptions.Remove(FormatWebProcessor.Format, out StringValues format))
{
queryString.Add(FormatWebProcessor.Format, format[0]);
queryString.Add(FormatWebProcessor.Format, format.ToString());
}

if (options.Quality is not null)
if (options.Quality is int quality)
{
queryString.Add(QualityWebProcessor.Quality, options.Quality?.ToString(CultureInfo.InvariantCulture));
queryString.Add(QualityWebProcessor.Quality, quality.ToString(CultureInfo.InvariantCulture));
}

foreach (KeyValuePair<string, StringValues> kvp in furtherOptions)
{
queryString.Add(kvp.Key, kvp.Value);
}

if (options.CacheBusterValue is not null && !string.IsNullOrWhiteSpace(options.CacheBusterValue))
if (options.CacheBusterValue is string cacheBusterValue && !string.IsNullOrEmpty(cacheBusterValue))
{
queryString.Add("v", cacheBusterValue);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache buster value is now using the v parameter, since it represents the version (or 'just' a value, not a random value like rnd suggested) and makes the URL slightly shorter.

}

if (_requestAuthorizationUtilities is not null)
{
queryString.Add("rnd", options.CacheBusterValue);
var uri = QueryHelpers.AddQueryString(options.ImageUrl, queryString);
if (_requestAuthorizationUtilities.ComputeHMAC(uri, CommandHandling.Sanitize) is string token && !string.IsNullOrEmpty(token))
{
queryString.Add(RequestAuthorizationUtilities.TokenCommand, token);
}
}

return QueryHelpers.AddQueryString(options.ImageUrl, queryString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options for image resize settings.
/// Typed configuration options for image resize settings.
/// </summary>
public class ImagingResizeSettings
{
internal const int StaticMaxWidth = 5000;
internal const int StaticMaxHeight = 5000;

/// <summary>
/// Gets or sets a value for the maximim resize width.
/// Gets or sets a value for the maximum resize width.
/// </summary>
[DefaultValue(StaticMaxWidth)]
public int MaxWidth { get; set; } = StaticMaxWidth;

/// <summary>
/// Gets or sets a value for the maximim resize height.
/// Gets or sets a value for the maximum resize height.
/// </summary>
[DefaultValue(StaticMaxHeight)]
public int MaxHeight { get; set; } = StaticMaxHeight;
Expand Down
15 changes: 12 additions & 3 deletions src/Umbraco.Core/Configuration/Models/ImagingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@
namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options for imaging settings.
/// Typed configuration options for imaging settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigImaging)]
public class ImagingSettings
{
/// <summary>
/// Gets or sets a value for imaging cache settings.
/// Gets or sets a value for the Hash-based Message Authentication Code (HMAC) secret key for request authentication.
/// </summary>
/// <remarks>
/// Setting or updating this value will cause all existing generated URLs to become invalid and return a 400 Bad Request response code.
/// When set, the maximum resize settings are not used/validated anymore, because you can only request URLs with a valid HMAC token anyway.
/// </remarks>
public byte[] HMACSecretKey { get; set; } = Array.Empty<byte>();

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

/// <summary>
/// Gets or sets a value for imaging resize settings.
/// Gets or sets a value for imaging resize settings.
/// </summary>
public ImagingResizeSettings Resize { get; set; } = new();
}
Loading