btcpayserver/BTCPayServer/Extensions.cs

451 lines
18 KiB
C#
Raw Normal View History

2020-06-29 04:44:35 +02:00
using System;
2017-09-13 08:47:34 +02:00
using System.Collections.Generic;
2020-06-28 10:55:27 +02:00
using System.Globalization;
using System.IO;
2020-06-28 10:55:27 +02:00
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
2020-06-28 10:55:27 +02:00
using System.Security.Claims;
2017-09-13 08:47:34 +02:00
using System.Text;
2017-11-06 09:31:02 +01:00
using System.Threading;
2020-06-28 10:55:27 +02:00
using System.Threading.Tasks;
2021-12-31 08:59:02 +01:00
using BTCPayServer.BIP78.Sender;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Configuration;
using BTCPayServer.Data;
2023-05-28 16:44:10 +02:00
using BTCPayServer.HostedServices;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Lightning;
using BTCPayServer.Logging;
2018-04-03 04:50:41 +02:00
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Security;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
2020-06-28 10:55:27 +02:00
using Microsoft.Extensions.Configuration;
2023-05-28 16:44:10 +02:00
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
2020-06-28 10:55:27 +02:00
using NBitcoin;
using NBitcoin.Payment;
using NBXplorer.Models;
using Newtonsoft.Json;
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer
{
public static class Extensions
{
2023-02-14 09:03:12 +01:00
public static Task<BufferizedFormFile> Bufferize(this IFormFile formFile)
{
return BufferizedFormFile.Bufferize(formFile);
}
/// <summary>
/// Unescape Uri string for %2F
/// See details at: https://github.com/dotnet/aspnetcore/issues/14170#issuecomment-533342396
/// </summary>
/// <param name="uriString">The Uri string.</param>
/// <returns>Unescaped back slash Uri string.</returns>
public static string UnescapeBackSlashUriString(string uriString)
{
if (uriString == null)
{
return null;
}
return uriString.Replace("%2f", "%2F").Replace("%2F", "/");
}
public static bool IsValidEmail(this string email)
{
if (string.IsNullOrEmpty(email))
{
return false;
}
return MailboxAddressValidator.TryParse(email, out var ma) && ma.ToString() == ma.Address;
}
2020-06-17 14:43:56 +02:00
public static bool TryGetPayjoinEndpoint(this BitcoinUrlBuilder bip21, out Uri endpoint)
{
2022-01-07 04:32:00 +01:00
endpoint = bip21.UnknownParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null;
2020-06-17 14:43:56 +02:00
return endpoint != null;
}
public static bool IsSafe(this LightningConnectionString connectionString)
{
if (connectionString.CookieFilePath != null ||
connectionString.MacaroonDirectoryPath != null ||
connectionString.MacaroonFilePath != null)
return false;
var uri = connectionString.BaseUri;
if (uri.Scheme.Equals("unix", StringComparison.OrdinalIgnoreCase))
return false;
if (!NBitcoin.Utils.TryParseEndpoint(uri.DnsSafeHost, 80, out var endpoint))
return false;
return !Extensions.IsLocalNetwork(uri.DnsSafeHost);
}
2020-06-28 10:55:27 +02:00
2019-10-04 15:55:11 +02:00
public static IQueryable<TEntity> Where<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj, System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class
2019-10-04 10:17:11 +02:00
{
2019-10-04 15:55:11 +02:00
return System.Linq.Queryable.Where(obj, predicate);
2019-10-04 10:17:11 +02:00
}
2019-10-04 15:55:11 +02:00
2018-04-18 11:23:39 +02:00
public static string PrettyPrint(this TimeSpan expiration)
{
StringBuilder builder = new StringBuilder();
if (expiration.Days >= 1)
builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture));
if (expiration.Hours >= 1)
builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture));
2021-12-27 05:46:31 +01:00
builder.Append(CultureInfo.InvariantCulture, $"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}");
2018-04-18 11:23:39 +02:00
return builder.ToString();
}
public static decimal RoundUp(decimal value, int precision)
{
try
{
for (int i = 0; i < precision; i++)
{
value = value * 10m;
}
value = Math.Ceiling(value);
for (int i = 0; i < precision; i++)
{
value = value / 10m;
}
return value;
}
catch (OverflowException)
{
return value;
}
}
2023-05-28 16:44:10 +02:00
public static IServiceCollection AddScheduledTask<T>(this IServiceCollection services, TimeSpan every)
where T : class, IPeriodicTask
{
services.AddSingleton<T>();
services.AddTransient<ScheduledTask>(o => new ScheduledTask(typeof(T), every));
return services;
}
public static PaymentMethodId GetpaymentMethodId(this InvoiceCryptoInfo info)
{
2019-06-04 02:40:36 +02:00
return new PaymentMethodId(info.CryptoCode, PaymentTypes.Parse(info.PaymentType));
}
2018-02-12 19:27:36 +01:00
public static async Task CloseSocket(this WebSocket webSocket)
{
try
{
if (webSocket.State == WebSocketState.Open)
{
2022-01-14 09:50:29 +01:00
using CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
2018-02-12 19:27:36 +01:00
}
}
catch { }
finally { try { webSocket.Dispose(); } catch { } }
}
2020-03-29 17:28:22 +02:00
public static IEnumerable<BitcoinLikePaymentData> GetAllBitcoinPaymentData(this InvoiceEntity invoice, bool accountedOnly)
2020-03-29 17:28:22 +02:00
{
return invoice.GetPayments(accountedOnly)
.Where(p => p.GetPaymentMethodId()?.PaymentType == PaymentTypes.BTCLike)
.Select(p => (BitcoinLikePaymentData)p.GetCryptoPaymentData())
.Where(data => data != null);
2020-03-29 17:28:22 +02:00
}
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, uint256[] hashes, bool includeOffchain = false, CancellationToken cts = default(CancellationToken))
2017-11-06 09:31:02 +01:00
{
hashes = hashes.Distinct().ToArray();
var transactions = hashes
2020-03-29 17:28:22 +02:00
.Select(async o => await client.GetTransactionAsync(o, includeOffchain, cts))
2017-11-06 09:31:02 +01:00
.ToArray();
await Task.WhenAll(transactions).ConfigureAwait(false);
return transactions.Select(t => t.Result).Where(t => t != null).ToDictionary(o => o.Transaction.GetHash());
}
2020-06-28 10:55:27 +02:00
#nullable enable
public static IPayoutHandler? FindPayoutHandler(this IEnumerable<IPayoutHandler> handlers, PaymentMethodId paymentMethodId)
{
return handlers.FirstOrDefault(h => h.CanHandle(paymentMethodId));
}
#nullable restore
2020-03-29 17:28:22 +02:00
public static async Task<PSBT> UpdatePSBT(this ExplorerClientProvider explorerClientProvider, DerivationSchemeSettings derivationSchemeSettings, PSBT psbt)
{
var result = await explorerClientProvider.GetExplorerClient(psbt.Network.NetworkSet.CryptoCode).UpdatePSBTAsync(new UpdatePSBTRequest()
{
PSBT = psbt,
DerivationScheme = derivationSchemeSettings.AccountDerivation,
AlwaysIncludeNonWitnessUTXO = true
2020-03-29 17:28:22 +02:00
});
if (result == null)
return null;
derivationSchemeSettings.RebaseKeyPaths(result.PSBT);
return result.PSBT;
}
2020-06-28 10:55:27 +02:00
2018-07-12 10:38:21 +02:00
public static void SetHeaderOnStarting(this HttpResponse resp, string name, string value)
{
if (resp.HasStarted)
return;
resp.OnStarting(() =>
{
SetHeader(resp, name, value);
return Task.CompletedTask;
});
}
public static void SetHeader(this HttpResponse resp, string name, string value)
{
var existing = resp.Headers[name].FirstOrDefault();
if (existing != null && value == null)
resp.Headers.Remove(name);
else
resp.Headers[name] = value;
}
public static bool IsLocalNetwork(string server)
{
ArgumentNullException.ThrowIfNull(server);
if (Uri.CheckHostName(server) == UriHostNameType.Dns)
{
return server.EndsWith(".internal", StringComparison.OrdinalIgnoreCase) ||
server.EndsWith(".local", StringComparison.OrdinalIgnoreCase) ||
server.EndsWith(".lan", StringComparison.OrdinalIgnoreCase) ||
server.IndexOf('.', StringComparison.OrdinalIgnoreCase) == -1;
}
2020-06-28 10:55:27 +02:00
if (IPAddress.TryParse(server, out var ip))
{
return ip.IsLocal() || ip.IsRFC1918();
}
return false;
}
public static bool IsOnion(this Uri uri)
{
2020-02-24 16:10:07 +01:00
if (uri == null || !uri.IsAbsoluteUri)
return false;
return uri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);
}
2018-04-27 19:09:24 +02:00
public static string GetSIN(this ClaimsPrincipal principal)
{
2019-10-12 13:35:30 +02:00
return principal.Claims.Where(c => c.Type == Security.Bitpay.BitpayClaims.SIN).Select(c => c.Value).FirstOrDefault();
}
public static void SetIsBitpayAPI(this HttpContext ctx, bool value)
{
NBitcoin.Extensions.TryAdd(ctx.Items, "IsBitpayAPI", value);
}
public static bool GetIsBitpayAPI(this HttpContext ctx)
{
return ctx.Items.TryGetValue("IsBitpayAPI", out object obj) &&
obj is bool b && b;
}
public static void SetBitpayAuth(this HttpContext ctx, (string Signature, String Id, String Authorization) value)
{
NBitcoin.Extensions.TryAdd(ctx.Items, "BitpayAuth", value);
}
2019-06-11 11:40:47 +02:00
public static bool TryGetBitpayAuth(this HttpContext ctx, out (string Signature, String Id, String Authorization) result)
{
2019-06-11 11:40:47 +02:00
if (ctx.Items.TryGetValue("BitpayAuth", out object obj))
{
result = ((string Signature, String Id, String Authorization))obj;
return true;
}
result = default;
return false;
}
2021-12-31 08:59:02 +01:00
public static UserPrefsCookie GetUserPrefsCookie(this HttpContext ctx)
{
var prefCookie = new UserPrefsCookie();
ctx.Request.Cookies.TryGetValue(nameof(UserPrefsCookie), out var strPrefCookie);
if (!string.IsNullOrEmpty(strPrefCookie))
{
try
{
prefCookie = JsonConvert.DeserializeObject<UserPrefsCookie>(strPrefCookie);
}
catch { /* ignore cookie deserialization failures */ }
}
return prefCookie;
}
public static void DeleteUserPrefsCookie(this HttpContext ctx)
{
ctx.Response.Cookies.Delete(nameof(UserPrefsCookie));
}
private static void SetCurrentStoreId(this HttpContext ctx, string storeId)
{
var prefCookie = ctx.GetUserPrefsCookie();
if (prefCookie.CurrentStoreId != storeId)
{
prefCookie.CurrentStoreId = storeId;
ctx.Response.Cookies.Append(nameof(UserPrefsCookie), JsonConvert.SerializeObject(prefCookie));
}
}
public static string GetCurrentStoreId(this HttpContext ctx)
{
return ctx.GetImplicitStoreId() ?? ctx.GetUserPrefsCookie()?.CurrentStoreId;
}
public static StoreData GetStoreData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.STOREDATA") as StoreData;
}
2021-12-31 08:59:02 +01:00
public static void SetStoreData(this HttpContext ctx, StoreData storeData)
{
ctx.Items["BTCPAY.STOREDATA"] = storeData;
2021-12-31 08:59:02 +01:00
SetCurrentStoreId(ctx, storeData.Id);
}
2020-03-19 11:11:15 +01:00
public static StoreData[] GetStoresData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[];
}
2021-12-31 08:59:02 +01:00
2020-03-19 11:11:15 +01:00
public static void SetStoresData(this HttpContext ctx, StoreData[] storeData)
{
ctx.Items["BTCPAY.STORESDATA"] = storeData;
}
public static InvoiceEntity GetInvoiceData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.INVOICEDATA") as InvoiceEntity;
}
2021-12-31 08:59:02 +01:00
public static void SetInvoiceData(this HttpContext ctx, InvoiceEntity invoiceEntity)
{
ctx.Items["BTCPAY.INVOICEDATA"] = invoiceEntity;
}
public static PaymentRequestData GetPaymentRequestData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.PAYMENTREQUESTDATA") as PaymentRequestData;
}
2021-12-31 08:59:02 +01:00
public static void SetPaymentRequestData(this HttpContext ctx, PaymentRequestData paymentRequestData)
{
ctx.Items["BTCPAY.PAYMENTREQUESTDATA"] = paymentRequestData;
}
public static AppData GetAppData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.APPDATA") as AppData;
}
2021-12-31 08:59:02 +01:00
public static void SetAppData(this HttpContext ctx, AppData appData)
{
ctx.Items["BTCPAY.APPDATA"] = appData;
}
public static bool SupportChain(this IConfiguration conf, string cryptoCode)
{
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.ToUpperInvariant()).ToHashSet();
return supportedChains.Contains(cryptoCode.ToUpperInvariant());
}
public static IActionResult RedirectToRecoverySeedBackup(this Controller controller, RecoverySeedBackupViewModel vm)
{
var redirectVm = new PostRedirectViewModel
{
2022-01-07 04:32:00 +01:00
AspController = "UIHome",
AspAction = "RecoverySeedBackup",
FormParameters =
{
{ "cryptoCode", vm.CryptoCode },
{ "mnemonic", vm.Mnemonic },
{ "passphrase", vm.Passphrase },
{ "isStored", vm.IsStored ? "true" : "false" },
{ "requireConfirm", vm.RequireConfirm ? "true" : "false" },
{ "returnUrl", vm.ReturnUrl }
}
};
return controller.View("PostRedirect", redirectVm);
}
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
var relationalCommandCache = enumerator.Private("_relationalCommandCache");
var selectExpression = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression>("_selectExpression");
var factory = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
var sqlGenerator = factory.Create();
var command = sqlGenerator.GetCommand(selectExpression);
string sql = command.CommandText;
return sql;
}
2021-11-22 09:16:08 +01:00
public static BTCPayNetworkProvider ConfigureNetworkProvider(this IConfiguration configuration, Logs logs)
{
var _networkType = DefaultConfiguration.GetNetworkType(configuration);
var supportedChains = configuration.GetOrDefault<string>("chains", "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.ToUpperInvariant()).ToHashSet();
2021-12-11 06:31:41 +01:00
foreach (var c in supportedChains.ToList())
{
if (new[] { "ETH", "USDT20", "FAU" }.Contains(c, StringComparer.OrdinalIgnoreCase))
{
logs.Configuration.LogWarning($"'{c}' is not anymore supported, please remove it from 'chains'");
supportedChains.Remove(c);
}
}
var networkProvider = new BTCPayNetworkProvider(_networkType);
var filtered = networkProvider.Filter(supportedChains.ToArray());
#if ALTCOINS
supportedChains.AddRange(filtered.GetAllElementsSubChains(networkProvider));
#endif
#if !ALTCOINS
var onlyBTC = supportedChains.Count == 1 && supportedChains.First() == "BTC";
if (!onlyBTC)
throw new ConfigException($"This build of BTCPay Server does not support altcoins");
#endif
var result = networkProvider.Filter(supportedChains.ToArray());
foreach (var chain in supportedChains)
{
if (result.GetNetwork<BTCPayNetworkBase>(chain) == null)
throw new ConfigException($"Invalid chains \"{chain}\"");
}
2021-11-22 09:16:08 +01:00
logs.Configuration.LogInformation(
"Supported chains: " + String.Join(',', supportedChains.ToArray()));
return result;
}
public static DataDirectories Configure(this DataDirectories dataDirectories, IConfiguration configuration)
{
var networkType = DefaultConfiguration.GetNetworkType(configuration);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
dataDirectories.DataDir = configuration["datadir"] ?? defaultSettings.DefaultDataDirectory;
dataDirectories.PluginDir = configuration["plugindir"] ?? defaultSettings.DefaultPluginDirectory;
2021-12-31 08:59:02 +01:00
dataDirectories.StorageDir = Path.Combine(dataDirectories.DataDir, Storage.Services.Providers.FileSystemStorage.FileSystemFileProviderService.LocalStorageDirectoryName);
dataDirectories.TempStorageDir = Path.Combine(dataDirectories.StorageDir, "tmp");
2022-03-31 11:54:25 +02:00
dataDirectories.TempDir = Path.Combine(dataDirectories.DataDir, "tmp");
return dataDirectories;
}
private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}
2017-09-13 08:47:34 +02:00
}