mirror of
https://github.com/pantor/inja.git
synced 2026-02-17 09:03:58 +00:00
Add HTML autoescape (#292)
* add and document set_html_autoescape * add render_to to Environment that accepts a string (and turns it into a Template) * code style, update single include * update ci * revert macos-14 test --------- Co-authored-by: pantor <lars.berscheid@online.de>
This commit is contained in:
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -56,18 +56,18 @@ jobs:
|
|||||||
os: windows-2022
|
os: windows-2022
|
||||||
compiler: msvc
|
compiler: msvc
|
||||||
|
|
||||||
- name: macOS-11-gcc
|
- name: macOS-12-gcc
|
||||||
os: macOS-11
|
os: macOS-12
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
|
|
||||||
- name: macOS-11-clang
|
|
||||||
os: macOS-11
|
|
||||||
compiler: clang
|
|
||||||
|
|
||||||
- name: macOS-12-clang
|
- name: macOS-12-clang
|
||||||
os: macOS-12
|
os: macOS-12
|
||||||
compiler: clang
|
compiler: clang
|
||||||
|
|
||||||
|
# - name: macOS-14-clang
|
||||||
|
# os: macOS-14
|
||||||
|
# compiler: clang
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ env.set_expression("{{", "}}"); // Expressions
|
|||||||
env.set_comment("{#", "#}"); // Comments
|
env.set_comment("{#", "#}"); // Comments
|
||||||
env.set_statement("{%", "%}"); // Statements {% %} for many things, see below
|
env.set_statement("{%", "%}"); // Statements {% %} for many things, see below
|
||||||
env.set_line_statement("##"); // Line statements ## (just an opener)
|
env.set_line_statement("##"); // Line statements ## (just an opener)
|
||||||
|
env.set_html_autoescape(true); // Perform HTML escaping on all strings
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
@@ -364,6 +365,13 @@ render("{% if neighbour in guests -%} I was there{% endif -%} !", data); //
|
|||||||
|
|
||||||
Stripping behind a statement or expression also removes any newlines.
|
Stripping behind a statement or expression also removes any newlines.
|
||||||
|
|
||||||
|
### HTML escaping
|
||||||
|
|
||||||
|
Templates are frequently used to creat HTML pages. Source data that contains
|
||||||
|
characters that have meaning within HTML (like <. >, &) needs to be escaped.
|
||||||
|
It is often inconvenient to perform such escaping within the JSON data. With `Environment::set_html_autoescape(true)`, Inja can be configured to
|
||||||
|
HTML escape each and every string created.
|
||||||
|
|
||||||
### Comments
|
### Comments
|
||||||
|
|
||||||
Comments can be written with the `{# ... #}` syntax.
|
Comments can be written with the `{# ... #}` syntax.
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ struct ParserConfig {
|
|||||||
*/
|
*/
|
||||||
struct RenderConfig {
|
struct RenderConfig {
|
||||||
bool throw_at_missing_includes {true};
|
bool throw_at_missing_includes {true};
|
||||||
|
bool html_autoescape {false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace inja
|
} // namespace inja
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ public:
|
|||||||
render_config.throw_at_missing_includes = will_throw;
|
render_config.throw_at_missing_includes = will_throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether we'll automatically perform HTML escape
|
||||||
|
void set_html_autoescape(bool will_escape) {
|
||||||
|
render_config.html_autoescape = will_escape;
|
||||||
|
}
|
||||||
|
|
||||||
Template parse(std::string_view input) {
|
Template parse(std::string_view input) {
|
||||||
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
||||||
return parser.parse(input, input_path);
|
return parser.parse(input, input_path);
|
||||||
@@ -155,6 +160,10 @@ public:
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& render_to(std::ostream& os, const std::string_view input, const json& data) {
|
||||||
|
return render_to(os, parse(input), data);
|
||||||
|
}
|
||||||
|
|
||||||
std::string load_file(const std::string& filename) {
|
std::string load_file(const std::string& filename) {
|
||||||
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
||||||
return Parser::load_file(input_path + filename);
|
return Parser::load_file(input_path + filename);
|
||||||
|
|||||||
@@ -53,9 +53,29 @@ class Renderer : public NodeVisitor {
|
|||||||
return !data->empty();
|
return !data->empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string htmlescape(const std::string& data) {
|
||||||
|
std::string buffer;
|
||||||
|
buffer.reserve(1.1 * data.size());
|
||||||
|
for (size_t pos = 0; pos != data.size(); ++pos) {
|
||||||
|
switch (data[pos]) {
|
||||||
|
case '&': buffer.append("&"); break;
|
||||||
|
case '\"': buffer.append("""); break;
|
||||||
|
case '\'': buffer.append("'"); break;
|
||||||
|
case '<': buffer.append("<"); break;
|
||||||
|
case '>': buffer.append(">"); break;
|
||||||
|
default: buffer.append(&data[pos], 1); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
void print_data(const std::shared_ptr<json> value) {
|
void print_data(const std::shared_ptr<json> value) {
|
||||||
if (value->is_string()) {
|
if (value->is_string()) {
|
||||||
*output_stream << value->get_ref<const json::string_t&>();
|
if (config.html_autoescape) {
|
||||||
|
*output_stream << htmlescape(value->get_ref<const json::string_t&>());
|
||||||
|
} else {
|
||||||
|
*output_stream << value->get_ref<const json::string_t&>();
|
||||||
|
}
|
||||||
} else if (value->is_number_unsigned()) {
|
} else if (value->is_number_unsigned()) {
|
||||||
*output_stream << value->get<const json::number_unsigned_t>();
|
*output_stream << value->get<const json::number_unsigned_t>();
|
||||||
} else if (value->is_number_integer()) {
|
} else if (value->is_number_integer()) {
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ struct ParserConfig {
|
|||||||
*/
|
*/
|
||||||
struct RenderConfig {
|
struct RenderConfig {
|
||||||
bool throw_at_missing_includes {true};
|
bool throw_at_missing_includes {true};
|
||||||
|
bool html_autoescape {false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace inja
|
} // namespace inja
|
||||||
@@ -2124,9 +2125,29 @@ class Renderer : public NodeVisitor {
|
|||||||
return !data->empty();
|
return !data->empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string htmlescape(const std::string& data) {
|
||||||
|
std::string buffer;
|
||||||
|
buffer.reserve(1.1 * data.size());
|
||||||
|
for (size_t pos = 0; pos != data.size(); ++pos) {
|
||||||
|
switch (data[pos]) {
|
||||||
|
case '&': buffer.append("&"); break;
|
||||||
|
case '\"': buffer.append("""); break;
|
||||||
|
case '\'': buffer.append("'"); break;
|
||||||
|
case '<': buffer.append("<"); break;
|
||||||
|
case '>': buffer.append(">"); break;
|
||||||
|
default: buffer.append(&data[pos], 1); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
void print_data(const std::shared_ptr<json> value) {
|
void print_data(const std::shared_ptr<json> value) {
|
||||||
if (value->is_string()) {
|
if (value->is_string()) {
|
||||||
*output_stream << value->get_ref<const json::string_t&>();
|
if (config.html_autoescape) {
|
||||||
|
*output_stream << htmlescape(value->get_ref<const json::string_t&>());
|
||||||
|
} else {
|
||||||
|
*output_stream << value->get_ref<const json::string_t&>();
|
||||||
|
}
|
||||||
} else if (value->is_number_unsigned()) {
|
} else if (value->is_number_unsigned()) {
|
||||||
*output_stream << value->get<const json::number_unsigned_t>();
|
*output_stream << value->get<const json::number_unsigned_t>();
|
||||||
} else if (value->is_number_integer()) {
|
} else if (value->is_number_integer()) {
|
||||||
@@ -2382,7 +2403,7 @@ class Renderer : public NodeVisitor {
|
|||||||
} break;
|
} break;
|
||||||
case Op::Capitalize: {
|
case Op::Capitalize: {
|
||||||
auto result = get_arguments<1>(node)[0]->get<json::string_t>();
|
auto result = get_arguments<1>(node)[0]->get<json::string_t>();
|
||||||
result[0] = std::toupper(result[0]);
|
result[0] = static_cast<char>(::toupper(result[0]));
|
||||||
std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast<char>(::tolower(c)); });
|
std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast<char>(::tolower(c)); });
|
||||||
make_result(std::move(result));
|
make_result(std::move(result));
|
||||||
} break;
|
} break;
|
||||||
@@ -2792,6 +2813,11 @@ public:
|
|||||||
render_config.throw_at_missing_includes = will_throw;
|
render_config.throw_at_missing_includes = will_throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether we'll automatically perform HTML escape
|
||||||
|
void set_html_autoescape(bool will_escape) {
|
||||||
|
render_config.html_autoescape = will_escape;
|
||||||
|
}
|
||||||
|
|
||||||
Template parse(std::string_view input) {
|
Template parse(std::string_view input) {
|
||||||
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
||||||
return parser.parse(input, input_path);
|
return parser.parse(input, input_path);
|
||||||
@@ -2854,6 +2880,10 @@ public:
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& render_to(std::ostream& os, const std::string_view input, const json& data) {
|
||||||
|
return render_to(os, parse(input), data);
|
||||||
|
}
|
||||||
|
|
||||||
std::string load_file(const std::string& filename) {
|
std::string load_file(const std::string& filename) {
|
||||||
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
Parser parser(parser_config, lexer_config, template_storage, function_storage);
|
||||||
return Parser::load_file(input_path + filename);
|
return Parser::load_file(input_path + filename);
|
||||||
|
|||||||
Reference in New Issue
Block a user