Skip to content

Commit

Permalink
Address review feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia committed Jun 30, 2021
1 parent 2b9a5ec commit 3ac5592
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal static class JsonConstants
// When transcoding from UTF8 -> UTF16, the byte count threshold where we rent from the array pool before performing a normal alloc.
public const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024;

public const int MaxRawValueLength = int.MaxValue / MaxExpansionFactorWhileTranscoding;

public const int MaxEscapedTokenSize = 1_000_000_000; // Max size for already escaped value.
public const int MaxUnescapedTokenSize = MaxEscapedTokenSize / MaxExpansionFactorWhileEscaping; // 166_666_666 bytes
public const int MaxBase64ValueTokenSize = (MaxEscapedTokenSize >> 2) * 3 / MaxExpansionFactorWhileEscaping; // 125_000_000 bytes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,93 @@ namespace System.Text.Json
public sealed partial class Utf8JsonWriter
{
/// <summary>
/// Writes the input as JSON content.
/// Writes the input as JSON content. It is expected that the input content is a single complete JSON value.
/// </summary>
/// <param name="json">The raw JSON content to write.</param>
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
/// <param name="skipInputValidation">Whether to validate if the input is an RFC 8259-compliant JSON payload.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="json"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown if the length of the input is zero or greater than 715,827,882.</exception>
/// <exception cref="JsonException">Thrown if <paramref name="skipInputValidation"/> is <see langword="false"/>, and the input is not RFC 8259-compliant.</exception>
/// <remarks>
/// When writing untrused JSON values, do not set <paramref name="skipInputValidation"/> to <see langword="true"/> as this can result in invalid JSON
/// being written, and/or the overall payload being written to the writer instance being invalid.
///
/// When using this method, the input content will be written to the writer destination as-is, unless validation fails.
///
/// The <see cref="JsonWriterOptions.SkipValidation"/> value for the writer instance is honored when using this method.
///
/// The <see cref="JsonWriterOptions.Indented"/> and <see cref="JsonWriterOptions.Encoder"/> values for the writer instance are not applied when using this method.
/// </remarks>
public void WriteRawValue(string json, bool skipInputValidation = false)
{
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (json == null)
{
throw new ArgumentNullException(nameof(json));
}

WriteRawValue(json.AsSpan(), skipInputValidation);
TranscodeAndWriteRawValue(json.AsSpan(), skipInputValidation);
}

/// <summary>
/// Writes the input as JSON content.
/// Writes the input as JSON content. It is expected that the input content is a single complete JSON value.
/// </summary>
/// <param name="json">The raw JSON content to write.</param>
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
/// <param name="skipInputValidation">Whether to validate if the input is an RFC 8259-compliant JSON payload.</param>
/// <exception cref="ArgumentException">Thrown if the length of the input is zero or greater than 715,827,882.</exception>
/// <exception cref="JsonException">Thrown if <paramref name="skipInputValidation"/> is <see langword="false"/>, and the input is not RFC 8259-compliant.</exception>
/// <remarks>
/// When writing untrused JSON values, do not set <paramref name="skipInputValidation"/> to <see langword="true"/> as this can result in invalid JSON
/// being written, and/or the overall payload being written to the writer instance being invalid.
///
/// When using this method, the input content will be written to the writer destination as-is, unless validation fails.
///
/// The <see cref="JsonWriterOptions.SkipValidation"/> value for the writer instance is honored when using this method.
///
/// The <see cref="JsonWriterOptions.Indented"/> and <see cref="JsonWriterOptions.Encoder"/> values for the writer instance are not applied when using this method.
/// </remarks>
public void WriteRawValue(ReadOnlySpan<char> json, bool skipInputValidation = false)
{
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

TranscodeAndWriteRawValue(json, skipInputValidation);
}

/// <summary>
/// Writes the input as JSON content. It is expected that the input content is a single complete JSON value.
/// </summary>
/// <param name="utf8Json">The raw JSON content to write.</param>
/// <param name="skipInputValidation">Whether to validate if the input is an RFC 8259-compliant JSON payload.</param>
/// <exception cref="ArgumentException">Thrown if the length of the input is zero or greater than 715,827,882.</exception>
/// <exception cref="JsonException">Thrown if <paramref name="skipInputValidation"/> is <see langword="false"/>, and the input is not RFC 8259-compliant.</exception>
/// <remarks>
/// When writing untrused JSON values, do not set <paramref name="skipInputValidation"/> to <see langword="true"/> as this can result in invalid JSON
/// being written, and/or the overall payload being written to the writer instance being invalid.
///
/// When using this method, the input content will be written to the writer destination as-is, unless validation fails.
///
/// The <see cref="JsonWriterOptions.SkipValidation"/> value for the writer instance is honored when using this method.
///
/// The <see cref="JsonWriterOptions.Indented"/> and <see cref="JsonWriterOptions.Encoder"/> values for the writer instance are not applied when using this method.
/// </remarks>
public void WriteRawValue(ReadOnlySpan<byte> utf8Json, bool skipInputValidation = false)
{
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

WriteRawValueInternal(utf8Json, skipInputValidation);
}

private void TranscodeAndWriteRawValue(ReadOnlySpan<char> json, bool skipInputValidation)
{
byte[]? tempArray = null;

Expand Down Expand Up @@ -55,33 +122,40 @@ public void WriteRawValue(ReadOnlySpan<char> json, bool skipInputValidation = fa
}
}

/// <summary>
/// Writes the input as JSON content.
/// </summary>
/// <param name="utf8Json">The raw JSON content to write.</param>
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
public void WriteRawValue(ReadOnlySpan<byte> utf8Json, bool skipInputValidation = false)
private void WriteRawValueInternal(ReadOnlySpan<byte> utf8Json, bool skipInputValidation)
{
if (utf8Json.Length == 0)
int len = utf8Json.Length;

if (len == 0)
{
ThrowHelper.ThrowArgumentException(SR.ExpectedJsonTokens);
}

if (!skipInputValidation)
else if (len > JsonConstants.MaxRawValueLength)
{
Utf8JsonReader reader = new Utf8JsonReader(utf8Json);
ThrowHelper.ThrowArgumentException_ValueTooLarge(len);
}

try
{
while (reader.Read());
}
catch (JsonReaderException ex)
if (skipInputValidation)
{
// Treat all unvalidated raw JSON value writes as string. If the payload is valid, this approach does
// not affect structural validation since a string token is equivalent to a complete object, array,
// or other complete JSON tokens when considering structural validation on subsequent writer calls.
// If the payload is not valid, then we make no guarantees about the structural validation of the final payload.
_tokenType = JsonTokenType.String;
}
else
{
// Utilize reader validation.
Utf8JsonReader reader = new(utf8Json);
while (reader.Read())
{
ThrowHelper.ThrowArgumentException(ex.Message);
_tokenType = reader.TokenType;
}
}

int maxRequired = utf8Json.Length + 1; // Optionally, 1 list separator
// TODO (https://github.com/dotnet/runtime/issues/29293):
// investigate writing this in chunks, rather than requesting one potentially long, contiguous buffer.
int maxRequired = len + 1; // Optionally, 1 list separator

if (_memory.Length - BytesPending < maxRequired)
{
Expand All @@ -96,12 +170,10 @@ public void WriteRawValue(ReadOnlySpan<byte> utf8Json, bool skipInputValidation
}

utf8Json.CopyTo(output.Slice(BytesPending));
BytesPending += utf8Json.Length;
BytesPending += len;

SetFlagToAddListSeparatorBeforeNextItem();

// Treat all raw JSON value writes as string.
_tokenType = JsonTokenType.String;
SetFlagToAddListSeparatorBeforeNextItem();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@
<Compile Include="Utf8JsonReaderTests.TryGet.Date.cs" />
<Compile Include="Utf8JsonReaderTests.ValueTextEquals.cs" />
<Compile Include="Utf8JsonWriterTests.cs" />
<Compile Include="Utf8JsonWriterTests.WriteRaw.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\src\System\Text\Json\BitStack.cs" Link="BitStack.cs" />
<Compile Include="Utf8JsonWriterTests.WriteRaw.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
<Compile Include="$(CommonPath)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
Expand Down
Loading

0 comments on commit 3ac5592

Please sign in to comment.