actionview 5.1.4 → 6.1.1

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

Potentially problematic release.


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

Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +199 -168
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -5
  5. data/lib/action_view.rb +10 -4
  6. data/lib/action_view/base.rb +87 -23
  7. data/lib/action_view/buffers.rb +17 -0
  8. data/lib/action_view/cache_expiry.rb +52 -0
  9. data/lib/action_view/context.rb +7 -11
  10. data/lib/action_view/dependency_tracker.rb +12 -4
  11. data/lib/action_view/digestor.rb +24 -23
  12. data/lib/action_view/flows.rb +2 -1
  13. data/lib/action_view/gem_version.rb +4 -2
  14. data/lib/action_view/helpers.rb +4 -2
  15. data/lib/action_view/helpers/active_model_helper.rb +9 -4
  16. data/lib/action_view/helpers/asset_tag_helper.rb +220 -57
  17. data/lib/action_view/helpers/asset_url_helper.rb +28 -23
  18. data/lib/action_view/helpers/atom_feed_helper.rb +5 -2
  19. data/lib/action_view/helpers/cache_helper.rb +39 -28
  20. data/lib/action_view/helpers/capture_helper.rb +13 -7
  21. data/lib/action_view/helpers/controller_helper.rb +3 -1
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +5 -3
  24. data/lib/action_view/helpers/date_helper.rb +78 -33
  25. data/lib/action_view/helpers/debug_helper.rb +4 -2
  26. data/lib/action_view/helpers/form_helper.rb +357 -106
  27. data/lib/action_view/helpers/form_options_helper.rb +45 -39
  28. data/lib/action_view/helpers/form_tag_helper.rb +42 -27
  29. data/lib/action_view/helpers/javascript_helper.rb +28 -12
  30. data/lib/action_view/helpers/number_helper.rb +16 -8
  31. data/lib/action_view/helpers/output_safety_helper.rb +3 -1
  32. data/lib/action_view/helpers/rendering_helper.rb +20 -9
  33. data/lib/action_view/helpers/sanitize_helper.rb +15 -19
  34. data/lib/action_view/helpers/tag_helper.rb +100 -24
  35. data/lib/action_view/helpers/tags.rb +3 -1
  36. data/lib/action_view/helpers/tags/base.rb +30 -21
  37. data/lib/action_view/helpers/tags/check_box.rb +3 -2
  38. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -1
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -1
  42. data/lib/action_view/helpers/tags/collection_select.rb +3 -1
  43. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/date_select.rb +5 -4
  46. data/lib/action_view/helpers/tags/datetime_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  48. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -1
  52. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/label.rb +6 -5
  54. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  55. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +2 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +3 -2
  59. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +2 -0
  61. data/lib/action_view/helpers/tags/select.rb +4 -3
  62. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +3 -1
  64. data/lib/action_view/helpers/tags/text_field.rb +3 -2
  65. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  66. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  68. data/lib/action_view/helpers/tags/translator.rb +3 -6
  69. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  71. data/lib/action_view/helpers/text_helper.rb +11 -10
  72. data/lib/action_view/helpers/translation_helper.rb +102 -52
  73. data/lib/action_view/helpers/url_helper.rb +150 -32
  74. data/lib/action_view/layouts.rb +15 -15
  75. data/lib/action_view/log_subscriber.rb +32 -15
  76. data/lib/action_view/lookup_context.rb +67 -39
  77. data/lib/action_view/model_naming.rb +2 -0
  78. data/lib/action_view/path_set.rb +5 -12
  79. data/lib/action_view/railtie.rb +46 -21
  80. data/lib/action_view/record_identifier.rb +4 -3
  81. data/lib/action_view/renderer/abstract_renderer.rb +144 -11
  82. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  83. data/lib/action_view/renderer/object_renderer.rb +34 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +33 -283
  85. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +64 -17
  86. data/lib/action_view/renderer/renderer.rb +61 -4
  87. data/lib/action_view/renderer/streaming_template_renderer.rb +14 -8
  88. data/lib/action_view/renderer/template_renderer.rb +36 -26
  89. data/lib/action_view/rendering.rb +57 -38
  90. data/lib/action_view/routing_url_for.rb +15 -12
  91. data/lib/action_view/tasks/cache_digests.rake +2 -0
  92. data/lib/action_view/template.rb +69 -76
  93. data/lib/action_view/template/error.rb +32 -18
  94. data/lib/action_view/template/handlers.rb +4 -2
  95. data/lib/action_view/template/handlers/builder.rb +5 -6
  96. data/lib/action_view/template/handlers/erb.rb +20 -19
  97. data/lib/action_view/template/handlers/erb/erubi.rb +17 -9
  98. data/lib/action_view/template/handlers/html.rb +3 -1
  99. data/lib/action_view/template/handlers/raw.rb +4 -2
  100. data/lib/action_view/template/html.rb +8 -7
  101. data/lib/action_view/template/inline.rb +22 -0
  102. data/lib/action_view/template/raw_file.rb +25 -0
  103. data/lib/action_view/template/renderable.rb +24 -0
  104. data/lib/action_view/template/resolver.rb +194 -152
  105. data/lib/action_view/template/sources.rb +13 -0
  106. data/lib/action_view/template/sources/file.rb +17 -0
  107. data/lib/action_view/template/text.rb +5 -4
  108. data/lib/action_view/template/types.rb +3 -1
  109. data/lib/action_view/test_case.rb +38 -30
  110. data/lib/action_view/testing/resolvers.rb +20 -27
  111. data/lib/action_view/unbound_template.rb +31 -0
  112. data/lib/action_view/version.rb +2 -0
  113. data/lib/action_view/view_paths.rb +61 -40
  114. data/lib/assets/compiled/rails-ujs.js +84 -23
  115. metadata +34 -23
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -21
  117. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +0 -9
  118. data/lib/action_view/template/handlers/erb/erubis.rb +0 -81
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "action_view/helpers/tag_helper"
2
4
 
