phlex 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

data/lib/phlex/html.rb CHANGED
@@ -1,157 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
4
- using Overrides::Symbol::Name
5
- end
6
-
7
3
  module Phlex
8
- class HTML
9
- DOCTYPE = "<!DOCTYPE html>"
10
-
11
- STANDARD_ELEMENTS = {
12
- a: "a",
13
- abbr: "abbr",
14
- address: "address",
15
- article: "article",
16
- aside: "aside",
17
- b: "b",
18
- bdi: "bdi",
19
- bdo: "bdo",
20
- blockquote: "blockquote",
21
- body: "body",
22
- button: "button",
23
- caption: "caption",
24
- cite: "cite",
25
- code: "code",
26
- colgroup: "colgroup",
27
- data: "data",
28
- datalist: "datalist",
29
- dd: "dd",
30
- del: "del",
31
- details: "details",
32
- dfn: "dfn",
33
- dialog: "dialog",
34
- div: "div",
35
- dl: "dl",
36
- dt: "dt",
37
- em: "em",
38
- fieldset: "fieldset",
39
- figcaption: "figcaption",
40
- figure: "figure",
41
- footer: "footer",
42
- form: "form",
43
- g: "g",
44
- h1: "h1",
45
- h2: "h2",
46
- h3: "h3",
47
- h4: "h4",
48
- h5: "h5",
49
- h6: "h6",
50
- head: "head",
51
- header: "header",
52
- html: "html",
53
- i: "i",
54
- iframe: "iframe",
55
- ins: "ins",
56
- kbd: "kbd",
57
- label: "label",
58
- legend: "legend",
59
- li: "li",
60
- main: "main",
61
- map: "map",
62
- mark: "mark",
63
- menuitem: "menuitem",
64
- meter: "meter",
65
- nav: "nav",
66
- noscript: "noscript",
67
- object: "object",
68
- ol: "ol",
69
- optgroup: "optgroup",
70
- option: "option",
71
- output: "output",
72
- p: "p",
73
- path: "path",
74
- picture: "picture",
75
- pre: "pre",
76
- progress: "progress",
77
- q: "q",
78
- rp: "rp",
79
- rt: "rt",
80
- ruby: "ruby",
81
- s: "s",
82
- samp: "samp",
83
- script: "script",
84
- section: "section",
85
- select: "select",
86
- slot: "slot",
87
- small: "small",
88
- span: "span",
89
- strong: "strong",
90
- style: "style",
91
- sub: "sub",
92
- summary: "summary",
93
- sup: "sup",
94
- svg: "svg",
95
- table: "table",
96
- tbody: "tbody",
97
- td: "td",
98
- template_tag: "template",
99
- textarea: "textarea",
100
- tfoot: "tfoot",
101
- th: "th",
102
- thead: "thead",
103
- time: "time",
104
- title: "title",
105
- tr: "tr",
106
- u: "u",
107
- ul: "ul",
108
- video: "video",
109
- wbr: "wbr",
110
- }.freeze
111
-
112
- VOID_ELEMENTS = {
113
- area: "area",
114
- br: "br",
115
- embed: "embed",
116
- hr: "hr",
117
- img: "img",
118
- input: "input",
119
- link: "link",
120
- meta: "meta",
121
- param: "param",
122
- source: "source",
123
- track: "track",
124
- col: "col",
125
- }.freeze
126
-
127
- EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
4
+ class HTML < SGML
5
+ # A list of HTML attributes that have the potential to execute unsafe JavaScript.
6
+ EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
128
7
 
129
8
  UNBUFFERED_MUTEX = Mutex.new
130
9
 
131
- extend Elements
132
- include Helpers
133
-
134
10
  class << self
135
- def call(...)
136
- new(...).call
137
- end
138
- alias_method :render, :call
139
-
140
- def new(*args, **kwargs, &block)
141
- if block
142
- object = super(*args, **kwargs, &nil)
143
- object.instance_variable_set(:@_content_block, block)
144
- object
145
- else
146
- super
147
- end
148
- end
149
-
150
- def rendered_at_least_once!
151
- alias_method :__attributes__, :__final_attributes__
152
- alias_method :call, :__final_call__
153
- end
154
-
11
+ # @api private
155
12
  def __unbuffered_class__
156
13
  UNBUFFERED_MUTEX.synchronize do
157
14
  if defined? @unbuffered_class
@@ -163,281 +20,28 @@ module Phlex
163
20
  end
164
21
  end
165
22
 
166
- def call(...)
167
- __final_call__(...).tap do
168
- self.class.rendered_at_least_once!
169
- end
170
- end
171
-
172
- def __final_call__(buffer = +"", view_context: nil, parent: nil, &block)
173
- @_target = buffer
174
- @_view_context = view_context
175
- @_parent = parent
176
-
177
- block ||= @_content_block
178
-
179
- return buffer unless render?
180
-
181
- around_template do
182
- if block
183
- if DeferredRender === self
184
- __vanish__(self, &block)
185
- template
186
- else
187
- template do |*args|
188
- if args.length > 0
189
- yield_content_with_args(*args, &block)
190
- else
191
- yield_content(&block)
192
- end
193
- end
194
- end
195
- else
196
- template
197
- end
198
- end
199
-
200
- buffer
201
- end
202
-
203
- def render(renderable, &block)
204
- case renderable
205
- when Phlex::HTML
206
- renderable.call(@_target, view_context: @_view_context, parent: self, &block)
207
- when Class
208
- if renderable < Phlex::HTML
209
- renderable.new.call(@_target, view_context: @_view_context, parent: self, &block)
210
- end
211
- else
212
- raise ArgumentError, "You can't render a #{renderable}."
213
- end
23
+ extend Elements
24
+ include Helpers, VoidElements, StandardElements
214
25
 
26
+ # Output an HTML doctype.
27
+ def doctype
28
+ @_target << "<!DOCTYPE html>"
215
29
  nil
216
30
  end
217
31
 
218
- def format
219
- :html
220
- end
221
-
222
- STANDARD_ELEMENTS.each do |method_name, tag|
223
- register_element(method_name, tag: tag)
224
- end
225
-
226
- VOID_ELEMENTS.each do |method_name, tag|
227
- register_void_element(method_name, tag: tag)
228
- end
229
-
230
- def text(content)
231
- @_target << ERB::Util.html_escape(
232
- case content
233
- when String then content
234
- when Symbol then content.name
235
- when Integer then content.to_s
236
- else format_object(content) || content.to_s
32
+ def svg(...)
33
+ super do
34
+ render Phlex::SVG.new do |svg|
35
+ yield(svg)
237
36
  end
238
- )
239
-
240
- nil
241
- end
242
-
243
- def whitespace
244
- @_target << " "
245
-
246
- if block_given?
247
- yield
248
- @_target << " "
249
37
  end
250
-
251
- nil
252
- end
253
-
254
- def comment(&block)
255
- @_target << "<!-- "
256
- yield_content(&block)
257
- @_target << " -->"
258
-
259
- nil
260
- end
261
-
262
- def doctype
263
- @_target << DOCTYPE
264
- nil
265
- end
266
-
267
- def unsafe_raw(content = nil)
268
- return nil unless content
269
-
270
- @_target << content
271
- end
272
-
273
- def capture
274
- return unless block_given?
275
-
276
- original_buffer = @_target
277
- new_buffer = +""
278
- @_target = new_buffer
279
-
280
- yield
281
-
282
- new_buffer
283
- ensure
284
- @_target = original_buffer
285
38
  end
286
39
 
40
+ # @api private
287
41
  def unbuffered
288
42
  self.class.__unbuffered_class__.new(self)
289
43
  end
290
44
 
291
- # Like `capture` but the output is vanished into a BlackHole buffer.
292
- # Becuase the BlackHole does nothing with the output, this should be faster.
293
- private def __vanish__(*args)
294
- return unless block_given?
295
-
296
- original_buffer = @_target
297
- @_target = BlackHole
298
-
299
- yield(*args)
300
- nil
301
- ensure
302
- @_target = original_buffer
303
- end
304
-
305
- # Default render predicate can be overridden to prevent rendering
306
- private def render?
307
- true
308
- end
309
-
310
- private def format_object(object)
311
- case object
312
- when Float
313
- object.to_s
314
- end
315
- end
316
-
317
- private def around_template
318
- before_template
319
- yield
320
- after_template
321
- end
322
-
323
- private def before_template
324
- nil
325
- end
326
-
327
- private def after_template
328
- nil
329
- end
330
-
331
- private def yield_content
332
- return unless block_given?
333
-
334
- original_length = @_target.length
335
- content = yield(self)
336
- unchanged = (original_length == @_target.length)
337
-
338
- if unchanged
339
- case content
340
- when String
341
- @_target << ERB::Util.html_escape(content)
342
- when Symbol
343
- @_target << ERB::Util.html_escape(content.name)
344
- when Integer
345
- @_target << ERB::Util.html_escape(content.to_s)
346
- else
347
- if (formatted_object = format_object(content))
348
- @_target << ERB::Util.html_escape(formatted_object)
349
- end
350
- end
351
- end
352
-
353
- nil
354
- end
355
-
356
- private def yield_content_with_args(*args)
357
- return unless block_given?
358
-
359
- original_length = @_target.length
360
- content = yield(*args)
361
- unchanged = (original_length == @_target.length)
362
-
363
- if unchanged
364
- case content
365
- when String
366
- @_target << ERB::Util.html_escape(content)
367
- when Symbol
368
- @_target << ERB::Util.html_escape(content.name)
369
- when Integer, Float
370
- @_target << ERB::Util.html_escape(content.to_s)
371
- else
372
- if (formatted_object = format_object(content))
373
- @_target << ERB::Util.html_escape(formatted_object)
374
- end
375
- end
376
- end
377
-
378
- nil
379
- end
380
-
381
- private def __attributes__(**attributes)
382
- __final_attributes__(**attributes).tap do |buffer|
383
- Phlex::ATTRIBUTE_CACHE[attributes.hash] = buffer.freeze
384
- end
385
- end
386
-
387
- private def __final_attributes__(**attributes)
388
- if attributes[:href]&.start_with?(/\s*javascript:/)
389
- attributes.delete(:href)
390
- end
391
-
392
- if attributes["href"]&.start_with?(/\s*javascript:/)
393
- attributes.delete("href")
394
- end
395
-
396
- buffer = +""
397
- __build_attributes__(attributes, buffer: buffer)
398
-
399
- buffer
400
- end
401
-
402
- private def __build_attributes__(attributes, buffer:)
403
- attributes.each do |k, v|
404
- next unless v
405
-
406
- name = case k
407
- when String then k
408
- when Symbol then k.name.tr("_", "-")
409
- else k.to_s
410
- end
411
-
412
- # Detect unsafe attribute names. Attribute names are considered unsafe if they match an event attribute or include unsafe characters.
413
- if HTML::EVENT_ATTRIBUTES[name] || name.match?(/[<>&"']/)
414
- raise ArgumentError, "Unsafe attribute name detected: #{k}."
415
- end
416
-
417
- case v
418
- when true
419
- buffer << " " << name
420
- when String
421
- buffer << " " << name << '="' << ERB::Util.html_escape(v) << '"'
422
- when Symbol
423
- buffer << " " << name << '="' << ERB::Util.html_escape(v.name) << '"'
424
- when Hash
425
- __build_attributes__(
426
- v.transform_keys { |subkey|
427
- case subkey
428
- when Symbol then"#{k}-#{subkey.name.tr('_', '-')}"
429
- else "#{k}-#{subkey}"
430
- end
431
- }, buffer: buffer
432
- )
433
- else
434
- buffer << " " << name << '="' << ERB::Util.html_escape(v.to_s) << '"'
435
- end
436
- end
437
-
438
- buffer
439
- end
440
-
441
45
  # This should be the last method defined
442
46
  def self.method_added(method_name)
443
47
  if method_name[0] == "_" && Phlex::HTML.instance_methods.include?(method_name) && instance_method(method_name).owner != Phlex::HTML
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Overrides::Symbol::Name
3
+ module Phlex::Overrides::Symbol::Name
4
4
  refine(Symbol) { alias_method :name, :to_s }
5
5
  end