Skip to content

Commit 0c6a1ad

Browse files
authored
Merge branch 'master' into license-expression
2 parents 194b3b8 + 80727a7 commit 0c6a1ad

15 files changed

Lines changed: 228 additions & 34 deletions

File tree

.github/FUNDING.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ jobs:
6262
dotnet-version: 3.1.x
6363
- uses: actions/setup-java@v4
6464
with:
65-
java-version: '17' # The JDK version to make available on the path.
65+
java-version: '21' # The JDK version to make available on the path.
66+
distribution: 'zulu'
6667
- name: Clean package cache as a temporary workaround for https://github.com/actions/setup-dotnet/issues/155
6768
working-directory: ./source
6869
run: dotnet clean -c Release && dotnet nuget locals all --clear
@@ -144,4 +145,4 @@ jobs:
144145
steps:
145146
- uses: release-drafter/release-drafter@v5
146147
env:
147-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
148+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/pull_request.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ jobs:
6060
uses: actions/setup-dotnet@v1
6161
with:
6262
dotnet-version: 3.1.x
63-
- uses: actions/setup-java@v1
63+
- uses: actions/setup-java@v4
6464
with:
65-
java-version: '17' # The JDK version to make available on the path.
65+
java-version: '21' # The JDK version to make available on the path.
66+
distribution: 'zulu'
6667
- name: Clean package cache as a temporary workaround for https://github.com/actions/setup-dotnet/issues/155
6768
working-directory: ./source
6869
run: dotnet clean -c Release && dotnet nuget locals all --clear
@@ -136,4 +137,4 @@ jobs:
136137
uses: actions/upload-artifact@v2
137138
with:
138139
name: Benchmark
139-
path: source/Handlebars.Benchmark/BenchmarkDotNet.Artifacts/results/
140+
path: source/Handlebars.Benchmark/BenchmarkDotNet.Artifacts/results/

source/Handlebars.Test/ExceptionTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,14 @@ public void TestLooseClosingBlockInIteratorExpressionException()
5757
Handlebars.Compile("{{#each enumerateMe}}test{{/if}}{{/each}}")(data);
5858
});
5959
}
60+
61+
[Fact]
62+
public void TestNonClosingIgnoreBlockException()
63+
{
64+
Assert.Throws<HandlebarsParserException>(() =>
65+
{
66+
Handlebars.Compile("{{ [test }}")(new { });
67+
});
68+
}
6069
}
6170
}

source/Handlebars.Test/InlinePartialTests.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Xunit;
1+
using System.Collections.Generic;
2+
using Xunit;
23

34
namespace HandlebarsDotNet.Test
45
{
@@ -355,6 +356,63 @@ public void InlinePartialInEach()
355356
var result = template(data);
356357
Assert.Equal("12", result);
357358
}
359+
360+
[Fact]
361+
public void RecursionUnboundedInlinePartial()
362+
{
363+
string source = "{{#*inline \"list\"}}{{>list}}{{/inline}}{{>list}}";
364+
365+
var template = Handlebars.Compile(source);
366+
367+
string Result() => template(null);
368+
var ex = Assert.Throws<HandlebarsRuntimeException>(Result);
369+
while (ex.InnerException != null)
370+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
371+
Assert.Equal("Runtime error while rendering partial 'list', exceeded recursion depth limit of 100", ex.Message);
372+
}
373+
374+
[Fact]
375+
public void RecursionBoundedToLimitInlinePartial()
376+
{
377+
string source = "{{#*inline \"list\"}}x{{#each items}}{{#if items}}{{>list}}{{/if}}{{/each}}{{/inline}}{{>list}}{{>list}}";
378+
379+
var template = Handlebars.Compile(source);
380+
381+
var data = new Dictionary<string, object>();
382+
var items = data;
383+
for (var depth = 0; depth < Handlebars.Configuration.PartialRecursionDepthLimit; depth++)
384+
{
385+
var nestedItems = new Dictionary<string, object>();
386+
items.Add("items", new[] { nestedItems });
387+
items = nestedItems;
388+
}
389+
390+
var result = template(data);
391+
Assert.Equal(new string('x', Handlebars.Configuration.PartialRecursionDepthLimit * 2), result);
392+
}
393+
394+
[Fact]
395+
public void RecursionBoundedAboveLimitInlinePartial()
396+
{
397+
string source = "{{#*inline \"list\"}}x{{#each items}}{{#if items}}{{>list}}{{/if}}{{/each}}{{/inline}}{{>list}}";
398+
399+
var template = Handlebars.Compile(source);
400+
401+
var data = new Dictionary<string, object>();
402+
var items = data;
403+
for (var depth = 0; depth < Handlebars.Configuration.PartialRecursionDepthLimit + 1; depth++)
404+
{
405+
var nestedItems = new Dictionary<string, object>();
406+
items.Add("items", new[] { nestedItems });
407+
items = nestedItems;
408+
}
409+
410+
string Result() => template(data);
411+
var ex = Assert.Throws<HandlebarsRuntimeException>(Result);
412+
while (ex.InnerException != null)
413+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
414+
Assert.Equal("Runtime error while rendering partial 'list', exceeded recursion depth limit of 100", ex.Message);
415+
}
358416
}
359417
}
360418

source/Handlebars.Test/PartialTests.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,30 @@ public void BlockPartialWithNestedSpecialNamedPartial2()
660660
Assert.Equal("A 1 B 3 C 4 D 2 E", result);
661661
}
662662

663+
[Fact]
664+
public void RecursionUnboundedBlockPartialWithSpecialNamedPartial()
665+
{
666+
string source = "{{#>myPartial}}{{>myPartial}}{{/myPartial}}";
667+
668+
var template = Handlebars.Compile(source);
669+
670+
var partialSource = "{{> @partial-block }}";
671+
using (var reader = new StringReader(partialSource))
672+
{
673+
var partialTemplate = Handlebars.Compile(reader);
674+
Handlebars.RegisterTemplate("myPartial", partialTemplate);
675+
}
676+
677+
string Result() => template(null);
678+
var ex = Assert.Throws<HandlebarsRuntimeException>(Result);
679+
Assert.Equal("Runtime error while rendering partial 'myPartial', see inner exception for more information", ex.Message);
680+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
681+
Assert.Equal("Runtime error while rendering partial 'myPartial', see inner exception for more information", ex.Message);
682+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
683+
Assert.Equal("Referenced partial name @partial-block could not be resolved", ex.Message);
684+
Assert.Null(ex.InnerException);
685+
}
686+
663687
[Fact]
664688
public void TemplateWithSpecialNamedPartial()
665689
{
@@ -717,6 +741,84 @@ public void SubExpressionPartial()
717741
var result = template(data);
718742
Assert.Equal("Hello, world!", result);
719743
}
744+
745+
[Fact]
746+
public void RecursionUnboundedPartial()
747+
{
748+
string source = "{{>list}}";
749+
750+
var template = Handlebars.Compile(source);
751+
752+
var partialSource = "{{>list}}";
753+
using (var reader = new StringReader(partialSource))
754+
{
755+
var partialTemplate = Handlebars.Compile(reader);
756+
Handlebars.RegisterTemplate("list", partialTemplate);
757+
}
758+
759+
string Result() => template(null);
760+
var ex = Assert.Throws<HandlebarsRuntimeException>(Result);
761+
while (ex.InnerException != null)
762+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
763+
Assert.Equal("Runtime error while rendering partial 'list', exceeded recursion depth limit of 100", ex.Message);
764+
}
765+
766+
[Fact]
767+
public void RecursionBoundedToLimitPartial()
768+
{
769+
string source = "{{>list}}{{>list}}";
770+
771+
var template = Handlebars.Compile(source);
772+
773+
var partialSource = "x{{#each items}}{{#if items}}{{>list}}{{/if}}{{/each}}";
774+
using (var reader = new StringReader(partialSource))
775+
{
776+
var partialTemplate = Handlebars.Compile(reader);
777+
Handlebars.RegisterTemplate("list", partialTemplate);
778+
}
779+
780+
var data = new Dictionary<string, object>();
781+
var items = data;
782+
for (var depth = 0; depth < Handlebars.Configuration.PartialRecursionDepthLimit; depth++)
783+
{
784+
var nestedItems = new Dictionary<string, object>();
785+
items.Add("items", new[] { nestedItems });
786+
items = nestedItems;
787+
}
788+
789+
var result = template(data);
790+
Assert.Equal(new string('x', Handlebars.Configuration.PartialRecursionDepthLimit * 2), result);
791+
}
792+
793+
[Fact]
794+
public void RecursionBoundedAboveLimitPartial()
795+
{
796+
string source = "{{>list}}";
797+
798+
var template = Handlebars.Compile(source);
799+
800+
var partialSource = "x{{#each items}}{{#if items}}{{>list}}{{/if}}{{/each}}";
801+
using (var reader = new StringReader(partialSource))
802+
{
803+
var partialTemplate = Handlebars.Compile(reader);
804+
Handlebars.RegisterTemplate("list", partialTemplate);
805+
}
806+
807+
var data = new Dictionary<string, object>();
808+
var items = data;
809+
for (var depth = 0; depth < Handlebars.Configuration.PartialRecursionDepthLimit + 1; depth++)
810+
{
811+
var nestedItems = new Dictionary<string, object>();
812+
items.Add("items", new[] { nestedItems });
813+
items = nestedItems;
814+
}
815+
816+
string Result() => template(data);
817+
var ex = Assert.Throws<HandlebarsRuntimeException>(Result);
818+
while (ex.InnerException != null)
819+
ex = Assert.IsType<HandlebarsRuntimeException>(ex.InnerException);
820+
Assert.Equal("Runtime error while rendering partial 'list', exceeded recursion depth limit of 100", ex.Message);
821+
}
720822
}
721823
}
722824