3
5
  module ActionView
4
- module Helpers
6
+ module Helpers #:nodoc:
5
7
  module JavaScriptHelper
6
8
  JS_ESCAPE_MAP = {
7
9
  '\\' => '\\\\',
@@ -10,11 +12,13 @@ module ActionView
10
12
  "\n" => '\n',
11
13
  "\r" => '\n',
12
14
  '"' => '\\"',
13
- "'" => "\\'"
15
+ "'" => "\\'",
16
+ "`" => "\\`",
17
+ "$" => "\\$"
14
18
  }
15
19
 
16
- JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = "
"
17
- JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = "
"
20
+ JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "
"
21
+ JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "
"
18
22
 
19
23
  # Escapes carriage returns and single and double quotes for JavaScript segments.
20
24
  #
@@ -23,12 +27,13 @@ module ActionView
23
27
  #
24
28
  # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
25
29
  def escape_javascript(javascript)
26
- if javascript
27
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
28
- javascript.html_safe? ? result.html_safe : result
30
+ javascript = javascript.to_s
31
+ if javascript.empty?
32
+ result = ""
29
33
  else
30
- ""
34
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
31
35
  end
36
+ javascript.html_safe? ? result.html_safe : result
32
37
  end
33
38
 
34
39
  alias_method :j, :escape_javascript
@@ -46,10 +51,10 @@ module ActionView
46
51
  # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
47
52
  # tag.
48
53
  #
49
- # javascript_tag "alert('All is good')", defer: 'defer'
54
+ # javascript_tag "alert('All is good')", type: 'application/javascript'
50
55
  #
51
56
  # Returns:
52
- # <script defer="defer">
57
+ # <script type="application/javascript">
53
58
  # //<![CDATA[
54
59
  # alert('All is good')
55
60
  # //]]>
@@ -58,7 +63,14 @@ module ActionView
58
63
  # Instead of passing the content as an argument, you can also use a block
59
64
  # in which case, you pass your +html_options+ as the first parameter.
60
65
  #
61
- # <%= javascript_tag defer: 'defer' do -%>
66
+ # <%= javascript_tag type: 'application/javascript' do -%>
67
+ # alert('All is good')
68
+ # <% end -%>
69
+ #
70
+ # If you have a content security policy enabled then you can add an automatic
71
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
72
+ #
73
+ # <%= javascript_tag nonce: true do -%>
62
74
  # alert('All is good')
63
75
  # <% end -%>
64
76
  def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
@@ -70,7 +82,11 @@ module ActionView
70
82
  content_or_options_with_block
71
83
  end
72
84
 
73
- content_tag("script".freeze, javascript_cdata_section(content), html_options)
85
+ if html_options[:nonce] == true
86
+ html_options[:nonce] = content_security_policy_nonce
87
+ end
88
+
89
+ content_tag("script", javascript_cdata_section(content), html_options)
74
90
  end
75
91
 
76
92
  def javascript_cdata_section(content) #:nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/keys"
2
4
  require "active_support/core_ext/string/output_safety"
3
5
  require "active_support/number_helper"
@@ -55,7 +57,7 @@ module ActionView
55
57
  #
56
58
  # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
57
59
  # # => "(755) 6123-4567"
58
- # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
60
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
59
61
  # # => "133-1234-5678"
60
62
  def number_to_phone(number, options = {})
61
63
  return unless number
@@ -98,6 +100,9 @@ module ActionView
98
100
  # absolute value of the number.
99
101
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
100
102
  # the argument is invalid.
103
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
104
+ # insignificant zeros after the decimal separator (defaults to
105
+ # +false+).
101
106
  #
102
107
  # ==== Examples
103
108
  #
@@ -109,12 +114,16 @@ module ActionView
109
114
  #
110
115
  # number_to_currency("123a456", raise: true) # => InvalidNumberError
111
116
  #
117
+ # number_to_currency(-0.456789, precision: 0)
118
+ # # => "$0"
112
119
  # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
113
120
  # # => ($1,234,567,890.50)
114
121
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
115
122
  # # => R$1234567890,50
116
123
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
117
124
  # # => 1234567890,50 R$
125
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
126
+ # # => "$1,234,567,890.5"
118
127
  def number_to_currency(number, options = {})
119
128
  delegate_number_helper_method(:number_to_currency, number, options)
120
129
  end
@@ -244,7 +253,7 @@ module ActionView
244
253
  end
245
254
 
246
255
  # Formats the bytes in +number+ into a more understandable
247
- # representation (e.g., giving it 1500 yields 1.5 KB). This
256
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
248
257
  # method is useful for reporting file sizes to users. You can
249
258
  # customize the format in the +options+ hash.
250
259
  #
@@ -290,7 +299,7 @@ module ActionView
290
299
  end
291
300
 
292
301
  # Pretty prints (formats and approximates) a number in a way it
293
- # is more readable by humans (eg.: 1200000000 becomes "1.2
302
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
294
303
  # Billion"). This is useful for numbers that can get very large
295
304
  # (and too hard to read).
296
305
  #
@@ -298,7 +307,7 @@ module ActionView
298
307
  # size.
299
308
  #
300
309
  # You can also define your own unit-quantifier names if you want
301
- # to use other decimal units (eg.: 1500 becomes "1.5
310
+ # to use other decimal units (e.g.: 1500 becomes "1.5
302
311
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
303
312
  # define a wide range of unit quantifiers, even fractional ones
304
313
  # (centi, deci, mili, etc).
@@ -396,7 +405,6 @@ module ActionView
396
405
  end
397
406
 
398
407
  private
399
-
400
408
  def delegate_number_helper_method(method, number, options)
401
409
  return unless number
402
410
  options = escape_unsafe_options(options.symbolize_keys)
@@ -417,9 +425,9 @@ module ActionView
417
425
  end
418
426
 
419
427
  def escape_units(units)
420
- Hash[units.map do |k, v|
421
- [k, ERB::Util.html_escape(v)]
422
- end]
428
+ units.transform_values do |v|
429
+ ERB::Util.html_escape(v)
430
+ end
423
431
  end
424
432
 
425
433
  def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/string/output_safety"
2
4
 
3
5
  module ActionView #:nodoc:
@@ -36,7 +38,7 @@ module ActionView #:nodoc:
36
38
 
37
39
  # Converts the array to a comma-separated sentence where the last element is
38
40
  # joined by the connector word. This is the html_safe-aware version of
39
- # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
41
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
40
42
  #
41
43
  def to_sentence(array, options = {})
42
44
  options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
- module Helpers
4
+ module Helpers #:nodoc:
3
5
  # = Action View Rendering
4
6
  #
5
7
  # Implements methods that allow rendering from a view context.
@@ -11,7 +13,6 @@ module ActionView
11
13
  # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
12
14
  # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
13
15
  # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
