From 4e1b18e2bb26193fe79622ab91fd6a46e80df150 Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Wed, 28 Apr 2021 09:49:10 +0200 Subject: [PATCH] =?UTF-8?q?do=20not=20crash=20invoice=20if=20wellknown=20m?= =?UTF-8?q?etadata=20keys=20used=20with=20different=20e=E2=80=A6=20(#2448)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * do not crash invoice if wellknown metadata keys used with different expected types * fix * add bits from alt PR --- BTCPayServer.Tests/UnitTest1.cs | 4 +- .../Controllers/InvoiceController.UI.cs | 9 +- BTCPayServer/Plugins/PluginManager.cs | 3 +- .../Services/Invoices/InvoiceEntity.cs | 180 ++++++++++++------ 4 files changed, 131 insertions(+), 65 deletions(-) diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 9a4ad8c1f..e77ac21d3 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -2366,7 +2366,9 @@ namespace BTCPayServer.Tests { ("{ invalidjson file here}", new Dictionary() {{String.Empty, "{ invalidjson file here}"}}) - } + }, + // Duplicate keys should not crash things + {("{ \"key\": true, \"key\": true}", new Dictionary() {{"key", "True"}})} }; testCases.ForEach(tuple => diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 1fec3445b..24b466674 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -903,21 +903,20 @@ namespace BTCPayServer.Controllers var jObject = JObject.Parse(posData); foreach (var item in jObject) { - switch (item.Value.Type) { case JTokenType.Array: var items = item.Value.AsEnumerable().ToList(); for (var i = 0; i < items.Count; i++) { - result.Add($"{item.Key}[{i}]", ParsePosData(items[i].ToString())); + result.TryAdd($"{item.Key}[{i}]", ParsePosData(items[i].ToString())); } break; case JTokenType.Object: - result.Add(item.Key, ParsePosData(item.Value.ToString())); + result.TryAdd(item.Key, ParsePosData(item.Value.ToString())); break; default: - result.Add(item.Key, item.Value.ToString()); + result.TryAdd(item.Key, item.Value.ToString()); break; } @@ -925,7 +924,7 @@ namespace BTCPayServer.Controllers } catch { - result.Add(string.Empty, posData); + result.TryAdd(string.Empty, posData); } return result; } diff --git a/BTCPayServer/Plugins/PluginManager.cs b/BTCPayServer/Plugins/PluginManager.cs index e098960a3..183b76e00 100644 --- a/BTCPayServer/Plugins/PluginManager.cs +++ b/BTCPayServer/Plugins/PluginManager.cs @@ -5,7 +5,6 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices.ComTypes; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Configuration; using McMaster.NETCore.Plugins; @@ -28,7 +27,7 @@ namespace BTCPayServer.Plugins public static bool IsExceptionByPlugin(Exception exception) { - return _pluginAssemblies.Any(assembly => assembly.FullName.Contains(exception.Source, StringComparison.OrdinalIgnoreCase)); + return _pluginAssemblies.Any(assembly => assembly?.FullName?.Contains(exception.Source!, StringComparison.OrdinalIgnoreCase) is true); } public static IMvcBuilder AddPlugins(this IMvcBuilder mvcBuilder, IServiceCollection serviceCollection, IConfiguration config, ILoggerFactory loggerFactory) diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 1db57c503..09300f28d 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -30,70 +30,136 @@ namespace BTCPayServer.Services.Invoices seria.ContractResolver = new CamelCasePropertyNamesContractResolver(); MetadataSerializer = seria; } - public string OrderId { get; set; } - [JsonProperty(PropertyName = "buyerName")] - public string BuyerName { get; set; } - [JsonProperty(PropertyName = "buyerEmail")] - public string BuyerEmail { get; set; } - [JsonProperty(PropertyName = "buyerCountry")] - public string BuyerCountry { get; set; } - [JsonProperty(PropertyName = "buyerZip")] - public string BuyerZip { get; set; } - [JsonProperty(PropertyName = "buyerState")] - public string BuyerState { get; set; } - [JsonProperty(PropertyName = "buyerCity")] - public string BuyerCity { get; set; } - [JsonProperty(PropertyName = "buyerAddress2")] - public string BuyerAddress2 { get; set; } - [JsonProperty(PropertyName = "buyerAddress1")] - public string BuyerAddress1 { get; set; } - - [JsonProperty(PropertyName = "buyerPhone")] - public string BuyerPhone { get; set; } - - [JsonProperty(PropertyName = "itemDesc")] - public string ItemDesc { get; set; } - [JsonProperty(PropertyName = "itemCode")] - public string ItemCode { get; set; } - [JsonProperty(PropertyName = "physical")] - public bool? Physical { get; set; } - - [JsonProperty(PropertyName = "taxIncluded", DefaultValueHandling = DefaultValueHandling.Ignore)] - public decimal? TaxIncluded { get; set; } - + + [JsonIgnore] + public string OrderId + { + get => GetMetadata("orderId"); + set => SetMetadata("orderId", value); + } + [JsonIgnore] + public string BuyerName{ + get => GetMetadata("buyerName"); + set => SetMetadata("buyerName", value); + } + [JsonIgnore] + public string BuyerEmail { + get => GetMetadata("buyerEmail"); + set => SetMetadata("buyerEmail", value); + } + [JsonIgnore] + public string BuyerCountry { + get => GetMetadata("buyerCountry"); + set => SetMetadata("buyerCountry", value); + } + [JsonIgnore] + public string BuyerZip { + get => GetMetadata("buyerZip"); + set => SetMetadata("buyerZip", value); + } + [JsonIgnore] + public string BuyerState{ + get => GetMetadata("buyerState"); + set => SetMetadata("buyerState", value); + } + [JsonIgnore] + public string BuyerCity { + get => GetMetadata("buyerCity"); + set => SetMetadata("buyerCity", value); + } + [JsonIgnore] + public string BuyerAddress2{ + get => GetMetadata("buyerAddress2"); + set => SetMetadata("buyerAddress2", value); + } + [JsonIgnore] + public string BuyerAddress1 { + get => GetMetadata("buyerAddress1"); + set => SetMetadata("buyerAddress1", value); + } + [JsonIgnore] + public string BuyerPhone { + get => GetMetadata("buyerPhone"); + set => SetMetadata("buyerPhone", value); + } + [JsonIgnore] + public string ItemDesc { + get => GetMetadata("itemDesc"); + set => SetMetadata("itemDesc", value); + } + [JsonIgnore] + public string ItemCode{ + get => GetMetadata("itemCode"); + set => SetMetadata("itemCode", value); + } + [JsonIgnore] + public bool? Physical { + get => GetMetadata("physical"); + set => SetMetadata("physical", value); + } + [JsonIgnore] + public decimal? TaxIncluded { + get => GetMetadata("taxIncluded"); + set => SetMetadata("taxIncluded", value); + } [JsonIgnore] public string PosData { - get - { - return PosRawData?.ToString(); - } - set - { - if (value is null) - { - PosRawData = JValue.CreateNull(); - } - else - { - try - { - PosRawData = JToken.Parse(value); - } - catch (Exception ) - { - PosRawData = JToken.FromObject(value); - } - } - - } + get => GetMetadata("posData"); + set => SetMetadata("posData", value); } - - [JsonProperty(PropertyName = "posData")] - public JToken PosRawData { get; set; } [JsonExtensionData] public IDictionary AdditionalData { get; set; } + public T GetMetadata(string propName) + { + if (AdditionalData == null || !(AdditionalData.TryGetValue(propName, out var jt) is true)) return default; + if (jt.Type == JTokenType.Null) + return default; + if (typeof(T) == typeof(string)) + { + return (T)(object)jt.ToString(); + } + + try + { + return jt.Value(); + } + catch (Exception) + { + return default; + } + } + public void SetMetadata(string propName, T value) + { + JToken data; + if (value is null) + { + AdditionalData?.Remove(propName); + } + else + { + try + { + if (value is string s) + { + data = JToken.Parse(s); + } + else + { + data = JToken.FromObject(value); + } + } + catch (Exception ) + { + data = JToken.FromObject(value); + } + + AdditionalData ??= new Dictionary(); + AdditionalData.AddOrReplace(propName, data); + } + } + public static InvoiceMetadata FromJObject(JObject jObject) { return jObject.ToObject(MetadataSerializer);