actionview 5.2.7.1 → 6.1.4.6

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -112
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/dependency_tracker.rb +10 -4
  10. data/lib/action_view/digestor.rb +15 -22
  11. data/lib/action_view/flows.rb +0 -1
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  16. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  17. data/lib/action_view/helpers/cache_helper.rb +23 -22
  18. data/lib/action_view/helpers/capture_helper.rb +4 -0
  19. data/lib/action_view/helpers/csp_helper.rb +4 -2
  20. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  21. data/lib/action_view/helpers/date_helper.rb +73 -30
  22. data/lib/action_view/helpers/form_helper.rb +305 -37
  23. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  24. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  25. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  26. data/lib/action_view/helpers/number_helper.rb +14 -8
  27. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  28. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  29. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  30. data/lib/action_view/helpers/tag_helper.rb +100 -55
  31. data/lib/action_view/helpers/tags/base.rb +18 -11
  32. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  34. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  36. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  37. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  39. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  40. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -1
  42. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  44. data/lib/action_view/helpers/tags/select.rb +1 -2
  45. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  46. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  47. data/lib/action_view/helpers/tags/translator.rb +1 -6
  48. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  49. data/lib/action_view/helpers/text_helper.rb +4 -5
  50. data/lib/action_view/helpers/translation_helper.rb +94 -54
  51. data/lib/action_view/helpers/url_helper.rb +136 -28
  52. data/lib/action_view/helpers.rb +0 -2
  53. data/lib/action_view/layouts.rb +8 -10
  54. data/lib/action_view/log_subscriber.rb +30 -15
  55. data/lib/action_view/lookup_context.rb +63 -35
  56. data/lib/action_view/path_set.rb +3 -12
  57. data/lib/action_view/railtie.rb +42 -26
  58. data/lib/action_view/record_identifier.rb +2 -3
  59. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  60. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  61. data/lib/action_view/renderer/object_renderer.rb +34 -0
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/renderer.rb +59 -4
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  66. data/lib/action_view/renderer/template_renderer.rb +35 -27
  67. data/lib/action_view/rendering.rb +54 -33
  68. data/lib/action_view/routing_url_for.rb +13 -12
  69. data/lib/action_view/template/error.rb +30 -15
  70. data/lib/action_view/template/handlers/builder.rb +2 -2
  71. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  72. data/lib/action_view/template/handlers/erb.rb +16 -11
  73. data/lib/action_view/template/handlers/html.rb +1 -1
  74. data/lib/action_view/template/handlers/raw.rb +2 -2
  75. data/lib/action_view/template/handlers.rb +1 -1
  76. data/lib/action_view/template/html.rb +5 -6
  77. data/lib/action_view/template/inline.rb +22 -0
  78. data/lib/action_view/template/raw_file.rb +25 -0
  79. data/lib/action_view/template/renderable.rb +24 -0
  80. data/lib/action_view/template/resolver.rb +191 -150
  81. data/lib/action_view/template/sources/file.rb +17 -0
  82. data/lib/action_view/template/sources.rb +13 -0
  83. data/lib/action_view/template/text.rb +2 -3
  84. data/lib/action_view/template.rb +66 -75
  85. data/lib/action_view/test_case.rb +21 -29
  86. data/lib/action_view/testing/resolvers.rb +18 -27
  87. data/lib/action_view/unbound_template.rb +31 -0
  88. data/lib/action_view/view_paths.rb +59 -38
  89. data/lib/action_view.rb +7 -2
  90. data/lib/assets/compiled/rails-ujs.js +32 -6
  91. metadata +29 -18
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -4,6 +4,7 @@ require "cgi"
4
4
  require "action_view/helpers/tag_helper"
5
5
  require "active_support/core_ext/string/output_safety"
6
6
  require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/core_ext/symbol/starts_ends_with"
7
8
 
8
9
  module ActionView
9
10
  # = Action View Form Tag Helpers
@@ -22,7 +23,9 @@ module ActionView
22
23
  mattr_accessor :embed_authenticity_token_in_remote_forms
23
24
  self.embed_authenticity_token_in_remote_forms = nil
24
25
 
25
- # Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
26
+ mattr_accessor :default_enforce_utf8, default: true
27
+
28
+ # Starts a form tag that points the action to a URL configured with <tt>url_for_options</tt> just like
26
29
  # ActionController::Base#url_for. The method for the form defaults to POST.
27
30
  #
28
31
  # ==== Options
@@ -132,10 +135,11 @@ module ActionView
132
135
  # # <option selected="selected">MasterCard</option></select>
133
136
  def select_tag(name, option_tags = nil, options = {})
