using System.Globalization;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using SixLabors.ImageSharp.Web.Middleware;
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;
using static Umbraco.Cms.Core.Models.ImageUrlGenerationOptions;
namespace Umbraco.Cms.Imaging.ImageSharp.Media;
///
/// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp.
///
///
public sealed class ImageSharpImageUrlGenerator : IImageUrlGenerator
{
private readonly RequestAuthorizationUtilities? _requestAuthorizationUtilities;
private readonly ImageSharpMiddlewareOptions _options;
///
/// Initializes a new instance of the class.
///
/// The ImageSharp configuration.
/// Contains helpers that allow authorization of image requests.
///
public ImageSharpImageUrlGenerator(
Configuration configuration,
RequestAuthorizationUtilities? requestAuthorizationUtilities,
IOptions options)
: this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(), options, requestAuthorizationUtilities)
{
}
///
/// Initializes a new instance of the class.
///
/// The supported image file types/extensions.
/// The ImageSharp middleware options.
/// Contains helpers that allow authorization of image requests.
///
/// This constructor is only used for testing.
///
internal ImageSharpImageUrlGenerator(IEnumerable supportedImageFileTypes, IOptions options, RequestAuthorizationUtilities? requestAuthorizationUtilities = null)
{
SupportedImageFileTypes = supportedImageFileTypes;
_requestAuthorizationUtilities = requestAuthorizationUtilities;
_options = options.Value;
}
///
public IEnumerable SupportedImageFileTypes { get; }
///
public string? GetImageUrl(ImageUrlGenerationOptions? options)
{
if (options?.ImageUrl == null)
{
return null;
}
var queryString = new Dictionary();
Dictionary furtherOptions = QueryHelpers.ParseQuery(options.FurtherOptions);
if (options.Crop is CropCoordinates crop)
{
queryString.Add(CropWebProcessor.Coordinates, FormattableString.Invariant($"{crop.Left},{crop.Top},{crop.Right},{crop.Bottom}"));
}
if (options.FocalPoint is FocalPointPosition focalPoint)
{
queryString.Add(ResizeWebProcessor.Xy, FormattableString.Invariant($"{focalPoint.Left},{focalPoint.Top}"));
}
if (options.ImageCropMode is ImageCropMode imageCropMode)
{
queryString.Add(ResizeWebProcessor.Mode, imageCropMode.ToString().ToLowerInvariant());
}
if (options.ImageCropAnchor is ImageCropAnchor imageCropAnchor)
{
queryString.Add(ResizeWebProcessor.Anchor, imageCropAnchor.ToString().ToLowerInvariant());
}
if (options.Width is int width)
{
queryString.Add(ResizeWebProcessor.Width, width.ToString(CultureInfo.InvariantCulture));
}
if (options.Height is int height)
{
queryString.Add(ResizeWebProcessor.Height, height.ToString(CultureInfo.InvariantCulture));
}
if (furtherOptions.Remove(FormatWebProcessor.Format, out StringValues format))
{
queryString.Add(FormatWebProcessor.Format, format.ToString());
}
if (options.Quality is int quality)
{
queryString.Add(QualityWebProcessor.Quality, quality.ToString(CultureInfo.InvariantCulture));
}
foreach (KeyValuePair kvp in furtherOptions)
{
queryString.Add(kvp.Key, kvp.Value);
}
if (options.CacheBusterValue is string cacheBusterValue && !string.IsNullOrEmpty(cacheBusterValue))
{
queryString.Add("v", cacheBusterValue);
}
// 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)
{
var uri = QueryHelpers.AddQueryString(options.ImageUrl, queryString);
var token = _requestAuthorizationUtilities.ComputeHMAC(uri, CommandHandling.Sanitize);
if (string.IsNullOrEmpty(token) is false)
{
queryString.Add(RequestAuthorizationUtilities.TokenCommand, token);
}
}
return QueryHelpers.AddQueryString(options.ImageUrl, queryString);
}
}