Skip to content

Commit 108a615

Browse files
Merge pull request #281 from SixLabors/js/auth-utils
Add Authorization Utilitites
2 parents 0a62c3b + eadeef6 commit 108a615

18 files changed

+516
-102
lines changed

src/Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
2828
<PackageReference Update="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
29+
<PackageReference Update="Microsoft.AspNetCore.Http" Version="2.2.0"/>
2930
<PackageReference Update="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
3031
<PackageReference Update="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
3132
<PackageReference Update="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Globalization;
57
using System.IO;
8+
using System.Threading;
69
using System.Threading.Tasks;
710
using Amazon.S3;
811
using Amazon.S3.Model;
@@ -119,5 +122,53 @@ private static async Task<PutBucketResponse> CreateIfNotExistsAsync(
119122

120123
return null;
121124
}
125+
126+
/// <summary>
127+
/// <see href="https://github.com/aspnet/AspNetIdentity/blob/b7826741279450c58b230ece98bd04b4815beabf/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs"/>
128+
/// </summary>
129+
private static class AsyncHelper
130+
{
131+
private static readonly TaskFactory TaskFactory
132+
= new(
133+
CancellationToken.None,
134+
TaskCreationOptions.None,
135+
TaskContinuationOptions.None,
136+
TaskScheduler.Default);
137+
138+
/// <summary>
139+
/// Executes an async <see cref="Task"/> method synchronously.
140+
/// </summary>
141+
/// <param name="task">The task to excecute.</param>
142+
public static void RunSync(Func<Task> task)
143+
{
144+
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
145+
CultureInfo culture = CultureInfo.CurrentCulture;
146+
TaskFactory.StartNew(() =>
147+
{
148+
Thread.CurrentThread.CurrentCulture = culture;
149+
Thread.CurrentThread.CurrentUICulture = cultureUi;
150+
return task();
151+
}).Unwrap().GetAwaiter().GetResult();
152+
}
153+
154+
/// <summary>
155+
/// Executes an async <see cref="Task{TResult}"/> method which has
156+
/// a <paramref name="task"/> return type synchronously.
157+
/// </summary>
158+
/// <typeparam name="TResult">The type of result to return.</typeparam>
159+
/// <param name="task">The task to excecute.</param>
160+
/// <returns>The <typeparamref name="TResult"/>.</returns>
161+
public static TResult RunSync<TResult>(Func<Task<TResult>> task)
162+
{
163+
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
164+
CultureInfo culture = CultureInfo.CurrentCulture;
165+
return TaskFactory.StartNew(() =>
166+
{
167+
Thread.CurrentThread.CurrentCulture = culture;
168+
Thread.CurrentThread.CurrentUICulture = cultureUi;
169+
return task();
170+
}).Unwrap().GetAwaiter().GetResult();
171+
}
172+
}
122173
}
123174
}

src/ImageSharp.Web.Providers.AWS/AsyncHelper.cs renamed to src/ImageSharp.Web/AsyncHelper.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Web
1313
/// </summary>
1414
internal static class AsyncHelper
1515
{
16-
private static readonly TaskFactory TaskFactory = new
17-
(CancellationToken.None,
18-
TaskCreationOptions.None,
19-
TaskContinuationOptions.None,
20-
TaskScheduler.Default);
16+
private static readonly TaskFactory TaskFactory
17+
= new(
18+
CancellationToken.None,
19+
TaskCreationOptions.None,
20+
TaskContinuationOptions.None,
21+
TaskScheduler.Default);
2122

2223
/// <summary>
2324
/// Executes an async <see cref="Task"/> method synchronously.

src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public class UriAbsoluteCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.None, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildAbsolute(
17+
CaseHandlingUriBuilder.CaseHandling.None,
18+
context.Request.Host,
19+
context.Request.PathBase,
20+
context.Request.Path,
21+
QueryString.Create(commands));
1722
}
1823
}

src/ImageSharp.Web/Caching/UriAbsoluteLowerInvariantCacheKey.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public class UriAbsoluteLowerInvariantCacheKey : ICacheKey
1313
{
1414
/// <inheritdoc/>
1515
public string Create(HttpContext context, CommandCollection commands)
16-
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.LowerInvariant, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
16+
=> CaseHandlingUriBuilder.BuildAbsolute(
17+
CaseHandlingUriBuilder.CaseHandling.LowerInvariant,
18+
context.Request.Host,
19+
context.Request.PathBase,
20+
context.Request.Path,
21+
QueryString.Create(commands));
1722
}
1823
}