134
137
  option_tags ||= ""
135
- html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
138
+ html_name = (options[:multiple] == true && !name.end_with?("[]")) ? "#{name}[]" : name
136
139
 
137
140
  if options.include?(:include_blank)
138
- include_blank = options.delete(:include_blank)
141
+ include_blank = options[:include_blank]
142
+ options = options.except(:include_blank)
139
143
  options_for_blank_options_tag = { value: "" }
140
144
 
141
145
  if include_blank == true
@@ -144,15 +148,15 @@ module ActionView
144
148
  end
145
149
 
146
150
  if include_blank
147
- option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags)
151
+ option_tags = content_tag("option", include_blank, options_for_blank_options_tag).safe_concat(option_tags)
148
152
  end
149
153
  end
150
154
 
151
155
  if prompt = options.delete(:prompt)
152
- option_tags = content_tag("option".freeze, prompt, value: "").safe_concat(option_tags)
156
+ option_tags = content_tag("option", prompt, value: "").safe_concat(option_tags)
153
157
  end
154
158
 
155
- content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
159
+ content_tag "select", option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
156
160
  end
157
161
 
158
162
  # Creates a standard text field; use these text fields to input smaller chunks of text like a username
@@ -389,8 +393,8 @@ module ActionView
389
393
  # * Any other key creates standard HTML options for the tag.
390
394
  #
391
395
  # ==== Examples
392
- # radio_button_tag 'gender', 'male'
393
- # # => <input id="gender_male" name="gender" type="radio" value="male" />
396
+ # radio_button_tag 'favorite_color', 'maroon'
397
+ # # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
394
398
  #
395
399
  # radio_button_tag 'receive_updates', 'no', true
396
400
  # # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
@@ -577,7 +581,7 @@ module ActionView
577
581
  # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
578
582
  def field_set_tag(legend = nil, options = nil, &block)
579
583
  output = tag(:fieldset, options, true)
580
- output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank?
584
+ output.safe_concat(content_tag("legend", legend)) unless legend.blank?
581
585
  output.concat(capture(&block)) if block_given?
582
586
  output.safe_concat("</fieldset>")
583
587
  end
@@ -869,7 +873,7 @@ module ActionView
869
873
  })
870
874
  end
871
875
 
872
- if html_options.delete("enforce_utf8") { true }
876
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
873
877
  utf8_enforcer_tag + method_tag
874
878
  else
875
879
  method_tag
@@ -893,16 +897,15 @@ module ActionView
893
897
  end
894
898
 
895
899
  def set_default_disable_with(value, tag_options)
896
- return unless ActionView::Base.automatically_disable_submit_tag
897
- data = tag_options["data"]
900
+ data = tag_options.fetch("data", {})
898
901
 
899
- unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false)
902
+ if tag_options["data-disable-with"] == false || data["disable_with"] == false
903
+ data.delete("disable_with")
904
+ elsif ActionView::Base.automatically_disable_submit_tag
900
905
  disable_with_text = tag_options["data-disable-with"]
901
- disable_with_text ||= data["disable_with"] if data
906
+ disable_with_text ||= data["disable_with"]
902
907
  disable_with_text ||= value.to_s.clone
903
908
  tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
904
- else
905
- data.delete("disable_with") if data
906
909
  end
907
910
 
908
911
  tag_options.delete("data-disable-with")
@@ -17,8 +17,8 @@ module ActionView
17
17
  "$" => "\\$"
18
18
  }
19
19
 
20
- JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
21
- JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
20
+ JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
21
+ JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
22
22
 
23
23
  # Escapes carriage returns and single and double quotes for JavaScript segments.
24
24
  #
@@ -27,12 +27,13 @@ module ActionView
27
27
  #
28
28
  # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
29
29
  def escape_javascript(javascript)
30
- if javascript
31
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u) { |match| JS_ESCAPE_MAP[match] }
32
- javascript.html_safe? ? result.html_safe : result
30
+ javascript = javascript.to_s
31
+ if javascript.empty?
32
+ result = ""
33
33
  else
34
- ""
34
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
35
35
  end
36
+ javascript.html_safe? ? result.html_safe : result
36
37
  end
37
38
 
38
39
  alias_method :j, :escape_javascript
@@ -50,10 +51,10 @@ module ActionView
50
51
  # +html_options+ may be a hash of attributes for the <tt>\<script></tt>
51
52
  # tag.
52
53
  #
53
- # javascript_tag "alert('All is good')", defer: 'defer'
54
+ # javascript_tag "alert('All is good')", type: 'application/javascript'
54
55
  #
55
56
  # Returns:
56
- # <script defer="defer">
57
+ # <script type="application/javascript">
57
58
  # //<![CDATA[
58
59
  # alert('All is good')
59
60
  # //]]>
@@ -62,12 +63,12 @@ module ActionView
62
63
  # Instead of passing the content as an argument, you can also use a block
63
64
  # in which case, you pass your +html_options+ as the first parameter.
64
65
  #
65
- # <%= javascript_tag defer: 'defer' do -%>
66
+ # <%= javascript_tag type: 'application/javascript' do -%>
66
67
  # alert('All is good')
67
68
  # <% end -%>
68
69
  #
69
70
  # If you have a content security policy enabled then you can add an automatic
70
- # nonce value by passing +nonce: true+ as part of +html_options+. Example:
71
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
71
72
  #
72
73
  # <%= javascript_tag nonce: true do -%>
73
74
  # alert('All is good')
@@ -85,7 +86,7 @@ module ActionView
85
86
  html_options[:nonce] = content_security_policy_nonce
86
87
  end
87
88
 
88
- content_tag("script".freeze, javascript_cdata_section(content), html_options)
89
+ content_tag("script", javascript_cdata_section(content), html_options)
89
90
  end
90
91
 
91
92
  def javascript_cdata_section(content) #:nodoc:
@@ -57,7 +57,7 @@ module ActionView
57
57
  #
58
58
  # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
59
59
  # # => "(755) 6123-4567"
60
- # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
60
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
61
61
  # # => "133-1234-5678"
62
62
  def number_to_phone(number, options = {})
63
63
  return unless number
@@ -100,6 +100,9 @@ module ActionView
100
100
  # absolute value of the number.
101
101
  # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
102
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+).
103
106
  #
104
107
  # ==== Examples
105
108
  #
@@ -111,12 +114,16 @@ module ActionView
111
114
  #
112
115
  # number_to_currency("123a456", raise: true) # => InvalidNumberError
113
116
  #
117
+ # number_to_currency(-0.456789, precision: 0)
118
+ # # => "$0"
114
119
  # number_to_currency(-1234567890.50, negative_format: "(%u%n)")
115
120
  # # => ($1,234,567,890.50)
116
121
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
117
122
  # # => R$1234567890,50
118
123
  # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
119
124
  # # => 1234567890,50 R$
125
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
126
+ # # => "$1,234,567,890.5"
120
127
  def number_to_currency(number, options = {})
121
128
  delegate_number_helper_method(:number_to_currency, number, options)
122
129
  end
@@ -246,7 +253,7 @@ module ActionView
246
253
  end
247
254
 
248
255
  # Formats the bytes in +number+ into a more understandable
249
- # representation (e.g., giving it 1500 yields 1.5 KB). This
256
+ # representation (e.g., giving it 1500 yields 1.46 KB). This
250
257
  # method is useful for reporting file sizes to users. You can
251
258
  # customize the format in the +options+ hash.
252
259
  #
@@ -292,7 +299,7 @@ module ActionView
292
299
  end
293
300
 
294
301
  # Pretty prints (formats and approximates) a number in a way it
295
- # is more readable by humans (eg.: 1200000000 becomes "1.2
302
+ # is more readable by humans (e.g.: 1200000000 becomes "1.2
296
303
  # Billion"). This is useful for numbers that can get very large
297
304
  # (and too hard to read).
298
305
  #
@@ -300,7 +307,7 @@ module ActionView
300
307
  # size.
301
308
  #
302
309
  # You can also define your own unit-quantifier names if you want
303
- # to use other decimal units (eg.: 1500 becomes "1.5
310
+ # to use other decimal units (e.g.: 1500 becomes "1.5
304
311
  # kilometers", 0.150 becomes "150 milliliters", etc). You may
305
312
  # define a wide range of unit quantifiers, even fractional ones
306
313
  # (centi, deci, mili, etc).
@@ -398,7 +405,6 @@ module ActionView
398
405
  end
399
406
 
400
407
  private
401
-
402
408
  def delegate_number_helper_method(method, number, options)
403
409
  return unless number
404
410
  options = escape_unsafe_options(options.symbolize_keys)
@@ -419,9 +425,9 @@ module ActionView
419
425
  end
420
426
 
421
427
  def escape_units(units)
422
- Hash[units.map do |k, v|
423
- [k, ERB::Util.html_escape(v)]
424
- end]
428
+ units.transform_values do |v|
429
+ ERB::Util.html_escape(v)
430
+ end
425
431
  end
426
432
 
427
433
  def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
@@ -38,7 +38,7 @@ module ActionView #:nodoc:
38
38
 
