phlex 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0de463dbf4f663fc2ac8dd8d0b0a080dc8d7bf90ba209d56b366b36634e9cc9
4
- data.tar.gz: ccad039d4962eb42076c562cb9f9617197f878a1860b13f66aae2b34f2686333
3
+ metadata.gz: 58ef5e091c9feed2677149231356e32305898a8d2bb271a0e603bcc208ead0fd
4
+ data.tar.gz: bab9a697db8f529f0221afb1fd491e30d2f4b0c702a253f7a0446f8a075ddc82
5
5
  SHA512:
6
- metadata.gz: 959b8c7b1bdbbf281b0da7576257719da2ba077045e9ad1d673348d60a01dfa731b6b9b2e9031cce81e896f98a195478008d667e99bf471e50348bc6b978c393
7
- data.tar.gz: 93dab7d61efa8f2009698cb8ae7d12748b4cb1bfe5c2bf48e58c41e5c930f923789c00ff3c346e878107f9cbe280951e23f35fe5482c453799f5c7e81293715e
6
+ metadata.gz: 1e92858e2c2a177a120579e7cbb4c7489456a8faa0a40ecbce9fe6070f7ef01ace1e4f13baf72a9f9baf841ed57ea9b74f9cf2aa262fc774eaa95178a9248b63
7
+ data.tar.gz: 8040edc5a9640fb638280bccb4e879ab97d855b739f5eab04fc7ffcf7fcb7d7ca50a06ae808c31e67f9fdb7c286062c35ff40483abfaf42ac9637e2abe32c67e
data/lib/phlex/csv.rb CHANGED
@@ -168,7 +168,9 @@ class Phlex::CSV
168
168
  value = value.strip
169
169
 
170
170
  if escape_csv_injection
171
- if FORMULA_PREFIXES_MAP[value.getbyte(0)]
171
+ if value.empty?
172
+ buffer << value
173
+ elsif FORMULA_PREFIXES_MAP[value.getbyte(0)]
172
174
  value.gsub!('"', '""')
173
175
  buffer << '"\'' << value << '"'
174
176
  elsif value.match?(escape_regex)
@@ -184,7 +186,9 @@ class Phlex::CSV
184
186
  if escape_csv_injection
185
187
  first_byte = value.getbyte(0)
186
188
 
187
- if FORMULA_PREFIXES_MAP[first_byte]
189
+ if value.empty?
190
+ buffer << '""'
191
+ elsif FORMULA_PREFIXES_MAP[first_byte]
188
192
  buffer << '"\'' << value.gsub('"', '""') << '"'
189
193
  elsif value.match?(escape_regex)
190
194
  buffer << '"' << value.gsub('"', '""') << '"'
@@ -192,7 +196,9 @@ class Phlex::CSV
192
196
  buffer << value
193
197
  end
194
198
  else # not escaping CSV injection
195
- if value.match?(escape_regex)
199
+ if value.empty?
200
+ buffer << '""'
201
+ elsif value.match?(escape_regex)
196
202
  buffer << '"' << value.gsub('"', '""') << '"'
197
203
  else
198
204
  buffer << value
data/lib/phlex/html.rb CHANGED
@@ -58,7 +58,7 @@ class Phlex::HTML < Phlex::SGML
58
58
  raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
59
59
  end
60
60
 
61
- if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
61
+ if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
62
62
  if attributes.length > 0 # with attributes
63
63
  if block_given # with content block
64
64
  buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
@@ -14,16 +14,7 @@ class Phlex::SGML::State
14
14
 
15
15
  attr_accessor :capturing, :user_context
16
16
 
17
- attr_reader :fragments, :fragment_depth, :output_buffer
18
-
19
- def buffer
20
- case @buffer
21
- when Proc
22
- @buffer.call
23
- else
24
- @buffer
25
- end
26
- end
17
+ attr_reader :fragments, :fragment_depth, :output_buffer, :buffer
27
18
 
28
19
  def around_render(component)
29
20
  stack = @stack
@@ -85,17 +76,23 @@ class Phlex::SGML::State
85
76
  end
86
77
 
87
78
  def caching(&)
88
- buffer = +""
89
- @cache_stack.push([buffer, {}].freeze)
90
- capturing_into(buffer, &)
91
- @cache_stack.pop
79
+ result = nil
80
+
81
+ capture do
82
+ @cache_stack.push([buffer, {}].freeze)
83
+ yield
84
+ result = @cache_stack.pop
85
+ end
86
+
87
+ result
92
88
  end
93
89
 
94
90
  def caching?
95
91
  @cache_stack.length > 0
96
92
  end
97
93
 
98
- def capturing_into(new_buffer)
94
+ def capture
95
+ new_buffer = +""
99
96
  original_buffer = @buffer
100
97
  original_capturing = @capturing
101
98
  original_fragments = @fragments
data/lib/phlex/sgml.rb CHANGED
@@ -3,7 +3,13 @@
3
3
  # **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
4
4
  class Phlex::SGML
5
5
  UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
