Skip to content

Commit

Permalink
Use stackalloc char&byte arrays for decoding Url values in HttpUtiliy…
Browse files Browse the repository at this point in the history
….ParseQueryString (#102745)
  • Loading branch information
TrayanZapryanov authored May 29, 2024
1 parent 81adb34 commit 49c10ed
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
}
else
{
name = UrlDecode(query.Substring(namePos, valuePos - namePos - 1), encoding);
name = UrlDecode(query.AsSpan(namePos, valuePos - namePos - 1), encoding);
}

if (valueEnd < 0)
Expand All @@ -126,7 +126,7 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
}

namePos = valueEnd + 1;
string value = UrlDecode(query.Substring(valuePos, valueEnd - valuePos), encoding);
string value = UrlDecode(query.AsSpan(valuePos, valueEnd - valuePos), encoding);
result.Add(name, value);
}

Expand Down Expand Up @@ -237,6 +237,8 @@ public static NameValueCollection ParseQueryString(string query, Encoding encodi
[return: NotNullIfNotNull(nameof(str))]
public static string? UrlDecode(string? str, Encoding e) => HttpEncoder.UrlDecode(str, e);

private static string UrlDecode(ReadOnlySpan<char> str, Encoding e) => HttpEncoder.UrlDecode(str, e);

[return: NotNullIfNotNull(nameof(bytes))]
public static string? UrlDecode(byte[]? bytes, int offset, int count, Encoding e) =>
HttpEncoder.UrlDecode(bytes, offset, count, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace System.Web.Util
{
internal static class HttpEncoder
{
private const int MaxStackAllocUrlLength = 256;
private static void AppendCharAsUnicodeJavaScript(StringBuilder builder, char c)
{
builder.Append($"\\u{(int)c:x4}");
Expand Down Expand Up @@ -258,7 +259,9 @@ internal static byte[] UrlDecode(ReadOnlySpan<byte> bytes)
return null;
}

UrlDecoder helper = new UrlDecoder(count, encoding);
UrlDecoder helper = count <= MaxStackAllocUrlLength
? new UrlDecoder(stackalloc char[MaxStackAllocUrlLength], stackalloc byte[MaxStackAllocUrlLength], encoding)
: new UrlDecoder(new char[count], new byte[count], encoding);

// go through the bytes collapsing %XX and %uXXXX and appending
// each byte as byte, with exception of %uXXXX constructs that
Expand Down Expand Up @@ -321,8 +324,20 @@ internal static byte[] UrlDecode(ReadOnlySpan<byte> bytes)
return null;
}

return UrlDecode(value.AsSpan(), encoding);
}

internal static string UrlDecode(ReadOnlySpan<char> value, Encoding encoding)
{
if (value.IsEmpty)
{
return string.Empty;
}

int count = value.Length;
UrlDecoder helper = new UrlDecoder(count, encoding);
UrlDecoder helper = count <= MaxStackAllocUrlLength
? new UrlDecoder(stackalloc char[MaxStackAllocUrlLength], stackalloc byte[MaxStackAllocUrlLength], encoding)
: new UrlDecoder(new char[count], new byte[count], encoding);

// go through the string's chars collapsing %XX and %uXXXX and
// appending each char as char, with exception of %XX constructs
Expand Down Expand Up @@ -626,17 +641,15 @@ private static bool ValidateUrlEncodingParameters([NotNullWhen(true)] byte[]? by
}

// Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes
private sealed class UrlDecoder
private ref struct UrlDecoder
{
private readonly int _bufferSize;

// Accumulate characters in a special array
private int _numChars;
private readonly char[] _charBuffer;
private readonly Span<char> _charBuffer;

// Accumulate bytes for decoding into characters in a special array
private int _numBytes;
private byte[]? _byteBuffer;
private readonly Span<byte> _byteBuffer;

// Encoding to convert chars to bytes
private readonly Encoding _encoding;
Expand All @@ -645,19 +658,17 @@ private void FlushBytes()
{
if (_numBytes > 0)
{
Debug.Assert(_byteBuffer != null);
_numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
Debug.Assert(!_byteBuffer.IsEmpty);
_numChars += _encoding.GetChars(_byteBuffer.Slice(0, _numBytes), _charBuffer.Slice(_numChars));
_numBytes = 0;
}
}

internal UrlDecoder(int bufferSize, Encoding encoding)
internal UrlDecoder(Span<char> charBuffer, Span<byte> byteBuffer, Encoding encoding)
{
_bufferSize = bufferSize;
_charBuffer = charBuffer;
_byteBuffer = byteBuffer;
_encoding = encoding;

_charBuffer = new char[bufferSize];
// byte buffer created on demand
}

internal void AddChar(char ch)
Expand All @@ -681,8 +692,6 @@ internal void AddByte(byte b)
else
*/
{
_byteBuffer ??= new byte[_bufferSize];

_byteBuffer[_numBytes++] = b;
}
}
Expand All @@ -694,7 +703,7 @@ internal string GetString()
FlushBytes();
}

return _numChars > 0 ? new string(_charBuffer, 0, _numChars) : "";
return _charBuffer.Slice(0, _numChars).ToString();
}
}
}
Expand Down

0 comments on commit 49c10ed

Please sign in to comment.