Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal static partial class JsonWriterHelper
// and exclude characters that need to be escaped by adding a backslash: '\n', '\r', '\t', '\\', '\b', '\f'
//
// non-zero = allowed, 0 = disallowed
// This exactly matches the set used by JavaScriptEncoder.Default.
// SearchValues instances below also represent the same ASCII set, validated to match in the debug static ctor below.
public const int LastAsciiCharacter = 0x7F;
private static ReadOnlySpan<byte> AllowList => // byte.MaxValue + 1
[
Expand All @@ -42,6 +44,19 @@ internal static partial class JsonWriterHelper
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // U+00F0..U+00FF
];

#if DEBUG && NET
static JsonWriterHelper()
{
for (int i = 0; i < 128; i++)
{
bool needsEscaping = AllowList[i] == 0;
Debug.Assert(needsEscaping == JavaScriptEncoder.Default.WillEncode(i));
Debug.Assert(needsEscaping == !s_allowedBytes.Contains((byte)i));
Debug.Assert(needsEscaping == !s_allowedChars.Contains((char)i));
}
}
#endif

#if NET
private const string HexFormatString = "X4";
#endif
Expand All @@ -52,13 +67,37 @@ internal static partial class JsonWriterHelper

private static bool NeedsEscapingNoBoundsCheck(char value) => AllowList[value] == 0;

#if NET
// The characters allowed by AllowList, used to vectorize the default (no custom encoder) escaping
// scan directly instead of routing through System.Text.Encodings.Web.
// Validated to match the AllowList and JavaScriptEncoder.Default in the static ctor above.
private static readonly SearchValues<byte> s_allowedBytes = SearchValues.Create(
" !#$%()*,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"u8);
Comment thread
MihaZupan marked this conversation as resolved.
private static readonly SearchValues<char> s_allowedChars = SearchValues.Create(
" !#$%()*,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~");
#endif

public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder? encoder)
{
#if NET
if (encoder is null || ReferenceEquals(encoder, JavaScriptEncoder.Default))
{
return value.IndexOfAnyExcept(s_allowedBytes);
}
#endif

return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value);
}

public static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder? encoder)
{
#if NET
if (encoder is null || ReferenceEquals(encoder, JavaScriptEncoder.Default))
{
return value.IndexOfAnyExcept(s_allowedChars);
}
#endif

// Some implementations of JavaScriptEncoder.FindFirstCharacterToEncode may not accept
// null pointers and guard against that. Hence, check up-front to return -1.
if (value.IsEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Unicode;

namespace System.Text.Json
Expand Down Expand Up @@ -304,7 +303,7 @@ internal static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> des

internal static unsafe T WriteString<T>(ReadOnlySpan<byte> utf8Value, WriteCallback<T> writeCallback)
{
int firstByteToEscape = JsonWriterHelper.NeedsEscaping(utf8Value, JavaScriptEncoder.Default);
int firstByteToEscape = JsonWriterHelper.NeedsEscaping(utf8Value, encoder: null);

if (firstByteToEscape == -1)
{
Expand Down Expand Up @@ -353,7 +352,7 @@ internal static unsafe T WriteString<T>(ReadOnlySpan<byte> utf8Value, WriteCallb
}

escapedValue[0] = JsonConstants.Quote;
JsonWriterHelper.EscapeString(utf8Value, escapedValue.Slice(1), firstByteToEscape, JavaScriptEncoder.Default, out int written);
JsonWriterHelper.EscapeString(utf8Value, escapedValue.Slice(1), firstByteToEscape, encoder: null, out int written);
escapedValue[1 + written] = JsonConstants.Quote;

return writeCallback(escapedValue.Slice(0, written + 2));
Expand Down
Loading