Update JsonHelper to escape HTML.
- This functionality can be disabled by setting the `Switch.Microsoft.AspNetCore.Mvc.AllowJsonHtml` switch in an app.config. - Updated tests to react to this new behavior. - Exposed a new `PublicSerializerSettings` property on `JsonOutputFormatter` so we can accurately copy settings from the used output formatter in `JsonHelper`.
This commit is contained in:
parent
82fd1c51c7
commit
7cea779b7a
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -62,6 +63,16 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
/// </remarks>
|
||||
protected JsonSerializerSettings SerializerSettings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any modifications to the <see cref="JsonSerializerSettings"/> object after this
|
||||
/// <see cref="JsonOutputFormatter"/> has been used will have no effect.
|
||||
/// </remarks>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public JsonSerializerSettings PublicSerializerSettings => SerializerSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given <paramref name="value"/> as JSON using the given
|
||||
/// <paramref name="writer"/>.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// </summary>
|
||||
public class JsonHelper : IJsonHelper
|
||||
{
|
||||
private const string AllowJsonHtml = "Switch.Microsoft.AspNetCore.Mvc.AllowJsonHtml";
|
||||
private readonly JsonOutputFormatter _jsonOutputFormatter;
|
||||
private readonly ArrayPool<char> _charPool;
|
||||
|
||||
|
|
@ -46,7 +47,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <inheritdoc />
|
||||
public IHtmlContent Serialize(object value)
|
||||
{
|
||||
return SerializeInternal(_jsonOutputFormatter, value);
|
||||
if (AppContext.TryGetSwitch(AllowJsonHtml, out var allowJsonHtml) && allowJsonHtml)
|
||||
{
|
||||
return SerializeInternal(_jsonOutputFormatter, value);
|
||||
}
|
||||
|
||||
var settings = ShallowCopy(_jsonOutputFormatter.PublicSerializerSettings);
|
||||
settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
|
||||
|
||||
return Serialize(value, settings);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -69,5 +78,42 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
return new HtmlString(stringWriter.ToString());
|
||||
}
|
||||
|
||||
private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings)
|
||||
{
|
||||
var copiedSettings = new JsonSerializerSettings
|
||||
{
|
||||
FloatParseHandling = settings.FloatParseHandling,
|
||||
FloatFormatHandling = settings.FloatFormatHandling,
|
||||
DateParseHandling = settings.DateParseHandling,
|
||||
DateTimeZoneHandling = settings.DateTimeZoneHandling,
|
||||
DateFormatHandling = settings.DateFormatHandling,
|
||||
Formatting = settings.Formatting,
|
||||
MaxDepth = settings.MaxDepth,
|
||||
DateFormatString = settings.DateFormatString,
|
||||
Context = settings.Context,
|
||||
Error = settings.Error,
|
||||
SerializationBinder = settings.SerializationBinder,
|
||||
TraceWriter = settings.TraceWriter,
|
||||
Culture = settings.Culture,
|
||||
ReferenceResolverProvider = settings.ReferenceResolverProvider,
|
||||
EqualityComparer = settings.EqualityComparer,
|
||||
ContractResolver = settings.ContractResolver,
|
||||
ConstructorHandling = settings.ConstructorHandling,
|
||||
TypeNameAssemblyFormatHandling = settings.TypeNameAssemblyFormatHandling,
|
||||
MetadataPropertyHandling = settings.MetadataPropertyHandling,
|
||||
TypeNameHandling = settings.TypeNameHandling,
|
||||
PreserveReferencesHandling = settings.PreserveReferencesHandling,
|
||||
Converters = settings.Converters,
|
||||
DefaultValueHandling = settings.DefaultValueHandling,
|
||||
NullValueHandling = settings.NullValueHandling,
|
||||
ObjectCreationHandling = settings.ObjectCreationHandling,
|
||||
MissingMemberHandling = settings.MissingMemberHandling,
|
||||
ReferenceLoopHandling = settings.ReferenceLoopHandling,
|
||||
CheckAdditionalContent = settings.CheckAdditionalContent,
|
||||
};
|
||||
|
||||
return copiedSettings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,12 +245,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public async Task JsonHelper_RendersJson_WithCamelCaseNames()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"id\":9000,\"fullName\":\"John <b>Smith</b>\"}";
|
||||
var expectedBody = string.Format(
|
||||
@"<script type=""text/javascript"">
|
||||
var json = {0};
|
||||
</script>",
|
||||
json);
|
||||
var expectedBody =
|
||||
@"<script type=""text/javascript"">
|
||||
var json = {""id"":9000,""fullName"":""John \u003cb\u003eSmith\u003c/b\u003e""};
|
||||
</script>";
|
||||
|
||||
// Act
|
||||
var response = await Client.GetAsync("Home/JsonHelperInView");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
// 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.Buffers;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
{
|
||||
public class JsonHelperTest
|
||||
{
|
||||
[Fact]
|
||||
public void Serialize_EscapesHtmlByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var settings = new JsonSerializerSettings()
|
||||
{
|
||||
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
|
||||
};
|
||||
var helper = new JsonHelper(
|
||||
new JsonOutputFormatter(settings, ArrayPool<char>.Shared),
|
||||
ArrayPool<char>.Shared);
|
||||
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]
|
||||
public void Serialize_MaintainsSettingsAndEscapesHtml()
|
||||
{
|
||||
// Arrange
|
||||
var settings = new JsonSerializerSettings()
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new CamelCaseNamingStrategy(),
|
||||
},
|
||||
};
|
||||
var helper = new JsonHelper(
|
||||
new JsonOutputFormatter(settings, ArrayPool<char>.Shared),
|
||||
ArrayPool<char>.Shared);
|
||||
var obj = new
|
||||
{
|
||||
FullHtml = "<b>John Doe</b>"
|
||||
};
|
||||
var expectedOutput = "{\"fullHtml\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
|
||||
|
||||
// Act
|
||||
var result = helper.Serialize(obj);
|
||||
|
||||
// Assert
|
||||
var htmlString = Assert.IsType<HtmlString>(result);
|
||||
Assert.Equal(expectedOutput, htmlString.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue