Skip to content

Commit 75ff72d

Browse files
authored
Tag Escaping (#33)
* Bump version to 2.0 since we have a breaking change * Add escaping by default to helper tag and explict excludes Brings the helper tag inline with InteroplationTag * Add and update tests for escaping and using FluentAssertions * Update readme for esaping * Bump versions to latest for internal and test packages
1 parent 4d0df7e commit 75ff72d

10 files changed

+297
-124
lines changed

readme.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ var stubble = new StubbleBuilder()
1212
.Configure(conf => conf.AddHelpers(helpers))
1313
.Build();
1414

15-
var result = stubble.Render("{{FormatCurrency Count}}", new { Count = 100.26m });
15+
// Note the '{{{' here to escape the £ symbol, by default the result is HtmlEscaped like the standard mustache tag
16+
var result = stubble.Render("{{{FormatCurrency Count}}}", new { Count = 100.26m });
1617

1718
Assert.Equal("£100.26", result);
1819
```
@@ -41,7 +42,7 @@ var stubble = new StubbleBuilder()
4142
.Configure(conf => conf.AddHelpers(helpers))
4243
.Build();
4344

44-
var result = stubble.Render("{{FormatCurrency 10}}", new { });
45+
var result = stubble.Render("{{{FormatCurrency 10}}}", new { });
4546

4647
Assert.Equal("£10.00", result);
4748
```

src/Stubble.Helpers/HelperTagParser.cs

+30-8
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,48 @@ public override bool Match(Processor processor, ref StringSlice slice)
2323
{
2424
throw new System.ArgumentNullException(nameof(processor));
2525
}
26-
2726
var tagStart = slice.Start - processor.CurrentTags.StartTag.Length;
27+
var escapeResult = true;
28+
var isTripleMustache = false;
2829
var index = slice.Start;
2930

3031
while (slice[index].IsWhitespace())
3132
{
3233
index++;
3334
}
3435

36+
var match = slice[index];
37+
if (match == '&')
38+
{
39+
escapeResult = false;
40+
index++;
41+
}
42+
else if (match == '{')
43+
{
44+
escapeResult = false;
45+
isTripleMustache = true;
46+
index++;
47+
}
48+
49+
while (slice[index].IsWhitespace())
50+
{
51+
index++;
52+
}
53+
54+
var endTag = isTripleMustache ? '}' + processor.CurrentTags.EndTag : processor.CurrentTags.EndTag;
55+
3556
var nameStart = index;
3657

3758
// Skip whitespace or until end tag
38-
while (!slice[index].IsWhitespace() && !slice.Match(processor.CurrentTags.EndTag, index - slice.Start))
59+
while (!slice[index].IsWhitespace() && !slice.Match(endTag, index - slice.Start))
3960
{
4061
index++;
4162
}
4263

4364
var name = slice.ToString(nameStart, index);
4465

4566
// Skip whitespace or until end tag
46-
while (slice[index].IsWhitespace() && !slice.Match(processor.CurrentTags.EndTag, index - slice.Start))
67+
while (slice[index].IsWhitespace() && !slice.Match(endTag, index - slice.Start))
4768
{
4869
index++;
4970
}
@@ -60,7 +81,7 @@ public override bool Match(Processor processor, ref StringSlice slice)
6081
var argsStart = index;
6182
slice.Start = index;
6283

63-
while (!slice.IsEmpty && !slice.Match(processor.CurrentTags.EndTag))
84+
while (!slice.IsEmpty && !slice.Match(endTag))
6485
{
6586
slice.NextChar();
6687
}
@@ -73,15 +94,15 @@ public override bool Match(Processor processor, ref StringSlice slice)
7394
}
7495
else
7596
{
76-
while (!slice.IsEmpty && !slice.Match(processor.CurrentTags.EndTag))
97+
while (!slice.IsEmpty && !slice.Match(endTag))
7798
{
7899
slice.NextChar();
79100
}
80101

81102
contentEnd = slice.Start;
82103
}
83104

