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

Commit

Permalink
Update JsonHelper to escape HTML.
Browse files Browse the repository at this point in the history
- 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`.
  • Loading branch information
NTaylorMullen committed Apr 26, 2017
1 parent eec16ff commit fe2b673
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 8 deletions.
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Buffers;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -61,6 +62,16 @@ public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<
/// </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"/>.
Expand Down
Expand Up @@ -3,8 +3,14 @@

using System;
using System.Buffers;
#if NET451
using System.Configuration;
#endif
using System.Globalization;
using System.IO;
#if NET451
using System.Linq;
#endif
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Rendering;
Expand All @@ -17,6 +23,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;

Expand Down Expand Up @@ -46,7 +53,23 @@ public JsonHelper(JsonOutputFormatter jsonOutputFormatter, ArrayPool<char> charP
/// <inheritdoc />
public IHtmlContent Serialize(object value)
{
return SerializeInternal(_jsonOutputFormatter, value);
var allowJsonHtml = false;
#if NET451
var switchValue = ConfigurationManager.AppSettings.GetValues(AllowJsonHtml)?.FirstOrDefault();
bool.TryParse(switchValue, out allowJsonHtml);
#else
AppContext.TryGetSwitch(AllowJsonHtml, out allowJsonHtml);
#endif

if (allowJsonHtml)
{
return SerializeInternal(_jsonOutputFormatter, value);
}

var settings = ShallowCopy(_jsonOutputFormatter.PublicSerializerSettings);
settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;

return Serialize(value, settings);
}

/// <inheritdoc />
Expand All @@ -69,5 +92,43 @@ private IHtmlContent SerializeInternal(JsonOutputFormatter jsonOutputFormatter,

return new HtmlString(stringWriter.ToString());
}

private static JsonSerializerSettings ShallowCopy(JsonSerializerSettings settings)
{
var copiedSettings = new JsonSerializerSettings
{
Binder = settings.Binder,
CheckAdditionalContent = settings.CheckAdditionalContent,
ConstructorHandling = settings.ConstructorHandling,
Context = settings.Context,
ContractResolver = settings.ContractResolver,
Converters = settings.Converters,
Culture = settings.Culture,
DateFormatHandling = settings.DateFormatHandling,
DateFormatString = settings.DateFormatString,
DateParseHandling = settings.DateParseHandling,
DateTimeZoneHandling = settings.DateTimeZoneHandling,
DefaultValueHandling = settings.DefaultValueHandling,
EqualityComparer = settings.EqualityComparer,
Error = settings.Error,
FloatFormatHandling = settings.FloatFormatHandling,
FloatParseHandling = settings.FloatParseHandling,
Formatting = settings.Formatting,
MaxDepth = settings.MaxDepth,
MetadataPropertyHandling = settings.MetadataPropertyHandling,
MissingMemberHandling = settings.MissingMemberHandling,
NullValueHandling = settings.NullValueHandling,
ObjectCreationHandling = settings.ObjectCreationHandling,
PreserveReferencesHandling = settings.PreserveReferencesHandling,
ReferenceLoopHandling = settings.ReferenceLoopHandling,
ReferenceResolverProvider = settings.ReferenceResolverProvider,
StringEscapeHandling = settings.StringEscapeHandling,
TraceWriter = settings.TraceWriter,
TypeNameAssemblyFormat = settings.TypeNameAssemblyFormat,
TypeNameHandling = settings.TypeNameHandling,
};

return copiedSettings;
}
}
}
6 changes: 5 additions & 1 deletion src/Microsoft.AspNetCore.Mvc.ViewFeatures/project.json
Expand Up @@ -57,7 +57,11 @@
"System.Buffers": "4.3.0"
},
"frameworks": {
"net451": {},
"net451": {
"frameworkAssemblies": {
"System.Configuration": ""
}
},
"netstandard1.6": {
"dependencies": {
"System.Runtime.Serialization.Primitives": "4.3.0"
Expand Down
10 changes: 4 additions & 6 deletions test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs
Expand Up @@ -221,12 +221,10 @@ public async Task ActionWithRequireHttps_AllowsHttpsRequests(string method)
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");
Expand Down
@@ -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());
}
}
}

0 comments on commit fe2b673

Please sign in to comment.