source/Handlebars.Test/PathInfoTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public void DotPath()
5050
[InlineData("a/[b.c].[b/c]/d", new [] {"a", "[b.c].[b/c]", "d"})]
5151
[InlineData("a/[b/c]/d", new [] {"a", "[b/c]", "d"})]
5252
[InlineData("a/[b.c/d]/e", new [] {"a", "[b.c/d]", "e"})]
53+
[InlineData("a/[b.c/d/e/f]/e", new [] {"a", "[b.c/d/e/f]", "e"})]
54+
[InlineData("a/[a//b/c.d/e]/e", new [] {"a", "[a//b/c.d/e]", "e"})]
55+
[InlineData("a/[b/c/d].[e/f/g]/h", new [] {"a", "[b/c/d].[e/f/g]", "h"})]
5356
public void SlashPath(string input, string[] expected)
5457
{
5558
var pathInfo = PathInfo.Parse(input);
@@ -62,4 +65,4 @@ public void SlashPath(string input, string[] expected)
6265
}
6366
}
6467
}
65-
}
68+
}

source/Handlebars/BindingContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ out WellKnownVariables[(int) WellKnownVariable.Parent]
117117

118118
internal TemplateDelegate PartialBlockTemplate { get; set; }
119119

120+
internal short PartialDepth { get; set; }
121+
120122
public object Value { get; set; }
121123

122124
[MethodImpl(MethodImplOptions.AggressiveInlining)]

source/Handlebars/Compiler/Lexer/Parsers/WordParser.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,7 @@ private static string AccumulateWord(ExtendedStringReader reader)
4444

4545
while (true)
4646
{
47-
if (isEscaped)
48-
{
49-
var c = (char) reader.Read();
50-
if (c == ']') isEscaped = false;
51-
52-
buffer.Append(c);
53-
continue;
54-
}
55-
56-
if (!inString)
47+
if (!inString && !isEscaped)
5748
{
5849
var peek = (char) reader.Peek();
5950

@@ -70,6 +61,15 @@ private static string AccumulateWord(ExtendedStringReader reader)
7061
throw new HandlebarsParserException("Reached end of template before the expression was closed.", reader.GetContext());
7162
}
7263

64+
if (isEscaped)
65+
{
66+
var c = (char) node;
67+
if (c == ']') isEscaped = false;
68+
69+
buffer.Append(c);
70+
continue;
71+
}
72+
7373
if (node == '[' && !inString)
7474
{
7575
isEscaped = true;

source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,24 @@ private static bool InvokePartial(
133133
return true;
134134
}
135135

136+
void IncreaseDepth()
137+
{
138+
if (++context.PartialDepth > configuration.PartialRecursionDepthLimit)
139+
throw new HandlebarsRuntimeException($"Runtime error while rendering partial '{partialName}', exceeded recursion depth limit of {configuration.PartialRecursionDepthLimit}");
140+
}
141+
136142
//if we have an inline partial, skip the file system and RegisteredTemplates collection
137143
if (context.InlinePartialTemplates.TryGetValue(partialName, out var partial))
138144
{
139-
partial(writer, context);
145+
IncreaseDepth();
146+
try
147+
{
148+
partial(writer, context);
149+
}
150+
finally
151+
{
152+
context.PartialDepth--;
153+
}
140154
return true;
141155
}
142156

@@ -152,6 +166,7 @@ private static bool InvokePartial(
152166
}
153167
}
154168

169+
IncreaseDepth();
155170
try
156171
{
157172
using var textWriter = writer.CreateWrapper();
@@ -162,6 +177,10 @@ private static bool InvokePartial(
162177
{
163178
throw new HandlebarsRuntimeException($"Runtime error while rendering partial '{partialName}', see inner exception for more information", exception);
164179
}
180+
finally
181+
{
182+
context.PartialDepth--;
183+
}
165184
}
166185
}
167186
}

0 commit comments

Comments
 (0)