84-
if (!slice.Match(processor.CurrentTags.EndTag))
105+
if (!slice.Match(endTag))
85106
{
86107
throw new StubbleException($"Unclosed Tag at {slice.Start.ToString(CultureInfo.InvariantCulture)}");
87108
}
@@ -92,11 +113,12 @@ public override bool Match(Processor processor, ref StringSlice slice)
92113
ContentStartPosition = nameStart,
93114
Name = name,
94115
Args = argsList,
116+
EscapeResult = escapeResult,
95117
ContentEndPosition = contentEnd,
96-
TagEndPosition = slice.Start + processor.CurrentTags.EndTag.Length,
118+
TagEndPosition = slice.Start + endTag.Length,
97119
IsClosed = true
98120
};
99-
slice.Start += processor.CurrentTags.EndTag.Length;
121+
slice.Start += endTag.Length;
100122

101123
processor.CurrentToken = tag;
102124
processor.HasSeenNonSpaceOnLine = true;

src/Stubble.Helpers/HelperTagRenderer.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Immutable;
33
using System.Globalization;
4+
using System.Net.Http.Headers;
45
using System.Threading.Tasks;
56
using Stubble.Core.Contexts;
67
using Stubble.Core.Renderers.StringRenderer;
@@ -61,14 +62,23 @@ protected override void Write(StringRender renderer, HelperToken obj, Context co
6162
}
6263

6364
var result = helper.Delegate.Method.Invoke(helper.Delegate.Target, arr);
65+
66+
string value = null;
6467
if (result is string str)
6568
{
66-
renderer.Write(str);
69+
value = str;
6770
}
6871
else if (result is object)
6972
{
70-
renderer.Write(Convert.ToString(result, context.RenderSettings.CultureInfo));
73+
value = Convert.ToString(result, context.RenderSettings.CultureInfo);
7174
}
75+
76+
if (!context.RenderSettings.SkipHtmlEncoding && obj.EscapeResult && value is object)
77+
{
78+
value = context.RendererSettings.EncodingFuction(value);
79+
}
80+
81+
renderer.Write(value);
7282
}
7383
}
7484
}

src/Stubble.Helpers/HelperToken.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ public class HelperToken : InlineToken<HelperToken>, INonSpace
99

1010
public ImmutableArray<HelperArgument> Args { get; set; }
1111

12+
public bool EscapeResult { get; set; }
13+
1214
public override bool Equals(HelperToken other) =>
1315
other is object
1416
&& (other.TagStartPosition, other.TagEndPosition, other.ContentStartPosition, other.ContentEndPosition, other.IsClosed)
1517
== (TagStartPosition, TagEndPosition, ContentStartPosition, ContentEndPosition, IsClosed)
1618
&& other.Content.Equals(Content)
1719
&& other.Name.Equals(Name, System.StringComparison.OrdinalIgnoreCase)
18-
&& CompareHelper.CompareImmutableArraysWithEquatable(Args, other.Args);
20+
&& CompareHelper.CompareImmutableArraysWithEquatable(Args, other.Args)
21+
&& other.EscapeResult == EscapeResult;
1922

2023
public override bool Equals(object obj)
2124
=> obj is HelperToken a && Equals(a);
@@ -24,6 +27,6 @@ public override bool Equals(object obj)
2427
/// </summary>
2528
/// <returns></returns>
2629
public override int GetHashCode()
27-
=> (Name, Args, TagStartPosition, TagEndPosition, ContentStartPosition, ContentEndPosition, Content, IsClosed).GetHashCode();
30+
=> (Name, Args, TagStartPosition, TagEndPosition, ContentStartPosition, ContentEndPosition, Content, IsClosed, EscapeResult).GetHashCode();
2831
}
2932
}

src/Stubble.Helpers/Stubble.Helpers.csproj

+2-5
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7">
16-
<PrivateAssets>all</PrivateAssets>
17-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18-
</PackageReference>
15+
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0" PrivateAssets="All" />
1916
<PackageReference Include="Stubble.Core" Version="1.5.4" />
2017
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
21-
<PackageReference Include="Nerdbank.GitVersioning" Version="3.0.50" PrivateAssets="All" />
18+
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119" PrivateAssets="All" />
2219
</ItemGroup>
2320

2421
</Project>

0 commit comments

Comments
 (0)