Ensure SystemTextJsonHelper always HTML encodes output. (#12808)
* Ensure JsonSerializer always HTML encodes output. * Update JsonOptions.JsonSerializerOptions to use encoder scheme that does not encode non-ASCII characters by default. This makes the encoding comparable to Json.NET's defaults * In SystemTextJsonHelper, ensure that the content is always HTML-encoded * Unskip skipped test Fixes https://github.com/aspnet/AspNetCore/issues/9946 Fixes https://github.com/aspnet/AspNetCore/issues/11459
This commit is contained in:
parent
709b390157
commit
397f924e8d
|
|
@ -2210,7 +2210,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
public partial class SystemTextJsonOutputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter
|
public partial class SystemTextJsonOutputFormatter : Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter
|
||||||
{
|
{
|
||||||
public SystemTextJsonOutputFormatter(Microsoft.AspNetCore.Mvc.JsonOptions options) { }
|
public SystemTextJsonOutputFormatter(System.Text.Json.JsonSerializerOptions jsonSerializerOptions) { }
|
||||||
public System.Text.Json.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
public System.Text.Json.JsonSerializerOptions SerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||||
public sealed override System.Threading.Tasks.Task WriteResponseBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext context, System.Text.Encoding selectedEncoding) { throw null; }
|
public sealed override System.Threading.Tasks.Task WriteResponseBodyAsync(Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext context, System.Text.Encoding selectedEncoding) { throw null; }
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -17,14 +18,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SystemTextJsonOutputFormatter : TextOutputFormatter
|
public class SystemTextJsonOutputFormatter : TextOutputFormatter
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new <see cref="SystemTextJsonOutputFormatter"/> instance.
|
/// Initializes a new <see cref="SystemTextJsonOutputFormatter"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The <see cref="JsonOptions"/>.</param>
|
/// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/>.</param>
|
||||||
public SystemTextJsonOutputFormatter(JsonOptions options)
|
public SystemTextJsonOutputFormatter(JsonSerializerOptions jsonSerializerOptions)
|
||||||
{
|
{
|
||||||
SerializerOptions = options.JsonSerializerOptions;
|
SerializerOptions = jsonSerializerOptions;
|
||||||
|
|
||||||
SupportedEncodings.Add(Encoding.UTF8);
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
SupportedEncodings.Add(Encoding.Unicode);
|
SupportedEncodings.Add(Encoding.Unicode);
|
||||||
|
|
@ -33,6 +33,19 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
|
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static SystemTextJsonOutputFormatter CreateFormatter(JsonOptions jsonOptions)
|
||||||
|
{
|
||||||
|
var jsonSerializerOptions = jsonOptions.JsonSerializerOptions;
|
||||||
|
|
||||||
|
if (jsonSerializerOptions.Encoder is null)
|
||||||
|
{
|
||||||
|
// If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
|
||||||
|
jsonSerializerOptions = jsonSerializerOptions.Copy(JavaScriptEncoder.UnsafeRelaxedJsonEscaping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SystemTextJsonOutputFormatter(jsonSerializerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="JsonSerializerOptions"/> used to configure the <see cref="JsonSerializer"/>.
|
/// Gets the <see cref="JsonSerializerOptions"/> used to configure the <see cref="JsonSerializer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
|
namespace System.Text.Json
|
||||||
|
{
|
||||||
|
internal static class JsonSerializerOptionsCopyConstructor
|
||||||
|
{
|
||||||
|
public static JsonSerializerOptions Copy(this JsonSerializerOptions serializerOptions, JavaScriptEncoder encoder)
|
||||||
|
{
|
||||||
|
var copiedOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
AllowTrailingCommas = serializerOptions.AllowTrailingCommas,
|
||||||
|
DefaultBufferSize = serializerOptions.DefaultBufferSize,
|
||||||
|
DictionaryKeyPolicy = serializerOptions.DictionaryKeyPolicy,
|
||||||
|
IgnoreNullValues = serializerOptions.IgnoreNullValues,
|
||||||
|
IgnoreReadOnlyProperties = serializerOptions.IgnoreReadOnlyProperties,
|
||||||
|
MaxDepth = serializerOptions.MaxDepth,
|
||||||
|
PropertyNameCaseInsensitive = serializerOptions.PropertyNameCaseInsensitive,
|
||||||
|
PropertyNamingPolicy = serializerOptions.PropertyNamingPolicy,
|
||||||
|
ReadCommentHandling = serializerOptions.ReadCommentHandling,
|
||||||
|
WriteIndented = serializerOptions.WriteIndented
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < serializerOptions.Converters.Count; i++)
|
||||||
|
{
|
||||||
|
copiedOptions.Converters.Add(serializerOptions.Converters[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedOptions.Encoder = encoder;
|
||||||
|
|
||||||
|
return copiedOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,9 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
options.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||||
options.OutputFormatters.Add(new StringOutputFormatter());
|
options.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.OutputFormatters.Add(new StreamOutputFormatter());
|
options.OutputFormatters.Add(new StreamOutputFormatter());
|
||||||
options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(_jsonOptions.Value));
|
|
||||||
|
var jsonOutputFormatter = SystemTextJsonOutputFormatter.CreateFormatter(_jsonOptions.Value);
|
||||||
|
options.OutputFormatters.Add(jsonOutputFormatter);
|
||||||
|
|
||||||
// Set up ValueProviders
|
// Set up ValueProviders
|
||||||
options.ValueProviderFactories.Add(new FormValueProviderFactory());
|
options.ValueProviderFactories.Add(new FormValueProviderFactory());
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var options = Options.Create(new MvcOptions());
|
var options = Options.Create(new MvcOptions());
|
||||||
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var options = Options.Create(new MvcOptions());
|
var options = Options.Create(new MvcOptions());
|
||||||
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var options = Options.Create(new MvcOptions());
|
var options = Options.Create(new MvcOptions());
|
||||||
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
// Set up default output formatters.
|
// Set up default output formatters.
|
||||||
MvcOptions.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
MvcOptions.OutputFormatters.Add(new HttpNoContentOutputFormatter());
|
||||||
MvcOptions.OutputFormatters.Add(new StringOutputFormatter());
|
MvcOptions.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
MvcOptions.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
MvcOptions.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
// Set up default mapping for json extensions to content type
|
// Set up default mapping for json extensions to content type
|
||||||
MvcOptions.FormatterMappings.SetMediaTypeMappingForFormat(
|
MvcOptions.FormatterMappings.SetMediaTypeMappingForFormat(
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,71 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(WriteCorrectCharacterEncoding))]
|
||||||
|
public async Task WriteToStreamAsync_UsesCorrectCharacterEncoding(
|
||||||
|
string content,
|
||||||
|
string encodingAsString,
|
||||||
|
bool isDefaultEncoding)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = GetOutputFormatter();
|
||||||
|
var expectedContent = "\"" + content + "\"";
|
||||||
|
var mediaType = MediaTypeHeaderValue.Parse(string.Format("application/json; charset={0}", encodingAsString));
|
||||||
|
var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
|
||||||
|
|
||||||
|
|
||||||
|
var body = new MemoryStream();
|
||||||
|
var actionContext = GetActionContext(mediaType, body);
|
||||||
|
|
||||||
|
var outputFormatterContext = new OutputFormatterWriteContext(
|
||||||
|
actionContext.HttpContext,
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
typeof(string),
|
||||||
|
content)
|
||||||
|
{
|
||||||
|
ContentType = new StringSegment(mediaType.ToString()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualContent = encoding.GetString(body.ToArray());
|
||||||
|
Assert.Equal(expectedContent, actualContent, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteResponseBodyAsync_Encodes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var formatter = GetOutputFormatter();
|
||||||
|
var expectedContent = "{\"key\":\"Hello \\n <b>Wörld</b>\"}";
|
||||||
|
var content = new { key = "Hello \n <b>Wörld</b>" };
|
||||||
|
|
||||||
|
var mediaType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
|
||||||
|
var encoding = CreateOrGetSupportedEncoding(formatter, "utf-8", isDefaultEncoding: true);
|
||||||
|
|
||||||
|
var body = new MemoryStream();
|
||||||
|
var actionContext = GetActionContext(mediaType, body);
|
||||||
|
|
||||||
|
var outputFormatterContext = new OutputFormatterWriteContext(
|
||||||
|
actionContext.HttpContext,
|
||||||
|
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
||||||
|
typeof(string),
|
||||||
|
content)
|
||||||
|
{
|
||||||
|
ContentType = new StringSegment(mediaType.ToString()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding("utf-8"));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actualContent = encoding.GetString(body.ToArray());
|
||||||
|
Assert.Equal(expectedContent, actualContent);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets()
|
public async Task ErrorDuringSerialization_DoesNotCloseTheBrackets()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Formatters
|
namespace Microsoft.AspNetCore.Mvc.Formatters
|
||||||
{
|
{
|
||||||
public class SystemTextJsonOutputFormatterTest : JsonOutputFormatterTestBase
|
public class SystemTextJsonOutputFormatterTest : JsonOutputFormatterTestBase
|
||||||
{
|
{
|
||||||
protected override TextOutputFormatter GetOutputFormatter()
|
protected override TextOutputFormatter GetOutputFormatter()
|
||||||
{
|
{
|
||||||
return new SystemTextJsonOutputFormatter(new JsonOptions());
|
return SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions());
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(WriteCorrectCharacterEncoding))]
|
|
||||||
public async Task WriteToStreamAsync_UsesCorrectCharacterEncoding(
|
|
||||||
string content,
|
|
||||||
string encodingAsString,
|
|
||||||
bool isDefaultEncoding)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var formatter = GetOutputFormatter();
|
|
||||||
var expectedContent = "\"" + JavaScriptEncoder.Default.Encode(content) + "\"";
|
|
||||||
var mediaType = MediaTypeHeaderValue.Parse(string.Format("application/json; charset={0}", encodingAsString));
|
|
||||||
var encoding = CreateOrGetSupportedEncoding(formatter, encodingAsString, isDefaultEncoding);
|
|
||||||
|
|
||||||
|
|
||||||
var body = new MemoryStream();
|
|
||||||
var actionContext = GetActionContext(mediaType, body);
|
|
||||||
|
|
||||||
var outputFormatterContext = new OutputFormatterWriteContext(
|
|
||||||
actionContext.HttpContext,
|
|
||||||
new TestHttpResponseStreamWriterFactory().CreateWriter,
|
|
||||||
typeof(string),
|
|
||||||
content)
|
|
||||||
{
|
|
||||||
ContentType = new StringSegment(mediaType.ToString()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await formatter.WriteResponseBodyAsync(outputFormatterContext, Encoding.GetEncoding(encodingAsString));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var actualContent = encoding.GetString(body.ToArray());
|
|
||||||
Assert.Equal(expectedContent, actualContent, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var options = Options.Create(new MvcOptions());
|
var options = Options.Create(new MvcOptions());
|
||||||
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
var options = Options.Create(new MvcOptions());
|
var options = Options.Create(new MvcOptions());
|
||||||
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
options.Value.OutputFormatters.Add(new StringOutputFormatter());
|
||||||
options.Value.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonOptions()));
|
options.Value.OutputFormatters.Add(SystemTextJsonOutputFormatter.CreateFormatter(new JsonOptions()));
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
services.AddSingleton<IActionResultExecutor<ObjectResult>>(new ObjectResultExecutor(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -10,11 +11,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
internal class SystemTextJsonHelper : IJsonHelper
|
internal class SystemTextJsonHelper : IJsonHelper
|
||||||
{
|
{
|
||||||
private readonly JsonOptions _options;
|
private readonly JsonSerializerOptions _htmlSafeJsonSerializerOptions;
|
||||||
|
|
||||||
public SystemTextJsonHelper(IOptions<JsonOptions> options)
|
public SystemTextJsonHelper(IOptions<JsonOptions> options)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
_htmlSafeJsonSerializerOptions = GetHtmlSafeSerializerOptions(options.Value.JsonSerializerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -22,8 +23,18 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
// JsonSerializer always encodes non-ASCII chars, so we do not need
|
// JsonSerializer always encodes non-ASCII chars, so we do not need
|
||||||
// to do anything special with the SerializerOptions
|
// to do anything special with the SerializerOptions
|
||||||
var json = JsonSerializer.Serialize(value, _options.JsonSerializerOptions);
|
var json = JsonSerializer.Serialize(value, _htmlSafeJsonSerializerOptions);
|
||||||
return new HtmlString(json);
|
return new HtmlString(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JsonSerializerOptions GetHtmlSafeSerializerOptions(JsonSerializerOptions serializerOptions)
|
||||||
|
{
|
||||||
|
if (serializerOptions.Encoder is null || serializerOptions.Encoder == JavaScriptEncoder.Default)
|
||||||
|
{
|
||||||
|
return serializerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializerOptions.Copy(JavaScriptEncoder.Default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var htmlString = Assert.IsType<HtmlString>(result);
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
Assert.Equal(expectedOutput, htmlString.ToString(), ignoreCase: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -71,14 +71,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
HTML = $"Hello pingüino"
|
HTML = $"Hello pingüino"
|
||||||
};
|
};
|
||||||
var expectedOutput = "{\"html\":\"Hello ping\\u00fcino\"}";
|
var expectedOutput = "{\"html\":\"Hello pingüino\"}";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = helper.Serialize(obj);
|
var result = helper.Serialize(obj);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var htmlString = Assert.IsType<HtmlString>(result);
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
Assert.Equal(expectedOutput, htmlString.ToString(), ignoreCase: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -99,5 +99,25 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
var htmlString = Assert.IsType<HtmlString>(result);
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public virtual void Serialize_WithHTMLNonAsciiAndControlChars()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var helper = GetJsonHelper();
|
||||||
|
var obj = new
|
||||||
|
{
|
||||||
|
HTML = "<b>Hello \n pingüino</b>"
|
||||||
|
};
|
||||||
|
var expectedOutput = "{\"html\":\"\\u003cb\\u003eHello \\n pingüino\\u003c/b\\u003e\"}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = helper.Serialize(obj);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
|
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved
|
// Copyright (c) .NET Foundation. All rights reserved
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -10,31 +10,13 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
{
|
{
|
||||||
public class SystemTextJsonHelperTest : JsonHelperTestBase
|
public class SystemTextJsonHelperTest : JsonHelperTestBase
|
||||||
{
|
{
|
||||||
protected override IJsonHelper GetJsonHelper()
|
protected override IJsonHelper GetJsonHelper() => GetJsonHelper(new JsonOptions());
|
||||||
|
|
||||||
|
private static IJsonHelper GetJsonHelper(JsonOptions options)
|
||||||
{
|
{
|
||||||
var options = new JsonOptions() { JsonSerializerOptions = { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } };
|
|
||||||
return new SystemTextJsonHelper(Options.Create(options));
|
return new SystemTextJsonHelper(Options.Create(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public override void Serialize_EscapesHtmlByDefault()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var helper = GetJsonHelper();
|
|
||||||
var obj = new
|
|
||||||
{
|
|
||||||
HTML = "<b>John Doe</b>"
|
|
||||||
};
|
|
||||||
var expectedOutput = "{\"html\":\"\\u003Cb\\u003EJohn Doe\\u003C/b\\u003E\"}";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = helper.Serialize(obj);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var htmlString = Assert.IsType<HtmlString>(result);
|
|
||||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public override void Serialize_WithNonAsciiChars()
|
public override void Serialize_WithNonAsciiChars()
|
||||||
{
|
{
|
||||||
|
|
@ -53,5 +35,56 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||||
var htmlString = Assert.IsType<HtmlString>(result);
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public override void Serialize_WithHTMLNonAsciiAndControlChars()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var helper = GetJsonHelper();
|
||||||
|
var obj = new
|
||||||
|
{
|
||||||
|
HTML = "<b>Hello \n pingüino</b>"
|
||||||
|
};
|
||||||
|
var expectedOutput = "{\"html\":\"\\u003Cb\\u003EHello \\n ping\\u00FCino\\u003C/b\\u003E\"}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = helper.Serialize(obj);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
|
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Serialize_UsesOptionsConfiguredInTheProvider()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// This should use property-casing and indentation, but the result should be HTML-safe
|
||||||
|
var options = new JsonOptions
|
||||||
|
{
|
||||||
|
JsonSerializerOptions =
|
||||||
|
{
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
|
PropertyNamingPolicy = null,
|
||||||
|
WriteIndented = true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var helper = GetJsonHelper(options);
|
||||||
|
var obj = new
|
||||||
|
{
|
||||||
|
HTML = "<b>John</b>"
|
||||||
|
};
|
||||||
|
var expectedOutput =
|
||||||
|
@"{
|
||||||
|
""HTML"": ""\u003Cb\u003EJohn\u003C/b\u003E""
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = helper.Serialize(obj);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var htmlString = Assert.IsType<HtmlString>(result);
|
||||||
|
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
@ -11,6 +10,7 @@ using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FormatterWebSite.Controllers;
|
using FormatterWebSite.Controllers;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -21,13 +21,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
protected JsonOutputFormatterTestBase(MvcTestFixture<TStartup> fixture)
|
protected JsonOutputFormatterTestBase(MvcTestFixture<TStartup> fixture)
|
||||||
{
|
{
|
||||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||||
Client = factory.CreateDefaultClient();
|
Client = Factory.CreateDefaultClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||||
builder.UseStartup<TStartup>();
|
builder.UseStartup<TStartup>();
|
||||||
|
|
||||||
|
public WebApplicationFactory<TStartup> Factory { get; }
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -100,6 +101,17 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("\"Hello Mr. 🦊\"", await response.Content.ReadAsStringAsync());
|
Assert.Equal("\"Hello Mr. 🦊\"", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public virtual async Task Formatting_StringValueWithNonAsciiCharacters()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await Client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.StringWithNonAsciiContent)}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
Assert.Equal("\"Une bête de cirque\"", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public virtual async Task Formatting_SimpleModel()
|
public virtual async Task Formatting_SimpleModel()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FormatterWebSite.Controllers;
|
using FormatterWebSite.Controllers;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
@ -15,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/11459")]
|
[Fact]
|
||||||
public override Task SerializableErrorIsReturnedInExpectedFormat() => base.SerializableErrorIsReturnedInExpectedFormat();
|
public override Task SerializableErrorIsReturnedInExpectedFormat() => base.SerializableErrorIsReturnedInExpectedFormat();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -29,6 +31,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Equal("\"Hello Mr. \\uD83E\\uDD8A\"", await response.Content.ReadAsStringAsync());
|
Assert.Equal("\"Hello Mr. \\uD83E\\uDD8A\"", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Formatting_WithCustomEncoder()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
static void ConfigureServices(IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
serviceCollection.AddControllers()
|
||||||
|
.AddJsonOptions(o => o.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default);
|
||||||
|
}
|
||||||
|
var client = Factory.WithWebHostBuilder(c => c.ConfigureServices(ConfigureServices)).CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync($"/JsonOutputFormatter/{nameof(JsonOutputFormatterController.StringWithNonAsciiContent)}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
Assert.Equal("\"Une b\\u00EAte de cirque\"", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public override Task Formatting_DictionaryType() => base.Formatting_DictionaryType();
|
public override Task Formatting_DictionaryType() => base.Formatting_DictionaryType();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ namespace FormatterWebSite.Controllers
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult<string> StringWithUnicodeResult() => "Hello Mr. 🦊";
|
public ActionResult<string> StringWithUnicodeResult() => "Hello Mr. 🦊";
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public ActionResult<string> StringWithNonAsciiContent() => "Une bête de cirque";
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult<SimpleModel> SimpleModelResult() =>
|
public ActionResult<SimpleModel> SimpleModelResult() =>
|
||||||
new SimpleModel { Id = 10, Name = "Test", StreetName = "Some street" };
|
new SimpleModel { Id = 10, Name = "Test", StreetName = "Some street" };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue