Skip to content
Open
Changes from all 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
91 changes: 50 additions & 41 deletions src/Http/Http.Extensions/src/UriHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;

namespace Microsoft.AspNetCore.Http.Extensions;
Expand All @@ -17,7 +15,6 @@ public static class UriHelper
private const char Hash = '#';
private const char QuestionMark = '?';
private static readonly string SchemeDelimiter = Uri.SchemeDelimiter;
private static readonly SpanAction<char, (string scheme, string host, string pathBase, string path, string query, string fragment)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);

/// <summary>
/// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
Expand Down Expand Up @@ -90,7 +87,41 @@ public static string BuildAbsolute(
length--;
}

return string.Create(length, (scheme, hostText, pathBaseText, pathText, queryText, fragmentText), InitializeAbsoluteUriStringSpanAction);
return string.Create(
length,
new UriComponents(scheme, hostText, pathBaseText, pathText, queryText, fragmentText),
static (buffer, uriComponents) =>
{
uriComponents.Scheme.AsSpan().CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Scheme.Length);

Uri.SchemeDelimiter.CopyTo(buffer);
buffer = buffer.Slice(Uri.SchemeDelimiter.Length);

uriComponents.Host.AsSpan().CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Host.Length);

var pathBaseSpan = uriComponents.PathBase.AsSpan();

if (uriComponents.Path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
{
// If the path string has a trailing slash and the other string has a leading slash, we need
// to trim one of them.
// Trim the last slash from pathBase. The total length was decremented before the call to string.Create.
pathBaseSpan = pathBaseSpan[..^1];
}

pathBaseSpan.CopyTo(buffer);
buffer = buffer.Slice(pathBaseSpan.Length);

uriComponents.Path.CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Path.Length);

uriComponents.Query.CopyTo(buffer);
buffer = buffer.Slice(uriComponents.Query.Length);

uriComponents.Fragment.CopyTo(buffer);
});
}

/// <summary>
Expand Down Expand Up @@ -171,7 +202,7 @@ public static string Encode(Uri uri)
}
else
{
return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
return uri.GetComponents(System.UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
}

Expand Down Expand Up @@ -224,45 +255,23 @@ public static string GetDisplayUrl(this HttpRequest request)
.ToString();
}

/// <summary>
/// Copies the specified <paramref name="text"/> to the specified <paramref name="buffer"/> starting at the specified <paramref name="index"/>.
/// </summary>
/// <param name="buffer">The buffer to copy text to.</param>
/// <param name="index">The buffer start index.</param>
/// <param name="text">The text to copy.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CopyTextToBuffer(Span<char> buffer, int index, ReadOnlySpan<char> text)
{
text.CopyTo(buffer.Slice(index, text.Length));
return index + text.Length;
}

/// <summary>
/// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(string, HostString, PathString, PathString, QueryString, FragmentString)"/>.
/// </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, (string scheme, string host, string pathBase, string path, string query, string fragment) uriParts)
private readonly struct UriComponents
{
var index = 0;

var pathBaseSpan = uriParts.pathBase.AsSpan();
public readonly string Scheme;
public readonly string Host;
public readonly string PathBase;
public readonly string Path;
public readonly string Query;
public readonly string Fragment;

if (uriParts.path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
public UriComponents(string scheme, string host, string pathBase, string path, string query, string fragment)
{
// If the path string has a trailing slash and the other string has a leading slash, we need
// to trim one of them.
// Trim the last slahs from pathBase. The total length was decremented before the call to string.Create.
pathBaseSpan = pathBaseSpan[..^1];
Scheme = scheme;
Host = host;
PathBase = pathBase;
Path = path;
Query = query;
Fragment = fragment;
}

index = CopyTextToBuffer(buffer, index, uriParts.scheme.AsSpan());
index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
index = CopyTextToBuffer(buffer, index, uriParts.host.AsSpan());
index = CopyTextToBuffer(buffer, index, pathBaseSpan);
index = CopyTextToBuffer(buffer, index, uriParts.path.AsSpan());
index = CopyTextToBuffer(buffer, index, uriParts.query.AsSpan());
_ = CopyTextToBuffer(buffer, index, uriParts.fragment.AsSpan());
}
}
Loading