2022-04-29 13:16:24 +02:00
using System.Globalization ;
using Microsoft.AspNetCore.WebUtilities ;
2023-05-11 11:01:03 +02:00
using Microsoft.Extensions.DependencyInjection ;
2023-06-09 09:20:07 +02:00
using Microsoft.Extensions.Options ;
2022-04-29 13:16:24 +02:00
using Microsoft.Extensions.Primitives ;
2023-06-09 09:20:07 +02:00
using SixLabors.ImageSharp.Web.Middleware ;
2022-04-29 13:16:24 +02:00
using SixLabors.ImageSharp.Web.Processors ;
2023-05-11 11:01:03 +02:00
using Umbraco.Cms.Core.DependencyInjection ;
2022-04-29 13:16:24 +02:00
using Umbraco.Cms.Core.Media ;
using Umbraco.Cms.Core.Models ;
2022-09-27 14:22:34 +02:00
using Umbraco.Cms.Imaging.ImageSharp.ImageProcessors ;
2022-04-29 13:16:24 +02:00
using static Umbraco . Cms . Core . Models . ImageUrlGenerationOptions ;
2022-09-27 14:22:34 +02:00
namespace Umbraco.Cms.Imaging.ImageSharp.Media ;
2022-05-09 09:39:46 +02:00
/// <summary>
2023-05-11 11:01:03 +02:00
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
2022-05-09 09:39:46 +02:00
/// </summary>
/// <seealso cref="IImageUrlGenerator" />
2022-09-27 14:22:34 +02:00
public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator
2022-04-29 13:16:24 +02:00
{
2023-05-11 11:01:03 +02:00
private readonly RequestAuthorizationUtilities ? _requestAuthorizationUtilities ;
2023-06-09 09:20:07 +02:00
private readonly ImageSharpMiddlewareOptions _options ;
2023-05-11 11:01:03 +02:00
/// <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>
2023-06-09 09:20:07 +02:00
/// <param name="options"></param>
public ImageSharpImageUrlGenerator (
Configuration configuration ,
RequestAuthorizationUtilities ? requestAuthorizationUtilities ,
IOptions < ImageSharpMiddlewareOptions > options )
: this ( configuration . ImageFormats . SelectMany ( f = > f . FileExtensions ) . ToArray ( ) , options , requestAuthorizationUtilities )
{
}
2022-09-27 14:22:34 +02:00
2022-05-09 09:39:46 +02:00
/// <summary>
2023-05-11 11:01:03 +02:00
/// Initializes a new instance of the <see cref="ImageSharpImageUrlGenerator" /> class.
2022-04-29 13:16:24 +02:00
/// </summary>
2022-05-09 09:39:46 +02:00
/// <param name="supportedImageFileTypes">The supported image file types/extensions.</param>
2023-09-06 20:08:17 +02:00
/// <param name="options">The ImageSharp middleware options.</param>
2023-05-11 11:01:03 +02:00
/// <param name="requestAuthorizationUtilities">Contains helpers that allow authorization of image requests.</param>
2022-05-09 09:39:46 +02:00
/// <remarks>
2023-05-11 11:01:03 +02:00
/// This constructor is only used for testing.
2022-05-09 09:39:46 +02:00
/// </remarks>
2023-06-09 09:20:07 +02:00
internal ImageSharpImageUrlGenerator ( IEnumerable < string > supportedImageFileTypes , IOptions < ImageSharpMiddlewareOptions > options , RequestAuthorizationUtilities ? requestAuthorizationUtilities = null )
2023-05-11 11:01:03 +02:00
{
2022-05-09 09:39:46 +02:00
SupportedImageFileTypes = supportedImageFileTypes ;
2023-05-11 11:01:03 +02:00
_requestAuthorizationUtilities = requestAuthorizationUtilities ;
2023-06-09 09:20:07 +02:00
_options = options . Value ;
2023-05-11 11:01:03 +02:00
}
/// <inheritdoc />
public IEnumerable < string > SupportedImageFileTypes { get ; }
2022-05-09 09:39:46 +02:00
/// <inheritdoc />
public string? GetImageUrl ( ImageUrlGenerationOptions ? options )
2022-04-29 13:16:24 +02:00
{
2022-05-09 09:39:46 +02:00
if ( options ? . ImageUrl = = null )
2022-04-29 13:16:24 +02:00
{
2022-05-09 09:39:46 +02:00
return null ;
2022-04-29 13:16:24 +02:00
}
2022-05-09 09:39:46 +02:00
var queryString = new Dictionary < string , string? > ( ) ;
2022-11-10 13:56:29 +01:00
Dictionary < string , StringValues > furtherOptions = QueryHelpers . ParseQuery ( options . FurtherOptions ) ;
2022-05-09 09:39:46 +02:00
2023-05-11 11:01:03 +02:00
if ( options . Crop is CropCoordinates crop )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( CropWebProcessor . Coordinates , FormattableString . Invariant ( $"{crop.Left},{crop.Top},{crop.Right},{crop.Bottom}" ) ) ;
2022-05-09 09:39:46 +02:00
}
2023-05-11 11:01:03 +02:00
if ( options . FocalPoint is FocalPointPosition focalPoint )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( ResizeWebProcessor . Xy , FormattableString . Invariant ( $"{focalPoint.Left},{focalPoint.Top}" ) ) ;
2022-05-09 09:39:46 +02:00
}
2023-05-11 11:01:03 +02:00
if ( options . ImageCropMode is ImageCropMode imageCropMode )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( ResizeWebProcessor . Mode , imageCropMode . ToString ( ) . ToLowerInvariant ( ) ) ;
2022-05-09 09:39:46 +02:00
}
2023-05-11 11:01:03 +02:00
if ( options . ImageCropAnchor is ImageCropAnchor imageCropAnchor )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( ResizeWebProcessor . Anchor , imageCropAnchor . ToString ( ) . ToLowerInvariant ( ) ) ;
2022-05-09 09:39:46 +02:00
}
2023-05-11 11:01:03 +02:00
if ( options . Width is int width )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( ResizeWebProcessor . Width , width . ToString ( CultureInfo . InvariantCulture ) ) ;
2022-05-09 09:39:46 +02:00
}
2023-05-11 11:01:03 +02:00
if ( options . Height is int height )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( ResizeWebProcessor . Height , height . ToString ( CultureInfo . InvariantCulture ) ) ;
2022-05-09 09:39:46 +02:00
}
2022-11-10 13:56:29 +01:00
if ( furtherOptions . Remove ( FormatWebProcessor . Format , out StringValues format ) )
{
2023-05-11 11:01:03 +02:00
queryString . Add ( FormatWebProcessor . Format , format . ToString ( ) ) ;
2022-11-10 13:56:29 +01:00
}
2023-05-11 11:01:03 +02:00
if ( options . Quality is int quality )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
queryString . Add ( QualityWebProcessor . Quality , quality . ToString ( CultureInfo . InvariantCulture ) ) ;
2022-05-09 09:39:46 +02:00
}
2022-11-10 13:56:29 +01:00
foreach ( KeyValuePair < string , StringValues > kvp in furtherOptions )
2022-05-09 09:39:46 +02:00
{
queryString . Add ( kvp . Key , kvp . Value ) ;
}
2023-05-11 11:01:03 +02:00
if ( options . CacheBusterValue is string cacheBusterValue & & ! string . IsNullOrEmpty ( cacheBusterValue ) )
{
queryString . Add ( "v" , cacheBusterValue ) ;
}
2023-06-09 09:20:07 +02:00
// If no secret is we'll completely skip this whole thing, in theory the ComputeHMACAsync should just return null imidiately, but still no reason to create
if ( _options . HMACSecretKey . Length ! = 0 & & _requestAuthorizationUtilities is not null )
2022-05-09 09:39:46 +02:00
{
2023-05-11 11:01:03 +02:00
var uri = QueryHelpers . AddQueryString ( options . ImageUrl , queryString ) ;
2023-06-09 09:20:07 +02:00
2023-12-06 08:19:46 +01:00
var token = _requestAuthorizationUtilities . ComputeHMAC ( uri , CommandHandling . Sanitize ) ;
2023-06-09 09:20:07 +02:00
if ( string . IsNullOrEmpty ( token ) is false )
2023-05-11 11:01:03 +02:00
{
queryString . Add ( RequestAuthorizationUtilities . TokenCommand , token ) ;
}
2022-05-09 09:39:46 +02:00
}
return QueryHelpers . AddQueryString ( options . ImageUrl , queryString ) ;
2022-04-29 13:16:24 +02:00
}
}