Skip to content

Commit

Permalink
feat: Add SafeValue to render content without escaping
Browse files Browse the repository at this point in the history
  • Loading branch information
stephannv committed Sep 14, 2024
1 parent 0e4c2a7 commit 5c284e1
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 11 deletions.
21 changes: 21 additions & 0 deletions spec/blueprint/html/helpers_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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("<script>Attribute script</script>")) { safe("<script>Good Script</script>") }

span safe("<script>Another Good Script</script>")
end

private def c?
Expand All @@ -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
<div onclick="<script>Attribute script</script>">
<script>Good Script</script>
</div>
<span>
<script>Another Good Script</script>
</span>
HTML

page.to_s.should contain expected_html
end
end
end
2 changes: 1 addition & 1 deletion spec/blueprint/html/utils_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private class ExamplePage
comment "This is an html comment"

div do
unsafe_raw "<script>Dangerous script</script>"
raw safe("<script>Dangerous script</script>")
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions src/blueprint/html.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/blueprint/html/attributes_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions src/blueprint/html/block_renderer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions src/blueprint/html/buffer_appender.cr
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/blueprint/html/element_registrar.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/blueprint/html/element_renderer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 << ">"
Expand Down
4 changes: 4 additions & 0 deletions src/blueprint/html/helpers.cr
Original file line number Diff line number Diff line change
@@ -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 %}
Expand Down
6 changes: 3 additions & 3 deletions src/blueprint/html/utils.cr
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
2 changes: 2 additions & 0 deletions src/blueprint/safe_object.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Blueprint::SafeObject
end
11 changes: 11 additions & 0 deletions src/blueprint/safe_value.cr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5c284e1

Please sign in to comment.