|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#if QueryStringEnumerable_In_WebUtilities
namespace Microsoft.AspNetCore.WebUtilities;
#else
namespace Microsoft.AspNetCore.Internal;
#endif
/// <summary>
/// An enumerable that can supply the name/value pairs from a URI query string.
/// </summary>
#if QueryStringEnumerable_In_WebUtilities
public
#else
internal
#endif
readonly struct QueryStringEnumerable
{
private readonly ReadOnlyMemory<char> _queryString;
/// <summary>
/// Constructs an instance of <see cref="QueryStringEnumerable"/>.
/// </summary>
/// <param name="queryString">The query string.</param>
public QueryStringEnumerable(string? queryString)
: this(queryString.AsMemory())
{
}
/// <summary>
/// Constructs an instance of <see cref="QueryStringEnumerable"/>.
/// </summary>
/// <param name="queryString">The query string.</param>
public QueryStringEnumerable(ReadOnlyMemory<char> queryString)
{
_queryString = queryString;
}
/// <summary>
/// Retrieves an object that can iterate through the name/value pairs in the query string.
/// </summary>
/// <returns>An object that can iterate through the name/value pairs in the query string.</returns>
public Enumerator GetEnumerator()
=> new Enumerator(_queryString);
/// <summary>
/// Represents a single name/value pair extracted from a query string during enumeration.
/// </summary>
public readonly struct EncodedNameValuePair
{
/// <summary>
/// Gets the name from this name/value pair in its original encoded form.
/// To get the decoded string, call <see cref="DecodeName"/>.
/// </summary>
public readonly ReadOnlyMemory<char> EncodedName { get; }
/// <summary>
/// Gets the value from this name/value pair in its original encoded form.
/// To get the decoded string, call <see cref="DecodeValue"/>.
/// </summary>
public readonly ReadOnlyMemory<char> EncodedValue { get; }
internal EncodedNameValuePair(ReadOnlyMemory<char> encodedName, ReadOnlyMemory<char> encodedValue)
{
EncodedName = encodedName;
EncodedValue = encodedValue;
}
/// <summary>
/// Decodes the name from this name/value pair.
/// </summary>
/// <returns>Characters representing the decoded name.</returns>
public ReadOnlyMemory<char> DecodeName()
=> Decode(EncodedName);
/// <summary>
/// Decodes the value from this name/value pair.
/// </summary>
/// <returns>Characters representing the decoded value.</returns>
public ReadOnlyMemory<char> DecodeValue()
=> Decode(EncodedValue);
private static ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
{
ReadOnlySpan<char> source = chars.Span;
if (!source.ContainsAny('%', '+'))
{
return chars;
}
var buffer = new char[source.Length];
source.Replace(buffer, '+', ' ');
var success = Uri.TryUnescapeDataString(buffer, buffer, out var unescapedLength);
Debug.Assert(success);
return buffer.AsMemory(0, unescapedLength);
}
}
/// <summary>
/// An enumerator that supplies the name/value pairs from a URI query string.
/// </summary>
public struct Enumerator
{
private ReadOnlyMemory<char> _query;
internal Enumerator(ReadOnlyMemory<char> query)
{
Current = default;
_query = query.IsEmpty || query.Span[0] != '?'
? query
: query.Slice(1);
}
/// <summary>
/// Gets the currently referenced key/value pair in the query string being enumerated.
/// </summary>
public EncodedNameValuePair Current { get; private set; }
/// <summary>
/// Moves to the next key/value pair in the query string being enumerated.
/// </summary>
/// <returns>True if there is another key/value pair, otherwise false.</returns>
public bool MoveNext()
{
while (!_query.IsEmpty)
{
// Chomp off the next segment
ReadOnlyMemory<char> segment;
var delimiterIndex = _query.Span.IndexOf('&');
if (delimiterIndex >= 0)
{
segment = _query.Slice(0, delimiterIndex);
_query = _query.Slice(delimiterIndex + 1);
}
else
{
segment = _query;
_query = default;
}
// If it's nonempty, emit it
var equalIndex = segment.Span.IndexOf('=');
if (equalIndex >= 0)
{
Current = new EncodedNameValuePair(
segment.Slice(0, equalIndex),
segment.Slice(equalIndex + 1));
return true;
}
else if (!segment.IsEmpty)
{
Current = new EncodedNameValuePair(segment, default);
return true;
}
}
Current = default;
return false;
}
}
}
|