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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +250 -112
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +15 -22
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
- data/lib/action_view/helpers/asset_url_helper.rb +9 -6
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +23 -22
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +73 -30
- data/lib/action_view/helpers/form_helper.rb +305 -37
- data/lib/action_view/helpers/form_options_helper.rb +23 -23
- data/lib/action_view/helpers/form_tag_helper.rb +19 -16
- data/lib/action_view/helpers/javascript_helper.rb +12 -11
- data/lib/action_view/helpers/number_helper.rb +14 -8
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +17 -7
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +100 -55
- data/lib/action_view/helpers/tags/base.rb +18 -11
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -3
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
- data/lib/action_view/helpers/tags/label.rb +4 -1
- data/lib/action_view/helpers/tags/month_field.rb +1 -2
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +1 -2
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -2
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +4 -5
- data/lib/action_view/helpers/translation_helper.rb +94 -54
- data/lib/action_view/helpers/url_helper.rb +136 -28
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +30 -15
- data/lib/action_view/lookup_context.rb +63 -35
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +42 -26
- data/lib/action_view/record_identifier.rb +2 -3
- data/lib/action_view/renderer/abstract_renderer.rb +142 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
- data/lib/action_view/renderer/partial_renderer.rb +21 -273
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +54 -33
- data/lib/action_view/routing_url_for.rb +13 -12
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- data/lib/action_view/template/handlers/erb.rb +16 -11
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +5 -6
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +191 -150
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +2 -3
- data/lib/action_view/template.rb +66 -75
- data/lib/action_view/test_case.rb +21 -29
- data/lib/action_view/testing/resolvers.rb +18 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +59 -38
- data/lib/action_view.rb +7 -2
- data/lib/assets/compiled/rails-ujs.js +32 -6
- metadata +29 -18
- 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
|
-
|
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.
|
138
|
+
html_name = (options[:multiple] == true && !name.end_with?("[]")) ? "#{name}[]" : name
|
136
139
|
|
137
140
|
if options.include?(:include_blank)
|
138
|
-
include_blank = options
|
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"
|
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"
|
156
|
+
option_tags = content_tag("option", prompt, value: "").safe_concat(option_tags)
|
153
157
|
end
|
154
158
|
|
155
|
-
content_tag "select"
|
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 '
|
393
|
-
# # => <input id="
|
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"
|
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") {
|
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
|
-
|
897
|
-
data = tag_options["data"]
|
900
|
+
data = tag_options.fetch("data", {})
|
898
901
|
|
899
|
-
|
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"]
|
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".
|
21
|
-
JS_ESCAPE_MAP["\342\200\251".
|
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!] = "
"
|
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
|
-
|
31
|
-
|
32
|
-
|
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')",
|
54
|
+
# javascript_tag "alert('All is good')", type: 'application/javascript'
|
54
55
|
#
|
55
56
|
# Returns:
|
56
|
-
# <script
|
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
|
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
|
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"
|
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.
|
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 (
|
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 (
|
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
|
-
|
423
|
-
|
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}[
|
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
|
26
|
-
#
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
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::
|
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
|
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.
|
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.
|
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, :
|
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.
|
132
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_tags
|
136
133
|
end
|
137
134
|
|
138
135
|
def sanitized_allowed_attributes
|
139
|
-
sanitizer_vendor.
|
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::
|
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.
|
163
|
+
# config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
|
169
164
|
# end
|
170
|
-
|
171
|
-
|
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
|
17
|
-
compact controls declare default
|
18
|
-
defaultmuted defaultselected defer
|
19
|
-
enabled formnovalidate hidden indeterminate
|
20
|
-
ismap itemscope loop multiple muted nohref
|
21
|
-
noresize noshade novalidate nowrap open
|
22
|
-
pauseonexit readonly required reversed
|
23
|
-
seamless selected sortable truespeed
|
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
|
-
|
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
|
45
|
-
|
46
|
-
|
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,
|
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,
|
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 = ""
|
73
|
+
output = +""
|
69
74
|
sep = " "
|
70
75
|
options.each_pair do |key, value|
|
71
|
-
|
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
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
value = escape ? safe_join(value, " "
|
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
|
-
|
125
|
+
value = value.gsub('"', """) 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
|
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 & shut.png" />
|
259
262
|
#
|
260
|
-
# tag("img", {src: "open & shut.png"}, false, false)
|
263
|
+
# tag("img", { src: "open & shut.png" }, false, false)
|
261
264
|
# # => <img src="open & 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="["Chicago","IL"]" />
|
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
|