39
39
  # Converts the array to a comma-separated sentence where the last element is
40
40
  # joined by the connector word. This is the html_safe-aware version of
41
- # 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].
42
42
  #
43
43
  def to_sentence(array, options = {})
44
44
  options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
@@ -22,18 +22,28 @@ module ActionView
22
22
  # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
23
23
  # object.
24
24
  #
25
- # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
26
- # 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.
27
31
  def render(options = {}, locals = {}, &block)
28
32
  case options
29
33
  when Hash
30
- if block_given?
31
- view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
32
- else
33
- 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
34
40
  end
35
41
  else
36
- 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
37
47
  end
38
48
  end
39
49
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/object/try"
4
3
  require "rails-html-sanitizer"
5
4
 
6
5
  module ActionView
@@ -10,14 +9,14 @@ module ActionView
10
9
  # These helper methods extend Action View making them callable within your template files.
11
10
  module SanitizeHelper
12
11
  extend ActiveSupport::Concern
13
- # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
12
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
14
13
  #
15
14
  # It also strips href/src attributes with unsafe protocols like
16
15
  # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
17
16
  # ASCII, and hex character references to work around these protocol filters.
18
17
  # All special characters will be escaped.
19
18
  #
20
- # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
21
20
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
22
21
  #
23
22
  # Custom sanitization rules can also be provided.
@@ -40,7 +39,7 @@ module ActionView
40
39
  #
41
40
  # <%= sanitize @comment.body %>
42
41
  #
43
- # Providing custom whitelisted tags and attributes:
42
+ # Providing custom lists of permitted tags and attributes:
44
43
  #
45
44
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
46
45
  #
@@ -80,12 +79,12 @@ module ActionView
80
79
  # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
81
80
  # config.action_view.sanitized_allowed_attributes = ['href', 'title']
82
81
  def sanitize(html, options = {})
83
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
84
83
  end
85
84
 
86
85
  # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
87
86
  def sanitize_css(style)
88
- self.class.white_list_sanitizer.sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
89
88
  end
90
89
 
91
90
  # Strips all HTML tags from +html+, including comments and special characters.
@@ -123,20 +122,18 @@ module ActionView
123
122
  end
124
123
 
125
124
  module ClassMethods #:nodoc:
126
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
127
126
 
128
- # Vendors the full, link and white list sanitizers.
129
- # Provided strictly for compatibility and can be removed in Rails 5.1.
130
127
  def sanitizer_vendor
131
128
  Rails::Html::Sanitizer
132
129
  end
133
130
 
134
131
  def sanitized_allowed_tags
135
- sanitizer_vendor.white_list_sanitizer.allowed_tags
132
+ sanitizer_vendor.safe_list_sanitizer.allowed_tags
136
133
  end
137
134
 
138
135
  def sanitized_allowed_attributes
139
- sanitizer_vendor.white_list_sanitizer.allowed_attributes
136
+ sanitizer_vendor.safe_list_sanitizer.allowed_attributes
140
137
  end
141
138
 
142
139
  # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
@@ -145,7 +142,6 @@ module ActionView
145
142
  # class Application < Rails::Application
146
143
  # config.action_view.full_sanitizer = MySpecialSanitizer.new
147
144
  # end
148
- #
149
145
  def full_sanitizer
150
146
  @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
151
147
  end
@@ -156,20 +152,18 @@ module ActionView
156
152
  # class Application < Rails::Application
157
153
  # config.action_view.link_sanitizer = MySpecialSanitizer.new
158
154
  # end
159
- #
160
155
  def link_sanitizer
161
156
  @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
162
157
  end
163
158
 
164
- # 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+.
165
160
  # Replace with any object that responds to +sanitize+.
166
161
  #
167
162
  # class Application < Rails::Application
168
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
169
164
  # end
170
- #
171
- def white_list_sanitizer
172
- @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
173
167
  end
174
168
  end
175
169
  end
@@ -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,40 +49,55 @@ module ActionView
41
49
  @view_context = view_context
42
50
  end
43
51
 
44
- def tag_string(name, content = nil, **options, &block)
45
- escape = handle_deprecated_escape_options(options)
46
- content = @view_context.capture(self, &block) if block_given?
52
+ def p(*arguments, **options, &block)
53
+ tag_string(:p, *arguments, **options, &block)
54
+ end
47
55
 
56
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
57
+ content = @view_context.capture(self, &block) if block_given?
48
58
  if VOID_ELEMENTS.include?(name) && content.nil?
49
- "<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe
59
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
50
60
  else
