diff --git a/BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs b/BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs index 2f74cdb53..5cc4df6c5 100644 --- a/BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs +++ b/BTCPayServer.Abstractions/Contracts/BaseDbContextFactory.cs @@ -24,7 +24,9 @@ namespace BTCPayServer.Abstractions.Contracts class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator { +#pragma warning disable EF1001 // Internal EF Core API usage. public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, opts) +#pragma warning restore EF1001 // Internal EF Core API usage. { } diff --git a/BTCPayServer.Rating/Providers/HttpClientRequestMaker.cs b/BTCPayServer.Rating/Providers/HttpClientRequestMaker.cs index 16584b0db..0f0053964 100644 --- a/BTCPayServer.Rating/Providers/HttpClientRequestMaker.cs +++ b/BTCPayServer.Rating/Providers/HttpClientRequestMaker.cs @@ -10,104 +10,59 @@ namespace BTCPayServer.Services.Rates { internal class HttpClientRequestMaker : IAPIRequestMaker { - class InternalHttpWebRequest : IHttpWebRequest +#nullable enable + internal class InternalHttpWebRequest : IHttpWebRequest { - internal readonly HttpWebRequest Request; + internal readonly HttpRequestMessage Request; + internal HttpResponseMessage? Response; + private string? contentType; - public Uri RequestUri => Request.RequestUri; - - public string Method + public InternalHttpWebRequest(string method, Uri fullUri) { - get - { - return Request.Method; - } - set - { - Request.Method = value; - } - } - - public int Timeout - { - get - { - return Request.Timeout; - } - set - { - Request.Timeout = value; - } - } - - public int ReadWriteTimeout - { - get - { - return Request.ReadWriteTimeout; - } - set - { - Request.ReadWriteTimeout = value; - } - } - - public InternalHttpWebRequest(Uri fullUri) - { - Request = ((WebRequest.Create(fullUri) as HttpWebRequest) ?? throw new NullReferenceException("Failed to create HttpWebRequest")); - Request.KeepAlive = false; + Request = new HttpRequestMessage(new HttpMethod(method), fullUri); } public void AddHeader(string header, string value) { - switch (header.ToStringLowerInvariant()) + switch (header.ToLowerInvariant()) { case "content-type": - Request.ContentType = value; - break; - case "content-length": - Request.ContentLength = value.ConvertInvariant(0L); - break; - case "user-agent": - Request.UserAgent = value; - break; - case "accept": - Request.Accept = value; - break; - case "connection": - Request.Connection = value; + contentType = value; break; default: - Request.Headers[header] = value; + Request.Headers.TryAddWithoutValidation(header, value); break; } } + public Uri RequestUri + { + get { return Request.RequestUri!; } + } + + public string Method + { + get { return Request.Method.Method; } + set { Request.Method = new HttpMethod(value); } + } + + public int Timeout { get; set; } + + public int ReadWriteTimeout + { + get => Timeout; + set => Timeout = value; + } + + public Task WriteAllAsync(byte[] data, int index, int length) { - throw new NotImplementedException(); - } - - public HttpRequestMessage ToHttpRequestMessage() - { - var httpRequest = new HttpRequestMessage(HttpMethod.Get, Request.RequestUri); - CopyHeadersFrom(httpRequest, Request); - return httpRequest; - } - - internal void CopyHeadersFrom(HttpRequestMessage message, HttpWebRequest request) - { - foreach (string headerName in request.Headers) - { - string[] headerValues = request.Headers.GetValues(headerName); - if (!message.Headers.TryAddWithoutValidation(headerName, headerValues)) - { - if (message.Content != null) - message.Content.Headers.TryAddWithoutValidation(headerName, headerValues); - } - } + Request.Content = new ByteArrayContent(data, index, length); + Request.Content.Headers.Add("content-type", contentType); + return Task.CompletedTask; } } +#nullable restore class InternalHttpWebResponse : IHttpWebResponse { public InternalHttpWebResponse(HttpResponseMessage httpResponseMessage) @@ -164,44 +119,61 @@ namespace BTCPayServer.Services.Rates { url = "/" + url; } - string uri2 = (baseUrl ?? api.BaseUrl) + url; - if (method == null) - { - method = api.RequestMethod; - } - Uri uri = api.ProcessRequestUrl(new UriBuilder(uri2), payload, method); - InternalHttpWebRequest request = new InternalHttpWebRequest(uri) - { - Method = method - }; + + // prepare the request + string fullUrl = (baseUrl ?? api.BaseUrl) + url; + method ??= api.RequestMethod; + Uri uri = api.ProcessRequestUrl(new UriBuilder(fullUrl), payload, method); + InternalHttpWebRequest request = new InternalHttpWebRequest(method, uri); + request.AddHeader("accept-language", "en-US,en;q=0.5"); request.AddHeader("content-type", api.RequestContentType); - request.AddHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36"); - int num3 = request.Timeout = (request.ReadWriteTimeout = (int)api.RequestTimeout.TotalMilliseconds); + request.AddHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"); + request.Timeout = (int)api.RequestTimeout.TotalMilliseconds; await api.ProcessRequestAsync(request, payload); + + // send the request + var response = request.Response; + string responseString; + using var cancel = new CancellationTokenSource(request.Timeout); try { - RequestStateChanged?.Invoke(this, RequestMakerState.Begin, uri.AbsoluteUri); - using var webHttpRequest = request.ToHttpRequestMessage(); - using var webHttpResponse = await _httpClient.SendAsync(webHttpRequest, _cancellationToken); - string text = await webHttpResponse.Content.ReadAsStringAsync(); - if (!webHttpResponse.IsSuccessStatusCode) + RequestStateChanged?.Invoke(this, RequestMakerState.Begin, uri.AbsoluteUri);// when start make a request we send the uri, this helps developers to track the http requests. + response = await _httpClient.SendAsync(request.Request, cancel.Token); + if (response == null) { - if (string.IsNullOrWhiteSpace(text)) - { - throw new APIException($"{webHttpResponse.StatusCode.ConvertInvariant(0)} - {webHttpResponse.StatusCode}"); - } - throw new APIException(text); + throw new APIException("Unknown response from server"); } - api.ProcessResponse(new InternalHttpWebResponse(webHttpResponse)); + responseString = await response.Content.ReadAsStringAsync(); - RequestStateChanged?.Invoke(this, RequestMakerState.Finished, text); - return text; + if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created) + { + // 404 maybe return empty responseString + if (string.IsNullOrWhiteSpace(responseString)) + { + throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant(), response.StatusCode)); + } + + throw new APIException(responseString); + } + + api.ProcessResponse(new InternalHttpWebResponse(response)); + RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString); } - catch (Exception arg) + catch (OperationCanceledException ex) when (cancel.IsCancellationRequested) { - RequestStateChanged?.Invoke(this, RequestMakerState.Error, arg); + RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex); + throw new TimeoutException("APIRequest timeout", ex); + } + catch (Exception ex) + { + RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex); throw; } + finally + { + response?.Dispose(); + } + return responseString; } } } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 2d119c5d5..cd3de749b 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -2881,8 +2881,8 @@ namespace BTCPayServer.Tests //verify file is available and the same - var net = new System.Net.WebClient(); - var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId])); + using var net = new HttpClient(); + var data = await net.GetStringAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId])); Assert.Equal(fileContent, data); //create a temporary link to file @@ -2901,7 +2901,7 @@ namespace BTCPayServer.Tests .Replace("", string.Empty) .Replace("target='_blank'>", string.Empty); //verify tmpfile is available and the same - data = await net.DownloadStringTaskAsync(new Uri(url)); + data = await net.GetStringAsync(new Uri(url)); Assert.Equal(fileContent, data); diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index 3fa664789..44493ceeb 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -134,12 +135,12 @@ namespace BTCPayServer.Configuration builder.AppendLine("### NBXplorer settings ###"); foreach (var n in new BTCPayNetworkProvider(networkType).GetAll().OfType()) { - builder.AppendLine($"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}"); - builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.url={n.NBXplorerNetwork.DefaultSettings.DefaultUrl}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.explorer.cookiefile={ n.NBXplorerNetwork.DefaultSettings.DefaultCookieFile}"); if (n.SupportLightning) { - builder.AppendLine($"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc"); - builder.AppendLine($"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/"); + builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.lightning=/root/.lightning/lightning-rpc"); + builder.AppendLine(CultureInfo.InvariantCulture, $"#{n.CryptoCode}.lightning=https://apitoken:API_TOKEN_SECRET@charge.example.com/"); } } return builder.ToString(); diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index 75592d14b..2c3dc13d1 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Text; using System.Text.Encodings.Web; @@ -129,7 +130,7 @@ namespace BTCPayServer.Controllers if (settings.ShowCustomAmount) { StringBuilder builder = new StringBuilder(); - builder.AppendLine($"
"); + builder.AppendLine(CultureInfo.InvariantCulture, $""); builder.AppendLine($" "); builder.AppendLine($" "); builder.AppendLine($" "); @@ -143,12 +144,12 @@ namespace BTCPayServer.Controllers { var items = _appService.Parse(settings.Template, settings.Currency); var builder = new StringBuilder(); - builder.AppendLine($""); + builder.AppendLine(CultureInfo.InvariantCulture, $""); builder.AppendLine($" "); builder.AppendLine($" "); builder.AppendLine($" "); builder.AppendLine($" "); - builder.AppendLine($" "); + builder.AppendLine(CultureInfo.InvariantCulture, $" "); builder.AppendLine($"
"); vm.Example2 = builder.ToString(); } diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index 3c7cd6c96..54cc48a21 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -732,9 +732,9 @@ namespace BTCPayServer.Controllers.GreenField return GetFromActionResult(await _storesController.GetStores()); } - public override async Task GetStore(string storeId, CancellationToken token = default) + public override Task GetStore(string storeId, CancellationToken token = default) { - return GetFromActionResult(_storesController.GetStore(storeId)); + return Task.FromResult(GetFromActionResult(_storesController.GetStore(storeId))); } public override async Task RemoveStore(string storeId, CancellationToken token = default) diff --git a/BTCPayServer/Controllers/GreenField/StoreOnChainWalletsController.cs b/BTCPayServer/Controllers/GreenField/StoreOnChainWalletsController.cs index fc8516e23..2ba353412 100644 --- a/BTCPayServer/Controllers/GreenField/StoreOnChainWalletsController.cs +++ b/BTCPayServer/Controllers/GreenField/StoreOnChainWalletsController.cs @@ -538,7 +538,8 @@ namespace BTCPayServer.Controllers.GreenField derivationScheme = GetDerivationSchemeSettings(cryptoCode); if (derivationScheme?.AccountDerivation is null) { - actionResult = NotFound(); + actionResult = this.CreateAPIError("not-available", + $"{cryptoCode} doesn't have any derivation scheme set"); return true; } @@ -546,7 +547,7 @@ namespace BTCPayServer.Controllers.GreenField return false; } - private DerivationSchemeSettings GetDerivationSchemeSettings(string cryptoCode) + private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode) { var paymentMethod = Store .GetSupportedPaymentMethods(_btcPayNetworkProvider) diff --git a/BTCPayServer/Controllers/GreenField/UsersController.cs b/BTCPayServer/Controllers/GreenField/UsersController.cs index de7c1beba..7bac69797 100644 --- a/BTCPayServer/Controllers/GreenField/UsersController.cs +++ b/BTCPayServer/Controllers/GreenField/UsersController.cs @@ -91,11 +91,12 @@ namespace BTCPayServer.Controllers.GreenField } if (request.Password is null) ModelState.AddModelError(nameof(request.Password), "Password is missing"); - if (!ModelState.IsValid) { return this.CreateValidationError(ModelState); } + if (User.Identity is null) + throw new JsonHttpException(this.StatusCode(401)); var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any(); var policies = await _settingsRepository.GetSettingAsync() ?? new PoliciesSettings(); var isAuth = User.Identity.AuthenticationType == GreenFieldConstants.AuthenticationType; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 6dfb2d434..72f7a12f0 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -359,7 +359,7 @@ namespace BTCPayServer.Controllers Html = "Refund successfully created!
Share the link to this page with a customer.
The customer needs to enter their address and claim the refund.
Once a customer claims the refund, you will get a notification and would need to approve and initiate it from your Store > Payouts.", Severity = StatusMessageModel.StatusSeverity.Success }); - (await ctx.Invoices.FindAsync(new[] { invoice.Id }, cancellationToken)).CurrentRefundId = ppId; + (await ctx.Invoices.FindAsync(new[] { invoice.Id }, cancellationToken))!.CurrentRefundId = ppId; ctx.Refunds.Add(new RefundData() { InvoiceDataId = invoice.Id, diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index b43eb905e..cea64e559 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading; @@ -159,7 +160,7 @@ namespace BTCPayServer.Controllers if (!first) currencyPairsBuilder.Append(','); first = false; - currencyPairsBuilder.Append($"{baseCrypto}_{currencyCode}"); + currencyPairsBuilder.Append(CultureInfo.InvariantCulture, $"{baseCrypto}_{currencyCode}"); } return currencyPairsBuilder.ToString(); } diff --git a/BTCPayServer/Data/StoreBlob.cs b/BTCPayServer/Data/StoreBlob.cs index 0629d85f9..4dabe982b 100644 --- a/BTCPayServer/Data/StoreBlob.cs +++ b/BTCPayServer/Data/StoreBlob.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Globalization; using System.Linq; using System.Text; using BTCPayServer.Client.JsonConverters; @@ -144,7 +145,7 @@ namespace BTCPayServer.Data { if (network.DefaultRateRules.Length != 0) { - builder.AppendLine($"// Default rate rules for {network.CryptoCode}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"// Default rate rules for {network.CryptoCode}"); foreach (var line in network.DefaultRateRules) { builder.AppendLine(line); @@ -155,7 +156,7 @@ namespace BTCPayServer.Data } var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? CoinGeckoRateProvider.CoinGeckoName : PreferredExchange; - builder.AppendLine($"X_X = {preferredExchange}(X_X);"); + builder.AppendLine(CultureInfo.InvariantCulture, $"X_X = {preferredExchange}(X_X);"); BTCPayServer.Rating.RateRules.TryParse(builder.ToString(), out var rules); rules.Spread = Spread; diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 4f92d375a..8094d2ad9 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -91,7 +91,7 @@ namespace BTCPayServer builder.Append(expiration.Days.ToString(CultureInfo.InvariantCulture)); if (expiration.Hours >= 1) builder.Append(expiration.Hours.ToString("00", CultureInfo.InvariantCulture)); - builder.Append($"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); + builder.Append(CultureInfo.InvariantCulture, $"{expiration.Minutes.ToString("00", CultureInfo.InvariantCulture)}:{expiration.Seconds.ToString("00", CultureInfo.InvariantCulture)}"); return builder.ToString(); } public static decimal RoundUp(decimal value, int precision) diff --git a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs index 7f84e24be..9db69e835 100644 --- a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs +++ b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading; @@ -161,7 +162,7 @@ namespace BTCPayServer.HostedServices var result = new StringBuilder(); foreach (var transactionLabel in TransactionLabels) { - result.AppendLine( + result.AppendLine(CultureInfo.InvariantCulture, $"Adding {transactionLabel.Value.Count} labels to {transactionLabel.Key} in wallet {WalletId}"); } diff --git a/BTCPayServer/Plugins/PluginService.cs b/BTCPayServer/Plugins/PluginService.cs index c60934fec..228f3ef89 100644 --- a/BTCPayServer/Plugins/PluginService.cs +++ b/BTCPayServer/Plugins/PluginService.cs @@ -63,7 +63,10 @@ namespace BTCPayServer.Plugins } var filedest = Path.Join(dest, ext.Name); Directory.CreateDirectory(Path.GetDirectoryName(filedest)); - new WebClient().DownloadFile(new Uri(ext.DownloadUrl), filedest); + using var resp2 = await _githubClient.GetAsync(ext.DownloadUrl); + using var fs = new FileStream(filedest, FileMode.Create, FileAccess.ReadWrite); + await resp2.Content.CopyToAsync(fs); + await fs.FlushAsync(); } public void InstallPlugin(string plugin) diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index b13c91b6d..440bdcf0e 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Reflection; using System.Text; @@ -82,12 +83,12 @@ namespace BTCPayServer.Services public override string ToString() { StringBuilder txt = new StringBuilder(); - txt.Append($"@Copyright BTCPayServer v{Version}"); + txt.Append(CultureInfo.InvariantCulture, $"@Copyright BTCPayServer v{Version}"); if (AltcoinsVersion) txt.Append($" (altcoins)"); if (!Environment.IsProduction() || !Build.Equals("Release", StringComparison.OrdinalIgnoreCase)) { - txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}"); + txt.Append(CultureInfo.InvariantCulture, $" Environment: {Environment.EnvironmentName} Build: {Build}"); } return txt.ToString(); }