Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
1 change: 1 addition & 0 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Update="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Update="Microsoft.AspNetCore.Http" Version="2.2.0"/>
<PackageReference Update="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Update="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Update="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
Expand Down
51 changes: 51 additions & 0 deletions src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;
Expand Down Expand Up @@ -119,5 +122,53 @@ private static async Task<PutBucketResponse> CreateIfNotExistsAsync(

return null;
}

/// <summary>
/// <see href="https://github.com/aspnet/AspNetIdentity/blob/b7826741279450c58b230ece98bd04b4815beabf/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs"/>
/// </summary>
internal static class AsyncHelper
Comment thread
JimBobSquarePants marked this conversation as resolved.
Outdated
{
private static readonly TaskFactory TaskFactory
= new(
CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);

/// <summary>
/// Executes an async <see cref="Task"/> method synchronously.
/// </summary>
/// <param name="task">The task to excecute.</param>
public static void RunSync(Func<Task> task)
{
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
CultureInfo culture = CultureInfo.CurrentCulture;
TaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return task();
}).Unwrap().GetAwaiter().GetResult();
}

/// <summary>
/// Executes an async <see cref="Task{TResult}"/> method which has
/// a <paramref name="task"/> return type synchronously.
/// </summary>
/// <typeparam name="TResult">The type of result to return.</typeparam>
/// <param name="task">The task to excecute.</param>
/// <returns>The <typeparamref name="TResult"/>.</returns>
public static TResult RunSync<TResult>(Func<Task<TResult>> task)
{
CultureInfo cultureUi = CultureInfo.CurrentUICulture;
CultureInfo culture = CultureInfo.CurrentCulture;
return TaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return task();
}).Unwrap().GetAwaiter().GetResult();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Web
/// </summary>
internal static class AsyncHelper
{
private static readonly TaskFactory TaskFactory = new
(CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);
private static readonly TaskFactory TaskFactory
= new(
CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);

/// <summary>
/// Executes an async <see cref="Task"/> method synchronously.
Expand Down
7 changes: 6 additions & 1 deletion src/ImageSharp.Web/Caching/UriAbsoluteCacheKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class UriAbsoluteCacheKey : ICacheKey
{
/// <inheritdoc/>
public string Create(HttpContext context, CommandCollection commands)
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.None, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
=> CaseHandlingUriBuilder.BuildAbsolute(
CaseHandlingUriBuilder.CaseHandling.None,
context.Request.Host,
context.Request.PathBase,
context.Request.Path,
QueryString.Create(commands));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class UriAbsoluteLowerInvariantCacheKey : ICacheKey
{
/// <inheritdoc/>
public string Create(HttpContext context, CommandCollection commands)
=> CaseHandlingUriBuilder.BuildAbsolute(CaseHandlingUriBuilder.CaseHandling.LowerInvariant, context.Request.Host, context.Request.PathBase, context.Request.Path, QueryString.Create(commands));
=> CaseHandlingUriBuilder.BuildAbsolute(
CaseHandlingUriBuilder.CaseHandling.LowerInvariant,
context.Request.Host,
context.Request.PathBase,
context.Request.Path,
QueryString.Create(commands));
}
}
53 changes: 43 additions & 10 deletions src/ImageSharp.Web/CaseHandlingUriBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace SixLabors.ImageSharp.Web
/// </summary>
public static class CaseHandlingUriBuilder
{
private static readonly SpanAction<char, (bool LowerInvariant, string Host, string PathBase, string Path, string Query)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
private static readonly Uri FallbackBaseUri = new("http://localhost/");
private static readonly SpanAction<char, (bool LowerInvariant, string Scheme, string Host, string PathBase, string Path, string Query)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);

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

/// <summary>
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
/// Note that unicode in the HostString will be encoded as punycode and the scheme is not included
/// in the result.
/// </summary>
/// <param name="handling">Determines case handling for the result. <paramref name="query"/> is always converted to invariant lowercase.</param>
/// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
/// <param name="pathBase">The first portion of the request path associated with application root.</param>
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
/// <param name="query">The query, if any.</param>
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
public static string BuildAbsolute(
CaseHandling handling,
HostString host,
PathString pathBase = default,
PathString path = default,
QueryString query = default)
=> BuildAbsolute(handling, string.Empty, host, pathBase, path, query);

/// <summary>
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
/// Note that unicode in the HostString will be encoded as punycode.
/// </summary>
/// <param name="handling">Determines case handling for the result. <paramref name="query"/> is always converted to invariant lowercase.</param>
/// <param name="scheme">http, https, etc.</param>
/// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
/// <param name="pathBase">The first portion of the request path associated with application root.</param>
/// <param name="path">The portion of the request path that identifies the requested resource.</param>
/// <param name="query">The query, if any.</param>
/// <returns>The combined URI components, properly encoded for use in HTTP headers.</returns>
public static string BuildAbsolute(
CaseHandling handling,
string scheme,
HostString host,
PathString pathBase = default,
PathString path = default,
QueryString query = default)
{
Guard.NotNull(scheme, nameof(scheme));

string hostText = host.ToUriComponent();
string pathBaseText = pathBase.ToUriComponent();
string pathText = path.ToUriComponent();
string queryText = query.ToUriComponent();

// PERF: Calculate string length to allocate correct buffer size for string.Create.
int length =
(scheme.Length > 0 ? scheme.Length + Uri.SchemeDelimiter.Length : 0) +
hostText.Length +
pathBaseText.Length +
pathText.Length +
Expand All @@ -94,7 +119,10 @@ public static string BuildAbsolute(
length--;
}

return string.Create(length, (handling == CaseHandling.LowerInvariant, hostText, pathBaseText, pathText, queryText), InitializeAbsoluteUriStringSpanAction);
return string.Create(
length,
(handling == CaseHandling.LowerInvariant, scheme, hostText, pathBaseText, pathText, queryText),
InitializeAbsoluteUriStringSpanAction);
}

/// <summary>
Expand All @@ -121,19 +149,18 @@ public static string Encode(CaseHandling handling, Uri uri)
{
return BuildAbsolute(
handling,
scheme: uri.Scheme,
host: HostString.FromUriComponent(uri),
pathBase: PathString.FromUriComponent(uri),
query: QueryString.FromUriComponent(uri));
}
else
{
string components = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
if (handling == CaseHandling.LowerInvariant)
{
return components.ToLowerInvariant();
}

return components;
Uri faux = new(FallbackBaseUri, uri);
return BuildRelative(
handling,
pathBase: PathString.FromUriComponent(faux),
Comment thread
JimBobSquarePants marked this conversation as resolved.
Outdated
query: QueryString.FromUriComponent(faux));
}
}