14
- # * <tt>:text</tt> - Renders the text passed in out.
15
16
  # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
16
17
  # type as <tt>text/plain</tt>.
17
18
  # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
@@ -21,18 +22,28 @@ module ActionView
21
22
  # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
22
23
  # object.
23
24
  #
24
- # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
25
- # as the locals hash.
25
+ # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
26
+ #
27
+ # If an object responding to `render_in` is passed, `render_in` is called on the object,
28
+ # passing in the current view context.
29
+ #
30
+ # Otherwise, a partial is rendered using the second parameter as the locals hash.
26
31
  def render(options = {}, locals = {}, &block)
27
32
  case options
28
33
  when Hash
29
- if block_given?
30
- view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
31
- else
32
- view_renderer.render(self, options)
34
+ in_rendering_context(options) do |renderer|
35
+ if block_given?
36
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
37
+ else
38
+ view_renderer.render(self, options)
39
+ end
33
40
  end
34
41
  else
35
- view_renderer.render_partial(self, partial: options, locals: locals, &block)
42
+ if options.respond_to?(:render_in)
43
+ options.render_in(self, &block)
44
+ else
45
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
46
+ end
36
47
  end
37
48
  end
38
49
 
@@ -1,21 +1,22 @@
1
- require "active_support/core_ext/object/try"
1
+ # frozen_string_literal: true
2
+
2
3
  require "rails-html-sanitizer"
3
4
 
4
5
  module ActionView
5
6
  # = Action View Sanitize Helpers
6
- module Helpers
7
+ module Helpers #:nodoc:
7
8
  # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
8
9
  # These helper methods extend Action View making them callable within your template files.
9
10
  module SanitizeHelper
10
11
  extend ActiveSupport::Concern
11
- # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
12
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
12
13
  #
13
14
  # It also strips href/src attributes with unsafe protocols like
14
15
  # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
15
16
  # ASCII, and hex character references to work around these protocol filters.
16
17
  # All special characters will be escaped.
17
18
  #
18
- # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
19
20
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
20
21
  #
21
22
  # Custom sanitization rules can also be provided.
@@ -38,7 +39,7 @@ module ActionView
38
39
  #
39
40
  # <%= sanitize @comment.body %>
40
41
  #
41
- # Providing custom whitelisted tags and attributes:
42
+ # Providing custom lists of permitted tags and attributes:
42
43
  #
43
44
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
44
45
  #
@@ -78,12 +79,12 @@ module ActionView
78
79
  # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
79
80
  # config.action_view.sanitized_allowed_attributes = ['href', 'title']
80
81
  def sanitize(html, options = {})
81
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
82
83
  end
83
84
 
84
85
  # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
85
86
  def sanitize_css(style)
86
- self.class.white_list_sanitizer.sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
87
88
  end
88
89
 
89
90
  # Strips all HTML tags from +html+, including comments and special characters.
@@ -121,20 +122,18 @@ module ActionView
121
122
  end
122
123
 
123
124
  module ClassMethods #:nodoc:
124
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
125
126
 
126
- # Vendors the full, link and white list sanitizers.
127
- # Provided strictly for compatibility and can be removed in Rails 5.1.
128
127
  def sanitizer_vendor
129
128
  Rails::Html::Sanitizer
130
129
  end
131
130
 
132
131
  def sanitized_allowed_tags
133
- sanitizer_vendor.white_list_sanitizer.allowed_tags
132
+ sanitizer_vendor.safe_list_sanitizer.allowed_tags
134
133
  end
135
134
 
136
135
  def sanitized_allowed_attributes
137
- sanitizer_vendor.white_list_sanitizer.allowed_attributes
136
+ sanitizer_vendor.safe_list_sanitizer.allowed_attributes
138
137
  end
139
138
 
140
139
  # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
@@ -143,7 +142,6 @@ module ActionView
143
142
  # class Application < Rails::Application
144
143
  # config.action_view.full_sanitizer = MySpecialSanitizer.new
145
144
  # end
146
- #
147
145
  def full_sanitizer
148
146
  @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
149
147
  end
@@ -154,20 +152,18 @@ module ActionView
154
152
  # class Application < Rails::Application
155
153
  # config.action_view.link_sanitizer = MySpecialSanitizer.new
156
154
  # end
157
- #
158
155
  def link_sanitizer
