Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 7cea779

Browse files
N. Taylor Mullenpranavkm
N. Taylor Mullen
authored andcommitted
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`.
1 parent 82fd1c5 commit 7cea779

File tree

4 files changed

+130
-7
lines changed

4 files changed

+130
-7
lines changed

src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Buffers;
6+
using System.ComponentModel;
67
using System.IO;
78
using System.Text;
89
using System.Threading.Tasks;
@@ -62,6 +63,16 @@ public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<
6263
/// </remarks>
6364
protected JsonSerializerSettings SerializerSettings { get; }
6465

66+
/// <summary>
67+
/// Gets the <see cref="JsonSerializerSettings"/> used to configure the <see cref="JsonSerializer"/>.
68+
/// </summary>
69+
/// <remarks>
70+
/// Any modifications to the <see cref="JsonSerializerSettings"/> object after this
71+
/// <see cref="JsonOutputFormatter"/> has been used will have no effect.
72+
/// </remarks>
73+
[EditorBrowsable(EditorBrowsableState.Never)]
74+
public JsonSerializerSettings PublicSerializerSettings => SerializerSettings;
75+
6576
/// <summary>
6677
/// Writes the given <paramref name="value"/> as JSON using the given
6778
/// <paramref name="writer"/>.

src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs

+47-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
1717
/// </summary>
1818
public class JsonHelper : IJsonHelper
1919
{
20+
private const string AllowJsonHtml = "Switch.Microsoft.AspNetCore.Mvc.AllowJsonHtml";
2021
private readonly JsonOutputFormatter _jsonOutputFormatter;
2122
private readonly ArrayPool<char> _charPool;
2223

@@ -46,7 +47,15 @@ public JsonHelper(JsonOutputFormatter jsonOutputFormatter, ArrayPool<char> charP
4647
/// <inheritdoc />
4748
public IHtmlContent Serialize(object value)
4849
{
49-
return SerializeInternal(_jsonOutputFormatter, value);
50+
if (AppContext.TryGetSwitch(AllowJsonHtml, out var allowJsonHtml) && allowJsonHtml)
51+
{
52+
return SerializeInternal(_jsonOutputFormatter, value);
53+
}
54+
55+
var settings = ShallowCopy(_jsonOutputFormatter.PublicSerializerSettings);
56+
settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
57+
58+
return Serialize(value, settings);
5059
}
5160

5261
/// <inheritdoc />
@@ -69,5 +78,42 @@ private IHtmlContent SerializeInternal(JsonOutputFormatter jsonOutputFormatter,
6978

7079
return new HtmlString(stringWriter.ToString());
7180
}
81+
82+
private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings)
83+
{
84+
var copiedSettings = new JsonSerializerSettings
85+
{
86+
FloatParseHandling = settings.FloatParseHandling,
87+
FloatFormatHandling = settings.FloatFormatHandling,
88+
DateParseHandling = settings.DateParseHandling,
89+
DateTimeZoneHandling = settings.DateTimeZoneHandling,
90+
DateFormatHandling = settings.DateFormatHandling,
91+
Formatting = settings.Formatting,
92+
MaxDepth = settings.MaxDepth,
93+
DateFormatString = settings.DateFormatString,
94+
Context = settings.Context,
95+
Error = settings.Error,
96+
SerializationBinder = settings.SerializationBinder,
97+
TraceWriter = settings.TraceWriter,
98+
Culture = settings.Culture,
99+
ReferenceResolverProvider = settings.ReferenceResolverProvider,
100+
EqualityComparer = settings.EqualityComparer,
101+
ContractResolver = settings.ContractResolver,
102+
ConstructorHandling = settings.ConstructorHandling,
103+
TypeNameAssemblyFormatHandling = settings.TypeNameAssemblyFormatHandling,
104+
MetadataPropertyHandling = settings.MetadataPropertyHandling,
105+
TypeNameHandling = settings.TypeNameHandling,
106+
PreserveReferencesHandling = settings.PreserveReferencesHandling,
107+
Converters = settings.Converters,
108+
DefaultValueHandling = settings.DefaultValueHandling,
109+
NullValueHandling = settings.NullValueHandling,
110+
ObjectCreationHandling = settings.ObjectCreationHandling,
111+
MissingMemberHandling = settings.MissingMemberHandling,
112+
ReferenceLoopHandling = settings.ReferenceLoopHandling,
113+
CheckAdditionalContent = settings.CheckAdditionalContent,
114+
};
115+
116+
return copiedSettings;
117+
}
72118
}
73119
}

test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs

+4-6
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,10 @@ public async Task ActionWithRequireHttps_AllowsHttpsRequests(string method)
245245
public async Task JsonHelper_RendersJson_WithCamelCaseNames()
246246
{
247247
// Arrange
248-
var json = "{\"id\":9000,\"fullName\":\"John <b>Smith</b>\"}";
249-
var expectedBody = string.Format(
250-
@"<script type=""text/javascript"">
251-
var json = {0};
252-
</script>",
253-
json);
248+
var expectedBody =
249+
@"<script type=""text/javascript"">
250+
var json = {""id"":9000,""fullName"":""John \u003cb\u003eSmith\u003c/b\u003e""};
251+
</script>";
254252

255253
// Act
256254
var response = await Client.GetAsync("Home/JsonHelperInView");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Buffers;
5+
using Microsoft.AspNetCore.Html;
6+
using Microsoft.AspNetCore.Mvc.Formatters;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Serialization;
9+
using Xunit;
10+
11+
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
12+
{
13+
public class JsonHelperTest
14+
{
15+
[Fact]
16+
public void Serialize_EscapesHtmlByDefault()
17+
{
18+
// Arrange
19+
var settings = new JsonSerializerSettings()
20+
{
21+
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
22+
};
23+
var helper = new JsonHelper(
24+
new JsonOutputFormatter(settings, ArrayPool<char>.Shared),
25+
ArrayPool<char>.Shared);
26+
var obj = new
27+
{
28+
HTML = "<b>John Doe</b>"
29+
};
30+
var expectedOutput = "{\"HTML\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
31+
32+
// Act
33+
var result = helper.Serialize(obj);
34+
35+
// Assert
36+
var htmlString = Assert.IsType<HtmlString>(result);
37+
Assert.Equal(expectedOutput, htmlString.ToString());
38+
}
39+
40+
[Fact]
41+
public void Serialize_MaintainsSettingsAndEscapesHtml()
42+
{
43+
// Arrange
44+
var settings = new JsonSerializerSettings()
45+
{
46+
ContractResolver = new DefaultContractResolver
47+
{
48+
NamingStrategy = new CamelCaseNamingStrategy(),
49+
},
50+
};
51+
var helper = new JsonHelper(
52+
new JsonOutputFormatter(settings, ArrayPool<char>.Shared),
53+
ArrayPool<char>.Shared);
54+
var obj = new
55+
{
56+
FullHtml = "<b>John Doe</b>"
57+
};
58+
var expectedOutput = "{\"fullHtml\":\"\\u003cb\\u003eJohn Doe\\u003c/b\\u003e\"}";
59+
60+
// Act
61+
var result = helper.Serialize(obj);
62+
63+
// Assert
64+
var htmlString = Assert.IsType<HtmlString>(result);
65+
Assert.Equal(expectedOutput, htmlString.ToString());
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)