diff --git a/BTCPayServer.Abstractions/Services/Safe.cs b/BTCPayServer.Abstractions/Services/Safe.cs index 054237b8e..b01c326e1 100644 --- a/BTCPayServer.Abstractions/Services/Safe.cs +++ b/BTCPayServer.Abstractions/Services/Safe.cs @@ -16,6 +16,8 @@ namespace BTCPayServer.Abstractions.Services _htmlHelper = htmlHelper; _jsonHelper = jsonHelper; _htmlSanitizer = htmlSanitizer; + + } public IHtmlContent Raw(string value) @@ -32,5 +34,37 @@ namespace BTCPayServer.Abstractions.Services { return _htmlHelper.Raw(_jsonHelper.Serialize(model)); } + + public string RawMeta(string inputHtml, out bool isHtmlModified) + { + bool bHtmlModified; + HtmlSanitizer _metaSanitizer = new HtmlSanitizer(); + + _metaSanitizer.AllowedTags.Clear(); + _metaSanitizer.AllowedTags.Add("meta"); + + _metaSanitizer.AllowedAttributes.Clear(); + _metaSanitizer.AllowedAttributes.Add("name"); + _metaSanitizer.AllowedAttributes.Add("http-equiv"); + _metaSanitizer.AllowedAttributes.Add("content"); + _metaSanitizer.AllowedAttributes.Add("value"); + _metaSanitizer.AllowedAttributes.Add("property"); + + _metaSanitizer.AllowDataAttributes = false; + + _metaSanitizer.RemovingTag += (sender, e) => bHtmlModified = true; + _metaSanitizer.RemovingAtRule += (sender, e) => bHtmlModified = true; + _metaSanitizer.RemovingAttribute += (sender, e) => bHtmlModified = true; + _metaSanitizer.RemovingComment += (sender, e) => bHtmlModified = true; + _metaSanitizer.RemovingCssClass += (sender, e) => bHtmlModified = true; + _metaSanitizer.RemovingStyle += (sender, e) => bHtmlModified = true; + + bHtmlModified = false; + + var sRet = _metaSanitizer.Sanitize(inputHtml); + isHtmlModified = bHtmlModified; + + return sRet; + } } } diff --git a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs index c7f9618d6..7822bdfad 100644 --- a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs +++ b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs @@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Form; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Abstractions.Services; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Controllers; @@ -22,6 +23,7 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; +using Ganss.Xss; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Identity; @@ -51,7 +53,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers UserManager userManager, FormDataService formDataService, IStringLocalizer stringLocalizer, - CrowdfundAppType app) + CrowdfundAppType app, + Safe safe) { _currencies = currencies; _appService = appService; @@ -64,6 +67,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers _invoiceController = invoiceController; FormDataService = formDataService; StringLocalizer = stringLocalizer; + _safe = safe; } private readonly EventAggregator _eventAggregator; @@ -75,6 +79,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers private readonly UIInvoiceController _invoiceController; private readonly UserManager _userManager; private readonly CrowdfundAppType _app; + private readonly Safe _safe; + public FormDataService FormDataService { get; } public IStringLocalizer StringLocalizer { get; } @@ -403,6 +409,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers Enabled = settings.Enabled, EnforceTargetAmount = settings.EnforceTargetAmount, StartDate = settings.StartDate, + HtmlMetaTags= settings.HtmlMetaTags, + Language = settings.Language, TargetCurrency = settings.TargetCurrency, MainImageUrl = settings.MainImageUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), settings.MainImageUrl), Description = settings.Description, @@ -524,6 +532,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers app.Name = vm.AppName; app.Archived = vm.Archived; + + bool wasHtmlModified; var newSettings = new CrowdfundSettings { Title = vm.Title, @@ -531,6 +541,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers EnforceTargetAmount = vm.EnforceTargetAmount, StartDate = vm.StartDate?.ToUniversalTime(), TargetCurrency = vm.TargetCurrency, + HtmlMetaTags= _safe.RawMeta(vm.HtmlMetaTags, out wasHtmlModified), + Language = vm.Language, Description = vm.Description, EndDate = vm.EndDate?.ToUniversalTime(), TargetAmount = vm.TargetAmount, @@ -574,7 +586,14 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers StoreId = app.StoreDataId, Settings = newSettings }); - TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value; + if (wasHtmlModified) + { + TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value; + } + else + { + TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value; + } return RedirectToAction(nameof(UpdateCrowdfund), new { appId }); } diff --git a/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs b/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs index 68c27aaa4..16336d7a0 100644 --- a/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs +++ b/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs @@ -194,6 +194,8 @@ namespace BTCPayServer.Plugins.Crowdfund { Title = settings.Title, Tagline = settings.Tagline, + Lang = settings.Language, + HtmlMetaTags= settings.HtmlMetaTags, Description = settings.Description, StoreName = store.StoreName, StoreId = appData.StoreDataId, diff --git a/BTCPayServer/Plugins/Crowdfund/Models/UpdateCrowdfundViewModel.cs b/BTCPayServer/Plugins/Crowdfund/Models/UpdateCrowdfundViewModel.cs index 2c46c49bf..df00dcf15 100644 --- a/BTCPayServer/Plugins/Crowdfund/Models/UpdateCrowdfundViewModel.cs +++ b/BTCPayServer/Plugins/Crowdfund/Models/UpdateCrowdfundViewModel.cs @@ -28,6 +28,13 @@ namespace BTCPayServer.Plugins.Crowdfund.Models public string Tagline { get; set; } + + public string Language { get; set; } + + [Display(Name = "HTML Meta Tags")] + public string HtmlMetaTags{ get; set; } + + [Required] public string Description { get; set; } diff --git a/BTCPayServer/Plugins/Crowdfund/Models/ViewCrowdfundViewModel.cs b/BTCPayServer/Plugins/Crowdfund/Models/ViewCrowdfundViewModel.cs index 6c4871538..4b5a62c59 100644 --- a/BTCPayServer/Plugins/Crowdfund/Models/ViewCrowdfundViewModel.cs +++ b/BTCPayServer/Plugins/Crowdfund/Models/ViewCrowdfundViewModel.cs @@ -14,6 +14,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Models public string AppId { get; set; } public string Title { get; set; } public string Description { get; set; } + public string Lang { get; set; } + public string HtmlMetaTags{ get; set; } public string MainImageUrl { get; set; } public string StoreName { get; set; } public DateTime? StartDate { get; set; } diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs index 52e9beaf5..e6dec5144 100644 --- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs +++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Encodings.Web; using System.Text.RegularExpressions; @@ -11,6 +12,7 @@ using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Form; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Abstractions.Services; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Controllers; @@ -26,6 +28,7 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; +using Ganss.Xss; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http.Extensions; @@ -55,7 +58,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers IStringLocalizer stringLocalizer, DisplayFormatter displayFormatter, IRateLimitService rateLimitService, - IAuthorizationService authorizationService) + IAuthorizationService authorizationService, + Safe safe) { _currencies = currencies; _appService = appService; @@ -66,6 +70,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers _displayFormatter = displayFormatter; _rateLimitService = rateLimitService; _authorizationService = authorizationService; + _safe = safe; StringLocalizer = stringLocalizer; FormDataService = formDataService; } @@ -79,6 +84,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers private readonly DisplayFormatter _displayFormatter; private readonly IRateLimitService _rateLimitService; private readonly IAuthorizationService _authorizationService; + private readonly Safe _safe; public FormDataService FormDataService { get; } public IStringLocalizer StringLocalizer { get; } @@ -132,6 +138,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers CustomTipPercentages = settings.CustomTipPercentages, AppId = appId, StoreId = store.Id, + Lang = settings.Language, + HtmlMetaTags= settings.HtmlMetaTags, Description = settings.Description, }); } @@ -611,6 +619,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF, CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF, CustomTipPercentages = settings.CustomTipPercentages != null ? string.Join(",", settings.CustomTipPercentages) : string.Join(",", PointOfSaleSettings.CUSTOM_TIP_PERCENTAGES_DEF), + Language = settings.Language, + HtmlMetaTags= settings.HtmlMetaTags, Description = settings.Description, NotificationUrl = settings.NotificationUrl, RedirectUrl = settings.RedirectUrl, @@ -687,6 +697,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers return View("PointOfSale/UpdatePointOfSale", vm); } + bool wasHtmlModified; var settings = new PointOfSaleSettings { Title = vm.Title, @@ -705,6 +716,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers CustomTipPercentages = ListSplit(vm.CustomTipPercentages), NotificationUrl = vm.NotificationUrl, RedirectUrl = vm.RedirectUrl, + Language = vm.Language, + HtmlMetaTags = _safe.RawMeta(vm.HtmlMetaTags, out wasHtmlModified), Description = vm.Description, RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically), FormId = vm.FormId @@ -714,7 +727,12 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers app.Archived = vm.Archived; app.SetSettings(settings); await _appService.UpdateOrCreateApp(app); - TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value; + if (wasHtmlModified) + { + TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value; + } else { + TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value; + } return RedirectToAction(nameof(UpdatePointOfSale), new { appId }); } diff --git a/BTCPayServer/Plugins/PointOfSale/Models/UpdatePointOfSaleViewModel.cs b/BTCPayServer/Plugins/PointOfSale/Models/UpdatePointOfSaleViewModel.cs index f05fd8c93..e18513488 100644 --- a/BTCPayServer/Plugins/PointOfSale/Models/UpdatePointOfSaleViewModel.cs +++ b/BTCPayServer/Plugins/PointOfSale/Models/UpdatePointOfSaleViewModel.cs @@ -86,6 +86,11 @@ namespace BTCPayServer.Plugins.PointOfSale.Models new() { Text = "Use Store Settings", Value = "" } }, nameof(SelectListItem.Value), nameof(SelectListItem.Text), RedirectAutomatically); + public string Language { get; set; } + + [Display(Name = "HTML Meta Tags")] + public string HtmlMetaTags{ get; set; } + public string Description { get; set; } [Display(Name = "Request customer data on checkout")] diff --git a/BTCPayServer/Plugins/PointOfSale/Models/ViewPointOfSaleViewModel.cs b/BTCPayServer/Plugins/PointOfSale/Models/ViewPointOfSaleViewModel.cs index 0e4104a15..77c4fb91b 100644 --- a/BTCPayServer/Plugins/PointOfSale/Models/ViewPointOfSaleViewModel.cs +++ b/BTCPayServer/Plugins/PointOfSale/Models/ViewPointOfSaleViewModel.cs @@ -68,6 +68,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Models public string CustomButtonText { get; set; } public string CustomTipText { get; set; } public int[] CustomTipPercentages { get; set; } + public string Lang { get; set; } + public string HtmlMetaTags{ get; set; } public string Description { get; set; } public SelectList AllCategories { get; set; } public string StoreId { get; set; } diff --git a/BTCPayServer/Services/Apps/CrowdfundSettings.cs b/BTCPayServer/Services/Apps/CrowdfundSettings.cs index 72e6434dc..bb31b3f02 100644 --- a/BTCPayServer/Services/Apps/CrowdfundSettings.cs +++ b/BTCPayServer/Services/Apps/CrowdfundSettings.cs @@ -1,13 +1,22 @@ using System; using BTCPayServer.JsonConverters; using Newtonsoft.Json; +using System.Globalization; +using System.Linq; namespace BTCPayServer.Services.Apps { public class CrowdfundSettings { + public CrowdfundSettings() + { + Language = "en"; + } + public string Title { get; set; } public string Description { get; set; } + public string Language { get; set; } + public string HtmlMetaTags{ get; set; } public bool Enabled { get; set; } = true; public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } diff --git a/BTCPayServer/Services/Apps/PointOfSaleSettings.cs b/BTCPayServer/Services/Apps/PointOfSaleSettings.cs index d2e0c2bae..e788a8ab2 100644 --- a/BTCPayServer/Services/Apps/PointOfSaleSettings.cs +++ b/BTCPayServer/Services/Apps/PointOfSaleSettings.cs @@ -8,6 +8,7 @@ namespace BTCPayServer.Services.Apps public PointOfSaleSettings() { Title = "Tea shop"; + Language = "en"; Template = AppService.SerializeTemplate([ new AppItem { @@ -99,6 +100,8 @@ namespace BTCPayServer.Services.Apps public string CustomTipText { get; set; } = CUSTOM_TIP_TEXT_DEF; public static readonly int[] CUSTOM_TIP_PERCENTAGES_DEF = { 15, 18, 20 }; public int[] CustomTipPercentages { get; set; } = CUSTOM_TIP_PERCENTAGES_DEF; + public string Language { get; set; } + public string HtmlMetaTags{ get; set; } public string Description { get; set; } public string NotificationUrl { get; set; } public string RedirectUrl { get; set; } diff --git a/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml b/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml index 807d81ace..a18afdbdd 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml @@ -14,7 +14,7 @@ } } - + @@ -35,6 +35,8 @@ object-fit: scale-down; } + @* Html.Raw OK here since Html has been cleaned before in controller *@ + @Html.Raw(Model.HtmlMetaTags) diff --git a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml index 5098555c0..e4566b26d 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml @@ -240,6 +240,36 @@

Additional Options

+ +
+

+ +

+
+
+
+ + +
Fix the HTML page language
+ +
+
+ + + +
+
+
+
+

@@ -321,7 +351,7 @@

diff --git a/BTCPayServer/Views/Shared/PointOfSale/Public/_Layout.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/_Layout.cshtml index 391b5f7e0..88f9b51d7 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/Public/_Layout.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/_Layout.cshtml @@ -33,13 +33,15 @@ } } - + + @* Html.Raw OK here since Html has been cleaned before in controller *@ + @Html.Raw(Model.HtmlMetaTags) @await RenderSectionAsync("PageHeadContent", false) diff --git a/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml index bd952489e..1e14153b1 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml @@ -93,6 +93,7 @@
+
@@ -196,6 +197,36 @@

Additional Options

+ +
+

+ +

+
+
+
+ + +
Fix the HTML page language
+ +
+
+ + + +
+
+
+
+