diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/BuildDisplayFilter.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/BuildDisplayFilter.cs index b365682a9cb..946efdc4040 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/BuildDisplayFilter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/BuildDisplayFilter.cs @@ -14,6 +14,8 @@ namespace OrchardCore.Contents.Liquid { public class BuildDisplayFilter : ILiquidFilter { + private const int DefaultMaxContentItemRecursions = 20; + public ValueTask ProcessAsync(FluidValue input, FilterArguments arguments, TemplateContext ctx) { static async ValueTask Awaited(Task task) @@ -49,8 +51,11 @@ static async ValueTask Awaited(Task task) var buildDisplayRecursionHelper = serviceProvider.GetRequiredService>(); - // When {{ Model.ContentItem | shape_build_display | shape_render }} is called prevent recursion. - if (buildDisplayRecursionHelper.IsRecursive(contentItem)) + // When {{ Model.ContentItem | shape_build_display | shape_render }} is called prevent unlimited recursions. + // max_recursions is an optional argument to override the default limit of 20. + var maxRecursions = arguments["max_recursions"]; + var recursionLimit = maxRecursions.Type == FluidValues.Number ? Convert.ToInt32(maxRecursions.ToNumberValue()) : DefaultMaxContentItemRecursions; + if (buildDisplayRecursionHelper.IsRecursive(contentItem, recursionLimit)) { return new ValueTask(NilValue.Instance); } diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/ContentItemRecursionHelper.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/ContentItemRecursionHelper.cs index 3a558e75ef6..a0d78616374 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/ContentItemRecursionHelper.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Liquid/ContentItemRecursionHelper.cs @@ -13,24 +13,35 @@ public interface IContentItemRecursionHelper where T : ILiquidFilter /// /// Returns when the has already been evaluated during this request by the particular filter./> /// - bool IsRecursive(ContentItem contentItem); + bool IsRecursive(ContentItem contentItem, int maxRecursions = 1); } /// public class ContentItemRecursionHelper : IContentItemRecursionHelper where T : ILiquidFilter { - private HashSet _contentItemIds = new HashSet(StringComparer.OrdinalIgnoreCase); + private Dictionary _recursions = new Dictionary(); /// - public bool IsRecursive(ContentItem contentItem) + public bool IsRecursive(ContentItem contentItem, int maxRecursions = 1) { - if (_contentItemIds.Contains(contentItem.ContentItemId)) + if (_recursions.ContainsKey(contentItem)) { - return true; - } + var counter = _recursions[contentItem]; + if (maxRecursions < 1) + { + maxRecursions = 1; + } + + if (counter > maxRecursions) + { + return true; + } - _contentItemIds.Add(contentItem.ContentItemId); + _recursions[contentItem] = counter + 1; + return false; + } + _recursions[contentItem] = 1; return false; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Startup.cs index 05dd8373f61..0d6178dca27 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Startup.cs @@ -140,8 +140,7 @@ public override void ConfigureServices(IServiceCollection services) services.AddLiquidFilter("display_url"); services.AddLiquidFilter("full_text"); - services.AddScoped, ContentItemRecursionHelper>(); - services.AddScoped, ContentItemRecursionHelper>(); + services.AddScoped(typeof(IContentItemRecursionHelper<>), typeof(ContentItemRecursionHelper<>)); } public override void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)