src/ImageSharp.Web/CaseHandlingUriBuilder.cs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ namespace SixLabors.ImageSharp.Web
1414
/// </summary>
1515
public static class CaseHandlingUriBuilder
1616
{
17-
private static readonly SpanAction<char, (bool LowerInvariant, string Host, string PathBase, string Path, string Query)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
17+
private static readonly Uri FallbackBaseUri = new("http://localhost/");
18+
private static readonly SpanAction<char, (bool LowerInvariant, string Scheme, string Host, string PathBase, string Path, string Query)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
1819

1920
/// <summary>
2021
/// Provides Uri case handling options.
@@ -49,30 +50,54 @@ public static string BuildRelative(
4950
// Take any potential performance hit vs concatination for code reading sanity.
5051
=> BuildAbsolute(handling, default, pathBase, path, query);
5152

53+
/// <summary>
54+
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
55+
/// Note that unicode in the HostString will be encoded as punycode and the scheme is not included
56+
/// in the result.
57+
/// </summary>
58+
/// <param name="handling">Determines case handling for the result. <paramref name="query"/> is always converted to invariant lowercase.</param>
59+
/// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
60+
/// <param name="pathBase">The first portion of the request path associated with application root.</param>
61+
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
62+
/// <param name="query">The query, if any.</param>
63+
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
64+
public static string BuildAbsolute(
65+
CaseHandling handling,
66+
HostString host,
67+
PathString pathBase = default,
68+
PathString path = default,
69+
QueryString query = default)
70+
=> BuildAbsolute(handling, string.Empty, host, pathBase, path, query);
71+
5272
/// <summary>
5373
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
5474
/// Note that unicode in the HostString will be encoded as punycode.
5575
/// </summary>
5676
/// <param name="handling">Determines case handling for the result. <paramref name="query"/> is always converted to invariant lowercase.</param>
77+
/// <param name="scheme">http, https, etc.</param>
5778
/// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
5879
/// <param name="pathBase">The first portion of the request path associated with application root.</param>
5980
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
6081
/// <param name="query">The query, if any.</param>
6182
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
6283
public static string BuildAbsolute(
6384
CaseHandling handling,
85+
string scheme,
6486
HostString host,
6587
PathString pathBase = default,
6688
PathString path = default,
6789
QueryString query = default)
6890
{
91+
Guard.NotNull(scheme, nameof(scheme));
92+
6993
string hostText = host.ToUriComponent();
7094
string pathBaseText = pathBase.ToUriComponent();
7195
string pathText = path.ToUriComponent();
7296
string queryText = query.ToUriComponent();
7397

7498
// PERF: Calculate string length to allocate correct buffer size for string.Create.
7599
int length =
100+
(scheme.Length > 0 ? scheme.Length + Uri.SchemeDelimiter.Length : 0) +
76101
hostText.Length +
77102
pathBaseText.Length +
78103
pathText.Length +
@@ -94,7 +119,10 @@ public static string BuildAbsolute(
94119
length--;
95120
}
96121

97-
return string.Create(length, (handling == CaseHandling.LowerInvariant, hostText, pathBaseText, pathText, queryText), InitializeAbsoluteUriStringSpanAction);
122+
return string.Create(
123+
length,
124+
(handling == CaseHandling.LowerInvariant, scheme, hostText, pathBaseText, pathText, queryText),
125+
InitializeAbsoluteUriStringSpanAction);
98126
}
99127

100128
/// <summary>
@@ -105,7 +133,10 @@ public static string BuildAbsolute(
105133
/// <param name="uri">The Uri to encode.</param>
106134
/// <returns>The encoded string version of <paramref name="uri"/>.</returns>
107135
public static string Encode(CaseHandling handling, string uri)
108-
=> Encode(handling, new Uri(uri, UriKind.RelativeOrAbsolute));
136+
{
137+
Guard.NotNull(uri, nameof(uri));
138+
return Encode(handling, new Uri(uri, UriKind.RelativeOrAbsolute));
139+
}
109140

110141
/// <summary>
111142
/// Generates a string from the given absolute or relative Uri that is appropriately encoded for use in
@@ -121,19 +152,18 @@ public static string Encode(CaseHandling handling, Uri uri)
121152
{
122153
return BuildAbsolute(
123154
handling,
155+
scheme: uri.Scheme,
124156
host: HostString.FromUriComponent(uri),
125157
pathBase: PathString.FromUriComponent(uri),
126158
query: QueryString.FromUriComponent(uri));
127159
}
128160
else
129161
{
130-
string components = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
131-
if (handling == CaseHandling.LowerInvariant)
132-
{
133-
return components.ToLowerInvariant();
134-
}
135-
136-
return components;
162+
Uri faux = new(FallbackBaseUri, uri);
163+
return BuildRelative(
164+
handling,
165+
path: PathString.FromUriComponent(faux),
166+
query: QueryString.FromUriComponent(faux));
137167
}
138168
}
139169

@@ -168,7 +198,7 @@ private static int CopyTextToBufferLowerInvariant(Span<char> buffer, int index,
168198
/// </summary>
169199
/// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
170200
/// <param name="uriParts">The URI parts.</param>
171-
private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower, string Host, string PathBase, string Path, string Query) uriParts)
201+
private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower, string Scheme, string Host, string PathBase, string Path, string Query) uriParts)
172202
{
173203
int index = 0;
174204
ReadOnlySpan<char> pathBaseSpan = uriParts.PathBase.AsSpan();
@@ -181,6 +211,12 @@ private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower,
181211
pathBaseSpan = pathBaseSpan.Slice(0, pathBaseSpan.Length - 1);
182212
}
183213

