Skip to content

Commit

Permalink
Fix issue #1181, refactor for the json string escape in Json writer
Browse files Browse the repository at this point in the history
  • Loading branch information
xuzhg committed Sep 14, 2018
1 parent cc684fe commit 862897b
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 58 deletions.
Expand Up @@ -814,6 +814,9 @@
<Compile Include="$(ODataCrossTargettingSourcePath)\ODataStreamReferenceValue.cs">
<Link>Microsoft\OData\Core\ODataStreamReferenceValue.cs</Link>
</Compile>
<Compile Include="$(ODataCrossTargettingSourcePath)\ODataStringEscapeOption.cs">
<Link>Microsoft\OData\Core\ODataStringEscapeOption.cs</Link>
</Compile>
<Compile Include="$(ODataCrossTargettingSourcePath)\ODataTypeAnnotation.cs">
<Link>Microsoft\OData\Core\ODataTypeAnnotation.cs</Link>
</Compile>
Expand Down
Expand Up @@ -887,6 +887,9 @@
<Compile Include="..\ODataStreamReferenceValue.cs">
<Link>ODataStreamReferenceValue.cs</Link>
</Compile>
<Compile Include="..\ODataStringEscapeOption.cs">
<Link>ODataStringEscapeOption.cs</Link>
</Compile>
<Compile Include="..\ODataTypeAnnotation.cs">
<Link>ODataTypeAnnotation.cs</Link>
</Compile>
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Core/ContainerBuilderExtensions.cs
Expand Up @@ -121,7 +121,7 @@ public static IContainerBuilder AddDefaultODataServices(this IContainerBuilder b
Debug.Assert(builder != null, "builder != null");

builder.AddService<IJsonReaderFactory, DefaultJsonReaderFactory>(ServiceLifetime.Singleton);
builder.AddService<IJsonWriterFactory, DefaultJsonWriterFactory>(ServiceLifetime.Singleton);
builder.AddService<IJsonWriterFactory>(ServiceLifetime.Singleton, sp => new DefaultJsonWriterFactory());
builder.AddService(ServiceLifetime.Singleton, sp => ODataMediaTypeResolver.GetMediaTypeResolver(null));
builder.AddService<ODataMessageInfo>(ServiceLifetime.Scoped);
builder.AddServicePrototype(new ODataMessageReaderSettings());
Expand Down
33 changes: 31 additions & 2 deletions src/Microsoft.OData.Core/Json/DefaultJsonWriterFactory.cs
Expand Up @@ -4,15 +4,44 @@
// </copyright>
//---------------------------------------------------------------------

using System;
using System.IO;

namespace Microsoft.OData.Json
{
internal sealed class DefaultJsonWriterFactory : IJsonWriterFactory
/// <summary>
/// Default JSON writer factory
/// </summary>
public sealed class DefaultJsonWriterFactory : IJsonWriterFactory
{
private ODataStringEscapeOption _stringEscapeOption;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultJsonWriterFactory" />.
/// </summary>
public DefaultJsonWriterFactory()
: this(ODataStringEscapeOption.EscapeNonAscii)
{ }

/// <summary>
/// Initializes a new instance of the <see cref="DefaultJsonWriterFactory" />.
/// </summary>
/// <param name="stringEscapeOption">The string escape option.</param>
public DefaultJsonWriterFactory(ODataStringEscapeOption stringEscapeOption)
{
_stringEscapeOption = stringEscapeOption;
}

/// <summary>
/// Creates a new JSON writer of <see cref="IJsonWriter"/>.
/// </summary>
/// <param name="textWriter">Writer to which text needs to be written.</param>
/// <param name="isIeee754Compatible">True if it is IEEE754Compatible.</param>
/// <returns>The JSON writer created.</returns>
[CLSCompliant(false)]
public IJsonWriter CreateJsonWriter(TextWriter textWriter, bool isIeee754Compatible)
{
return new JsonWriter(textWriter, isIeee754Compatible);
return new JsonWriter(textWriter, isIeee754Compatible, _stringEscapeOption);
}
}
}
29 changes: 21 additions & 8 deletions src/Microsoft.OData.Core/Json/JsonValueUtils.cs
Expand Up @@ -38,7 +38,6 @@ internal static class JsonValueUtils
/// </summary>
internal static readonly NumberFormatInfo ODataNumberFormatInfo = JsonValueUtils.InitializeODataNumberFormatInfo();


/// <summary>
/// Const tick value for calculating tick values.
/// </summary>
Expand Down Expand Up @@ -296,8 +295,9 @@ internal static void WriteValue(TextWriter writer, sbyte value)
/// </summary>
/// <param name="writer">The text writer to write the output to.</param>
/// <param name="value">String value to be written.</param>
/// <param name="stringEscapeOption">The string escape option.</param>
/// <param name="buffer">Char buffer to use for streaming data</param>
internal static void WriteValue(TextWriter writer, string value, ref char[] buffer)
internal static void WriteValue(TextWriter writer, string value, ODataStringEscapeOption stringEscapeOption, ref char[] buffer)
{
Debug.Assert(writer != null, "writer != null");

Expand All @@ -307,7 +307,7 @@ internal static void WriteValue(TextWriter writer, string value, ref char[] buff
}
else
{
JsonValueUtils.WriteEscapedJsonString(writer, value, ref buffer);
JsonValueUtils.WriteEscapedJsonString(writer, value, stringEscapeOption, ref buffer);
}
}

Expand Down Expand Up @@ -337,16 +337,18 @@ internal static void WriteValue(TextWriter writer, byte[] value)
/// </summary>
/// <param name="writer">The text writer to write the output to.</param>
/// <param name="inputString">Input string value.</param>
/// <param name="stringEscapeOption">The string escape option.</param>
/// <param name="buffer">Char buffer to use for streaming data</param>
internal static void WriteEscapedJsonString(TextWriter writer, string inputString, ref char[] buffer)
internal static void WriteEscapedJsonString(TextWriter writer, string inputString,
ODataStringEscapeOption stringEscapeOption, ref char[] buffer)
{
Debug.Assert(writer != null, "writer != null");
Debug.Assert(inputString != null, "The string value must not be null.");

writer.Write(JsonConstants.QuoteCharacter);

int firstIndex;
if (!JsonValueUtils.CheckIfStringHasSpecialChars(inputString, out firstIndex))
if (!JsonValueUtils.CheckIfStringHasSpecialChars(inputString, stringEscapeOption, out firstIndex))
{
writer.Write(inputString);
}
Expand Down Expand Up @@ -387,7 +389,12 @@ internal static void WriteEscapedJsonString(TextWriter writer, string inputStrin
for (; currentIndex < inputStringLength; currentIndex++)
{
char c = inputString[currentIndex];
string escapedString = JsonValueUtils.SpecialCharToEscapedStringMap[c];
string escapedString = null;

if (stringEscapeOption == ODataStringEscapeOption.EscapeNonAscii || c < 0x7F)
{
escapedString = JsonValueUtils.SpecialCharToEscapedStringMap[c];
}

// Append the unhandled characters (that do not require special treament)
// to the buffer.
Expand Down Expand Up @@ -491,13 +498,14 @@ internal static string GetEscapedJsonString(string inputString)
}

/// <summary>
/// Checks if the string contains special char and returns the first index
/// Checks if the string contains special char and returns the first index
/// of special char if present.
/// </summary>
/// <param name="inputString">string that might contain special characters.</param>
/// <param name="stringEscapeOption">The string escape option.</param>
/// <param name="firstIndex">first index of the special char</param>
/// <returns>A value indicating whether the string contains special character</returns>
private static bool CheckIfStringHasSpecialChars(string inputString, out int firstIndex)
private static bool CheckIfStringHasSpecialChars(string inputString, ODataStringEscapeOption stringEscapeOption, out int firstIndex)
{
Debug.Assert(inputString != null, "The string value must not be null.");

Expand All @@ -507,6 +515,11 @@ private static bool CheckIfStringHasSpecialChars(string inputString, out int fir
{
char c = inputString[currentIndex];

if (stringEscapeOption == ODataStringEscapeOption.EscapeOnlyControls && c >= 0x7F)
{
continue;
}

// Append the un-handled characters (that do not require special treatment)
// to the string builder when special characters are detected.
if (JsonValueUtils.SpecialCharToEscapedStringMap[c] != null)
Expand Down
29 changes: 24 additions & 5 deletions src/Microsoft.OData.Core/Json/JsonWriter.cs
Expand Up @@ -38,6 +38,11 @@ internal sealed class JsonWriter : IJsonWriter
/// </summary>
private readonly bool isIeee754Compatible;

/// <summary>
/// Gets or sets a value indicating how to escape the string when writing JSON string.
/// </summary>
private readonly ODataStringEscapeOption stringEscapeOption;

/// <summary>
/// The buffer to help with streaming responses.
/// </summary>
Expand All @@ -47,12 +52,24 @@ internal sealed class JsonWriter : IJsonWriter
/// Creates a new instance of Json writer.
/// </summary>
/// <param name="writer">Writer to which text needs to be written.</param>
/// <param name="isIeee754Compatible">if it is IEEE754Compatible</param>
/// <param name="isIeee754Compatible">if it is IEEE754Compatible.</param>
internal JsonWriter(TextWriter writer, bool isIeee754Compatible)
: this(writer, isIeee754Compatible, ODataStringEscapeOption.EscapeNonAscii)
{
}

/// <summary>
/// Creates a new instance of Json writer.
/// </summary>
/// <param name="writer">Writer to which text needs to be written.</param>
/// <param name="isIeee754Compatible">if it is IEEE754Compatible.</param>
/// <param name="stringEscapeOption">Specifies how to escape string.</param>
internal JsonWriter(TextWriter writer, bool isIeee754Compatible, ODataStringEscapeOption stringEscapeOption)
{
this.writer = new NonIndentedTextWriter(writer);
this.scopes = new Stack<Scope>();
this.isIeee754Compatible = isIeee754Compatible;
this.stringEscapeOption = stringEscapeOption;
}

/// <summary>
Expand Down Expand Up @@ -167,7 +184,7 @@ public void WriteName(string name)

currentScope.ObjectCount++;

JsonValueUtils.WriteEscapedJsonString(this.writer, name, ref this.buffer);
JsonValueUtils.WriteEscapedJsonString(this.writer, name, this.stringEscapeOption, ref this.buffer);
this.writer.Write(JsonConstants.NameValueSeparator);
}

Expand Down Expand Up @@ -231,7 +248,8 @@ public void WriteValue(long value)
// if it is IEEE754Compatible, write numbers with quotes; otherwise, write numbers directly.
if (isIeee754Compatible)
{
JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), ref this.buffer);
JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture),
this.stringEscapeOption, ref this.buffer);
}
else
{
Expand Down Expand Up @@ -270,7 +288,8 @@ public void WriteValue(decimal value)
// if it is not IEEE754Compatible, write numbers directly without quotes;
if (isIeee754Compatible)
{
JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), ref this.buffer);
JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture),
this.stringEscapeOption, ref this.buffer);
}
else
{
Expand Down Expand Up @@ -345,7 +364,7 @@ public void WriteValue(sbyte value)
public void WriteValue(string value)
{
this.WriteValueSeparator();
JsonValueUtils.WriteValue(this.writer, value, ref this.buffer);
JsonValueUtils.WriteValue(this.writer, value, this.stringEscapeOption, ref this.buffer);
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.csproj
Expand Up @@ -63,6 +63,7 @@
<Compile Include="MultipartMixed\ODataMultipartMixedBatchReader.cs" />
<Compile Include="MultipartMixed\ODataMultipartMixedBatchReaderStream.cs" />
<Compile Include="MultipartMixed\ODataMultipartMixedBatchWriter.cs" />
<Compile Include="ODataStringEscapeOption.cs" />
<Compile Include="UnknownEntitySet.cs" />
<Compile Include="IContainerProvider.cs" />
<Compile Include="IDuplicatePropertyNameChecker.cs" />
Expand Down
25 changes: 25 additions & 0 deletions src/Microsoft.OData.Core/ODataStringEscapeOption.cs
@@ -0,0 +1,25 @@
//---------------------------------------------------------------------
// <copyright file="ODataStringEscapeOption.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData
{
/// <summary>
/// Enum type to specify how to escape string value when writing JSON text.
/// </summary>
public enum ODataStringEscapeOption
{
/// <summary>
/// All non-ASCII and control characters (e.g. newline) are escaped.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Ascii is meaningful")]
EscapeNonAscii,

/// <summary>
/// Only control characters (e.g. newline) are escaped.
/// </summary>
EscapeOnlyControls
}
}

0 comments on commit 862897b

Please sign in to comment.