actionview 4.1.13 → 6.1.3.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 (124) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +181 -359
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +12 -6
  5. data/lib/action_view/base.rb +115 -43
  6. data/lib/action_view/buffers.rb +22 -4
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -84
  11. data/lib/action_view/flows.rb +12 -13
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
  15. data/lib/action_view/helpers/asset_url_helper.rb +197 -80
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +109 -45
  18. data/lib/action_view/helpers/capture_helper.rb +20 -22
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +245 -140
  23. data/lib/action_view/helpers/debug_helper.rb +14 -17
  24. data/lib/action_view/helpers/form_helper.rb +875 -148
  25. data/lib/action_view/helpers/form_options_helper.rb +128 -82
  26. data/lib/action_view/helpers/form_tag_helper.rb +253 -91
  27. data/lib/action_view/helpers/javascript_helper.rb +37 -15
  28. data/lib/action_view/helpers/number_helper.rb +100 -77
  29. data/lib/action_view/helpers/output_safety_helper.rb +42 -10
  30. data/lib/action_view/helpers/rendering_helper.rb +26 -15
  31. data/lib/action_view/helpers/sanitize_helper.rb +79 -164
  32. data/lib/action_view/helpers/tag_helper.rb +277 -64
  33. data/lib/action_view/helpers/tags/base.rb +143 -92
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +41 -22
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +3 -0
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +7 -1
  61. data/lib/action_view/helpers/tags/text_field.rb +11 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +39 -0
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +4 -1
  69. data/lib/action_view/helpers/text_helper.rb +80 -45
  70. data/lib/action_view/helpers/translation_helper.rb +148 -67
  71. data/lib/action_view/helpers/url_helper.rb +289 -147
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +80 -13
  75. data/lib/action_view/lookup_context.rb +137 -92
  76. data/lib/action_view/model_naming.rb +4 -2
  77. data/lib/action_view/path_set.rb +30 -16
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +152 -13
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +61 -261
  85. data/lib/action_view/renderer/renderer.rb +67 -6
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +83 -75
  88. data/lib/action_view/rendering.rb +73 -46
  89. data/lib/action_view/routing_url_for.rb +54 -17
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +22 -9
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +267 -181
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +109 -99
  108. data/lib/action_view/test_case.rb +73 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +74 -44
  113. data/lib/action_view.rb +14 -9
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +71 -26
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
  118. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  119. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  120. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  121. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  122. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  123. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  124. data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -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,28 +13,37 @@ 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
- # type as <tt>text/plain</tt>.
17
- # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
18
- # performs html escape on the string first. Setting the content type as
19
- # <tt>text/html</tt>.
17
+ # type as <tt>text/plain</tt>.
18
+ # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
19
+ # performs HTML escape on the string first. Setting the content type as
20
+ # <tt>text/html</tt>.
20
21
  # * <tt>:body</tt> - Renders the text passed in, and inherits the content
21
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
22
- # object.
22
+ # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
23
+ # object.
24
+ #
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.
23
29
  #
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.
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)
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,73 +1,93 @@
1
- require 'active_support/core_ext/object/try'
2
- require 'action_view/vendor/html-scanner'
1
+ # frozen_string_literal: true
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
- # This +sanitize+ helper will html encode all tags and strip all attributes that
12
- # aren't specifically allowed.
12
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
13
13
  #
14
- # It also strips href/src tags with invalid protocols, like javascript: especially.
15
- # It does its best to counter any tricks that hackers may use, like throwing in
16
- # unicode/ascii/hex values to get past the javascript: filters. Check out
17
- # the extensive test suite.
14
+ # It also strips href/src attributes with unsafe protocols like
15
+ # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
16
+ # ASCII, and hex character references to work around these protocol filters.
17
+ # All special characters will be escaped.
18
18
  #
19
- # <%= sanitize @article.body %>
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
20
+ # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
20
21
  #
21
- # You can add or remove tags/attributes if you want to customize it a bit.
22
- # See ActionView::Base for full docs on the available options. You can add
23
- # tags/attributes for single uses of +sanitize+ by passing either the
24
- # <tt>:attributes</tt> or <tt>:tags</tt> options:
22
+ # Custom sanitization rules can also be provided.
25
23
  #
26
- # Normal Use
24
+ # Please note that sanitizing user-provided text does not guarantee that the
25
+ # resulting markup is valid or even well-formed.
27
26
  #
28
- # <%= sanitize @article.body %>
27
+ # ==== Options
29
28
  #
30
- # Custom Use (only the mentioned tags and attributes are allowed, nothing else)
29
+ # * <tt>:tags</tt> - An array of allowed tags.
30
+ # * <tt>:attributes</tt> - An array of allowed attributes.
31
+ # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
32
+ # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
33
+ # defines custom sanitization rules. A custom scrubber takes precedence over
34
+ # custom tags and attributes.
31
35
  #
32
- # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
36
+ # ==== Examples
33
37
  #
34
- # Add table tags to the default allowed tags
38
+ # Normal use:
35
39
  #
36
- # class Application < Rails::Application
37
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
38
- # end
40
+ # <%= sanitize @comment.body %>
39
41
  #
40
- # Remove tags to the default allowed tags
42
+ # Providing custom lists of permitted tags and attributes:
41
43
  #
42
- # class Application < Rails::Application
43
- # config.after_initialize do
44
- # ActionView::Base.sanitized_allowed_tags.delete 'div'
44
+ # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
45
+ #
46
+ # Providing a custom Rails::Html scrubber:
47
+ #
48
+ # class CommentScrubber < Rails::Html::PermitScrubber
49
+ # def initialize
50
+ # super
51
+ # self.tags = %w( form script comment blockquote )
52
+ # self.attributes = %w( style )
53
+ # end
54
+ #
55
+ # def skip_node?(node)
56
+ # node.text?
45
57
  # end
46
58
  # end
47
59
  #
48
- # Change allowed default attributes
60
+ # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
61
+ #
62
+ # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
63
+ # documentation about Rails::Html scrubbers.
64
+ #
65
+ # Providing a custom Loofah::Scrubber:
49
66
  #
50
- # class Application < Rails::Application
51
- # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
67
+ # scrubber = Loofah::Scrubber.new do |node|
68
+ # node.remove if node.name == 'script'
52
69
  # end
53
70
  #
54
- # Please note that sanitizing user-provided text does not guarantee that the
55
- # resulting markup is valid (conforming to a document type) or even well-formed.
56
- # The output may still contain e.g. unescaped '<', '>', '&' characters and
57
- # confuse browsers.
71
+ # <%= sanitize @comment.body, scrubber: scrubber %>
72
+ #
73
+ # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
74
+ # information about defining custom Loofah::Scrubber objects.
58
75
  #
76
+ # To set the default allowed tags or attributes across your application:
77
+ #
78
+ # # In config/application.rb
79
+ # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
80
+ # config.action_view.sanitized_allowed_attributes = ['href', 'title']
59
81
  def sanitize(html, options = {})
60
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
61
83
  end
62
84
 
63
85
  # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
64
86
  def sanitize_css(style)
65
- self.class.white_list_sanitizer.sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
66
88
  end
67
89
 
68
- # Strips all HTML tags from the +html+, including comments. This uses the
69
- # html-scanner tokenizer and so its HTML parsing ability is limited by
70
- # that of html-scanner.
90
+ # Strips all HTML tags from +html+, including comments and special characters.
71
91
  #
72
92
  # strip_tags("Strip <i>these</i> tags!")
73
93
  # # => Strip these tags!
@@ -77,11 +97,14 @@ module ActionView
77
97
  #
78
98
  # strip_tags("<div id='top-bar'>Welcome to my website!</div>")
79
99
  # # => Welcome to my website!
100
+ #
101
+ # strip_tags("> A quote from Smith & Wesson")
102
+ # # => &gt; A quote from Smith &amp; Wesson
80
103
  def strip_tags(html)
81
104
  self.class.full_sanitizer.sanitize(html)
82
105
  end
83
106
 
84
- # Strips all link tags from +text+ leaving just the link text.
107
+ # Strips all link tags from +html+ leaving just the link text.
85
108
  #
86
109
  # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
87
110
  # # => Ruby on Rails
@@ -91,164 +114,56 @@ module ActionView
91
114
  #
92
115
  # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
93
116
  # # => Blog: Visit.
117
+ #
118
+ # strip_links('<<a href="https://example.org">malformed & link</a>')
119
+ # # => &lt;malformed &amp; link
94
120
  def strip_links(html)
95
121
  self.class.link_sanitizer.sanitize(html)
96
122
  end
97
123
 
98
124
  module ClassMethods #:nodoc:
99
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
100
-
101
- def sanitized_protocol_separator
102
- white_list_sanitizer.protocol_separator
103
- end
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
104
126
 
105
- def sanitized_uri_attributes
106
- white_list_sanitizer.uri_attributes
107
- end
108
-
109
- def sanitized_bad_tags
110
- white_list_sanitizer.bad_tags
127
+ def sanitizer_vendor
128
+ Rails::Html::Sanitizer
111
129
  end
112
130
 
113
131
  def sanitized_allowed_tags
114
- white_list_sanitizer.allowed_tags
132
+ sanitizer_vendor.safe_list_sanitizer.allowed_tags
115
133
  end
116
134
 
117
135
  def sanitized_allowed_attributes
118
- white_list_sanitizer.allowed_attributes
136
+ sanitizer_vendor.safe_list_sanitizer.allowed_attributes
119
137
  end
120
138
 
121
- def sanitized_allowed_css_properties
122
- white_list_sanitizer.allowed_css_properties
123
- end
124
-
125
- def sanitized_allowed_css_keywords
126
- white_list_sanitizer.allowed_css_keywords
127
- end
128
-
129
- def sanitized_shorthand_css_properties
130
- white_list_sanitizer.shorthand_css_properties
131
- end
132
-
133
- def sanitized_allowed_protocols
134
- white_list_sanitizer.allowed_protocols
135
- end
136
-
137
- def sanitized_protocol_separator=(value)
138
- white_list_sanitizer.protocol_separator = value
139
- end
140
-
141
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
139
+ # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
142
140
  # any object that responds to +sanitize+.
143
141
  #
144
142
  # class Application < Rails::Application
145
143
  # config.action_view.full_sanitizer = MySpecialSanitizer.new
146
144
  # end
147
- #
148
145
  def full_sanitizer
149
- @full_sanitizer ||= HTML::FullSanitizer.new
146
+ @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
150
147
  end
151
148
 
152
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
153
- # any object that responds to +sanitize+.
149
+ # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
150
+ # Replace with any object that responds to +sanitize+.
154
151
  #
155
152
  # class Application < Rails::Application
156
153
  # config.action_view.link_sanitizer = MySpecialSanitizer.new
157
154
  # end
158
- #
159
155
  def link_sanitizer
160
- @link_sanitizer ||= HTML::LinkSanitizer.new
156
+ @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
161
157
  end
162
158
 
163
- # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
159
+ # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
164
160
  # Replace with any object that responds to +sanitize+.
165
161
  #
166
162
  # class Application < Rails::Application
167
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
168
- # end
169
- #
170
- def white_list_sanitizer
171
- @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
172
- end
173
-
174
- # Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
175
- #
176
- # class Application < Rails::Application
177
- # config.action_view.sanitized_uri_attributes = ['lowsrc', 'target']
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
178
164
  # end
179
- #
180
- def sanitized_uri_attributes=(attributes)
181
- HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
182
- end
183
-
184
- # Adds to the Set of 'bad' tags for the +sanitize+ helper.
185
- #
186
- # class Application < Rails::Application
187
- # config.action_view.sanitized_bad_tags = ['embed', 'object']
188
- # end
189
- #
190
- def sanitized_bad_tags=(attributes)
191
- HTML::WhiteListSanitizer.bad_tags.merge(attributes)
192
- end
193
-
194
- # Adds to the Set of allowed tags for the +sanitize+ helper.
195
- #
196
- # class Application < Rails::Application
197
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
198
- # end
199
- #
200
- def sanitized_allowed_tags=(attributes)
201
- HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
202
- end
203
-
204
- # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
205
- #
206
- # class Application < Rails::Application
207
- # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
208
- # end
209
- #
210
- def sanitized_allowed_attributes=(attributes)
211
- HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
212
- end
213
-
214
- # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
215
- #
216
- # class Application < Rails::Application
217
- # config.action_view.sanitized_allowed_css_properties = ['expression']
218
- # end
219
- #
220
- def sanitized_allowed_css_properties=(attributes)
221
- HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
222
- end
223
-
224
- # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
225
- #
226
- # class Application < Rails::Application
227
- # config.action_view.sanitized_allowed_css_keywords = ['expression']
228
- # end
229
- #
230
- def sanitized_allowed_css_keywords=(attributes)
231
- HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
232
- end
233
-
234
- # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
235
- #
236
- # class Application < Rails::Application
237
- # config.action_view.sanitized_shorthand_css_properties = ['expression']
238
- # end
239
- #
240
- def sanitized_shorthand_css_properties=(attributes)
241
- HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
242
- end
243
-
244
- # Adds to the Set of allowed protocols for the +sanitize+ helper.
245
- #
246
- # class Application < Rails::Application
247
- # config.action_view.sanitized_allowed_protocols = ['ssh', 'feed']
248
- # end
249
- #
250
- def sanitized_allowed_protocols=(attributes)
251
- HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
252
167
  end
253
168
  end
254
169
  end
@@ -1,35 +1,240 @@
1
- require 'active_support/core_ext/string/output_safety'
2
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+ require "set"
3
5
 
4
6
  module ActionView
5
7
  # = Action View Tag Helpers
6
8
  module Helpers #:nodoc:
7
- # Provides methods to generate HTML tags programmatically when you can't use
8
- # a Builder. By default, they output XHTML compliant tags.
9
+ # Provides methods to generate HTML tags programmatically both as a modern
10
+ # HTML5 compliant builder style and legacy XHTML compliant tags.
9
11
  module TagHelper
10
12
  extend ActiveSupport::Concern
11
13
  include CaptureHelper
14
+ include OutputSafetyHelper
15
+
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
+
26
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
27
+ BOOLEAN_ATTRIBUTES.freeze
28
+
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
37
+
38
+ PRE_CONTENT_STRINGS = Hash.new { "" }
39
+ PRE_CONTENT_STRINGS[:textarea] = "\n"
40
+ PRE_CONTENT_STRINGS["textarea"] = "\n"
41
+
42
+ class TagBuilder #:nodoc:
43
+ include CaptureHelper
44
+ include OutputSafetyHelper
45
+
46
+ VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
47
+
48
+ def initialize(view_context)
49
+ @view_context = view_context
50
+ end
51
+
52
+ def p(*arguments, **options, &block)
53
+ tag_string(:p, *arguments, **options, &block)
54
+ end
55
+
56
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
57
+ content = @view_context.capture(self, &block) if block_given?
58
+ if VOID_ELEMENTS.include?(name) && content.nil?
59
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
60
+ else
61
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
62
+ end
63
+ end
64
+
65
+ def content_tag_string(name, content, options, escape = true)
66
+ tag_options = tag_options(options, escape) if options
67
+ content = ERB::Util.unwrapped_html_escape(content) if escape
68
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
69
+ end
70
+
71
+ def tag_options(options, escape = true)
72
+ return if options.blank?
73
+ output = +""
74
+ sep = " "
75
+ options.each_pair do |key, value|
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)
84
+ value.each_pair do |k, v|
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
+
97
+ output << sep
98
+ output << prefix_tag_option(key, k, v, escape)
99
+ end
100
+ elsif type == :boolean
101
+ if value
102
+ output << sep
103
+ output << boolean_tag_option(key)
104
+ end
105
+ elsif !value.nil?
106
+ output << sep
107
+ output << tag_option(key, value, escape)
108
+ end
109
+ end
110
+ output unless output.empty?
111
+ end
12
112
 
