Skip to content

Commit 06dde77

Browse files
Merge branch 'main' into bugfix/insert-provider-idempotency
2 parents a102a87 + 72b9368 commit 06dde77

27 files changed

Lines changed: 465 additions & 192 deletions

src/Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
<PackageReference Update="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
3232
<PackageReference Update="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
3333
<PackageReference Update="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
34+
<PackageReference Update="Microsoft.Extensions.FileProviders.Physical" Version="2.2.0"/>
3435
</ItemGroup>
3536
</Project>

src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions,
6767
string cacheRootPath = cacheOptions.CacheRootPath ?? webRootPath;
6868
if (string.IsNullOrEmpty(cacheRootPath))
6969
{
70-
throw new InvalidOperationException("The cache root path can't be determined, make sure it's explicitly configured or the webroot is set.");
70+
throw new InvalidOperationException("The cache root path cannot be determined, make sure it's explicitly configured or the webroot is set.");
7171
}
7272

7373
if (!Path.IsPathFullyQualified(cacheRootPath))

src/ImageSharp.Web/Caching/SHA256CacheHash.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,16 @@ public SHA256CacheHash(IOptions<ImageSharpMiddlewareOptions> options)
3333
public string Create(string value, uint length)
3434
{
3535
int byteCount = Encoding.ASCII.GetByteCount(value);
36-
37-
// Allocating a buffer from the pool is ~27% slower than stackalloc so use that for short strings
38-
if (byteCount < 257)
39-
{
40-
return HashValue(value, length, stackalloc byte[byteCount]);
41-
}
42-
4336
byte[] buffer = null;
37+
4438
try
4539
{
46-
buffer = ArrayPool<byte>.Shared.Rent(byteCount);
47-
return HashValue(value, length, buffer.AsSpan(0, byteCount));
40+
// Allocating a buffer from the pool is ~27% slower than stackalloc so use that for short strings
41+
Span<byte> bytes = byteCount <= 128
42+
? stackalloc byte[byteCount]
43+
: (buffer = ArrayPool<byte>.Shared.Rent(byteCount)).AsSpan(0, byteCount);
44+
45+
return HashValue(value, length, bytes);
4846
}
4947
finally
5048
{

src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public class UriAbsoluteCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CacheKeyHelper.BuildAbsoluteKey(CacheKeyHelper.CaseHandling.None, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.None, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
1717
}
1818
}

src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public class UriAbsoluteLowerInvariantCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CacheKeyHelper.BuildAbsoluteKey(CacheKeyHelper.CaseHandling.LowerInvariant, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.LowerInvariant, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
1717
}
1818
}

src/ImageSharp.Web/Caching/UriRelativeCacheKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public class UriRelativeCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CacheKeyHelper.BuildRelativeKey(CacheKeyHelper.CaseHandling.None, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildRelative(CaseHandlingUriBuilder.CaseHandling.None, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
1717
}
1818
}

src/ImageSharp.Web/Caching/UriRelativeLowerInvariantCacheKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public class UriRelativeLowerInvariantCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CacheKeyHelper.BuildRelativeKey(CacheKeyHelper.CaseHandling.LowerInvariant, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildRelative(CaseHandlingUriBuilder.CaseHandling.LowerInvariant, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
1717
}
1818
}

src/ImageSharp.Web/Caching/CacheKeyHelper.cs renamed to src/ImageSharp.Web/CaseHandlingUriBuilder.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@
66
using System.Runtime.CompilerServices;
77
using Microsoft.AspNetCore.Http;
88

