From ea5da426e955a0451f42753bbc0849bba29ae4c9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sat, 20 Feb 2021 17:20:35 +0100 Subject: [PATCH 01/12] Bump version to 8.12.0-rc --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 3ecfd20f03..cab7b9a12b 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.11.1")] -[assembly: AssemblyInformationalVersion("8.11.1")] +[assembly: AssemblyFileVersion("8.12.0")] +[assembly: AssemblyInformationalVersion("8.12.0-rc")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 69bdeba643..6da1e7bcdf 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -347,9 +347,9 @@ False True - 8111 + 8120 / - http://localhost:8111 + http://localhost:8120 False False From ad0b70fc04354876132c2b1129ed0f8977689091 Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 21 Feb 2021 23:00:00 +1300 Subject: [PATCH 02/12] Speed up boot times and Improve Json (De)Serialization performance and reduce memory usage by reusing JsonSerializerSettings (#9670) (cherry picked from commit 75ee3b96229d2fe5debfdf7fb274c321ae2342a8) --- .../ImageCropperValueConverter.cs | 12 ++-- .../NoTypeConverterJsonConverter.cs | 5 +- .../JsonSerializerSettingsBenchmarks.cs | 69 +++++++++++++++++++ .../Umbraco.Tests.Benchmarks.csproj | 1 + .../Editors/BackOfficeController.cs | 6 +- .../ImageCropperTemplateExtensions.cs | 14 ++-- src/Umbraco.Web/Mvc/JsonNetResult.cs | 9 +++ .../MultiUrlPickerValueEditor.cs | 11 +-- .../NuCache/DataSource/DatabaseDataSource.cs | 11 +-- 9 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 8926174c03..29e501f993 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -25,6 +25,12 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; + private static readonly JsonSerializerSettings ImageCropperValueJsonSerializerSettings = new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }; + /// public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { @@ -34,11 +40,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters ImageCropperValue value; try { - value = JsonConvert.DeserializeObject(sourceString, new JsonSerializerSettings - { - Culture = CultureInfo.InvariantCulture, - FloatParseHandling = FloatParseHandling.Decimal - }); + value = JsonConvert.DeserializeObject(sourceString, ImageCropperValueJsonSerializerSettings); } catch (Exception ex) { diff --git a/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs b/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs index b06ee870de..ab64d5b368 100644 --- a/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs +++ b/src/Umbraco.Core/Serialization/NoTypeConverterJsonConverter.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Serialization internal class NoTypeConverterJsonConverter : JsonConverter { static readonly IContractResolver resolver = new NoTypeConverterContractResolver(); + private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { ContractResolver = resolver }; private class NoTypeConverterContractResolver : DefaultContractResolver { @@ -41,12 +42,12 @@ namespace Umbraco.Core.Serialization public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType); + return JsonSerializer.CreateDefault(JsonSerializerSettings).Deserialize(reader, objectType); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value); + JsonSerializer.CreateDefault(JsonSerializerSettings).Serialize(writer, value); } } } diff --git a/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs new file mode 100644 index 0000000000..7f419547bd --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/JsonSerializerSettingsBenchmarks.cs @@ -0,0 +1,69 @@ +using BenchmarkDotNet.Attributes; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Tests.Benchmarks.Config; + +namespace Umbraco.Tests.Benchmarks +{ + [QuickRunConfig] + [MemoryDiagnoser] + public class JsonSerializerSettingsBenchmarks + { + [Benchmark] + public void SerializerSettingsInstantiation() + { + int instances = 1000; + for (int i = 0; i < instances; i++) + { + new JsonSerializerSettings(); + } + } + + [Benchmark(Baseline =true)] + public void SerializerSettingsSingleInstantiation() + { + new JsonSerializerSettings(); + } + +// // * Summary * + +// BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18362 +//Intel Core i5-8265U CPU 1.60GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4250.0 +// Job-JIATTD : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.8.4250.0 + +//IterationCount=3 IterationTime=100.0000 ms LaunchCount = 1 +//WarmupCount=3 + +// Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | +//-------------------------------------- |-------------:|-------------:|------------:|-------:|--------:|------------:|------------:|------------:|--------------------:| +// SerializerSettingsInstantiation | 29,120.48 ns | 5,532.424 ns | 303.2508 ns | 997.84 | 23.66 | 73.8122 | - | - | 232346 B | +// SerializerSettingsSingleInstantiation | 29.19 ns | 8.089 ns | 0.4434 ns | 1.00 | 0.00 | 0.0738 | - | - | 232 B | + +//// * Warnings * +//MinIterationTime +// JsonSerializerSettingsBenchmarks.SerializerSettingsSingleInstantiation: IterationCount= 3, IterationTime= 100.0000 ms, LaunchCount= 1, WarmupCount= 3->MinIterationTime = 96.2493 ms which is very small. It's recommended to increase it. + +//// * Legends * +// Mean : Arithmetic mean of all measurements +// Error : Half of 99.9% confidence interval +// StdDev : Standard deviation of all measurements +// Ratio : Mean of the ratio distribution ([Current]/[Baseline]) +// RatioSD : Standard deviation of the ratio distribution([Current]/[Baseline]) +// Gen 0/1k Op : GC Generation 0 collects per 1k Operations +// Gen 1/1k Op : GC Generation 1 collects per 1k Operations +// Gen 2/1k Op : GC Generation 2 collects per 1k Operations +// Allocated Memory/Op : Allocated memory per single operation(managed only, inclusive, 1KB = 1024B) +// 1 ns : 1 Nanosecond(0.000000001 sec) + +//// * Diagnostic Output - MemoryDiagnoser * + + +// // ***** BenchmarkRunner: End ***** +// Run time: 00:00:04 (4.88 sec), executed benchmarks: 2 + } +} diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 48d69cf757..58b45aa743 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -53,6 +53,7 @@ + diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 458c76b3ae..92b67cbf1b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -226,7 +226,7 @@ namespace Umbraco.Web.Editors .ToDictionary(pv => pv.Key, pv => pv.ToDictionary(pve => pve.valueAlias, pve => pve.value)); - return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None }; + return new JsonNetResult(JsonNetResult.DefaultJsonSerializerSettings) { Data = nestedDictionary, Formatting = Formatting.None }; } /// @@ -273,7 +273,7 @@ namespace Umbraco.Web.Editors GetAssetList, new TimeSpan(0, 2, 0)); - return new JsonNetResult { Data = result, Formatting = Formatting.None }; + return new JsonNetResult(JsonNetResult.DefaultJsonSerializerSettings) { Data = result, Formatting = Formatting.None }; } [UmbracoAuthorize(Order = 0)] @@ -281,7 +281,7 @@ namespace Umbraco.Web.Editors public JsonNetResult GetGridConfig() { var gridConfig = Current.Configs.Grids(); - return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; + return new JsonNetResult(JsonNetResult.DefaultJsonSerializerSettings) { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; } diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index 26dd2a5d36..78b55a8930 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -253,19 +253,20 @@ namespace Umbraco.Web ImageCropRatioMode? ratioMode = null, bool upScale = true) => ImageCropperTemplateCoreExtensions.GetCropUrl(imageUrl, Current.ImageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + private static readonly JsonSerializerSettings ImageCropperValueJsonSerializerSettings = new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture, + FloatParseHandling = FloatParseHandling.Decimal + }; internal static ImageCropperValue DeserializeImageCropperValue(this string json) { - var imageCrops = new ImageCropperValue(); + ImageCropperValue imageCrops = null; if (json.DetectIsJson()) { try { - imageCrops = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - Culture = CultureInfo.InvariantCulture, - FloatParseHandling = FloatParseHandling.Decimal - }); + imageCrops = JsonConvert.DeserializeObject(json, ImageCropperValueJsonSerializerSettings); } catch (Exception ex) { @@ -273,6 +274,7 @@ namespace Umbraco.Web } } + imageCrops = imageCrops ?? new ImageCropperValue(); return imageCrops; } } diff --git a/src/Umbraco.Web/Mvc/JsonNetResult.cs b/src/Umbraco.Web/Mvc/JsonNetResult.cs index da6780451e..3dd6c2f398 100644 --- a/src/Umbraco.Web/Mvc/JsonNetResult.cs +++ b/src/Umbraco.Web/Mvc/JsonNetResult.cs @@ -22,10 +22,19 @@ namespace Umbraco.Web.Mvc public JsonSerializerSettings SerializerSettings { get; set; } public Formatting Formatting { get; set; } + /// + /// Default, unchanged JsonSerializerSettings + /// + public static readonly JsonSerializerSettings DefaultJsonSerializerSettings = new JsonSerializerSettings(); + public JsonNetResult() { SerializerSettings = new JsonSerializerSettings(); } + public JsonNetResult(JsonSerializerSettings jsonSerializerSettings) + { + SerializerSettings = jsonSerializerSettings; + } public override void ExecuteResult(ControllerContext context) { diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs index 5a84e4b20c..560275b29a 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -121,6 +121,10 @@ namespace Umbraco.Web.PropertyEditors return base.ToEditor(property, dataTypeService, culture, segment); } + private static readonly JsonSerializerSettings LinkDisplayJsonSerializerSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }; public override object FromEditor(ContentPropertyData editorValue, object currentValue) { @@ -142,11 +146,8 @@ namespace Umbraco.Web.PropertyEditors Target = link.Target, Udi = link.Udi, Url = link.Udi == null ? link.Url : null, // only save the URL for external links - }, - new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }); + }, LinkDisplayJsonSerializerSettings + ); } catch (Exception ex) { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index f62014a368..f9ad0ac715 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -303,17 +303,18 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource return s; } + private static readonly JsonSerializerSettings NestedContentDataJsonSerializerSettings = new JsonSerializerSettings + { + Converters = new List { new ForceInt32Converter() } + }; + private static ContentNestedData DeserializeNestedData(string data) { // by default JsonConvert will deserialize our numeric values as Int64 // which is bad, because they were Int32 in the database - take care - var settings = new JsonSerializerSettings - { - Converters = new List { new ForceInt32Converter() } - }; - return JsonConvert.DeserializeObject(data, settings); + return JsonConvert.DeserializeObject(data, NestedContentDataJsonSerializerSettings); } } } From 64117921eada1761c93d6564b932441d92098c0c Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 21 Feb 2021 22:03:28 +1100 Subject: [PATCH 03/12] Fixes: After deleting a member, a reindex is attempted, which fails (#9807) (cherry picked from commit fa49d6ed1082dc0ad82ada8fb98c5281ed13271e) --- .../Cache/DistributedCacheExtensions.cs | 5 ++++- src/Umbraco.Web/Cache/MemberCacheRefresher.cs | 4 ++++ src/Umbraco.Web/Search/ExamineComponent.cs | 16 +++++++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index f360d37d03..92a9dd6e98 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -135,7 +135,10 @@ namespace Umbraco.Web.Cache public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) { if (members.Length == 0) return; - dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username))); + dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username) + { + Removed = true + })); } #endregion diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index 736a858af3..48ae40ce3b 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -33,6 +33,10 @@ namespace Umbraco.Web.Cache public int Id { get; } public string Username { get; } + // TODO: In netcore change this to be get only and adjust the ctor. We cannot do that now since that + // is a breaking change due to only having a single jsonconstructor allowed. + public bool Removed { get; set; } + } #region Define diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index c9d7b7cf56..eb6b81ba16 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -271,10 +271,20 @@ namespace Umbraco.Web.Search break; case MessageType.RefreshByPayload: var payload = (MemberCacheRefresher.JsonPayload[])args.MessageObject; - var members = payload.Select(x => _services.MemberService.GetById(x.Id)); - foreach(var m in members) + foreach(var p in payload) { - ReIndexForMember(m); + if (p.Removed) + { + DeleteIndexForEntity(p.Id, false); + } + else + { + var m = _services.MemberService.GetById(p.Id); + if (m != null) + { + ReIndexForMember(m); + } + } } break; case MessageType.RefreshAll: From 388e0283bc2b493a8c285f45d0bdbbea088fef20 Mon Sep 17 00:00:00 2001 From: Chad Date: Mon, 22 Feb 2021 21:53:53 +1300 Subject: [PATCH 04/12] Fixes #9615 - Upgrade to Htmlsanitizer v5 (#9856) (cherry picked from commit a6e1c2e90170dc796a2ed5f601b59f7d1a94a9a9) --- build/NuSpecs/UmbracoCms.Web.nuspec | 1 + src/Umbraco.Core/Constants-SvgSanitizer.cs | 23 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/Runtime/WebInitialComposer.cs | 9 ++++++++ src/Umbraco.Web/Services/IconService.cs | 22 ++++++++---------- src/Umbraco.Web/Umbraco.Web.csproj | 7 ++++-- 6 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Core/Constants-SvgSanitizer.cs diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index e0ffd57aff..fc31551d6c 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -42,6 +42,7 @@ + diff --git a/src/Umbraco.Core/Constants-SvgSanitizer.cs b/src/Umbraco.Core/Constants-SvgSanitizer.cs new file mode 100644 index 0000000000..c92b9f56c7 --- /dev/null +++ b/src/Umbraco.Core/Constants-SvgSanitizer.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the alias identifiers for Umbraco's core application sections. + /// + public static class SvgSanitizer + { + /// + /// Allowlist for SVG attributes. + /// + public static readonly IList Attributes = new [] { "accent-height", "accumulate", "additive", "alignment-baseline", "allowReorder", "alphabetic", "amplitude", "arabic-form", "ascent", "attributeName", "attributeType", "autoReverse", "azimuth", "baseFrequency", "baseline-shift", "baseProfile", "bbox", "begin", "bias", "by", "calcMode", "cap-height", "class", "clip", "clipPathUnits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "contentScriptType", "contentStyleType", "cursor", "cx", "cy", "d", "decelerate", "descent", "diffuseConstant", "direction", "display", "divisor", "dominant-baseline", "dur", "dx", "dy", "edgeMode", "elevation", "enable-background", "end", "exponent", "externalResourcesRequired", "Section", "fill", "fill-opacity", "fill-rule", "filter", "filterRes", "filterUnits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "format", "from", "fr", "fx", "fy", "g1", "g2", "glyph-name", "glyph-orientation-horizontal", "glyph-orientation-vertical", "glyphRef", "gradientTransform", "gradientUnits", "hanging", "height", "href", "hreflang", "horiz-adv-x", "horiz-origin-x", "ISection", "id", "ideographic", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kernelMatrix", "kernelUnitLength", "kerning", "keyPoints", "keySplines", "keyTimes", "lang", "lengthAdjust", "letter-spacing", "lighting-color", "limitingConeAngle", "local", "MSection", "marker-end", "marker-mid", "marker-start", "markerHeight", "markerUnits", "markerWidth", "mask", "maskContentUnits", "maskUnits", "mathematical", "max", "media", "method", "min", "mode", "NSection", "name", "numOctaves", "offset", "opacity", "operator", "order", "orient", "orientation", "origin", "overflow", "overline-position", "overline-thickness", "panose-1", "paint-order", "path", "pathLength", "patternContentUnits", "patternTransform", "patternUnits", "ping", "pointer-events", "points", "pointsAtX", "pointsAtY", "pointsAtZ", "preserveAlpha", "preserveAspectRatio", "primitiveUnits", "r", "radius", "referrerPolicy", "refX", "refY", "rel", "rendering-intent", "repeatCount", "repeatDur", "requiredExtensions", "requiredFeatures", "restart", "result", "rotate", "rx", "ry", "scale", "seed", "shape-rendering", "slope", "spacing", "specularConstant", "specularExponent", "speed", "spreadMethod", "startOffset", "stdDeviation", "stemh", "stemv", "stitchTiles", "stop-color", "stop-opacity", "strikethrough-position", "strikethrough-thickness", "string", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "surfaceScale", "systemLanguage", "tabindex", "tableValues", "target", "targetX", "targetY", "text-anchor", "text-decoration", "text-rendering", "textLength", "to", "transform", "type", "u1", "u2", "underline-position", "underline-thickness", "unicode", "unicode-bidi", "unicode-range", "units-per-em", "v-alphabetic", "v-hanging", "v-ideographic", "v-mathematical", "values", "vector-effect", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "viewBox", "viewTarget", "visibility", "width", "widths", "word-spacing", "writing-mode", "x", "x-height", "x1", "x2", "xChannelSelector", "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "y", "y1", "y2", "yChannelSelector", "z", "zoomAndPan" }; + + /// + /// Allowlist for SVG tabs. + /// + public static readonly IList Tags = new [] { "a", "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "discard", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hatch", "hatchpath", "hkern", "image", "line", "linearGradient", "marker", "mask", "mesh", "meshgradient", "meshpatch", "meshrow", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "solidcolor", "stop", "svg", "switch", "symbol", "text", "textPath", "title", "tref", "tspan", "unknown", "use", "view", "vkern" }; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b98ad64f7a..832b8a5801 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -256,6 +256,7 @@ + diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index ac7e34d5cb..b15641b503 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -40,6 +40,7 @@ using Current = Umbraco.Web.Composing.Current; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Models; using Umbraco.Web.Models; +using Ganss.XSS; namespace Umbraco.Web.Runtime { @@ -139,6 +140,14 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.Register(_ => + { + var sanitizer = new HtmlSanitizer(); + sanitizer.AllowedAttributes.UnionWith(Umbraco.Core.Constants.SvgSanitizer.Attributes); + sanitizer.AllowedCssProperties.UnionWith(Umbraco.Core.Constants.SvgSanitizer.Attributes); + sanitizer.AllowedTags.UnionWith(Umbraco.Core.Constants.SvgSanitizer.Tags); + return sanitizer; + },Lifetime.Singleton); composition.RegisterUnique(factory => ExamineManager.Instance); diff --git a/src/Umbraco.Web/Services/IconService.cs b/src/Umbraco.Web/Services/IconService.cs index a74fa909e0..175650fd12 100644 --- a/src/Umbraco.Web/Services/IconService.cs +++ b/src/Umbraco.Web/Services/IconService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Ganss.XSS; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -12,31 +13,24 @@ namespace Umbraco.Web.Services public class IconService : IIconService { private readonly IGlobalSettings _globalSettings; + private readonly IHtmlSanitizer _htmlSanitizer; - public IconService(IGlobalSettings globalSettings) + public IconService(IGlobalSettings globalSettings, IHtmlSanitizer htmlSanitizer) { _globalSettings = globalSettings; + _htmlSanitizer = htmlSanitizer; } /// public IList GetAllIcons() { - var icons = new List(); var directory = new DirectoryInfo(IOHelper.MapPath($"{_globalSettings.IconsPath}/")); var iconNames = directory.GetFiles("*.svg"); - iconNames.OrderBy(f => f.Name).ToList().ForEach(iconInfo => - { - var icon = GetIcon(iconInfo); + return iconNames.OrderBy(f => f.Name) + .Select(iconInfo => GetIcon(iconInfo)).WhereNotNull().ToList(); - if (icon != null) - { - icons.Add(icon); - } - }); - - return icons; } /// @@ -70,10 +64,12 @@ namespace Umbraco.Web.Services try { var svgContent = System.IO.File.ReadAllText(iconPath); + var sanitizedString = _htmlSanitizer.Sanitize(svgContent); + var svg = new IconModel { Name = iconName, - SvgString = svgContent + SvgString = sanitizedString }; return svg; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 80c8d19a58..ca29e69991 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -65,6 +65,9 @@ + + 5.0.376 + 2.7.0.100 @@ -1293,7 +1296,7 @@ - +