Skip to content

Commit

Permalink
Do not over-escape writing Base64 strings within JSON using the write…
Browse files Browse the repository at this point in the history
…r. (dotnet/corefx#39522)

Commit migrated from dotnet/corefx@063e38c
  • Loading branch information
ahsonkhan authored Jul 16, 2019
1 parent b9bf83d commit 004d698
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@ private void WriteBase64Minimized(ReadOnlySpan<char> escapedPropertyName, ReadOn
{
int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 6);
Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - encodedLength - 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;
// Optionally, 1 list separator, and up to 3x growth when transcoding.
int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + encodedLength + 6;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down Expand Up @@ -247,11 +247,11 @@ private void WriteBase64Minimized(ReadOnlySpan<byte> escapedPropertyName, ReadOn
{
int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(escapedPropertyName.Length < int.MaxValue - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 6);
Debug.Assert(escapedPropertyName.Length < int.MaxValue - encodedLength - 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;
// Optionally, 1 list separator.
int maxRequired = escapedPropertyName.Length + encodedLength + 6;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down Expand Up @@ -286,11 +286,11 @@ private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnl

int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - indent - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 7 - s_newLineLength);
Debug.Assert(escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding < int.MaxValue - indent - encodedLength - 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;
// Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding.
int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + encodedLength + 7 + s_newLineLength;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down Expand Up @@ -336,11 +336,11 @@ private void WriteBase64Indented(ReadOnlySpan<byte> escapedPropertyName, ReadOnl

int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - (encodedLength * JsonConstants.MaxExpansionFactorWhileEscaping) - 7 - s_newLineLength);
Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - encodedLength - 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;
// Optionally, 1 list separator, and 1-2 bytes for new line.
int maxRequired = indent + escapedPropertyName.Length + encodedLength + 7 + s_newLineLength;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ private void WriteBase64Minimized(ReadOnlySpan<byte> bytes)
{
int encodingLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(encodingLength < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping) - 3);
Debug.Assert(encodingLength < int.MaxValue - 3);

// 2 quotes to surround the base-64 encoded string value, with escaping which can by up to 6x.
// 2 quotes to surround the base-64 encoded string value.
// Optionally, 1 list separator
int maxRequired = (encodingLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 3;
int maxRequired = encodingLength + 3;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down Expand Up @@ -83,11 +83,11 @@ private void WriteBase64Indented(ReadOnlySpan<byte> bytes)

int encodingLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);

Debug.Assert(encodingLength < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping) - indent - 3 - s_newLineLength);
Debug.Assert(encodingLength < int.MaxValue - indent - 3 - s_newLineLength);

// indentation + 2 quotes to surround the base-64 encoded string value, with escaping which can by up to 6x.
// indentation + 2 quotes to surround the base-64 encoded string value.
// Optionally, 1 list separator, and 1-2 bytes for new line
int maxRequired = indent + (encodingLength * JsonConstants.MaxExpansionFactorWhileEscaping) + 3 + s_newLineLength;
int maxRequired = indent + encodingLength + 3 + s_newLineLength;

if (_memory.Length - BytesPending < maxRequired)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,9 @@ private void Base64EncodeAndWrite(ReadOnlySpan<byte> bytes, Span<byte> output, i
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, _options.Encoder, out written);
BytesPending += written;
}
Debug.Assert(destination.Length >= written);
encodedBytes.Slice(0, written).CopyTo(destination);
BytesPending += written;

if (outputText != null)
{
Expand Down
6 changes: 3 additions & 3 deletions src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2463,7 +2463,7 @@ public void WriteInvalidBase64(bool formatted, bool skipValidation)
}

[Fact]
public void WriteBase64Escapes()
public void WriteBase64DoesNotEscape()
{
var output = new ArrayBufferWriter<byte>(10);
using var jsonUtf8 = new Utf8JsonWriter(output);
Expand All @@ -2473,11 +2473,11 @@ public void WriteBase64Escapes()

jsonUtf8.Flush();

AssertContents("\"\\u002b\\u002b\\u002b\\u002b\"", output);
AssertContents("\"++++\"", output);
}

[Fact]
public void WriteBase64EscapesLarge()
public void WriteBase64DoesNotEscapeLarge()
{
var output = new ArrayBufferWriter<byte>(10);
using var jsonUtf8 = new Utf8JsonWriter(output);
Expand Down

0 comments on commit 004d698

Please sign in to comment.