51
- content_tag_string(name.to_s.dasherize, content || "", options, escape)
61
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
52
62
  end
53
63
  end
54
64
 
55
65
  def content_tag_string(name, content, options, escape = true)
56
66
  tag_options = tag_options(options, escape) if options
57
-
58
- if escape
59
- name = ERB::Util.xml_name_escape(name)
60
- content = ERB::Util.unwrapped_html_escape(content)
61
- end
62
-
67
+ content = ERB::Util.unwrapped_html_escape(content) if escape
63
68
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
64
69
  end
65
70
 
66
71
  def tag_options(options, escape = true)
67
72
  return if options.blank?
68
- output = "".dup
73
+ output = +""
69
74
  sep = " "
70
75
  options.each_pair do |key, value|
71
- 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)
72
84
  value.each_pair do |k, v|
73
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
+
74
97
  output << sep
75
98
  output << prefix_tag_option(key, k, v, escape)
76
99
  end
77
- elsif BOOLEAN_ATTRIBUTES.include?(key)
100
+ elsif type == :boolean
78
101
  if value
79
102
  output << sep
80
103
  output << boolean_tag_option(key)
@@ -92,14 +115,15 @@ module ActionView
92
115
  end
93
116
 
94
117
  def tag_option(key, value, escape)
95
- key = ERB::Util.xml_name_escape(key) if escape
96
-
97
- if value.is_a?(Array)
98
- 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(" ")
99
122
  else
100
123
  value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
101
124
  end
102
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
125
+ value = value.gsub('"', "&quot;") if value.include?('"')
126
+ %(#{key}="#{value}")
103
127
  end
104
128
 
105
129
  private
@@ -115,27 +139,6 @@ module ActionView
115
139
  true
116
140
  end
117
141
 
118
- def handle_deprecated_escape_options(options)
119
- # The option :escape_attributes has been merged into the options hash to be
120
- # able to warn when it is used, so we need to handle default values here.
121
- escape_option_provided = options.has_key?(:escape)
122
- escape_attributes_option_provided = options.has_key?(:escape_attributes)
123
-
124
- if escape_attributes_option_provided
125
- ActiveSupport::Deprecation.warn(<<~MSG)
126
- Use of the option :escape_attributes is deprecated. It currently \
127
- escapes both names and values of tags and attributes and it is \
128
- equivalent to :escape. If any of them are enabled, the escaping \
129
- is fully enabled.
130
- MSG
131
- end
132
-
133
- return true unless escape_option_provided || escape_attributes_option_provided
134
- escape_option = options.delete(:escape)
135
- escape_attributes_option = options.delete(:escape_attributes)
136
- escape_option || escape_attributes_option
137
- end
138
-
139
142
  def method_missing(called, *args, **options, &block)
140
143
  tag_string(called, *args, **options, &block)
141
144
  end
@@ -181,8 +184,8 @@ module ActionView
181
184
  # tag.input type: 'text', disabled: true
182
185
  # # => <input type="text" disabled="disabled">
183
186
  #
184
- # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
185
- # 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.
186
189
  #
187
190
  # To play nicely with JavaScript conventions, sub-attributes are dasherized.
188
191
  #
@@ -257,16 +260,18 @@ module ActionView
257
260
  # tag("img", src: "open & shut.png")
258
261
  # # => <img src="open &amp; shut.png" />
259
262
  #
260
- # tag("img", {src: "open &amp; shut.png"}, false, false)
263
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
261
264
  # # => <img src="open &amp; shut.png" />
262
265
  #
263
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
266
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
264
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" />
265
271
  def tag(name = nil, options = nil, open = false, escape = true)
266
272
  if name.nil?
267
273
  tag_builder
268
274
  else
269
- name = ERB::Util.xml_name_escape(name) if escape
270
275
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
271
276
  end
272
277
  end
@@ -290,6 +295,8 @@ module ActionView
290
295
  # # => <div class="strong"><p>Hello world!</p></div>
291
296
  # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
292
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>
293
300
  # content_tag("select", options, multiple: true)
294
301
  # # => <select multiple="multiple">...options...</select>
295
302
  #
@@ -306,6 +313,24 @@ module ActionView
306
313
  end
307
314
  end
308
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
+
309
334
  # Returns a CDATA section with the given +content+. CDATA sections
310
335
  # are used to escape blocks of text containing characters which would
311
336
  # otherwise be recognized as markup. CDATA sections begin with the string
@@ -336,6 +361,26 @@ module ActionView
336
361
  end
337
362
 
338
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
+
339
384
  def tag_builder
340
385
  @tag_builder ||= TagBuilder.new(self)
341
386
  end