Skip to content

Commit f87ce3e

Browse files
committed
feat(template): add safe and raw filters
1 parent 384e62e commit f87ce3e

2 files changed

Lines changed: 81 additions & 4 deletions

File tree

src/Renderer.cpp

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,30 @@ namespace vix::template_
401401
}
402402

403403
Value current = evaluate_expression(*instr.expression, context);
404-
current = apply_filters(current, instr.filters);
404+
405+
bool escape_output = auto_escape_html_;
406+
407+
for (const auto &filter : instr.filters)
408+
{
409+
const std::string &name = filter.name();
410+
411+
if (name == "safe" || name == "raw")
412+
{
413+
escape_output = false;
414+
continue;
415+
}
416+
417+
const auto it = filters_.find(name);
418+
if (it == filters_.end())
419+
{
420+
throw RendererError("unknown filter: " + name);
421+
}
422+
423+
current = it->second(current);
424+
}
405425

406426
std::string rendered = current.to_string();
407-
if (auto_escape_html_)
427+
if (escape_output)
408428
{
409429
rendered = Escape::html(rendered);
410430
}
@@ -1074,10 +1094,30 @@ namespace vix::template_
10741094
{
10751095
const auto &variable = static_cast<const VariableNode &>(node);
10761096
Value current = evaluate_expression(variable.expression(), context);
1077-
current = apply_filters(current, variable.filters());
1097+
1098+
bool escape_output = auto_escape_html_;
1099+
1100+
for (const auto &filter : variable.filters())
1101+
{
1102+
const std::string &name = filter.name();
1103+
1104+
if (name == "safe" || name == "raw")
1105+
{
1106+
escape_output = false;
1107+
continue;
1108+
}
1109+
1110+
const auto it = filters_.find(name);
1111+
if (it == filters_.end())
1112+
{
1113+
throw RendererError("unknown filter: " + name);
1114+
}
1115+
1116+
current = it->second(current);
1117+
}
10781118

10791119
std::string rendered = current.to_string();
1080-
if (auto_escape_html_)
1120+
if (escape_output)
10811121
{
10821122
rendered = Escape::html(rendered);
10831123
}

tests/renderer_test.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,40 @@ static std::string render_with_loader(
9696
return compiled.render(ctx, auto_escape_html).output;
9797
}
9898

99+
static void test_render_safe_filter()
100+
{
101+
Context ctx;
102+
ctx.set("html", "<strong>OK</strong>");
103+
104+
const std::string out = render("{{ html | safe }}", ctx);
105+
106+
assert(out == "<strong>OK</strong>");
107+
}
108+
109+
static void test_render_raw_filter()
110+
{
111+
Context ctx;
112+
ctx.set("html", "<div class=\"card\">OK</div>");
113+
114+
const std::string out = render("{{ html | raw }}", ctx);
115+
116+
assert(out == "<div class=\"card\">OK</div>");
117+
}
118+
119+
static void test_safe_filter_does_not_disable_escape_globally()
120+
{
121+
Context ctx;
122+
ctx.set("html", "<strong>OK</strong>");
123+
ctx.set("text", "<script>alert(1)</script>");
124+
125+
const std::string out =
126+
render("{{ html | safe }} {{ text }}", ctx);
127+
128+
assert(
129+
out ==
130+
"<strong>OK</strong> &lt;script&gt;alert(1)&lt;/script&gt;");
131+
}
132+
99133
static std::string render_stream_with_loader(
100134
const std::string &tpl,
101135
const Context &ctx,
@@ -1066,6 +1100,9 @@ int main()
10661100
test_render_if_with_expression_false();
10671101
test_render_for();
10681102
test_render_nested();
1103+
test_render_safe_filter();
1104+
test_render_raw_filter();
1105+
test_safe_filter_does_not_disable_escape_globally();
10691106
test_html_escape();
10701107
test_render_without_html_escape();
10711108
test_render_upper_filter();

0 commit comments

Comments
 (0)