13
- BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
14
- autoplay controls loop selected hidden scoped async
15
- defer reversed ismap seamless muted required
16
- autofocus novalidate formnovalidate open pubdate
17
- itemscope allowfullscreen default inert sortable
18
- truespeed typemustmatch).to_set
113
+ def boolean_tag_option(key)
114
+ %(#{key}="#{key}")
115
+ end
19
116
 
20
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
117
+ def tag_option(key, value, escape)
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(" ")
122
+ else
123
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
124
+ end
125
+ value = value.gsub('"', "&quot;") if value.include?('"')
126
+ %(#{key}="#{value}")
127
+ end
21
128
 
22
- PRE_CONTENT_STRINGS = {
23
- :textarea => "\n"
24
- }
129
+ private
130
+ def prefix_tag_option(prefix, key, value, escape)
131
+ key = "#{prefix}-#{key.to_s.dasherize}"
132
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
133
+ value = value.to_json
134
+ end
135
+ tag_option(key, value, escape)
136
+ end
25
137
 
26
- # Returns an empty HTML tag of type +name+ which by default is XHTML
138
+ def respond_to_missing?(*args)
139
+ true
140
+ end
141
+
142
+ def method_missing(called, *args, **options, &block)
143
+ tag_string(called, *args, **options, &block)
144
+ end
145
+ end
146
+
147
+ # Returns an HTML tag.
148
+ #
149
+ # === Building HTML tags
150
+ #
151
+ # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
152
+ #
153
+ # tag.<tag name>(optional content, options)
154
+ #
155
+ # where tag name can be e.g. br, div, section, article, or any tag really.
156
+ #
157
+ # ==== Passing content
158
+ #
159
+ # Tags can pass content to embed within it:
160
+ #
161
+ # tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
162
+ #
163
+ # tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
164
+ #
165
+ # Content can also be captured with a block, which is useful in templates:
166
+ #
167
+ # <%= tag.p do %>
168
+ # The next great American novel starts here.
169
+ # <% end %>
170
+ # # => <p>The next great American novel starts here.</p>
171
+ #
172
+ # ==== Options
173
+ #
174
+ # Use symbol keyed options to add attributes to the generated tag.
175
+ #
176
+ # tag.section class: %w( kitties puppies )
177
+ # # => <section class="kitties puppies"></section>
178
+ #
179
+ # tag.section id: dom_id(@post)
180
+ # # => <section id="<generated dom id>"></section>
181
+ #
182
+ # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
183
+ #
184
+ # tag.input type: 'text', disabled: true
185
+ # # => <input type="text" disabled="disabled">
186
+ #
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.
189
+ #
190
+ # To play nicely with JavaScript conventions, sub-attributes are dasherized.
191
+ #
192
+ # tag.article data: { user_id: 123 }
193
+ # # => <article data-user-id="123"></article>
194
+ #
195
+ # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
196
+ #
197
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols and
198
+ # BigDecimals.
199
+ # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
200
+ # from 1.4.3.
201
+ #
202
+ # tag.div data: { city_state: %w( Chicago IL ) }
203
+ # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
204
+ #
205
+ # The generated attributes are escaped by default. This can be disabled using
206
+ # +escape_attributes+.
207
+ #
208
+ # tag.img src: 'open & shut.png'
209
+ # # => <img src="open &amp; shut.png">
210
+ #
211
+ # tag.img src: 'open & shut.png', escape_attributes: false
212
+ # # => <img src="open & shut.png">
213
+ #
214
+ # The tag builder respects
215
+ # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
216
+ # if no content is passed, and omits closing tags for those elements.
217
+ #
218
+ # # A standard element:
219
+ # tag.div # => <div></div>
220
+ #
221
+ # # A void element:
222
+ # tag.br # => <br>
223
+ #
224
+ # === Legacy syntax
225
+ #
226
+ # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
227
+ #
228
+ # tag(name, options = nil, open = false, escape = true)
229
+ #
230
+ # It returns an empty HTML tag of type +name+ which by default is XHTML
27
231
  # compliant. Set +open+ to true to create an open tag compatible
28
232
  # with HTML 4.0 and below. Add HTML attributes by passing an attributes
29
233
  # hash to +options+. Set +escape+ to false to disable attribute value
30
234
  # escaping.
31
235
  #
32
236
  # ==== Options
237
+ #
33
238
  # You can use symbols or strings for the attribute names.
34
239
  #
35
240
  # Use +true+ with boolean attributes that can render with no value, like
@@ -38,15 +243,8 @@ module ActionView
38
243
  # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
39
244
  # pointing to a hash of sub-attributes.
40
245
  #
41
- # To play nicely with JavaScript conventions sub-attributes are dasherized.
42
- # For example, a key +user_id+ would render as <tt>data-user-id</tt> and
43
- # thus accessed as <tt>dataset.userId</tt>.
44
- #
45
- # Values are encoded to JSON, with the exception of strings and symbols.
46
- # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
47
- # from 1.4.3.
48
- #
49
246
  # ==== Examples
247
+ #
50
248
  # tag("br")
51
249
  # # => <br />
52
250
  #
@@ -56,16 +254,26 @@ module ActionView
56
254
  # tag("input", type: 'text', disabled: true)
57
255
  # # => <input type="text" disabled="disabled" />
58
256
  #
257
+ # tag("input", type: 'text', class: ["strong", "highlight"])
258
+ # # => <input class="strong highlight" type="text" />
259
+ #
59
260
  # tag("img", src: "open & shut.png")
60
261
  # # => <img src="open &amp; shut.png" />
61
262
  #
62
- # tag("img", {src: "open &amp; shut.png"}, false, false)
263
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
63
264
  # # => <img src="open &amp; shut.png" />
64
265
  #
65
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
266
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
66
267
  # # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
67
- def tag(name, options = nil, open = false, escape = true)
68
- "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
268
+ #
269
+ # tag("div", class: { highlight: current_user.admin? })
270
+ # # => <div class="highlight" />
271
+ def tag(name = nil, options = nil, open = false, escape = true)
272
+ if name.nil?
273
+ tag_builder
274
+ else
275
+ "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
276
+ end
69
277
  end
70
278
 
71
279
  # Returns an HTML block tag of type +name+ surrounding the +content+. Add
@@ -73,9 +281,10 @@ module ActionView
73
281
  # Instead of passing the content as an argument, you can also use a block
74
282
  # in which case, you pass your +options+ as the second parameter.
75
283
  # Set escape to false to disable attribute value escaping.
284
+ # Note: this is legacy syntax, see +tag+ method description for details.
76
285
  #
77
286
  # ==== Options
78
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
287
+ # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
79
288
  # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
80
289
  # symbols or strings for the attribute names.
81
290
  #
@@ -84,6 +293,10 @@ module ActionView
84
293
  # # => <p>Hello world!</p>
85
294
  # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
86
295
  # # => <div class="strong"><p>Hello world!</p></div>
296
+ # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
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>
87
300
  # content_tag("select", options, multiple: true)
88
301
  # # => <select multiple="multiple">...options...</select>
89
302
  #
@@ -94,12 +307,30 @@ module ActionView
94
307
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
95
308
  if block_given?
96
309
  options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
97
- content_tag_string(name, capture(&block), options, escape)
310
+ tag_builder.content_tag_string(name, capture(&block), options, escape)
98
311
  else
99
- content_tag_string(name, content_or_options_with_block, options, escape)
312
+ tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
100
313
  end
101
314
  end
102
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
+
103
334
  # Returns a CDATA section with the given +content+. CDATA sections
104
335
  # are used to escape blocks of text containing characters which would
105
336
  # otherwise be recognized as markup. CDATA sections begin with the string
@@ -114,7 +345,7 @@ module ActionView
114
345
  # cdata_section("hello]]>world")
115
346
  # # => <![CDATA[hello]]]]><![CDATA[>world]]>
116
347
  def cdata_section(content)
117
- splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>')
348
+ splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
118
349
  "<![CDATA[#{splitted}]]>".html_safe
119
350
  end
120
351
 
@@ -130,46 +361,28 @@ module ActionView
130
361
  end
131
362
 
132
363
  private
364
+ def build_tag_values(*args)
365
+ tag_values = []
133
366
 
134
- def content_tag_string(name, content, options, escape = true)
135
- tag_options = tag_options(options, escape) if options
136
- content = ERB::Util.h(content) if escape
137
- "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
138
- end
139
-
140
- def tag_options(options, escape = true)
141
- return if options.blank?
142
- attrs = []
143
- options.each_pair do |key, value|
144
- if key.to_s == 'data' && value.is_a?(Hash)
145
- value.each_pair do |k, v|
146
- attrs << data_tag_option(k, v, escape)
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?
147
372
  end
148
- elsif BOOLEAN_ATTRIBUTES.include?(key)
149
- attrs << boolean_tag_option(key) if value
150
- elsif !value.nil?
151
- attrs << tag_option(key, value, escape)
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?
152
377
  end
153
378
  end
154
- " #{attrs.sort! * ' '}" unless attrs.empty?
155
- end
156
-
157
- def data_tag_option(key, value, escape)
158
- key = "data-#{key.to_s.dasherize}"
159
- unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
160
- value = value.to_json
161
- end
162
- tag_option(key, value, escape)
163
- end
164
379
 
165
- def boolean_tag_option(key)
166
- %(#{key}="#{key}")
380
+ tag_values
167
381
  end
382
+ module_function :build_tag_values
168
383
 
169
- def tag_option(key, value, escape)
170
- value = value.join(" ") if value.is_a?(Array)
171
- value = ERB::Util.h(value) if escape
172
- %(#{key}="#{value}")
384
+ def tag_builder
385
+ @tag_builder ||= TagBuilder.new(self)
173
386
  end
174
387
  end
175
388
  end