159
156
  @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
160
157
  end
161
158
 
162
- # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
159
+ # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
163
160
  # Replace with any object that responds to +sanitize+.
164
161
  #
165
162
  # class Application < Rails::Application
166
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
167
164
  # end
168
- #
169
- def white_list_sanitizer
170
- @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
171
167
  end
172
168
  end
173
169
  end
@@ -1,4 +1,4 @@
1
- # frozen-string-literal: true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/output_safety"
4
4
  require "set"
@@ -13,19 +13,27 @@ module ActionView
13
13
  include CaptureHelper
14
14
  include OutputSafetyHelper
15
15
 
16
- BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
17
- compact controls declare default defaultchecked
18
- defaultmuted defaultselected defer disabled
19
- enabled formnovalidate hidden indeterminate inert
20
- ismap itemscope loop multiple muted nohref
21
- noresize noshade novalidate nowrap open
22
- pauseonexit readonly required reversed scoped
23
- seamless selected sortable truespeed typemustmatch
24
- visible).to_set
16
+ BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
17
+ autoplay checked compact controls declare default
18
+ defaultchecked defaultmuted defaultselected defer
19
+ disabled enabled formnovalidate hidden indeterminate
20
+ inert ismap itemscope loop multiple muted nohref
21
+ nomodule noresize noshade novalidate nowrap open
22
+ pauseonexit playsinline readonly required reversed
23
+ scoped seamless selected sortable truespeed
24
+ typemustmatch visible).to_set
25
25
 
26
26
  BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
27
+ BOOLEAN_ATTRIBUTES.freeze
27
28
 
28
- TAG_PREFIXES = ["aria", "data", :aria, :data].to_set
29
+ ARIA_PREFIXES = ["aria", :aria].to_set.freeze
30
+ DATA_PREFIXES = ["data", :data].to_set.freeze
31
+
32
+ TAG_TYPES = {}
33
+ TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
34
+ TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
35
+ TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
36
+ TAG_TYPES.freeze
29
37
 
30
38
  PRE_CONTENT_STRINGS = Hash.new { "" }
31
39
  PRE_CONTENT_STRINGS[:textarea] = "\n"
@@ -41,6 +49,10 @@ module ActionView
41
49
  @view_context = view_context
42
50
  end
43
51
 
52
+ def p(*arguments, **options, &block)
53
+ tag_string(:p, *arguments, **options, &block)
54
+ end
55
+
44
56
  def tag_string(name, content = nil, escape_attributes: true, **options, &block)
45
57
  content = @view_context.capture(self, &block) if block_given?
46
58
  if VOID_ELEMENTS.include?(name) && content.nil?
@@ -58,16 +70,34 @@ module ActionView
58
70
 
59
71
  def tag_options(options, escape = true)
60
72
  return if options.blank?
61
- output = "".dup
73
+ output = +""
62
74
  sep = " "
63
75
  options.each_pair do |key, value|
64
- if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
76
+ type = TAG_TYPES[key]
77
+ if type == :data && value.is_a?(Hash)
78
+ value.each_pair do |k, v|
79
+ next if v.nil?
80
+ output << sep
81
+ output << prefix_tag_option(key, k, v, escape)
82
+ end
83
+ elsif type == :aria && value.is_a?(Hash)
65
84
  value.each_pair do |k, v|
66
85
  next if v.nil?
86
+
87
+ case v
88
+ when Array, Hash
89
+ tokens = TagHelper.build_tag_values(v)
90
+ next if tokens.none?
91
+
92
+ v = safe_join(tokens, " ")
93
+ else
94
+ v = v.to_s
95
+ end
96
+
67
97
  output << sep
68
98
  output << prefix_tag_option(key, k, v, escape)
69
99
  end
70
- elsif BOOLEAN_ATTRIBUTES.include?(key)
100
+ elsif type == :boolean
71
101
  if value
72
102
  output << sep
73
103
  output << boolean_tag_option(key)
@@ -85,12 +115,15 @@ module ActionView
85
115
  end
86
116
 
87
117
  def tag_option(key, value, escape)
88
- if value.is_a?(Array)
89
- value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
118
+ case value
119
+ when Array, Hash
120
+ value = TagHelper.build_tag_values(value) if key.to_s == "class"
121
+ value = escape ? safe_join(value, " ") : value.join(" ")
90
122
  else
