using System; using System.Collections.Generic; using System.Linq; using EdjCase.JsonRpc.Core; using EdjCase.JsonRpc.Router.Abstractions; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using EdjCase.JsonRpc.Router.Utilities; using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; using System.IO; using Edjcase.JsonRpc.Router; namespace EdjCase.JsonRpc.Router.Defaults { /// /// Default Rpc parser that uses /// public class DefaultRpcParser : IRpcParser { /// /// Logger for logging Rpc parsing /// private ILogger logger { get; } private IOptions serverConfig { get; } /// /// /// /// Optional logger for logging Rpc parsing public DefaultRpcParser(ILogger logger, IOptions serverConfig) { this.logger = logger; this.serverConfig = serverConfig; } /// /// Parses all the requests from the json in the request /// /// Json from the http request /// If true, the request is a bulk request (even if there is only one) /// List of Rpc requests that were parsed from the json public ParsingResult ParseRequests(string jsonString) { this.logger?.LogDebug($"Attempting to parse Rpc request from the json string '{jsonString}'"); List rpcRequests; if (string.IsNullOrWhiteSpace(jsonString)) { throw new RpcException(RpcErrorCode.InvalidRequest, "Json request was empty"); } bool isBulkRequest; try { using (JsonReader jsonReader = new JsonTextReader(new StringReader(jsonString))) { //Fixes the date parsing issue https://github.com/JamesNK/Newtonsoft.Json/issues/862 jsonReader.DateParseHandling = DateParseHandling.None; JToken token = JToken.Load(jsonReader); switch (token.Type) { case JTokenType.Array: isBulkRequest = true; rpcRequests = ((JArray)token).Select(this.DeserializeRequest).ToList(); break; case JTokenType.Object: isBulkRequest = false; RpcRequestParseResult result = this.DeserializeRequest(token); rpcRequests = new List { result }; break; default: throw new RpcException(RpcErrorCode.ParseError, "Json body is not an array or an object."); } } } catch (Exception ex) when (!(ex is RpcException)) { string errorMessage = "Unable to parse json request into an rpc format."; this.logger?.LogException(ex, errorMessage); throw new RpcException(RpcErrorCode.InvalidRequest, errorMessage, ex); } if (rpcRequests == null || !rpcRequests.Any()) { throw new RpcException(RpcErrorCode.InvalidRequest, "No rpc json requests found"); } this.logger?.LogDebug($"Successfully parsed {rpcRequests.Count} Rpc request(s)"); var uniqueIds = new HashSet(); foreach (RpcRequestParseResult result in rpcRequests.Where(r => r.Request != null && r.Request.Id.HasValue)) { bool unique = uniqueIds.Add(result.Request.Id); if (!unique) { throw new RpcException(RpcErrorCode.InvalidRequest, "Duplicate ids in batch requests are not allowed"); } } return ParsingResult.FromResults(rpcRequests, isBulkRequest); } private RpcRequestParseResult DeserializeRequest(JToken token) { RpcId id = null; JToken idToken = token[JsonRpcContants.IdPropertyName]; if (idToken != null) { switch (idToken.Type) { case JTokenType.Null: break; case JTokenType.Integer: case JTokenType.Float: id = new RpcId(idToken.Value()); break; case JTokenType.String: case JTokenType.Guid: id = new RpcId(idToken.Value()); break; default: //Throw exception here because we need an id for the response throw new RpcException(RpcErrorCode.ParseError, "Unable to parse rpc id as string or number."); } } try { string rpcVersion = token.Value(JsonRpcContants.VersionPropertyName); if (string.IsNullOrWhiteSpace(rpcVersion)) { return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The jsonrpc version must be specified.")); } if (!string.Equals(rpcVersion, "2.0", StringComparison.OrdinalIgnoreCase)) { return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, $"The jsonrpc version '{rpcVersion}' is not supported. Supported versions: '2.0'")); } string method = token.Value(JsonRpcContants.MethodPropertyName); if (string.IsNullOrWhiteSpace(method)) { return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.InvalidRequest, "The request method is required.")); } RpcParameters parameters = default; JToken paramsToken = token[JsonRpcContants.ParamsPropertyName]; if (paramsToken != null) { switch (paramsToken.Type) { case JTokenType.Array: if (paramsToken.Any()) { parameters = RpcParameters.FromList(paramsToken.ToArray()); } break; case JTokenType.Object: if (paramsToken.Children().Any()) { Dictionary dict = paramsToken.ToObject>() .ToDictionary(kv => kv.Key, kv => (object)kv.Value); parameters = RpcParameters.FromDictionary(dict); } break; case JTokenType.Null: break; default: return RpcRequestParseResult.Fail(id, new RpcError(RpcErrorCode.ParseError, "Parameters field could not be parsed.")); } } return RpcRequestParseResult.Success(new RpcRequest(id, method, parameters)); } catch (Exception ex) { RpcError error; if (ex is RpcException rpcException) { error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions); } else { error = new RpcError(RpcErrorCode.ParseError, "Failed to parse request.", ex); } return RpcRequestParseResult.Fail(id, error); } } } }