214+
if (uriParts.Scheme.Length > 0)
215+
{
216+
index = CopyTextToBufferLowerInvariant(buffer, index, uriParts.Scheme.AsSpan());
217+
index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
218+
}
219+
184220
if (uriParts.Lower)
185221
{
186222
index = CopyTextToBufferLowerInvariant(buffer, index, uriParts.Host.AsSpan());
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using SixLabors.ImageSharp.Web.Commands;
5+
6+
namespace SixLabors.ImageSharp.Web
7+
{
8+
/// <summary>
9+
/// Provides enumeration for handling <see cref="CommandCollection"/> instances
10+
/// when processing a request.
11+
/// </summary>
12+
public enum CommandHandling
13+
{
14+
/// <summary>
15+
/// The command collection will be stripped of any unknown commands.
16+
/// </summary>
17+
Sanitize,
18+
19+
/// <summary>
20+
/// The command collection will be processed unaltered.
21+
/// </summary>
22+
None
23+
}
24+
}

src/ImageSharp.Web/Commands/PresetOnlyQueryCollectionRequestParser.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ public PresetOnlyQueryCollectionRequestParser(IOptions<PresetOnlyQueryCollection
3333
/// <inheritdoc/>
3434
public CommandCollection ParseRequestCommands(HttpContext context)
3535
{
36-
if (context.Request.Query.Count == 0 || !context.Request.Query.ContainsKey(QueryKey))
36+
IQueryCollection queryCollection = context.Request.Query;
37+
if (queryCollection is null
38+
|| queryCollection.Count == 0
39+
|| !queryCollection.ContainsKey(QueryKey))
3740
{
3841
// We return new here and below to ensure the collection is still mutable via events.
3942
return new();
4043
}
4144

42-
StringValues query = context.Request.Query[QueryKey];
45+
StringValues query = queryCollection[QueryKey];
4346
string requestedPreset = query[query.Count - 1];
4447
if (this.presets.TryGetValue(requestedPreset, out CommandCollection collection))
4548
{

src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections.Generic;
55
using Microsoft.AspNetCore.Http;
6-
using Microsoft.AspNetCore.WebUtilities;
76
using Microsoft.Extensions.Primitives;
87

98
namespace SixLabors.ImageSharp.Web.Commands
@@ -16,17 +15,15 @@ public sealed class QueryCollectionRequestParser : IRequestParser
1615
/// <inheritdoc/>
1716
public CommandCollection ParseRequestCommands(HttpContext context)
1817
{
19-
if (context.Request.Query.Count == 0)
18+
IQueryCollection query = context.Request.Query;
19+
if (query is null || query.Count == 0)
2020
{
2121
// We return new to ensure the collection is still mutable via events.
2222
return new();
2323
}
2424

25-
// TODO: Investigate skipping the double allocation here.
26-
// In .NET 6 we can directly use the QueryStringEnumerable type and enumerate stright to our command collection
27-
Dictionary<string, StringValues> parsed = QueryHelpers.ParseQuery(context.Request.QueryString.ToUriComponent());
2825
CommandCollection transformed = new();
29-
foreach (KeyValuePair<string, StringValues> pair in parsed)
26+
foreach (KeyValuePair<string, StringValues> pair in query)
3027
{
3128
// Use the indexer for both set and query. This replaces any previously parsed values.
3229
transformed[pair.Key] = pair.Value[pair.Value.Count - 1];

src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ private static void AddDefaultServices(
6060

6161
builder.SetRequestParser<QueryCollectionRequestParser>();
6262

63+
builder.Services.AddSingleton<ImageSharpRequestAuthorizationUtilities>();
64+
6365
builder.SetCache<PhysicalFileSystemCache>();
6466

6567
builder.SetCacheKey<UriRelativeLowerInvariantCacheKey>();

0 commit comments

Comments
 (0)