mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
Fix warnings
This commit is contained in:
parent
259f0b5aad
commit
c0e9f91bdc
@ -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.
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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<long>(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<int>(0)} - {webHttpResponse.StatusCode}");
|
||||
throw new APIException("Unknown response from server");
|
||||
}
|
||||
throw new APIException(text);
|
||||
}
|
||||
api.ProcessResponse(new InternalHttpWebResponse(webHttpResponse));
|
||||
responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
RequestStateChanged?.Invoke(this, RequestMakerState.Finished, text);
|
||||
return text;
|
||||
}
|
||||
catch (Exception arg)
|
||||
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
|
||||
{
|
||||
RequestStateChanged?.Invoke(this, RequestMakerState.Error, arg);
|
||||
// 404 maybe return empty responseString
|
||||
if (string.IsNullOrWhiteSpace(responseString))
|
||||
{
|
||||
throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant<int>(), response.StatusCode));
|
||||
}
|
||||
|
||||
throw new APIException(responseString);
|
||||
}
|
||||
|
||||
api.ProcessResponse(new InternalHttpWebResponse(response));
|
||||
RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString);
|
||||
}
|
||||
catch (OperationCanceledException ex) when (cancel.IsCancellationRequested)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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("</a>", 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);
|
||||
|
||||
|
||||
|
@ -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<BTCPayNetwork>())
|
||||
{
|
||||
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();
|
||||
|
@ -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($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine(CultureInfo.InvariantCulture, $"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"amount\" value=\"100\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
||||
@ -143,12 +144,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var items = _appService.Parse(settings.Template, settings.Currency);
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine(CultureInfo.InvariantCulture, $"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"orderId\" value=\"CustomOrderId\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"notificationUrl\" value=\"https://example.com/callbacks\" />");
|
||||
builder.AppendLine($" <input type=\"hidden\" name=\"redirectUrl\" value=\"https://example.com/thanksyou\" />");
|
||||
builder.AppendLine($" <button type=\"submit\" name=\"choiceKey\" value=\"{items[0].Id}\">Buy now</button>");
|
||||
builder.AppendLine(CultureInfo.InvariantCulture, $" <button type=\"submit\" name=\"choiceKey\" value=\"{items[0].Id}\">Buy now</button>");
|
||||
builder.AppendLine($"</form>");
|
||||
vm.Example2 = builder.ToString();
|
||||
}
|
||||
|
@ -732,9 +732,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return GetFromActionResult(await _storesController.GetStores());
|
||||
}
|
||||
|
||||
public override async Task<StoreData> GetStore(string storeId, CancellationToken token = default)
|
||||
public override Task<StoreData> GetStore(string storeId, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<StoreData>(_storesController.GetStore(storeId));
|
||||
return Task.FromResult(GetFromActionResult<StoreData>(_storesController.GetStore(storeId)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStore(string storeId, CancellationToken token = default)
|
||||
|
@ -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)
|
||||
|
@ -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<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
var isAuth = User.Identity.AuthenticationType == GreenFieldConstants.AuthenticationType;
|
||||
|
@ -359,7 +359,7 @@ namespace BTCPayServer.Controllers
|
||||
Html = "Refund successfully created!<br />Share the link to this page with a customer.<br />The customer needs to enter their address and claim the refund.<br />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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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}");
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user