Expand Down Expand Up @@ -168,7 +195,7 @@ private static int CopyTextToBufferLowerInvariant(Span<char> buffer, int index,
/// </summary>
/// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
/// <param name="uriParts">The URI parts.</param>
private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower, string Host, string PathBase, string Path, string Query) uriParts)
private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower, string Scheme, string Host, string PathBase, string Path, string Query) uriParts)
{
int index = 0;
ReadOnlySpan<char> pathBaseSpan = uriParts.PathBase.AsSpan();
Expand All @@ -181,6 +208,12 @@ private static void InitializeAbsoluteUriString(Span<char> buffer, (bool Lower,
pathBaseSpan = pathBaseSpan.Slice(0, pathBaseSpan.Length - 1);
}

if (uriParts.Scheme.Length > 0)
{
index = CopyTextToBuffer(buffer, index, uriParts.Scheme.AsSpan());
Comment thread
JimBobSquarePants marked this conversation as resolved.
Outdated
index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
}

if (uriParts.Lower)
{
index = CopyTextToBufferLowerInvariant(buffer, index, uriParts.Host.AsSpan());
Expand Down
24 changes: 24 additions & 0 deletions src/ImageSharp.Web/CommandHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.Web.Commands;

namespace SixLabors.ImageSharp.Web
{
/// <summary>
/// Provides enumeration for handling <see cref="CommandCollection"/> instances
/// when processing a request.
/// </summary>
public enum CommandHandling
{
/// <summary>
/// The command collection will be stripped of any unknown commands.
/// </summary>
Sanitize,

/// <summary>
/// The command collection will be processed unaltered.
/// </summary>
None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ public PresetOnlyQueryCollectionRequestParser(IOptions<PresetOnlyQueryCollection
/// <inheritdoc/>
public CommandCollection ParseRequestCommands(HttpContext context)
{
if (context.Request.Query.Count == 0 || !context.Request.Query.ContainsKey(QueryKey))
IQueryCollection queryCollection = context.Request.Query;
if (queryCollection is null
|| queryCollection.Count == 0
|| !queryCollection.ContainsKey(QueryKey))
{
// We return new here and below to ensure the collection is still mutable via events.
return new();
}

StringValues query = context.Request.Query[QueryKey];
StringValues query = queryCollection[QueryKey];
string requestedPreset = query[query.Count - 1];
if (this.presets.TryGetValue(requestedPreset, out CommandCollection collection))
{
Expand Down
9 changes: 3 additions & 6 deletions src/ImageSharp.Web/Commands/QueryCollectionRequestParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;

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

// TODO: Investigate skipping the double allocation here.
// In .NET 6 we can directly use the QueryStringEnumerable type and enumerate stright to our command collection
Dictionary<string, StringValues> parsed = QueryHelpers.ParseQuery(context.Request.QueryString.ToUriComponent());
CommandCollection transformed = new();
foreach (KeyValuePair<string, StringValues> pair in parsed)
foreach (KeyValuePair<string, StringValues> pair in context.Request.Query)
Comment thread
JimBobSquarePants marked this conversation as resolved.
Outdated
{
// Use the indexer for both set and query. This replaces any previously parsed values.
transformed[pair.Key] = pair.Value[pair.Value.Count - 1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ private static void AddDefaultServices(

builder.SetRequestParser<QueryCollectionRequestParser>();

builder.Services.AddSingleton<ImageSharpRequestAuthorizationUtilities>();

builder.SetCache<PhysicalFileSystemCache>();

builder.SetCacheKey<UriRelativeLowerInvariantCacheKey>();
Expand Down
5 changes: 1 addition & 4 deletions src/ImageSharp.Web/FormatUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ public FormatUtilities(IOptions<ImageSharpMiddlewareOptions> options)
{
string[] extensions = imageFormat.FileExtensions.ToArray();

foreach (string extension in extensions)
{
this.extensions.Add(extension);
}
this.extensions.AddRange(extensions);

this.extensionsByMimeType[imageFormat.DefaultMimeType] = extensions[0];
}
Expand Down
1 change: 1 addition & 0 deletions src/ImageSharp.Web/ImageSharp.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<PackageReference Include="Microsoft.AspNetCore.Http" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" />
Expand Down
Loading