91
123
  value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
92
124
  end
93
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
125
+ value = value.gsub('"', "&quot;") if value.include?('"')
126
+ %(#{key}="#{value}")
94
127
  end
95
128
 
96
129
  private
@@ -106,8 +139,8 @@ module ActionView
106
139
  true
107
140
  end
108
141
 
109
- def method_missing(called, *args, &block)
110
- tag_string(called, *args, &block)
142
+ def method_missing(called, *args, **options, &block)
143
+ tag_string(called, *args, **options, &block)
111
144
  end
112
145
  end
113
146
 
@@ -151,8 +184,8 @@ module ActionView
151
184
  # tag.input type: 'text', disabled: true
152
185
  # # => <input type="text" disabled="disabled">
153
186
  #
154
- # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
155
- # pointing to a hash of sub-attributes.
187
+ # HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
188
+ # single +data+ or +aria+ key pointing to a hash of sub-attributes.
156
189
  #
157
190
  # To play nicely with JavaScript conventions, sub-attributes are dasherized.
158
191
  #
@@ -166,7 +199,7 @@ module ActionView
166
199
  # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
167
200
  # from 1.4.3.
168
201
  #
169
- # tag.div data: { city_state: %w( Chigaco IL ) }
202
+ # tag.div data: { city_state: %w( Chicago IL ) }
170
203
  # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
171
204
  #
172
205
  # The generated attributes are escaped by default. This can be disabled using
@@ -227,11 +260,14 @@ module ActionView
227
260
  # tag("img", src: "open & shut.png")
228
261
  # # => <img src="open &amp; shut.png" />
229
262
  #
230
- # tag("img", {src: "open &amp; shut.png"}, false, false)
263
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
231
264
  # # => <img src="open &amp; shut.png" />
232
265
  #
233
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
266
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
234
267
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
268
+ #
269
+ # tag("div", class: { highlight: current_user.admin? })
270
+ # # => <div class="highlight" />
235
271
  def tag(name = nil, options = nil, open = false, escape = true)
236
272
  if name.nil?
237
273
  tag_builder
@@ -259,6 +295,8 @@ module ActionView
259
295
  # # => <div class="strong"><p>Hello world!</p></div>
260
296
  # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
261
297
  # # => <div class="strong highlight">Hello world!</div>
298
+ # content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
299
+ # # => <div class="strong highlight">Hello world!</div>
262
300
  # content_tag("select", options, multiple: true)
263
301
  # # => <select multiple="multiple">...options...</select>
264
302
  #
@@ -275,6 +313,24 @@ module ActionView
275
313
  end
276
314
  end
277
315
 
316
+ # Returns a string of tokens built from +args+.
317
+ #
318
+ # ==== Examples
319
+ # token_list("foo", "bar")
320
+ # # => "foo bar"
321
+ # token_list("foo", "foo bar")
322
+ # # => "foo bar"
323
+ # token_list({ foo: true, bar: false })
324
+ # # => "foo"
325
+ # token_list(nil, false, 123, "", "foo", { bar: true })
326
+ # # => "123 foo bar"
327
+ def token_list(*args)
328
+ tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
329
+
330
+ safe_join(tokens, " ")
331
+ end
332
+ alias_method :class_names, :token_list
333
+
278
334
  # Returns a CDATA section with the given +content+. CDATA sections
279
335
  # are used to escape blocks of text containing characters which would
280
336
  # otherwise be recognized as markup. CDATA sections begin with the string
@@ -305,6 +361,26 @@ module ActionView
305
361
  end
306
362
 
307
363
  private
364
+ def build_tag_values(*args)
365
+ tag_values = []
366
+
367
+ args.each do |tag_value|
368
+ case tag_value
369
+ when Hash
370
+ tag_value.each do |key, val|
371
+ tag_values << key.to_s if val && key.present?
372
+ end
373
+ when Array
374
+ tag_values.concat build_tag_values(*tag_value)
375
+ else
376
+ tag_values << tag_value.to_s if tag_value.present?
377
+ end
378
+ end
379
+
380
+ tag_values
381
+ end
382
+ module_function :build_tag_values
383
+
308
384
  def tag_builder
309
385
  @tag_builder ||= TagBuilder.new(self)
310
386
  end