public System.Text.Json.JsonElement.ObjectEnumerator EnumerateObject() { throw null; }
public int GetArrayLength() { throw null; }
public bool GetBoolean() { throw null; }
+ public byte[] GetBytesFromBase64() { throw null; }
public System.DateTime GetDateTime() { throw null; }
public System.DateTimeOffset GetDateTimeOffset() { throw null; }
public decimal GetDecimal() { throw null; }
[System.CLSCompliantAttribute(false)]
public ulong GetUInt64() { throw null; }
public override string ToString() { throw null; }
+ public bool TryGetBytesFromBase64(out byte[] value) { throw null; }
public bool TryGetDateTime(out System.DateTime value) { throw null; }
public bool TryGetDateTimeOffset(out System.DateTimeOffset value) { throw null; }
public bool TryGetDecimal(out decimal value) { throw null; }
public System.Buffers.ReadOnlySequence<byte> ValueSequence { get { throw null; } }
public System.ReadOnlySpan<byte> ValueSpan { get { throw null; } }
public bool GetBoolean() { throw null; }
+ public byte[] GetBytesFromBase64() { throw null; }
+ public string GetComment() { throw null; }
public System.DateTime GetDateTime() { throw null; }
public System.DateTimeOffset GetDateTimeOffset() { throw null; }
- public string GetComment() { throw null; }
public decimal GetDecimal() { throw null; }
public double GetDouble() { throw null; }
public System.Guid GetGuid() { throw null; }
public void Skip() { }
public bool TextEquals(System.ReadOnlySpan<byte> otherUtf8Text) { throw null; }
public bool TextEquals(System.ReadOnlySpan<char> otherText) { throw null; }
+ public bool TryGetBytesFromBase64(out byte[] value) { throw null; }
public bool TryGetDateTime(out System.DateTime value) { throw null; }
public bool TryGetDateTimeOffset(out System.DateTimeOffset value) { throw null; }
public bool TryGetDecimal(out decimal value) { throw null; }
public void Reset() { }
public void Reset(System.Buffers.IBufferWriter<byte> bufferWriter) { }
public void Reset(System.IO.Stream utf8Json) { }
+ public void WriteBase64String(System.ReadOnlySpan<byte> utf8PropertyName, System.ReadOnlySpan<byte> bytes) { }
+ public void WriteBase64String(System.ReadOnlySpan<char> propertyName, System.ReadOnlySpan<byte> bytes) { }
+ public void WriteBase64String(string propertyName, System.ReadOnlySpan<byte> bytes) { }
+ public void WriteBase64String(System.Text.Json.JsonEncodedText propertyName, System.ReadOnlySpan<byte> bytes) { }
+ public void WriteBase64StringValue(System.ReadOnlySpan<byte> bytes) { }
public void WriteBoolean(System.ReadOnlySpan<byte> utf8PropertyName, bool value) { }
public void WriteBoolean(System.ReadOnlySpan<char> propertyName, bool value) { }
public void WriteBoolean(string propertyName, bool value) { }
public static TValue Parse<TValue>(string json, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
public static System.Threading.Tasks.ValueTask<object> ReadAsync(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.ValueTask<TValue> ReadAsync<TValue>(System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
- public static byte[] ToUtf8Bytes(object value, System.Type type, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
- public static byte[] ToUtf8Bytes<TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
public static string ToString(object value, System.Type type, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
public static string ToString<TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+ public static byte[] ToUtf8Bytes(object value, System.Type type, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
+ public static byte[] ToUtf8Bytes<TValue>(TValue value, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; }
public static System.Threading.Tasks.Task WriteAsync(object value, System.Type type, System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task WriteAsync<TValue>(TValue value, System.IO.Stream utf8Json, System.Text.Json.Serialization.JsonSerializerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
<data name="CannotTranscodeInvalidUtf8" xml:space="preserve">
<value>Cannot transcode invalid UTF-8 JSON text to UTF-16 string.</value>
</data>
+ <data name="CannotDecodeInvalidBase64" xml:space="preserve">
+ <value>Cannot decode JSON text that is not encoded as valid Base64 to bytes.</value>
+ </data>
<data name="CannotTranscodeInvalidUtf16" xml:space="preserve">
<value>Cannot transcode invalid UTF-16 string to UTF-8 JSON text.</value>
</data>
<!-- Workaround for overriding the XML comments related warnings that are being supressed repo wide (within arcade): -->
<!-- https://github.com/dotnet/arcade/blob/ea6addfdc65e5df1b2c036f11614a5f922e36267/src/Microsoft.DotNet.Arcade.Sdk/tools/ProjectDefaults.props#L90 -->
<!-- For this project, we want warnings if there are public APIs/types without properly formatted XML comments (particularly CS1591). -->
- <NoWarn/>
+ <NoWarn />
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Writer\JsonWriterOptions.cs" />
<Compile Include="System\Text\Json\Writer\SequenceValidity.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.cs" />
+ <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Bytes.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.DateTime.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.DateTimeOffset.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.Decimal.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.SignedNumber.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.String.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteProperties.UnsignedNumber.cs" />
+ <Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Bytes.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Comment.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.DateTime.cs" />
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.DateTimeOffset.cs" />
return GetString(index - DbRow.Size, JsonTokenType.PropertyName);
}
+ internal bool TryGetValue(int index, out byte[] value)
+ {
+ CheckNotDisposed();
+
+ DbRow row = _parsedData.Get(index);
+
+ CheckExpectedType(JsonTokenType.String, row.TokenType);
+
+ ReadOnlySpan<byte> data = _utf8Json.Span;
+ ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);
+
+ // Segment needs to be unescaped
+ if (row.HasComplexChildren)
+ {
+ int idx = segment.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ return JsonReaderHelper.TryGetUnescapedBase64Bytes(segment, idx, out value);
+ }
+
+ Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);
+ return JsonReaderHelper.TryDecodeBase64(segment, out value);
+ }
+
internal bool TryGetValue(int index, out int value)
{
CheckNotDisposed();
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Buffers.Text;
using System.Collections.Generic;
using System.Diagnostics;
}
/// <summary>
+ /// Attempts to represent the current JSON string as bytes assuming it is base 64 encoded.
+ /// </summary>
+ /// <param name="value">Receives the value.</param>
+ /// <remarks>
+ /// This method does not create a byte[] representation of values other than bsae 64 encoded JSON strings.
+ /// </remarks>
+ /// <returns>
+ /// <see langword="true"/> if the entire token value is encoded as valid base 64 text and can be successfully decoded to bytes.
+ /// <see langword="false"/> otherwise.
+ /// </returns>
+ /// <exception cref="InvalidOperationException">
+ /// This value's <see cref="Type"/> is not <see cref="JsonValueType.String"/>.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// The parent <see cref="JsonDocument"/> has been disposed.
+ /// </exception>
+ public bool TryGetBytesFromBase64(out byte[] value)
+ {
+ CheckValidInstance();
+
+ return _parent.TryGetValue(_idx, out value);
+ }
+
+ /// <summary>
+ /// Gets the value of the element as bytes.
+ /// </summary>
+ /// <remarks>
+ /// This method does not create a byte[] representation of values other than base 64 encoded JSON strings.
+ /// </remarks>
+ /// <returns>The value decode to bytes.</returns>
+ /// <exception cref="InvalidOperationException">
+ /// This value's <see cref="Type"/> is not <see cref="JsonValueType.String"/>.
+ /// </exception>
+ /// <exception cref="FormatException">
+ /// The value is not encoded as base 64 text and hence cannot be decoded to bytes.
+ /// </exception>
+ /// <exception cref="ObjectDisposedException">
+ /// The parent <see cref="JsonDocument"/> has been disposed.
+ /// </exception>
+ /// <seealso cref="ToString"/>
+ public byte[] GetBytesFromBase64()
+ {
+ if (TryGetBytesFromBase64(out byte[] value))
+ {
+ return value;
+ }
+
+ throw new FormatException();
+ }
+
+ /// <summary>
/// Attempts to represent the current JSON number as an <see cref="int"/>.
/// </summary>
/// <param name="value">Receives the value.</param>
// All other UTF-16 characters can be represented by either 1 or 2 UTF-8 bytes.
public const int MaxExpansionFactorWhileTranscoding = 3;
- public const int MaxTokenSize = 2_000_000_000 / MaxExpansionFactorWhileEscaping; // 357_913_941 bytes
- public const int MaxCharacterTokenSize = 2_000_000_000 / MaxExpansionFactorWhileEscaping; // 357_913_941 characters
+ public const int MaxTokenSize = 1_000_000_000 / MaxExpansionFactorWhileEscaping; // 166_666_666 bytes
+ public const int MaxBase46ValueTokenSize = (1_000_000_000 >> 2 * 3) / MaxExpansionFactorWhileEscaping; // 125_000_000 bytes
+ public const int MaxCharacterTokenSize = 1_000_000_000 / MaxExpansionFactorWhileEscaping; // 166_666_666 characters
public const int MaximumFormatInt64Length = 20; // 19 + sign (i.e. -9223372036854775808)
public const int MaximumFormatUInt64Length = 20; // i.e. 18446744073709551615
{
internal static partial class JsonReaderHelper
{
+ public static bool TryGetUnescapedBase64Bytes(ReadOnlySpan<byte> utf8Source, int idx, out byte[] bytes)
+ {
+ byte[] unescapedArray = null;
+
+ Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[utf8Source.Length] :
+ (unescapedArray = ArrayPool<byte>.Shared.Rent(utf8Source.Length));
+
+ Unescape(utf8Source, utf8Unescaped, idx, out int written);
+ Debug.Assert(written > 0);
+
+ utf8Unescaped = utf8Unescaped.Slice(0, written);
+ Debug.Assert(!utf8Unescaped.IsEmpty);
+
+ bool result = TryDecodeBase64InPlace(utf8Unescaped, out bytes);
+
+ if (unescapedArray != null)
+ {
+ utf8Unescaped.Clear();
+ ArrayPool<byte>.Shared.Return(unescapedArray);
+ }
+
+ return result;
+ }
+
// Reject any invalid UTF-8 data rather than silently replacing.
public static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
return result;
}
+ public static bool TryDecodeBase64InPlace(Span<byte> utf8Unescaped, out byte[] bytes)
+ {
+ OperationStatus status = Base64.DecodeFromUtf8InPlace(utf8Unescaped, out int bytesWritten);
+ if (status != OperationStatus.Done)
+ {
+ bytes = null;
+ return false;
+ }
+ bytes = utf8Unescaped.Slice(0, bytesWritten).ToArray();
+ return true;
+ }
+
+ public static bool TryDecodeBase64(ReadOnlySpan<byte> utf8Unescaped, out byte[] bytes)
+ {
+ byte[] pooledArray = null;
+
+ Span<byte> byteSpan = utf8Unescaped.Length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[utf8Unescaped.Length] :
+ (pooledArray = ArrayPool<byte>.Shared.Rent(utf8Unescaped.Length));
+
+ OperationStatus status = Base64.DecodeFromUtf8(utf8Unescaped, byteSpan, out int bytesConsumed, out int bytesWritten);
+
+ if (status != OperationStatus.Done)
+ {
+ bytes = null;
+
+ if (pooledArray != null)
+ {
+ byteSpan.Clear();
+ ArrayPool<byte>.Shared.Return(pooledArray);
+ }
+
+ return false;
+ }
+ Debug.Assert(bytesConsumed == utf8Unescaped.Length);
+
+ bytes = byteSpan.Slice(0, bytesWritten).ToArray();
+
+ if (pooledArray != null)
+ {
+ byteSpan.Clear();
+ ArrayPool<byte>.Shared.Return(pooledArray);
+ }
+
+ return true;
+ }
+
public static string TranscodeHelper(ReadOnlySpan<byte> utf8Unescaped)
{
try
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="bool"/>.
- /// Returns true if the TokenType is JsonTokenType.True and false if the TokenType is JsonTokenType.False.
+ /// Returns <see langword="true"/> if the TokenType is JsonTokenType.True and <see langword="false"/> if the TokenType is JsonTokenType.False.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a boolean (i.e. <see cref="JsonTokenType.True"/> or <see cref="JsonTokenType.False"/>).
}
/// <summary>
+ /// Parses the current JSON token value from the source and decodes the base 64 encoded JSON string as bytes.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.String"/>.
+ /// <seealso cref="TokenType" />
+ /// </exception>
+ /// <exception cref="FormatException">
+ /// Thrown when the JSON string contains data outside of the expected base 64 range, or if it contains invalid/more than two padding characters,
+ /// or is incomplete (i.e. the JSON string length is not a multiple of 4).
+ /// </exception>
+ public byte[] GetBytesFromBase64()
+ {
+ if (!TryGetBytesFromBase64(out byte[] value))
+ {
+ throw ThrowHelper.GetFormatException(DateType.Base64String);
+ }
+
+ return value;
+ }
+
+ /// <summary>
/// Parses the current JSON token value from the source as an <see cref="int"/>.
/// Returns the value if the entire UTF-8 encoded token value can be successfully parsed to an <see cref="int"/>
/// value.
}
/// <summary>
+ /// Parses the current JSON token value from the source and decodes the base 64 encoded JSON string as bytes.
+ /// Returns <see langword="true"/> if the entire token value is encoded as valid base 64 text and can be successfully
+ /// decoded to bytes.
+ /// Returns <see langword="false"/> otherwise.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.String"/>.
+ /// <seealso cref="TokenType" />
+ /// </exception>
+ public bool TryGetBytesFromBase64(out byte[] value)
+ {
+ if (TokenType != JsonTokenType.String)
+ {
+ throw ThrowHelper.GetInvalidOperationException_ExpectedString(TokenType);
+ }
+
+ ReadOnlySpan<byte> span = HasValueSequence ? ValueSequence.ToArray() : ValueSpan;
+
+ if (_stringHasEscaping)
+ {
+ int idx = span.IndexOf(JsonConstants.BackSlash);
+ Debug.Assert(idx != -1);
+ return JsonReaderHelper.TryGetUnescapedBase64Bytes(span, idx, out value);
+ }
+
+ Debug.Assert(span.IndexOf(JsonConstants.BackSlash) == -1);
+ return JsonReaderHelper.TryDecodeBase64(span, out value);
+ }
+
+ /// <summary>
/// Parses the current JSON token value from the source as an <see cref="int"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to an <see cref="int"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="long"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="long"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="uint"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="uint"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="ulong"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="ulong"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="float"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="float"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="double"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="double"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="decimal"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="decimal"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.Number"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="DateTime"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="DateTime"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.String"/>.
/// <summary>
/// Parses the current JSON token value from the source as a <see cref="DateTimeOffset"/>.
- /// Returns true if the entire UTF-8 encoded token value can be successfully
+ /// Returns <see langword="true"/> if the entire UTF-8 encoded token value can be successfully
/// parsed to a <see cref="DateTimeOffset"/> value.
- /// Returns false otherwise.
+ /// Returns <see langword="false"/> otherwise.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown if trying to get the value of a JSON token that is not a <see cref="JsonTokenType.String"/>.
case DateType.DateTimeOffset:
message = SR.FormatDateTimeOffset;
break;
+ case DateType.Base64String:
+ message = SR.CannotDecodeInvalidBase64;
+ break;
default:
Debug.Fail($"The DateType enum value: {dateType} is not part of the switch. Add the appropriate case and exception message.");
break;
internal enum DateType
{
DateTime,
- DateTimeOffset
+ DateTimeOffset,
+ Base64String
}
}
private static ReadOnlySpan<byte> AllowList => new byte[byte.MaxValue + 1] {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1,
+ 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ValidateBytes(ReadOnlySpan<byte> bytes)
+ {
+ if (bytes.Length > JsonConstants.MaxBase46ValueTokenSize)
+ ThrowHelper.ThrowArgumentException_ValueTooLarge(bytes.Length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateDouble(double value)
{
#if BUILDING_INBOX_LIBRARY
ThrowHelper.ThrowArgumentException(propertyName, value);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ValidatePropertyAndBytes(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
+ {
+ if (propertyName.Length > JsonConstants.MaxCharacterTokenSize || bytes.Length > JsonConstants.MaxBase46ValueTokenSize)
+ ThrowHelper.ThrowArgumentException(propertyName, bytes);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ValidatePropertyAndBytes(ReadOnlySpan<byte> propertyName, ReadOnlySpan<byte> bytes)
+ {
+ if (propertyName.Length > JsonConstants.MaxTokenSize || bytes.Length > JsonConstants.MaxBase46ValueTokenSize)
+ ThrowHelper.ThrowArgumentException(propertyName, bytes);
+ }
+
internal static void ValidateNumber(ReadOnlySpan<byte> utf8FormattedNumber)
{
// This is a simplified version of the number reader from Utf8JsonReader.TryGetNumber,
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+ public sealed partial class Utf8JsonWriter
+ {
+ /// <summary>
+ /// Writes the pre-encoded property name and raw bytes value (as a base 64 encoded JSON string) as part of a name/value pair of a JSON object.
+ /// </summary>
+ /// <param name="propertyName">The JSON encoded property name of the JSON object to be transcoded and written as UTF-8.</param>
+ /// <param name="bytes">The binary data to be written as a base 64 encoded JSON string as part of the name/value pair.</param>
+ /// <remarks>
+ /// The property name should already be escaped when the instance of <see cref="JsonEncodedText"/> was created.
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+ /// </exception>
+ public void WriteBase64String(JsonEncodedText propertyName, ReadOnlySpan<byte> bytes)
+ => WriteBase64StringHelper(propertyName.EncodedUtf8Bytes, bytes);
+
+ private void WriteBase64StringHelper(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
+ {
+ Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxTokenSize);
+
+ JsonWriterHelper.ValidateBytes(bytes);
+
+ WriteBase64ByOptions(utf8PropertyName, bytes);
+
+ SetFlagToAddListSeparatorBeforeNextItem();
+ _tokenType = JsonTokenType.String;
+ }
+
+ /// <summary>
+ /// Writes the property name and raw bytes value (as a Base64 encoded JSON string) as part of a name/value pair of a JSON object.
+ /// </summary>
+ /// <param name="propertyName">The property name of the JSON object to be transcoded and written as UTF-8.</param>
+ /// <param name="bytes">The binary data to be written as a base 64 encoded JSON string as part of the name/value pair.</param>
+ /// <remarks>
+ /// The property name is escaped before writing.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when the specified property name is too large.
+ /// </exception>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+ /// </exception>
+ public void WriteBase64String(string propertyName, ReadOnlySpan<byte> bytes)
+ => WriteBase64String(propertyName.AsSpan(), bytes);
+
+ /// <summary>
+ /// Writes the property name and raw bytes value (as a base 64 encoded JSON string) as part of a name/value pair of a JSON object.
+ /// </summary>
+ /// <param name="propertyName">The property name of the JSON object to be transcoded and written as UTF-8.</param>
+ /// <param name="bytes">The binary data to be written as a base 64 encoded JSON string as part of the name/value pair.</param>
+ /// <remarks>
+ /// The property name is escaped before writing.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when the specified property name is too large.
+ /// </exception>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+ /// </exception>
+ public void WriteBase64String(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
+ {
+ JsonWriterHelper.ValidatePropertyAndBytes(propertyName, bytes);
+
+ WriteBase64Escape(propertyName, bytes);
+
+ SetFlagToAddListSeparatorBeforeNextItem();
+ _tokenType = JsonTokenType.String;
+ }
+
+ /// <summary>
+ /// Writes the property name and raw bytes value (as a base 64 encoded JSON string) as part of a name/value pair of a JSON object.
+ /// </summary>
+ /// <param name="utf8PropertyName">The UTF-8 encoded property name of the JSON object to be written.</param>
+ /// <param name="bytes">The binary data to be written as a base 64 encoded JSON string as part of the name/value pair.</param>
+ /// <remarks>
+ /// The property name is escaped before writing.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when the specified property name is too large.
+ /// </exception>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+ /// </exception>
+ public void WriteBase64String(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
+ {
+ JsonWriterHelper.ValidatePropertyAndBytes(utf8PropertyName, bytes);
+
+ WriteBase64Escape(utf8PropertyName, bytes);
+
+ SetFlagToAddListSeparatorBeforeNextItem();
+ _tokenType = JsonTokenType.String;
+ }
+
+ private void WriteBase64Escape(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
+ {
+ int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName);
+
+ Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length);
+
+ if (propertyIdx != -1)
+ {
+ WriteBase64EscapeProperty(propertyName, bytes, propertyIdx);
+ }
+ else
+ {
+ WriteBase64ByOptions(propertyName, bytes);
+ }
+ }
+
+ private void WriteBase64Escape(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
+ {
+ int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName);
+
+ Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length);
+
+ if (propertyIdx != -1)
+ {
+ WriteBase64EscapeProperty(utf8PropertyName, bytes, propertyIdx);
+ }
+ else
+ {
+ WriteBase64ByOptions(utf8PropertyName, bytes);
+ }
+ }
+
+ private void WriteBase64EscapeProperty(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes, int firstEscapeIndexProp)
+ {
+ Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= propertyName.Length);
+ Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < propertyName.Length);
+
+ char[] propertyArray = null;
+
+ int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);
+
+ Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+ stackalloc char[length] :
+ (propertyArray = ArrayPool<char>.Shared.Rent(length));
+
+ JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+ WriteBase64ByOptions(escapedPropertyName.Slice(0, written), bytes);
+
+ if (propertyArray != null)
+ {
+ ArrayPool<char>.Shared.Return(propertyArray);
+ }
+ }
+
+ private void WriteBase64EscapeProperty(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes, int firstEscapeIndexProp)
+ {
+ Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8PropertyName.Length);
+ Debug.Assert(firstEscapeIndexProp >= 0 && firstEscapeIndexProp < utf8PropertyName.Length);
+
+ byte[] propertyArray = null;
+
+ int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);
+
+ Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[length] :
+ (propertyArray = ArrayPool<byte>.Shared.Rent(length));
+
+ JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written);
+
+ WriteBase64ByOptions(escapedPropertyName.Slice(0, written), bytes);
+
+ if (propertyArray != null)
+ {
+ ArrayPool<byte>.Shared.Return(propertyArray);
+ }
+ }
+
+ private void WriteBase64ByOptions(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes)
+ {
+ ValidateWritingProperty();
+ if (Options.Indented)
+ {
+ WriteBase64Indented(propertyName, bytes);
+ }
+ else
+ {
+ WriteBase64Minimized(propertyName, bytes);
+ }
+ }
+
+ private void WriteBase64ByOptions(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
+ {
+ ValidateWritingProperty();
+ if (Options.Indented)
+ {
+ WriteBase64Indented(utf8PropertyName, bytes);
+ }
+ else
+ {
+ WriteBase64Minimized(utf8PropertyName, bytes);
+ }
+ }
+
+ private void WriteBase64Minimized(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> bytes)
+ {
+ int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 6);
+
+ // All ASCII, 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, and 1 colon => escapedPropertyName.Length + encodedLength + 5
+ // Optionally, 1 list separator, and up to 3x growth when transcoding, with escaping which can by up to 6x.
+ int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 6;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+ output[BytesPending++] = JsonConstants.Quote;
+
+ TranscodeAndWrite(escapedPropertyName, output);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodedLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+
+ private void WriteBase64Minimized(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> bytes)
+ {
+ int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(escapedPropertyName.Length < int.MaxValue - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 6);
+
+ // 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, and 1 colon => escapedPropertyName.Length + encodedLength + 5
+ // Optionally, 1 list separator, with escaping which can by up to 6x.
+ int maxRequired = escapedPropertyName.Length + (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 6;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+ output[BytesPending++] = JsonConstants.Quote;
+
+ escapedPropertyName.CopyTo(output.Slice(BytesPending));
+ BytesPending += escapedPropertyName.Length;
+
+ output[BytesPending++] = JsonConstants.Quote;
+ output[BytesPending++] = JsonConstants.KeyValueSeperator;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodedLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+
+ private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> bytes)
+ {
+ int indent = Indentation;
+ Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+ int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - indent - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 7 - s_newLineLength);
+
+ // All ASCII, 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, 1 colon, and 1 space => indent + escapedPropertyName.Length + encodedLength + 6
+ // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding, with escaping which can by up to 6x.
+ int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 7 + s_newLineLength;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+
+ if (_tokenType != JsonTokenType.None)
+ {
+ WriteNewLine(output);
+ }
+
+ JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+ BytesPending += indent;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ TranscodeAndWrite(escapedPropertyName, output);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ output[BytesPending++] = JsonConstants.KeyValueSeperator;
+ output[BytesPending++] = JsonConstants.Space;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodedLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+
+ private void WriteBase64Indented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> bytes)
+ {
+ int indent = Indentation;
+ Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+ int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 7 - s_newLineLength);
+
+ // 2 quotes for property name, 2 quotes to surround the base-64 encoded string value, 1 colon, and 1 space => indent + escapedPropertyName.Length + encodedLength + 6
+ // Optionally, 1 list separator, and 1-2 bytes for new line, with escaping which can by up to 6x.
+ int maxRequired = indent + escapedPropertyName.Length + (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 7 + s_newLineLength;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+
+ if (_tokenType != JsonTokenType.None)
+ {
+ WriteNewLine(output);
+ }
+
+ JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+ BytesPending += indent;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ escapedPropertyName.CopyTo(output.Slice(BytesPending));
+ BytesPending += escapedPropertyName.Length;
+
+ output[BytesPending++] = JsonConstants.Quote;
+ output[BytesPending++] = JsonConstants.KeyValueSeperator;
+ output[BytesPending++] = JsonConstants.Space;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodedLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Text;
+using System.Diagnostics;
+
+namespace System.Text.Json
+{
+ public sealed partial class Utf8JsonWriter
+ {
+ /// <summary>
+ /// Writes the raw bytes value as base 64 encoded JSON string as an element of a JSON array.
+ /// </summary>
+ /// <param name="bytes">The binary data to be written as a base 64 encoded JSON string element of a JSON array.</param>
+ /// <remarks>
+ /// The bytes are encoded before writing.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when the specified value is too large.
+ /// </exception>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
+ /// </exception>
+ public void WriteBase64StringValue(ReadOnlySpan<byte> bytes)
+ {
+ JsonWriterHelper.ValidateBytes(bytes);
+
+ WriteBase64ByOptions(bytes);
+
+ SetFlagToAddListSeparatorBeforeNextItem();
+ _tokenType = JsonTokenType.String;
+ }
+
+ private void WriteBase64ByOptions(ReadOnlySpan<byte> bytes)
+ {
+ ValidateWritingValue();
+
+ if (Options.Indented)
+ {
+ WriteBase64Indented(bytes);
+ }
+ else
+ {
+ WriteBase64Minimized(bytes);
+ }
+ }
+
+ // TODO: https://github.com/dotnet/corefx/issues/36958
+ private void WriteBase64Minimized(ReadOnlySpan<byte> bytes)
+ {
+ int encodingLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(encodingLength < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping) - 3);
+
+ // 2 quotes to surround the base-64 encoded string value, with escaping which can by up to 6x.
+ // Optionally, 1 list separator
+ int maxRequired = (encodingLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 3;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodingLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+
+ // TODO: https://github.com/dotnet/corefx/issues/36958
+ private void WriteBase64Indented(ReadOnlySpan<byte> bytes)
+ {
+ int indent = Indentation;
+ Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
+
+ int encodingLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
+
+ Debug.Assert(encodingLength < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping) - indent - 3 - s_newLineLength);
+
+ // indentation + 2 quotes to surround the base-64 encoded string value, with escaping which can by up to 6x.
+ // Optionally, 1 list separator, and 1-2 bytes for new line
+ int maxRequired = indent + (encodingLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 3 + s_newLineLength;
+
+ if (_memory.Length - BytesPending < maxRequired)
+ {
+ Grow(maxRequired);
+ }
+
+ Span<byte> output = _memory.Span;
+
+ if (_currentDepth < 0)
+ {
+ output[BytesPending++] = JsonConstants.ListSeparator;
+ }
+
+ if (_tokenType != JsonTokenType.None)
+ {
+ WriteNewLine(output);
+ }
+
+ JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent);
+ BytesPending += indent;
+
+ output[BytesPending++] = JsonConstants.Quote;
+
+ Base64EncodeAndWrite(bytes, output, encodingLength);
+
+ output[BytesPending++] = JsonConstants.Quote;
+ }
+ }
+}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Buffers;
+using System.Buffers.Text;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace System.Text.Json
{
}
}
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Base64EncodeAndWrite(ReadOnlySpan<byte> bytes, Span<byte> output, int encodingLength)
+ {
+ byte[] outputText = null;
+
+ Span<byte> encodedBytes = encodingLength <= JsonConstants.StackallocThreshold ?
+ stackalloc byte[encodingLength] :
+ (outputText = ArrayPool<byte>.Shared.Rent(encodingLength));
+
+ OperationStatus status = Base64.EncodeToUtf8(bytes, encodedBytes, out int consumed, out int written);
+ Debug.Assert(status == OperationStatus.Done);
+ Debug.Assert(consumed == bytes.Length);
+
+ encodedBytes = encodedBytes.Slice(0, written);
+ Span<byte> destination = output.Slice(BytesPending);
+
+ int firstEscapeIndexVal = encodedBytes.IndexOfAny(JsonConstants.Plus, JsonConstants.Slash);
+ if (firstEscapeIndexVal == -1)
+ {
+ Debug.Assert(destination.Length >= written);
+ encodedBytes.Slice(0, written).CopyTo(destination);
+ BytesPending += written;
+ }
+ else
+ {
+ Debug.Assert(destination.Length >= written * JsonConstants.MaxExpansionFactorWhileEscaping);
+ JsonWriterHelper.EscapeString(encodedBytes, destination, firstEscapeIndexVal, out written);
+ BytesPending += written;
+ }
+
+ if (outputText != null)
+ {
+ ArrayPool<byte>.Shared.Return(outputText);
+ }
+ }
}
}
null,
bytes => JsonDocument.Parse(new MemoryStream(Utf8Bom.Concat(bytes).ToArray())));
}
-
+
[Theory]
[MemberData(nameof(ReducedTestCases))]
public static void ParseJson_SeekableStream_Async_WithBOM(bool compactData, TestCaseType type, string jsonString)
}
}
- private static string PrintJson(this JsonDocument document, int sizeHint=0)
+ private static string PrintJson(this JsonDocument document, int sizeHint = 0)
{
return PrintJson(document.RootElement, sizeHint);
}
- private static string PrintJson(this JsonElement element, int sizeHint=0)
+ private static string PrintJson(this JsonElement element, int sizeHint = 0)
{
StringBuilder sb = new StringBuilder(sizeHint);
DepthFirstAppend(sb, element);
case JsonValueType.True:
case JsonValueType.String:
case JsonValueType.Number:
- {
- buf.Append(element.ToString());
- buf.Append(", ");
- break;
- }
- case JsonValueType.Object:
- {
- foreach (JsonProperty prop in element.EnumerateObject())
{
- buf.Append(prop.Name);
+ buf.Append(element.ToString());
buf.Append(", ");
- DepthFirstAppend(buf, prop.Value);
+ break;
}
+ case JsonValueType.Object:
+ {
+ foreach (JsonProperty prop in element.EnumerateObject())
+ {
+ buf.Append(prop.Name);
+ buf.Append(", ");
+ DepthFirstAppend(buf, prop.Value);
+ }
- break;
- }
+ break;
+ }
case JsonValueType.Array:
- {
- foreach (JsonElement child in element.EnumerateArray())
{
- DepthFirstAppend(buf, child);
- }
+ foreach (JsonElement child in element.EnumerateArray())
+ {
+ DepthFirstAppend(buf, child);
+ }
- break;
- }
+ break;
+ }
}
}
}
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
+ Assert.Throws<InvalidOperationException>(() => root.TryGetBytesFromBase64(out byte[] bytes));
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
}
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Equal(value, root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<FormatException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<FormatException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<FormatException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.TryGetUInt64(out ulong _));
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
+ Assert.Throws<InvalidOperationException>(() => root.TryGetBytesFromBase64(out byte[] _));
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<ObjectDisposedException>(() => root.GetUInt64());
Assert.Throws<ObjectDisposedException>(() => root.TryGetUInt64(out ulong _));
Assert.Throws<ObjectDisposedException>(() => root.GetString());
+ Assert.Throws<ObjectDisposedException>(() => root.GetBytesFromBase64());
+ Assert.Throws<ObjectDisposedException>(() => root.TryGetBytesFromBase64(out byte[] _));
Assert.Throws<ObjectDisposedException>(() => root.GetBoolean());
Assert.Throws<ObjectDisposedException>(() => root.GetRawText());
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.TryGetUInt64(out ulong _));
Assert.Throws<InvalidOperationException>(() => root.GetString());
+ Assert.Throws<InvalidOperationException>(() => root.GetBytesFromBase64());
+ Assert.Throws<InvalidOperationException>(() => root.TryGetBytesFromBase64(out byte[] _));
Assert.Throws<InvalidOperationException>(() => root.GetDateTime());
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
}
}
+ [Fact]
+ public static void GetBase64String_BadUtf8()
+ {
+ // The Arabic ligature Lam-Alef (U+FEFB) (which happens to, as a standalone, mean "no" in English)
+ // is UTF-8 EF BB BB. So let's leave out a BB and put it in quotes.
+ using (JsonDocument doc = JsonDocument.Parse(new byte[] { 0x22, 0xEF, 0xBB, 0x22 }))
+ {
+ JsonElement root = doc.RootElement;
+
+ Assert.Equal(JsonValueType.String, root.Type);
+ Assert.Throws<FormatException>(() => root.GetBytesFromBase64());
+ Assert.False(root.TryGetBytesFromBase64(out byte[] value));
+ Assert.Null(value);
+ }
+ }
+
+ [Fact]
+ public static void GetBase64Unescapes()
+ {
+ string jsonString = "\"\\u0031234\""; // equivalent to "\"1234\""
+
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ using (JsonDocument doc = JsonDocument.Parse(dataUtf8))
+ {
+ byte[] expected = Convert.FromBase64String("1234"); // new byte[3] { 215, 109, 248 }
+
+ Assert.Equal(expected, doc.RootElement.GetBytesFromBase64());
+
+ Assert.True(doc.RootElement.TryGetBytesFromBase64(out byte[] value));
+ Assert.Equal(expected, value);
+ }
+ }
+
+ [Theory]
+ [InlineData("\"ABC=\"")]
+ [InlineData("\"AB+D\"")]
+ [InlineData("\"ABCD\"")]
+ [InlineData("\"ABC/\"")]
+ [InlineData("\"++++\"")]
+ [InlineData(null)] // Large randomly generated string
+ public static void ReadBase64String(string jsonString)
+ {
+ if (jsonString == null)
+ {
+ var random = new Random(42);
+ var charArray = new char[502];
+ charArray[0] = '"';
+ for (int i = 1; i < charArray.Length; i++)
+ {
+ charArray[i] = (char)random.Next('A', 'Z'); // ASCII values (between 65 and 90) that constitute valid base 64 string.
+ }
+ charArray[charArray.Length - 1] = '"';
+ jsonString = new string(charArray);
+ }
+
+ byte[] expected = Convert.FromBase64String(jsonString.AsSpan(1, jsonString.Length - 2).ToString());
+
+ using (JsonDocument doc = JsonDocument.Parse(jsonString))
+ {
+ Assert.Equal(expected, doc.RootElement.GetBytesFromBase64());
+
+ Assert.True(doc.RootElement.TryGetBytesFromBase64(out byte[] value));
+ Assert.Equal(expected, value);
+ }
+ }
+
+ [Theory]
+ [InlineData("\"ABC===\"")]
+ [InlineData("\"ABC\"")]
+ [InlineData("\"ABC!\"")]
+ [InlineData(null)] // Large randomly generated string
+ public static void InvalidBase64(string jsonString)
+ {
+ if (jsonString == null)
+ {
+ var random = new Random(42);
+ var charArray = new char[500];
+ charArray[0] = '"';
+ for (int i = 1; i < charArray.Length; i++)
+ {
+ charArray[i] = (char)random.Next('?', '\\'); // ASCII values (between 63 and 91) that don't need to be escaped.
+ }
+
+ charArray[256] = '\\';
+ charArray[257] = '"';
+ charArray[charArray.Length - 1] = '"';
+ jsonString = new string(charArray);
+ }
+
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ using (JsonDocument doc = JsonDocument.Parse(dataUtf8))
+ {
+ Assert.False(doc.RootElement.TryGetBytesFromBase64(out byte[] value));
+ Assert.Null(value);
+
+ Assert.Throws<FormatException>(() => doc.RootElement.GetBytesFromBase64());
+ }
+ }
+
[Theory]
[InlineData(" { \"hi\": \"there\" }")]
[InlineData(" { \n\n\n\n } ")]
public static void GetRawText()
{
const string json =
- // Don't let there be a newline before the first embedded quote,
- // because the index would change across CRLF vs LF compile environments.
+// Don't let there be a newline before the first embedded quote,
+// because the index would change across CRLF vs LF compile environments.
@"{ "" weird property name""
:
{
ReadOnlyMemory<byte> data,
int segmentCount,
in JsonReaderState state,
- bool isFinalBlock=false)
+ bool isFinalBlock = false)
{
if (segmentCount == 0)
{
try
{
+ byte[] value = json.GetBytesFromBase64();
+ Assert.True(false, "Expected GetBytesFromBase64 to throw InvalidOperationException due to mismatch token type.");
+ }
+ catch (InvalidOperationException)
+ { }
+
+ try
+ {
+ json.TryGetBytesFromBase64(out byte[] value);
+ Assert.True(false, "Expected TryGetBytesFromBase64 to throw InvalidOperationException due to mismatch token type.");
+ }
+ catch (InvalidOperationException)
+ { }
+
+ try
+ {
DateTime value = json.GetDateTime();
Assert.True(false, "Expected GetDateTime to throw InvalidOperationException due to mismatched token type.");
}
}
}
+ [Fact]
+ public static void GetBase64Unescapes()
+ {
+ string jsonString = "\"\\u0031234\""; // equivalent to "\"1234\""
+
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default);
+ Assert.True(json.Read());
+
+ byte[] expected = Convert.FromBase64String("1234"); // new byte[3] { 215, 109, 248 }
+
+ byte[] value = json.GetBytesFromBase64();
+ Assert.Equal(expected, value);
+ Assert.True(json.TryGetBytesFromBase64(out value));
+ Assert.Equal(expected, value);
+ }
+
+ [Theory]
+ [InlineData("\"ABC=\"")]
+ [InlineData("\"AB+D\"")]
+ [InlineData("\"ABCD\"")]
+ [InlineData("\"ABC/\"")]
+ [InlineData("\"++++\"")]
+ [InlineData(null)] // Large randomly generated string
+ public static void ValidBase64(string jsonString)
+ {
+ if (jsonString == null)
+ {
+ var random = new Random(42);
+ var charArray = new char[502];
+ charArray[0] = '"';
+ for (int i = 1; i < charArray.Length; i++)
+ {
+ charArray[i] = (char)random.Next('A', 'Z'); // ASCII values (between 65 and 90) that constitute valid base 64 string.
+ }
+ charArray[charArray.Length - 1] = '"';
+ jsonString = new string(charArray);
+ }
+
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default);
+ Assert.True(json.Read());
+
+ byte[] expected = Convert.FromBase64String(jsonString.AsSpan(1, jsonString.Length - 2).ToString());
+
+ byte[] value = json.GetBytesFromBase64();
+ Assert.Equal(expected, value);
+ Assert.True(json.TryGetBytesFromBase64(out value));
+ Assert.Equal(expected, value);
+ }
+
+ [Theory]
+ [InlineData("\"ABC===\"")]
+ [InlineData("\"ABC\"")]
+ [InlineData("\"ABC!\"")]
+ [InlineData(null)] // Large randomly generated string
+ public static void InvalidBase64(string jsonString)
+ {
+ if (jsonString == null)
+ {
+ var random = new Random(42);
+ var charArray = new char[500];
+ charArray[0] = '"';
+ for (int i = 1; i < charArray.Length; i++)
+ {
+ charArray[i] = (char)random.Next('?', '\\'); // ASCII values (between 63 and 91) that don't need to be escaped.
+ }
+
+ charArray[256] = '\\';
+ charArray[257] = '"';
+ charArray[charArray.Length - 1] = '"';
+ jsonString = new string(charArray);
+ }
+
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state: default);
+ Assert.True(json.Read());
+ Assert.False(json.TryGetBytesFromBase64(out byte[] value));
+ Assert.Null(value);
+
+ try
+ {
+ byte[] val = json.GetBytesFromBase64();
+ Assert.True(false, "Expected InvalidOperationException when trying to decode base 64 string for invalid UTF-16 JSON text.");
+ }
+ catch (FormatException) { }
+ }
+
+ [Theory]
+ [InlineData("\"a\\uDD1E\"")]
+ [InlineData("\"a\\uDD1Eb\"")]
+ [InlineData("\"a\\uD834\"")]
+ [InlineData("\"a\\uD834\\u0030\"")]
+ [InlineData("\"a\\uD834\\uD834\"")]
+ [InlineData("\"a\\uD834b\"")]
+ [InlineData("\"a\\uDD1E\\uD834b\"")]
+ [InlineData("\"a\\\\uD834\\uDD1Eb\"")]
+ [InlineData("\"a\\uDD1E\\\\uD834b\"")]
+ public static void TestingGetBase64InvalidUTF16(string jsonString)
+ {
+ byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
+
+ foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling)))
+ {
+ var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling });
+ var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state);
+
+ Assert.True(json.Read());
+ Assert.Equal(JsonTokenType.String, json.TokenType);
+ try
+ {
+ byte[] val = json.GetBytesFromBase64();
+ Assert.True(false, "Expected InvalidOperationException when trying to decode base 64 string for invalid UTF-16 JSON text.");
+ }
+ catch (InvalidOperationException) { }
+
+ try
+ {
+ json.TryGetBytesFromBase64(out byte[] val);
+ Assert.True(false, "Expected InvalidOperationException when trying to decode base 64 string for invalid UTF-16 JSON text.");
+ }
+ catch (InvalidOperationException) { }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidUTF8Strings))]
+ public static void TestingGetBase64InvalidUTF8(byte[] dataUtf8)
+ {
+ foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling)))
+ {
+ var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling });
+ var json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state);
+
+ // It is expected that the Utf8JsonReader won't throw an exception here
+ Assert.True(json.Read());
+ Assert.Equal(JsonTokenType.String, json.TokenType);
+
+ while (json.Read())
+ ;
+
+ json = new Utf8JsonReader(dataUtf8, isFinalBlock: true, state);
+
+ while (json.Read())
+ {
+ if (json.TokenType == JsonTokenType.String)
+ {
+ try
+ {
+ byte[] val = json.GetBytesFromBase64();
+ Assert.True(false, "Expected InvalidOperationException when trying to decode base 64 string for invalid UTF-8 JSON text.");
+ }
+ catch (FormatException) { }
+
+ Assert.False(json.TryGetBytesFromBase64(out byte[] value));
+ Assert.Null(value);
+ }
+ }
+ }
+ }
+
[Theory]
[MemberData(nameof(GetCommentTestData))]
public static void TestingGetComment(string jsonData, string expected)
Assert.Throws<ArgumentException>(() => jsonUtf8.WriteStartArray(key));
}
+ [ConditionalTheory(nameof(IsX64))]
+ [OuterLoop]
+ [InlineData(true, true)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(false, false)]
+ public void WritingTooLargeBase64Bytes(bool formatted, bool skipValidation)
+ {
+ byte[] value;
+
+ try
+ {
+ value = new byte[200_000_000];
+ }
+ catch (OutOfMemoryException)
+ {
+ return;
+ }
+
+ value.AsSpan().Fill(255);
+
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+ var output = new ArrayBufferWriter<byte>(1024);
+
+ var jsonUtf8 = new Utf8JsonWriter(output, options);
+ Assert.Throws<ArgumentException>(() => jsonUtf8.WriteBase64StringValue(value));
+
+ jsonUtf8 = new Utf8JsonWriter(output, options);
+ jsonUtf8.WriteStartObject();
+ Assert.Throws<ArgumentException>(() => jsonUtf8.WriteBase64String("foo", value));
+
+ jsonUtf8 = new Utf8JsonWriter(output, options);
+ jsonUtf8.WriteStartObject();
+ Assert.Throws<ArgumentException>(() => jsonUtf8.WriteBase64String(Encoding.UTF8.GetBytes("foo"), value));
+
+ jsonUtf8 = new Utf8JsonWriter(output, options);
+ jsonUtf8.WriteStartObject();
+ Assert.Throws<ArgumentException>(() => jsonUtf8.WriteBase64String("foo".AsSpan(), value));
+
+ jsonUtf8 = new Utf8JsonWriter(output, options);
+ jsonUtf8.WriteStartObject();
+ Assert.Throws<ArgumentException>(() => jsonUtf8.WriteBase64String(JsonEncodedText.Encode("foo"), value));
+ }
+
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
+ public void WriteBase64String(bool formatted, bool skipValidation)
+ {
+ string propertyName = "message";
+ byte[] value = { 1, 2, 3, 4, 5 };
+ string expectedStr = GetBase64ExpectedString(prettyPrint: formatted, propertyName, value);
+
+ JsonEncodedText encodedPropertyName = JsonEncodedText.Encode(propertyName);
+
+ byte[] utf8PropertyName = Encoding.UTF8.GetBytes("message");
+
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+ for (int i = 0; i < 4; i++)
+ {
+ var output = new ArrayBufferWriter<byte>(32);
+ var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+ jsonUtf8.WriteStartObject();
+
+ switch (i)
+ {
+ case 0:
+ jsonUtf8.WriteBase64String(propertyName, value);
+ jsonUtf8.WriteBase64String(propertyName, value);
+ break;
+ case 1:
+ jsonUtf8.WriteBase64String(propertyName.AsSpan(), value);
+ jsonUtf8.WriteBase64String(propertyName.AsSpan(), value);
+ break;
+ case 2:
+ jsonUtf8.WriteBase64String(utf8PropertyName, value);
+ jsonUtf8.WriteBase64String(utf8PropertyName, value);
+ break;
+ case 3:
+ jsonUtf8.WriteBase64String(encodedPropertyName, value);
+ jsonUtf8.WriteBase64String(encodedPropertyName, value);
+ break;
+ }
+
+ jsonUtf8.WriteEndObject();
+ jsonUtf8.Flush();
+
+ AssertContents(expectedStr, output);
+ }
+ }
+
+ [Theory]
+ [InlineData(true, true)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(false, false)]
+ public void WriteBase64StringEscaped(bool formatted, bool skipValidation)
+ {
+ string propertyName = "mess><age";
+ byte[] value = { 1, 2, 3, 4, 5, 6 };
+ string expectedStr = GetBase64ExpectedString(prettyPrint: formatted, propertyName, value);
+
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+ ReadOnlySpan<char> propertyNameSpan = propertyName.AsSpan();
+ ReadOnlySpan<byte> propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName);
+ JsonEncodedText encodedPropertyName = JsonEncodedText.Encode(propertyName);
+
+ for (int i = 0; i < 4; i++)
+ {
+ var output = new ArrayBufferWriter<byte>(32);
+ var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+ jsonUtf8.WriteStartObject();
+
+ switch (i)
+ {
+ case 0:
+ jsonUtf8.WriteBase64String(propertyName, value);
+ jsonUtf8.WriteBase64String(propertyName, value);
+ break;
+ case 1:
+ jsonUtf8.WriteBase64String(propertyNameSpan, value);
+ jsonUtf8.WriteBase64String(propertyNameSpan, value);
+ break;
+ case 2:
+ jsonUtf8.WriteBase64String(propertyNameSpanUtf8, value);
+ jsonUtf8.WriteBase64String(propertyNameSpanUtf8, value);
+ break;
+ case 3:
+ jsonUtf8.WriteBase64String(encodedPropertyName, value);
+ jsonUtf8.WriteBase64String(encodedPropertyName, value);
+ break;
+ }
+
+ jsonUtf8.WriteEndObject();
+ jsonUtf8.Flush();
+
+ AssertContents(expectedStr, output);
+ }
+
+ // Verify that escaping does not change the input strings/spans.
+ Assert.Equal("mess><age", propertyName);
+ Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6 }, value);
+ Assert.True(propertyName.AsSpan().SequenceEqual(propertyNameSpan));
+ Assert.True(Encoding.UTF8.GetBytes(propertyName).AsSpan().SequenceEqual(propertyNameSpanUtf8));
+ }
+
+ [Theory]
+ [InlineData(true, true)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(false, false)]
+ public void WritePartialBase64String(bool formatted, bool skipValidation)
+ {
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+
+ var output = new ArrayBufferWriter<byte>(10);
+ var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+ jsonUtf8.WriteStartObject();
+
+ Assert.Equal(0, jsonUtf8.BytesCommitted);
+ Assert.Equal(1, jsonUtf8.BytesPending);
+
+ jsonUtf8.WriteBase64String("message", new byte[] { 201, 153, 199 });
+
+ Assert.Equal(0, jsonUtf8.BytesCommitted);
+ if (formatted)
+ Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space
+ else
+ Assert.Equal(17, jsonUtf8.BytesPending);
+
+ jsonUtf8.Flush();
+
+ if (formatted)
+ Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space
+ else
+ Assert.Equal(17, jsonUtf8.BytesCommitted);
+
+ Assert.Equal(0, jsonUtf8.BytesPending);
+
+ jsonUtf8.WriteBase64String("message", new byte[] { 201, 153, 199 });
+ jsonUtf8.WriteEndObject();
+
+ if (formatted)
+ Assert.Equal(17 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted);
+ else
+ Assert.Equal(17, jsonUtf8.BytesCommitted);
+
+ if (formatted)
+ Assert.Equal(18 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space
+ else
+ Assert.Equal(18, jsonUtf8.BytesPending);
+
+ jsonUtf8.Flush();
+
+ if (formatted)
+ Assert.Equal(35 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space
+ else
+ Assert.Equal(35, jsonUtf8.BytesCommitted);
+
+ Assert.Equal(0, jsonUtf8.BytesPending);
+ }
+
+ [Theory]
+ [InlineData(true, true)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(false, false)]
public void WriteInvalidPartialJson(bool formatted, bool skipValidation)
{
var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
+ public void WriteInvalidBase64(bool formatted, bool skipValidation)
+ {
+ {
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+ var output = new ArrayBufferWriter<byte>(10);
+ using var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+ jsonUtf8.WriteStartObject();
+
+ Assert.Equal(0, jsonUtf8.BytesCommitted);
+ Assert.Equal(1, jsonUtf8.BytesPending);
+
+ jsonUtf8.Flush();
+
+ Assert.Equal(1, jsonUtf8.BytesCommitted);
+ Assert.Equal(0, jsonUtf8.BytesPending);
+
+ if (skipValidation)
+ {
+ jsonUtf8.WriteBase64StringValue(new byte[] { 1, 2 });
+ jsonUtf8.WriteEndArray();
+ }
+ else
+ {
+ Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteBase64StringValue(new byte[] { 1, 2 }));
+ Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndArray());
+ }
+ }
+ {
+ var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation };
+ var output = new ArrayBufferWriter<byte>(10);
+ using var jsonUtf8 = new Utf8JsonWriter(output, options);
+
+ jsonUtf8.WriteStartArray();
+
+ Assert.Equal(0, jsonUtf8.BytesCommitted);
+ Assert.Equal(1, jsonUtf8.BytesPending);
+
+ jsonUtf8.Flush();
+
+ Assert.Equal(1, jsonUtf8.BytesCommitted);
+ Assert.Equal(0, jsonUtf8.BytesPending);
+
+ if (skipValidation)
+ {
+ jsonUtf8.WriteBase64String("foo", new byte[] { 1, 2 });
+ jsonUtf8.WriteEndObject();
+ }
+ else
+ {
+ Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteBase64String("foo", new byte[] { 1, 2 }));
+ Assert.Throws<InvalidOperationException>(() => jsonUtf8.WriteEndObject());
+ }
+ }
+ }
+
+ [Fact]
+ public void WriteBase64Escapes()
+ {
+ var output = new ArrayBufferWriter<byte>(10);
+ using var jsonUtf8 = new Utf8JsonWriter(output);
+
+ var bytes = new byte[3] { 0xFB, 0xEF, 0xBE };
+ jsonUtf8.WriteBase64StringValue(bytes);
+
+ jsonUtf8.Flush();
+
+ AssertContents("\"\\u002b\\u002b\\u002b\\u002b\"", output);
+ }
+
+ [Fact]
+ public void WriteBase64EscapesLarge()
+ {
+ var output = new ArrayBufferWriter<byte>(10);
+ using var jsonUtf8 = new Utf8JsonWriter(output);
+
+ var bytes = new byte[200];
+
+ bytes.AsSpan().Fill(100);
+ bytes[4] = 0xFB;
+ bytes[5] = 0xEF;
+ bytes[6] = 0xBE;
+ bytes[15] = 0;
+ bytes[16] = 0x10;
+ bytes[17] = 0xBF;
+
+ jsonUtf8.WriteBase64StringValue(bytes);
+
+ jsonUtf8.Flush();
+
+ var builder = new StringBuilder();
+ builder.Append("\"ZGRkZPvvvmRkZGRkZGRkABC\\u002f");
+ for (int i = 0; i < 60; i++)
+ {
+ builder.Append("ZGRk");
+ }
+ builder.Append("ZGQ=\"");
+ AssertContents(builder.ToString(), output);
+ }
+
+ [Theory]
+ [InlineData(true, true)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(false, false)]
public void WriteInvalidDepthPartial(bool formatted, bool skipValidation)
{
{
{
var propertyArray = new char[128];
- char[] specialCases = { '+', '`', (char)0x7F };
+ char[] specialCases = { '+', '`', (char)0x7F, '/' };
for (int i = 0; i < propertyArray.Length; i++)
{
if (Array.IndexOf(specialCases, (char)i) != -1)
return Encoding.UTF8.GetString(ms.ToArray());
}
+ private static string GetBase64ExpectedString(bool prettyPrint, string propertyName, byte[] value)
+ {
+ var ms = new MemoryStream();
+ TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true);
+
+ var json = new JsonTextWriter(streamWriter)
+ {
+ Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
+ StringEscapeHandling = StringEscapeHandling.EscapeHtml
+ };
+
+ json.WriteStartObject();
+ json.WritePropertyName(propertyName);
+ json.WriteValue(value);
+ json.WritePropertyName(propertyName);
+ json.WriteValue(value);
+ json.WriteEnd();
+
+ json.Flush();
+
+ return Encoding.UTF8.GetString(ms.ToArray());
+ }
+
private static string GetCommentExpectedString(bool prettyPrint, string comment)
{
var ms = new MemoryStream();