Fix losing UTC DateTimeKind on ISO8601 UTC values (#2317)

This commit is contained in:
James Newton-King 2018-05-19 18:43:14 +12:00 committed by GitHub
parent 96bbe70cd8
commit 879646aba3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 3 deletions

View File

@ -153,6 +153,29 @@ namespace Microsoft.AspNetCore.Internal
return true; return true;
} }
public static bool ReadForType(JsonTextReader reader, Type type)
{
// Explicity read values as dates from JSON with reader.
// We do this because otherwise dates are read as strings
// and the JsonSerializer will use a conversion method that won't
// preserve UTC in DateTime.Kind for UTC ISO8601 dates
if (type == typeof(DateTime) || type == typeof(DateTime?))
{
reader.ReadAsDateTime();
}
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
{
reader.ReadAsDateTimeOffset();
}
else
{
reader.Read();
}
// TokenType will be None if there is no more content
return reader.TokenType != JsonToken.None;
}
private class JsonArrayPool<T> : IArrayPool<T> private class JsonArrayPool<T> : IArrayPool<T>
{ {
private readonly ArrayPool<T> _inner; private readonly ArrayPool<T> _inner;

View File

@ -168,12 +168,12 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
error = JsonUtils.ReadAsString(reader, ErrorPropertyName); error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
break; break;
case ResultPropertyName: case ResultPropertyName:
JsonUtils.CheckRead(reader);
hasResult = true; hasResult = true;
if (string.IsNullOrEmpty(invocationId)) if (string.IsNullOrEmpty(invocationId))
{ {
JsonUtils.CheckRead(reader);
// If we don't have an invocation id then we need to store it as a JToken so we can parse it later // If we don't have an invocation id then we need to store it as a JToken so we can parse it later
resultToken = JToken.Load(reader); resultToken = JToken.Load(reader);
} }
@ -181,6 +181,12 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
{ {
// If we have an invocation id already we can parse the end result // If we have an invocation id already we can parse the end result
var returnType = binder.GetReturnType(invocationId); var returnType = binder.GetReturnType(invocationId);
if (!JsonUtils.ReadForType(reader, returnType))
{
throw new JsonReaderException("Unexpected end when reading JSON");
}
result = PayloadSerializer.Deserialize(reader, returnType); result = PayloadSerializer.Deserialize(reader, returnType);
} }
break; break;
@ -599,6 +605,18 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
return new InvocationMessage(invocationId, target, arguments); return new InvocationMessage(invocationId, target, arguments);
} }
private bool ReadArgumentAsType(JsonTextReader reader, IReadOnlyList<Type> paramTypes, int paramIndex)
{
if (paramIndex < paramTypes.Count)
{
var paramType = paramTypes[paramIndex];
return JsonUtils.ReadForType(reader, paramType);
}
return reader.Read();
}
private object[] BindArguments(JsonTextReader reader, IReadOnlyList<Type> paramTypes) private object[] BindArguments(JsonTextReader reader, IReadOnlyList<Type> paramTypes)
{ {
object[] arguments = null; object[] arguments = null;
@ -606,7 +624,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol
var argumentsCount = 0; var argumentsCount = 0;
var paramCount = paramTypes.Count; var paramCount = paramTypes.Count;
while (reader.Read()) while (ReadArgumentAsType(reader, paramTypes, paramIndex))
{ {
if (reader.TokenType == JsonToken.EndArray) if (reader.TokenType == JsonToken.EndArray)
{ {

View File

@ -250,6 +250,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
[InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':[ 'abc', 'xyz']}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")] [InlineData("{'type':4,'invocationId':'42','target':'foo','arguments':[ 'abc', 'xyz']}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,'',{'1':1,'2':2}]}", "Invocation provides 3 argument(s) but target expects 2.")] [InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,'',{'1':1,'2':2}]}", "Invocation provides 3 argument(s) but target expects 2.")]
[InlineData("{'type':1,'arguments':[1,'',{'1':1,'2':2}]},'invocationId':'42','target':'foo'", "Invocation provides 3 argument(s) but target expects 2.")] [InlineData("{'type':1,'arguments':[1,'',{'1':1,'2':2}]},'invocationId':'42','target':'foo'", "Invocation provides 3 argument(s) but target expects 2.")]
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':[1,[]]}", "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.")]
public void ArgumentBindingErrors(string input, string expectedMessage) public void ArgumentBindingErrors(string input, string expectedMessage)
{ {
input = Frame(input); input = Frame(input);
@ -262,6 +263,37 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message); Assert.Equal(expectedMessage, bindingFailure.BindingFailure.SourceException.Message);
} }
[Theory]
[InlineData("{'type':1,'invocationId':'42','target':'foo','arguments':['2007-03-01T13:00:00Z']}")]
[InlineData("{'type':1,'invocationId':'42','arguments':['2007-03-01T13:00:00Z'],'target':'foo'}")]
public void DateTimeArgumentPreservesUtcKind(string input)
{
var binder = new TestBinder(new[] { typeof(DateTime) });
var protocol = new JsonHubProtocol();
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
protocol.TryParseMessage(ref data, binder, out var message);
var invocationMessage = Assert.IsType<InvocationMessage>(message);
Assert.Single(invocationMessage.Arguments);
var dt = Assert.IsType<DateTime>(invocationMessage.Arguments[0]);
Assert.Equal(DateTimeKind.Utc, dt.Kind);
}
[Theory]
[InlineData("{'type':3,'invocationId':'42','target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z'}")]
[InlineData("{'type':3,'target':'foo','arguments':[],'result':'2007-03-01T13:00:00Z','invocationId':'42'}")]
public void DateTimeReturnValuePreservesUtcKind(string input)
{
var binder = new TestBinder(typeof(DateTime));
var protocol = new JsonHubProtocol();
var data = new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes(Frame(input)));
protocol.TryParseMessage(ref data, binder, out var message);
var invocationMessage = Assert.IsType<CompletionMessage>(message);
var dt = Assert.IsType<DateTime>(invocationMessage.Result);
Assert.Equal(DateTimeKind.Utc, dt.Kind);
}
private static string Frame(string input) private static string Frame(string input)
{ {
var data = Encoding.UTF8.GetBytes(input); var data = Encoding.UTF8.GetBytes(input);