9-
namespace SixLabors.ImageSharp.Web.Caching
9+
namespace SixLabors.ImageSharp.Web
1010
{
1111
/// <summary>
12-
/// Optimized helper methods for generating cache keys from URI components. Much of this code has been adapted from the MIT licensed .NET runtime.
12+
/// Optimized helper methods for generating encoded Uris from URI components.
13+
/// Much of this code has been adapted from the MIT licensed .NET runtime.
1314
/// </summary>
14-
internal static class CacheKeyHelper
15+
public static class CaseHandlingUriBuilder
1516
{
1617
private static readonly SpanAction<char, (bool LowerInvariant, string Host, string PathBase, string Path, string Query)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
1718

19+
/// <summary>
20+
/// Provides Uri case handling options.
21+
/// </summary>
1822
public enum CaseHandling
1923
{
24+
/// <summary>
25+
/// No adjustments to casing are made.
26+
/// </summary>
2027
None,
28+
29+
/// <summary>
30+
/// All URI components are converted to lower case using the invariant culture before combining.
31+
/// </summary>
2132
LowerInvariant
2233
}
2334

@@ -29,14 +40,14 @@ public enum CaseHandling
2940
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
3041
/// <param name="query">The query, if any.</param>
3142
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
32-
public static string BuildRelativeKey(
43+
public static string BuildRelative(
3344
CaseHandling handling,
3445
PathString pathBase = default,
3546
PathString path = default,
3647
QueryString query = default)
3748

3849
// Take any potential performance hit vs concatination for code reading sanity.
39-
=> BuildAbsoluteKey(handling, default, pathBase, path, query);
50+
=> BuildAbsolute(handling, default, pathBase, path, query);
4051

4152
/// <summary>
4253
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
@@ -48,7 +59,7 @@ public static string BuildRelativeKey(
4859
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
4960
/// <param name="query">The query, if any.</param>
5061
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
51-
public static string BuildAbsoluteKey(
62+
public static string BuildAbsolute(
5263
CaseHandling handling,
5364
HostString host,
5465
PathString pathBase = default,
@@ -113,7 +124,7 @@ private static int CopyTextToBufferLowerInvariant(Span<char> buffer, int index,
113124
=> index + text.ToLowerInvariant(buffer.Slice(index, text.Length));
114125

115126
/// <summary>
116-
/// Initializes the URI <see cref="string"/> for <see cref="BuildAbsoluteKey(CaseHandling, HostString, PathString, PathString, QueryString)"/>.
127+
/// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(CaseHandling, HostString, PathString, PathString, QueryString)"/>.
117128
/// </summary>
118129
/// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
119130
/// <param name="uriParts">The URI parts.</param>

src/ImageSharp.Web/Commands/Converters/SimpleCommandConverter{T}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Web.Commands.Converters
1111
/// The generic converter for simple types that implement <see cref="IConvertible"/>.
1212
/// </summary>
1313
/// <typeparam name="T">The type of object to convert to.</typeparam>
14-
internal sealed class SimpleCommandConverter<T> : ICommandConverter<T>
14+
public sealed class SimpleCommandConverter<T> : ICommandConverter<T>
1515
where T : IConvertible
1616
{
1717
/// <inheritdoc/>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
using SixLabors.ImageSharp.Web.Caching;
10+
11+
namespace SixLabors.ImageSharp.Web
12+
{
13+
/// <summary>
14+
/// Provides methods to compute a Hash-based Message Authentication Code (HMAC).
15+
/// </summary>
16+
public static class HMACUtilities
17+
{
18+
/// <summary>
19+
/// The command used by image requests for transporting Hash-based Message Authentication Code (HMAC) tokens.
20+
/// </summary>
21+
public const string TokenCommand = "hmac";
22+
23+
/// <summary>
24+
/// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA256 hash function.
25+
/// </summary>
26+
/// <param name="value">The value to hash</param>
27+
/// <param name="secret">
28+
/// The secret key for <see cref="HMACSHA256"/> encryption.
29+
/// The key can be any length. However, the recommended size is 64 bytes.
30+
/// </param>
31+
/// <returns>The hashed <see cref="string"/>.</returns>
32+
public static unsafe string ComputeHMACSHA256(string value, byte[] secret)
33+
{
34+
// TODO: In .NET 6 we can use single instance versions
35+
using var hmac = new HMACSHA256(secret);
36+
return CreateHMAC(value, hmac);
37+
}
38+
39+
/// <summary>
40+
/// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA384 hash function.
41+
/// </summary>
42+
/// <param name="value">The value to hash</param>
43+
/// <param name="secret">
44+
/// The secret key for <see cref="HMACSHA256"/> encryption.
45+
/// The key can be any length. However, the recommended size is 128 bytes.
46+
/// </param>
47+
/// <returns>The hashed <see cref="string"/>.</returns>
48+
public static unsafe string ComputeHMACSHA384(string value, byte[] secret)
49+
{
50+
using var hmac = new HMACSHA384(secret);
51+
return CreateHMAC(value, hmac);
52+
}
53+
54+
/// <summary>
55+
/// Computes a Hash-based Message Authentication Code (HMAC) by using the SHA512 hash function.
56+
/// </summary>
57+
/// <param name="value">The value to hash</param>
58+
/// <param name="secret">
59+
/// The secret key for <see cref="HMACSHA256"/> encryption.
60+
/// The key can be any length. However, the recommended size is 128 bytes.
61+
/// </param>
62+
/// <returns>The hashed <see cref="string"/>.</returns>
63+
public static unsafe string ComputeHMACSHA512(string value, byte[] secret)
64+
{
65+
using var hmac = new HMACSHA512(secret);
66+
return CreateHMAC(value, hmac);
67+
}
68+
69+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
70+
private static unsafe string CreateHMAC(string value, HMAC hmac)
71+
{
72+
int byteCount = Encoding.ASCII.GetByteCount(value);
73+
byte[] buffer = null;
74+
75+
try
76+
{
77+
// Allocating a buffer from the pool is ~27% slower than stackalloc so use that for short strings
78+
Span<byte> bytes = byteCount <= 128
79+
? stackalloc byte[byteCount]
80+
: (buffer = ArrayPool<byte>.Shared.Rent(byteCount)).AsSpan(0, byteCount);
81+
82+
Encoding.ASCII.GetBytes(value, bytes);
83+
84+
// Safe to always stackalloc here. We max out at 64 bytes.
85+
Span<byte> hash = stackalloc byte[hmac.HashSize / 8];
86+
hmac.TryComputeHash(bytes, hash, out int _);
87+
88+
// Finally encode the hash to make it web safe.
89+
return HexEncoder.Encode(hash);
90+
}
91+
finally
92+
{
93+
if (buffer is not null)
94+
{
95+
ArrayPool<byte>.Shared.Return(buffer);
96+
}
97+
}
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)