DefaultRpcParser.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using EdjCase.JsonRpc.Core;
  5. using EdjCase.JsonRpc.Router.Abstractions;
  6. using Microsoft.Extensions.Logging;
  7. using Newtonsoft.Json;
  8. using EdjCase.JsonRpc.Router.Utilities;
  9. using Microsoft.Extensions.Options;
  10. using Newtonsoft.Json.Linq;
  11. using System.IO;
  12. using Edjcase.JsonRpc.Router;
  13. namespace EdjCase.JsonRpc.Router.Defaults
  14. {
  15. /// <summary>
  16. /// Default Rpc parser that uses <see cref="Newtonsoft.Json"/>
  17. /// </summary>
  18. public class DefaultRpcParser : IRpcParser
  19. {
  20. /// <summary>
  21. /// Logger for logging Rpc parsing
  22. /// </summary>
  23. private ILogger<DefaultRpcParser> logger { get; }
  24. private IOptions<RpcServerConfiguration> serverConfig { get; }
  25. /// <summary>
  26. ///
  27. /// </summary>
  28. /// <param name="logger">Optional logger for logging Rpc parsing</param>
  29. public DefaultRpcParser(ILogger<DefaultRpcParser> logger,
  30. IOptions<RpcServerConfiguration> serverConfig)
  31. {
  32. this.logger = logger;
  33. this.serverConfig = serverConfig;
  34. }
  35. /// <summary>
  36. /// Parses all the requests from the json in the request
  37. /// </summary>
  38. /// <param name="jsonString">Json from the http request</param>
  39. /// <param name="isBulkRequest">If true, the request is a bulk request (even if there is only one)</param>
  40. /// <returns>List of Rpc requests that were parsed from the json</returns>
  41. public ParsingResult ParseRequests(string jsonString)
  42. {
  43. this.logger?.LogDebug($"Attempting to parse Rpc request from the json string '{jsonString}'");
  44. List<RpcRequestParseResult> rpcRequests;
  45. if (string.IsNullOrWhiteSpace(jsonString))
  46. {
  47. throw new RpcException(RpcErrorCode.InvalidRequest, "Json request was empty");
  48. }
  49. bool isBulkRequest;
  50. try
  51. {
  52. using (JsonReader jsonReader = new JsonTextReader(new StringReader(jsonString)))
  53. {
  54. //Fixes the date parsing issue https://github.com/JamesNK/Newtonsoft.Json/issues/862
  55. jsonReader.DateParseHandling = DateParseHandling.None;
  56. JToken token = JToken.Load(jsonReader);
  57. switch (token.Type)
  58. {
  59. case JTokenType.Array:
  60. isBulkRequest = true;
  61. rpcRequests = ((JArray)token).Select(this.DeserializeRequest).ToList();
  62. break;
  63. case JTokenType.Object:
  64. isBulkRequest = false;
  65. RpcRequestParseResult result = this.DeserializeRequest(token);
  66. rpcRequests = new List<RpcRequestParseResult> { result };
  67. break;
  68. default:
  69. throw new RpcException(RpcErrorCode.ParseError, "Json body is not an array or an object.");
  70. }
  71. }
  72. }
  73. catch (Exception ex) when (!(ex is RpcException))
  74. {
  75. string errorMessage = "Unable to parse json request into an rpc format.";
  76. this.logger?.LogException(ex, errorMessage);
  77. throw new RpcException(RpcErrorCode.InvalidRequest, errorMessage, ex);
  78. }
  79. if (rpcRequests == null || !rpcRequests.Any())
  80. {
  81. throw new RpcException(RpcErrorCode.InvalidRequest, "No rpc json requests found");
  82. }
  83. this.logger?.LogDebug($"Successfully parsed {rpcRequests.Count} Rpc request(s)");
  84. var uniqueIds = new HashSet<RpcId>();
  85. foreach (RpcRequestParseResult result in rpcRequests.Where(r => r.Request != null && r.Request.Id.HasValue))
  86. {
  87. bool unique = uniqueIds.Add(result.Request.Id);
  88. if (!unique)
  89. {
  90. throw new RpcException(RpcErrorCode.InvalidRequest, "Duplicate ids in batch requests are not allowed");
  91. }
  92. }
  93. return ParsingResult.FromResults(rpcRequests, isBulkRequest);
  94. }
  95. private RpcRequestParseResult DeserializeRequest(JToken token)
  96. {
  97. RpcId id = null;
  98. JToken idToken = token[JsonRpcContants.IdPropertyName];
  99. if (idToken != null)
  100. {
  101. switch (idToken.Type)
  102. {
  103. case JTokenType.Null:
  104. break;
  105. case JTokenType.Integer:
  106. case JTokenType.Float:
  107. id = new RpcId(idToken.Value<double>());
  108. break;
  109. case JTokenType.String:
  110. case JTokenType.Guid:
  111. id = new RpcId(idToken.Value<string>());
  112. break;
  113. default:
  114. //Throw exception here because we need an id for the response
  115. throw new RpcException(RpcErrorCode.ParseError, "Unable to parse rpc id as string or number.");
  116. }
  117. }
  118. try
  119. {
  120. string rpcVersion = token.Value<string>(JsonRpcContants.VersionPropertyName);
  121. if (string.IsNullOrWhiteSpace(rpcVersion))
  122. {
  123. return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The jsonrpc version must be specified."));
  124. }
  125. if (!string.Equals(rpcVersion, "2.0", StringComparison.OrdinalIgnoreCase))
  126. {
  127. return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, $"The jsonrpc version '{rpcVersion}' is not supported. Supported versions: '2.0'"));
  128. }
  129. string method = token.Value<string>(JsonRpcContants.MethodPropertyName);
  130. if (string.IsNullOrWhiteSpace(method))
  131. {
  132. return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The request method is required."));
  133. }
  134. RpcParameters parameters = default;
  135. JToken paramsToken = token[JsonRpcContants.ParamsPropertyName];
  136. if (paramsToken != null)
  137. {
  138. switch (paramsToken.Type)
  139. {
  140. case JTokenType.Array:
  141. if (paramsToken.Any())
  142. {
  143. parameters = RpcParameters.FromList(paramsToken.ToArray());
  144. }
  145. break;
  146. case JTokenType.Object:
  147. if (paramsToken.Children().Any())
  148. {
  149. Dictionary<string, object> dict = paramsToken.ToObject<Dictionary<string, JToken>>()
  150. .ToDictionary(kv => kv.Key, kv => (object)kv.Value);
  151. parameters = RpcParameters.FromDictionary(dict);
  152. }
  153. break;
  154. case JTokenType.Null:
  155. break;
  156. default:
  157. return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.ParseError, "Parameters field could not be parsed."));
  158. }
  159. }
  160. return RpcRequestParseResult.Success(new RpcRequest(id, method, parameters));
  161. }
  162. catch (Exception ex)
  163. {
  164. RpcError error;
  165. if (ex is RpcException rpcException)
  166. {
  167. error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions);
  168. }
  169. else
  170. {
  171. error = new RpcError(RpcErrorCode.ParseError, "Failed to parse request.", ex);
  172. }
  173. return RpcRequestParseResult.Fail(id, error);
  174. }
  175. }
  176. }
  177. }