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 << ""
@buffer << tag_name
@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