6
- REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze
6
+ REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping xlinkhref]).freeze
7
+ NAMED_CHARACTER_REFERENCES = {
8
+ "colon" => ":",
9
+ "tab" => "\t",
10
+ "newline" => "\n",
11
+ }.freeze
12
+ UNSAFE_ATTRIBUTE_NAME_CHARS = %r([<>&"'/=\s\x00])
7
13
 
8
14
  ERBCompiler = ERB::Compiler.new("<>").tap do |compiler|
9
15
  compiler.pre_cmd = [""]
@@ -217,9 +223,9 @@ class Phlex::SGML
217
223
  return "" unless block
218
224
 
219
225
  if args.length > 0
220
- @_state.capturing_into(+"") { __yield_content_with_args__(*args, &block) }
226
+ @_state.capture { __yield_content_with_args__(*args, &block) }
221
227
  else
222
- @_state.capturing_into(+"") { __yield_content__(&block) }
228
+ @_state.capture { __yield_content__(&block) }
223
229
  end
224
230
  end
225
231
 
@@ -298,6 +304,8 @@ class Phlex::SGML
298
304
  ].freeze
299
305
 
300
306
  low_level_cache(full_key, **, &content)
307
+
308
+ nil
301
309
  end
302
310
 
303
311
  # Cache a block of content where you control the entire cache key.
@@ -331,6 +339,8 @@ class Phlex::SGML
331
339
  end
332
340
  end
333
341
  end
342
+
343
+ nil
334
344
  end
335
345
 
336
346
  def json_escape(string)
@@ -353,15 +363,8 @@ class Phlex::SGML
353
363
  false
354
364
  end
355
365
 
356
- def vanish(*args)
357
- return unless block_given?
358
-
359
- if args.length > 0
360
- @_state.capturing_into(Phlex::Vanish) { yield(*args) }
361
- else
362
- @_state.capturing_into(Phlex::Vanish) { yield(self) }
363
- end
364
-
366
+ def vanish(...)
367
+ capture(...)
365
368
  nil
366
369
  end
367
370
 
@@ -526,7 +529,9 @@ class Phlex::SGML
526
529
  if value != true && REF_ATTRIBUTES.include?(normalized_name)
527
530
  case value
528
531
  when String
529
- if value.downcase.delete("^a-z:").start_with?("javascript:")
532
+ decoded_value = decode_html_character_references(value)
533
+
534
+ if decoded_value.downcase.delete("^a-z:").start_with?("javascript:")
530
535
  # We just ignore these because they were likely not specified by the developer.
531
536
  next
532
537
  end
@@ -544,7 +549,7 @@ class Phlex::SGML
544
549
  end
545
550
  end
546
551
 
547
- if name.match?(/[<>&"']/)
552
+ if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
548
553
  raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
549
554
  end
550
555
 
@@ -575,7 +580,7 @@ class Phlex::SGML
575
580
  else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
576
581
  end
577
582
 
578
- if name.match?(/[<>&"']/)
583
+ if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
579
584
  raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
580
585
  end
581
586
 
@@ -651,6 +656,27 @@ class Phlex::SGML
651
656
  buffer.gsub('"', "&quot;")
652
657
  end
653
658
 
659
+ private def decode_html_character_references(value)
660
+ value
661
+ .gsub(/&#x([0-9a-f]+);?/i) {
662
+ begin
663
+ [$1.to_i(16)].pack("U*")
664
+ rescue
665
+ ""
666
+ end
667
+ }
668
+ .gsub(/&#(\d+);?/) {
669
+ begin
670
+ [$1.to_i].pack("U*")
671
+ rescue
672
+ ""
673
+ end
674
+ }
675
+ .gsub(/&([a-z][a-z0-9]+);?/i) {
676
+ NAMED_CHARACTER_REFERENCES[$1.downcase] || ""
677
+ }
678
+ end
679
+
654
680
  # Result is **unsafe**, so it should be escaped!
655
681
  def __styles__(styles)
656
682
  case styles
data/lib/phlex/svg.rb CHANGED
@@ -43,7 +43,7 @@ class Phlex::SVG < Phlex::SGML
43
43
  raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
44
44
  end
45
45
 
46
- if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
46
+ if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
47
47
  if attributes.length > 0 # with attributes
48
48
  if block_given # with content block
49
49
  buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
data/lib/phlex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- VERSION = "2.1.1"
4
+ VERSION = "2.1.3"
5
5
  end
data/lib/phlex.rb CHANGED
@@ -8,7 +8,6 @@ module Phlex
8
8
 
9
9
  autoload :Kit, "phlex/kit"
10
10
  autoload :FIFO, "phlex/fifo"
11
- autoload :Vanish, "phlex/vanish"
12
11
  autoload :Helpers, "phlex/helpers"
13
12
  autoload :FIFOCacheStore, "phlex/fifo_cache_store"
14
13
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  - Will Cosgrove
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-07 00:00:00.000000000 Z
11
+ date: 2026-02-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Build HTML, SVG and CSV views with Ruby classes.
14
14
  email:
@@ -40,7 +40,6 @@ files:
40
40
  - lib/phlex/sgml/state.rb
41
41
  - lib/phlex/svg.rb
42
42
  - lib/phlex/svg/standard_elements.rb
43
- - lib/phlex/vanish.rb
44
43
  - lib/phlex/version.rb
45
44
  homepage: https://www.phlex.fun
46
45
  licenses:
@@ -65,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
64
  - !ruby/object:Gem::Version
66
65
  version: '0'
67
66
  requirements: []
68
- rubygems_version: 3.6.2
67
+ rubygems_version: 3.6.6
69
68
  specification_version: 4
70
69
  summary: Object-oriented views in Ruby.
71
70
  test_files: []
data/lib/phlex/vanish.rb DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- module Phlex::Vanish
5
- extend self
6
-
7
- def <<(anything)
8
- self
9
- end
10
-
11
- def bytesize
12
- 0
13
- end
14
-
15
- def dup
16
- self
17
- end
18
-
19
- def clear
20
- self
21
- end
22
- end