From 5c284e1d9a541105bd8a06a450273fda3f2cef2d Mon Sep 17 00:00:00 2001 From: stephann <3025661+stephannv@users.noreply.github.com> Date: Sat, 14 Sep 2024 07:27:58 -0300 Subject: [PATCH] feat: Add SafeValue to render content without escaping --- spec/blueprint/html/helpers_spec.cr | 21 +++++++++++++++++++++ spec/blueprint/html/utils_spec.cr | 2 +- src/blueprint/html.cr | 5 +++++ src/blueprint/html/attributes_handler.cr | 2 +- src/blueprint/html/block_renderer.cr | 6 +++--- src/blueprint/html/buffer_appender.cr | 16 ++++++++++++++++ src/blueprint/html/element_registrar.cr | 2 +- src/blueprint/html/element_renderer.cr | 4 ++-- src/blueprint/html/helpers.cr | 4 ++++ src/blueprint/html/utils.cr | 6 +++--- src/blueprint/safe_object.cr | 2 ++ src/blueprint/safe_value.cr | 11 +++++++++++ 12 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 src/blueprint/html/buffer_appender.cr create mode 100644 src/blueprint/safe_object.cr create mode 100644 src/blueprint/safe_value.cr diff --git a/spec/blueprint/html/helpers_spec.cr b/spec/blueprint/html/helpers_spec.cr index f0517d3..95b7d9a 100644 --- a/spec/blueprint/html/helpers_spec.cr +++ b/spec/blueprint/html/helpers_spec.cr @@ -5,6 +5,10 @@ private class ExamplePage private def blueprint div("Tokens", class: ["a", "b", tokens(c?: "c", d?: "d", e?: "e")]) + + div(onclick: safe("")) { safe("") } + + span safe("") end private def c? @@ -31,4 +35,21 @@ describe "helpers" do page.to_s.should contain expected_html end end + + describe "#safe" do + it "returns an object that Blueprint will understand as safe to render without escaping" do + page = ExamplePage.new + expected_html = normalize_html <<-HTML +
+ +
+ + + + + HTML + + page.to_s.should contain expected_html + end + end end diff --git a/spec/blueprint/html/utils_spec.cr b/spec/blueprint/html/utils_spec.cr index 910b2b2..d5315ff 100644 --- a/spec/blueprint/html/utils_spec.cr +++ b/spec/blueprint/html/utils_spec.cr @@ -17,7 +17,7 @@ private class ExamplePage comment "This is an html comment" div do - unsafe_raw "" + raw safe("") end end end diff --git a/src/blueprint/html.cr b/src/blueprint/html.cr index 8497115..5c5ceef 100644 --- a/src/blueprint/html.cr +++ b/src/blueprint/html.cr @@ -2,6 +2,7 @@ require "html" require "./html/attributes_handler" require "./html/block_renderer" +require "./html/buffer_appender" require "./html/builder" require "./html/component_registrar" require "./html/component_renderer" @@ -13,9 +14,13 @@ require "./html/style_builder" require "./html/svg" require "./html/utils" +require "./safe_object" +require "./safe_value" + module Blueprint::HTML include Blueprint::HTML::AttributesHandler include Blueprint::HTML::BlockRenderer + include Blueprint::HTML::BufferAppender include Blueprint::HTML::ComponentRegistrar include Blueprint::HTML::ComponentRenderer include Blueprint::HTML::ElementRegistrar diff --git a/src/blueprint/html/attributes_handler.cr b/src/blueprint/html/attributes_handler.cr index 2fc7a8f..aa2d2e1 100644 --- a/src/blueprint/html/attributes_handler.cr +++ b/src/blueprint/html/attributes_handler.cr @@ -24,7 +24,7 @@ module Blueprint::HTML::AttributesHandler @buffer << " " @buffer << parse_attribute_name(attribute_name) @buffer << %(=") - ::HTML.escape(attribute_value.to_s, @buffer) + append_to_buffer(attribute_value) @buffer << %(") end diff --git a/src/blueprint/html/block_renderer.cr b/src/blueprint/html/block_renderer.cr index e67740d..4862926 100644 --- a/src/blueprint/html/block_renderer.cr +++ b/src/blueprint/html/block_renderer.cr @@ -2,8 +2,8 @@ module Blueprint::HTML::BlockRenderer private def capture_content(&) : Nil buffer_size_before_block_evaluation = @buffer.bytesize content = yield - if buffer_size_before_block_evaluation == @buffer.bytesize - ::HTML.escape(content.to_s, @buffer) - end + return if buffer_size_before_block_evaluation != @buffer.bytesize # return if something was written to buffer + + append_to_buffer(content) end end diff --git a/src/blueprint/html/buffer_appender.cr b/src/blueprint/html/buffer_appender.cr new file mode 100644 index 0000000..154e98c --- /dev/null +++ b/src/blueprint/html/buffer_appender.cr @@ -0,0 +1,16 @@ +module Blueprint::HTML::BufferAppender + private def append_to_buffer(content : String) + ::HTML.escape(content, @buffer) + end + + private def append_to_buffer(content : SafeObject) + content.to_s(@buffer) + end + + private def append_to_buffer(content : Nil) + end + + private def append_to_buffer(content) + ::HTML.escape(content.to_s, @buffer) + end +end diff --git a/src/blueprint/html/element_registrar.cr b/src/blueprint/html/element_registrar.cr index 315e23d..a797986 100644 --- a/src/blueprint/html/element_registrar.cr +++ b/src/blueprint/html/element_registrar.cr @@ -10,7 +10,7 @@ module Blueprint::HTML::ElementRegistrar element({{tag}}, "", **attributes) end - private def {{method_name.id}}(__content__ : String, **attributes) : Nil + private def {{method_name.id}}(__content__, **attributes) : Nil element({{tag}}, __content__, **attributes) end end diff --git a/src/blueprint/html/element_renderer.cr b/src/blueprint/html/element_renderer.cr index c8cf80c..f147a0a 100644 --- a/src/blueprint/html/element_renderer.cr +++ b/src/blueprint/html/element_renderer.cr @@ -10,12 +10,12 @@ module Blueprint::HTML::ElementRenderer @buffer << ">" end - private def element(tag_name : String | Symbol, __content__ : String, **attributes) : Nil + private def element(tag_name : String | Symbol, __content__, **attributes) : Nil @buffer << "<" @buffer << tag_name append_attributes(attributes) @buffer << ">" - ::HTML.escape(__content__, @buffer) + append_to_buffer(__content__) @buffer << "" diff --git a/src/blueprint/html/helpers.cr b/src/blueprint/html/helpers.cr index b06085f..90ecf84 100644 --- a/src/blueprint/html/helpers.cr +++ b/src/blueprint/html/helpers.cr @@ -1,4 +1,8 @@ module Blueprint::HTML::Helpers + def safe(value) : SafeValue + Blueprint::SafeValue.new(value) + end + macro tokens(**conditions) String.build do |io| {% for key, value in conditions %} diff --git a/src/blueprint/html/utils.cr b/src/blueprint/html/utils.cr index ec23863..4407019 100644 --- a/src/blueprint/html/utils.cr +++ b/src/blueprint/html/utils.cr @@ -1,6 +1,6 @@ module Blueprint::HTML::Utils private def plain(content : String) : Nil - ::HTML.escape(content, @buffer) + append_to_buffer(content) end private def doctype : Nil @@ -17,7 +17,7 @@ module Blueprint::HTML::Utils @buffer << " " end - def unsafe_raw(content : String) : Nil - @buffer << content + def raw(content : SafeObject) : Nil + append_to_buffer(content) end end diff --git a/src/blueprint/safe_object.cr b/src/blueprint/safe_object.cr new file mode 100644 index 0000000..f9265bc --- /dev/null +++ b/src/blueprint/safe_object.cr @@ -0,0 +1,2 @@ +module Blueprint::SafeObject +end diff --git a/src/blueprint/safe_value.cr b/src/blueprint/safe_value.cr new file mode 100644 index 0000000..e711fa2 --- /dev/null +++ b/src/blueprint/safe_value.cr @@ -0,0 +1,11 @@ +class Blueprint::SafeValue(T) + include SafeObject + + getter value : T + + def initialize(@value : T); end + + def to_s(io : String::Builder) + @value.to_s(io) + end +end