draftjs_html 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e7da064863d6ce77e04957db9f87f957db61dbbc622bf853c43b7ee6f61b970
4
- data.tar.gz: 985ea5ed6ec84cb10a62065858b9fa100ff444a8b0d27f181685ea5f1de609a1
3
+ metadata.gz: 9194cbbbe5e94b79dd386efd86e23774128659b74fb40c98ddf43bc9b62fd9a9
4
+ data.tar.gz: 121e6900da99a52b8a5e0cf5b1cae7206492e979939b214d9ef3a112ebdc471d
5
5
  SHA512:
6
- metadata.gz: 991ad75b614edd4c5a46290ded23422ed8a7691ef2e1c79328daa5e41d96b43595e8ea025709ced743a3fbe44144efbc6032baad0d09ee165cc1527fa173fb1a
7
- data.tar.gz: 15d168a71001ac7fc6dd2505f8fc716c22fb221f5cf25a0b1fc12cc34bd3a9b46c21de5bde8c47d5f4cd643b40c07c219b7b86852cdda7536ce9228c7fc18fb6
6
+ metadata.gz: e574a85e36eb144f9f5b4e56a766cb3c83385fad293c311627d1ae5974408c8bd5ce39dc6a2081e9d7b9e695ce08dea54ef2050768c1f5660ef478d992e10671
7
+ data.tar.gz: 0dc0ce686a9d476a93c24e392d02ee7180b4fd556636cce72695562936acb2e5c3dec581c3151e9d8fd3ed32b9aa026d644cb524c2579925479fa8d4f8927e1e
data/README.md CHANGED
@@ -59,8 +59,8 @@ raw_draftjs = {
59
59
 
60
60
  DraftjsHtml.to_html(raw_draftjs, options: {
61
61
  entity_style_mappings: {
62
- abc: ->(entity, content) {
63
- %Q{<a href="https://example.com/?id=#{entity.data['user_id']}">#{content}</a>}
62
+ abc: ->(entity, content, *) {
63
+ DraftjsHtml::Node.new('a', { href: "https://example.com/?id=#{entity.data['user_id']}" }, content)
64
64
  },
65
65
  },
66
66
  }) # => <p>Hello <a href="https://example.com/?id=123">@Arya</a></p>
@@ -81,8 +81,9 @@ Defaults to `UTF-8`.
81
81
 
82
82
  Allows the author to specify special mapping functions for entities.
83
83
  By default, we render `LINK` and `IMAGE` entities using the standard `<a>` and `<img>` tags, respectively.
84
- The author may supply a Proc object that returns any object Nokogiri can consume for adding to HTML.
85
- This includes `String`, `Nokogiri::Document::Fragment`, and `Nokogiri::Document` objects.
84
+ The author may supply a `call`-able object that returns a `DraftjsHtml::Node`-able (or similar).
85
+ If returned a String, it's assumed this content is plaintext (or otherwise unsafe) and its content will be coerced to plaintext.
86
+ See the section on HTML Injection protection for more details.
86
87
 
87
88
  #### `:block_type_mapping`
88
89
 
@@ -128,13 +129,44 @@ However, if you "return" `nil` (or `false-y`) from the proc, it will fallback to
128
129
  DraftjsHtml.to_html(raw_draftjs, options: {
129
130
  inline_style_renderer: ->(style_names, content) {
130
131
  next if style_names != ['CUSTOM']
131
- "<pre>#{content}</pre>"
132
+ Nokogiri::XML::Node.new('pre', document).tap do |node|
133
+ node.content = content
134
+ end
132
135
  },
133
136
  })
134
137
 
135
138
  # This would use the default inline style rendering UNLESS the *only* applied style for this range was "CUSTOM"
136
139
  ```
137
140
 
141
+ #### HTML Injection protection
142
+
143
+ Working with user-generated content can be a dangerous thing.
144
+ While it allows for a lot of flexibility, it also creates some potential attack vectors that you need to be aware of.
145
+ We try to take a "safe by default" stance with this library, and not generate HTML that could be dangerous when we know better.
146
+
147
+ To facilitate this, we require a little work from you, dear programmer.
148
+ Namely, when specifying special algorithms for generating entities or inline styles, you need to help us keep you safe.
149
+ You can do this by returning a `Nokogiri::XML::Node` or `DraftjsHtml::Node` from any functions you provide that generate HTML.
150
+ This is similar to Ruby on Rails' `#to_html` method, but rather than a monkeypatch, we chose to provide a "marker class" (classes) that we know are safe.
151
+ These classes will handle escaping, encoding, and otherwise "safe generation" for you.
152
+ If you, on the other hand, return a bare `String` from one of the custom render functions, we assume it's _unsafe_ and encode it.
153
+
154
+ That is, a function like this:
155
+ ```ruby
156
+ ->(entity, content, document) do
157
+ "<p>hi!</p>"
158
+ end
159
+ # will become an HTML-entity escaped string (e.g. "&lt;p&gt;hi!&lt;/p&gt;")
160
+ ```
161
+
162
+ Where, a function like this:
163
+ ```ruby
164
+ ->(entity, content, document) do
165
+ DraftjsHtml::Node.new('p', {}, 'hi!')
166
+ end
167
+ # will nest HTML nodes as you probably want (e.g. "<p>hi!</p>")
168
+ ```
169
+
138
170
  ## Development
139
171
 
140
172
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -0,0 +1,32 @@
1
+ module DraftjsHtml
2
+ NokogiriNode = Struct.new(:node) do
3
+ def to_nokogiri(_document)
4
+ node
5
+ end
6
+ end
7
+
8
+ StringNode = Struct.new(:raw) do
9
+ def to_nokogiri(document)
10
+ raw
11
+ Nokogiri::XML::Text.new(raw, document)
12
+ end
13
+ end
14
+
15
+ Node = Struct.new(:element_name, :attributes, :content) do
16
+ def self.of(thing)
17
+ case thing
18
+ when Nokogiri::XML::Node then NokogiriNode.new(thing)
19
+ when self.class then thing
20
+ when String then StringNode.new(thing)
21
+ else thing
22
+ end
23
+ end
24
+
25
+ def to_nokogiri(document)
26
+ Nokogiri::XML::Node.new(element_name, document).tap do |node|
27
+ node.content = content
28
+ (attributes || {}).each { |k, v| node[k] = v }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'node'
2
+
1
3
  module DraftjsHtml
2
4
  class ToHtml
3
5
  BLOCK_TYPE_TO_HTML = {
@@ -27,28 +29,25 @@ module DraftjsHtml
27
29
  'UNDERLINE' => 'u',
28
30
  }.freeze
29
31
 
30
- DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars) { chars }
32
+ DEFAULT_ENTITY_STYLE_FN = ->(_entity, chars, _doc) { chars }
31
33
  ENTITY_ATTRIBUTE_NAME_MAP = {
32
34
  'className' => 'class',
33
35
  'url' => 'href',
34
36
  }.freeze
35
37
  ENTITY_CONVERSION_MAP = {
36
- 'LINK' => ->(entity, content) {
37
- node = Nokogiri::HTML::DocumentFragment.parse('<a>').children.first
38
- node.content = content
39
- entity.data.slice('url', 'rel', 'target', 'title', 'className').each do |attr, value|
40
- node[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
38
+ 'LINK' => ->(entity, content, *) {
39
+ attributes = entity.data.slice('url', 'rel', 'target', 'title', 'className').each_with_object({}) do |(attr, value), h|
40
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
41
41
  end
42
42
 
43
- node
43
+ DraftjsHtml::Node.new('a', attributes, content)
44
44
  },
45
- 'IMAGE' => ->(entity, _content) {
46
- node = Nokogiri::HTML::DocumentFragment.parse('<img>').children.first
47
- entity.data.slice('src', 'alt', 'className', 'width', 'height').each do |attr, value|
48
- node[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
45
+ 'IMAGE' => ->(entity, *) {
46
+ attributes = entity.data.slice('src', 'alt', 'className', 'width', 'height').each_with_object({}) do |(attr, value), h|
47
+ h[ENTITY_ATTRIBUTE_NAME_MAP.fetch(attr, attr)] = value
49
48
  end
50
49
 
51
- node
50
+ DraftjsHtml::Node.new('img', attributes)
52
51
  }
53
52
  }.freeze
54
53
 
@@ -94,18 +93,22 @@ module DraftjsHtml
94
93
  end
95
94
  end
96
95
 
97
- def apply_styles_to(html, style_names, text)
98
- return html.parent << text if style_names.empty?
96
+ def apply_styles_to(html, style_names, child)
97
+ return append_child(html, child) if style_names.empty?
99
98
 
100
- custom_render_content = @options[:inline_style_renderer].call(style_names, text)
101
- return html.parent << custom_render_content if custom_render_content
99
+ custom_render_content = @options[:inline_style_renderer].call(style_names, child, @document.parent)
100
+ return append_child(html, custom_render_content) if custom_render_content
102
101
 
103
102
  style, *rest = style_names
104
- html.public_send(style_element_for(style)) do
105
- apply_styles_to(html, rest, text)
103
+ html.public_send(style_element_for(style)) do |builder|
104
+ apply_styles_to(builder, rest, child)
106
105
  end
107
106
  end
108
107
 
108
+ def append_child(nokogiri, child)
109
+ nokogiri.parent.add_child(DraftjsHtml::Node.of(child).to_nokogiri(@document.doc))
110
+ end
111
+
109
112
  def block_element_for(block)
110
113
  return 'br' if block.blank?
111
114
 
@@ -119,7 +122,11 @@ module DraftjsHtml
119
122
  def try_apply_entity_to(draftjs, char_range)
120
123
  entity = draftjs.find_entity(char_range.entity_key)
121
124
  content = char_range.text
122
- content = (@options[:entity_style_mappings][entity.type] || DEFAULT_ENTITY_STYLE_FN).call(entity, content) if entity
125
+ if entity
126
+ style_fn = (@options[:entity_style_mappings][entity.type] || DEFAULT_ENTITY_STYLE_FN)
127
+ content = style_fn.call(entity, content, @document.parent)
128
+ end
129
+
123
130
  content
124
131
  end
125
132
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DraftjsHtml
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: draftjs_html
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - TJ Taylor
@@ -65,6 +65,7 @@ files:
65
65
  - lib/draftjs_html/draftjs/entity.rb
66
66
  - lib/draftjs_html/draftjs/entity_map.rb
67
67
  - lib/draftjs_html/draftjs/to_raw.rb
68
+ - lib/draftjs_html/node.rb
68
69
  - lib/draftjs_html/to_html.rb
69
70
  - lib/draftjs_html/version.rb
70
71
  homepage: https://github.com/dugancathal/draftjs_html