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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +181 -359
- data/MIT-LICENSE +1 -1
- data/README.rdoc +12 -6
- data/lib/action_view/base.rb +115 -43
- data/lib/action_view/buffers.rb +22 -4
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -84
- data/lib/action_view/flows.rb +12 -13
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
- data/lib/action_view/helpers/asset_url_helper.rb +197 -80
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +109 -45
- data/lib/action_view/helpers/capture_helper.rb +20 -22
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +245 -140
- data/lib/action_view/helpers/debug_helper.rb +14 -17
- data/lib/action_view/helpers/form_helper.rb +875 -148
- data/lib/action_view/helpers/form_options_helper.rb +128 -82
- data/lib/action_view/helpers/form_tag_helper.rb +253 -91
- data/lib/action_view/helpers/javascript_helper.rb +37 -15
- data/lib/action_view/helpers/number_helper.rb +100 -77
- data/lib/action_view/helpers/output_safety_helper.rb +42 -10
- data/lib/action_view/helpers/rendering_helper.rb +26 -15
- data/lib/action_view/helpers/sanitize_helper.rb +79 -164
- data/lib/action_view/helpers/tag_helper.rb +277 -64
- data/lib/action_view/helpers/tags/base.rb +143 -92
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +41 -22
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +3 -0
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +7 -1
- data/lib/action_view/helpers/tags/text_field.rb +11 -7
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags.rb +4 -1
- data/lib/action_view/helpers/text_helper.rb +80 -45
- data/lib/action_view/helpers/translation_helper.rb +148 -67
- data/lib/action_view/helpers/url_helper.rb +289 -147
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +80 -13
- data/lib/action_view/lookup_context.rb +137 -92
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +30 -16
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +152 -13
- 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 +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +61 -261
- data/lib/action_view/renderer/renderer.rb +67 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +83 -75
- data/lib/action_view/rendering.rb +73 -46
- data/lib/action_view/routing_url_for.rb +54 -17
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +23 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +22 -9
- data/lib/action_view/template/html.rb +10 -11
- 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 +267 -181
- 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 +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +109 -99
- data/lib/action_view/test_case.rb +73 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +74 -44
- data/lib/action_view.rb +14 -9
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +71 -26
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- 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
|
-
#
|
17
|
-
# * <tt>:html</tt> - Renders the
|
18
|
-
#
|
19
|
-
#
|
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
|
-
#
|
22
|
-
#
|
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
|
-
#
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
2
|
-
|
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
|
-
#
|
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
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
27
|
+
# ==== Options
|
29
28
|
#
|
30
|
-
#
|
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
|
-
#
|
36
|
+
# ==== Examples
|
33
37
|
#
|
34
|
-
#
|
38
|
+
# Normal use:
|
35
39
|
#
|
36
|
-
#
|
37
|
-
# config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
|
38
|
-
# end
|
40
|
+
# <%= sanitize @comment.body %>
|
39
41
|
#
|
40
|
-
#
|
42
|
+
# Providing custom lists of permitted tags and attributes:
|
41
43
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
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
|
-
#
|
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
|
-
#
|
51
|
-
#
|
67
|
+
# scrubber = Loofah::Scrubber.new do |node|
|
68
|
+
# node.remove if node.name == 'script'
|
52
69
|
# end
|
53
70
|
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
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.
|
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.
|
87
|
+
self.class.safe_list_sanitizer.sanitize_css(style)
|
66
88
|
end
|
67
89
|
|
68
|
-
# Strips all HTML tags from
|
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
|
+
# # => > A quote from Smith & 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 +
|
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
|
+
# # => <malformed & 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, :
|
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
|
106
|
-
|
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
|
-
|
132
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_tags
|
115
133
|
end
|
116
134
|
|
117
135
|
def sanitized_allowed_attributes
|
118
|
-
|
136
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_attributes
|
119
137
|
end
|
120
138
|
|
121
|
-
|
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 ||=
|
146
|
+
@full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
|
150
147
|
end
|
151
148
|
|
152
|
-
# Gets the
|
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 ||=
|
156
|
+
@link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
|
161
157
|
end
|
162
158
|
|
163
|
-
# Gets the
|
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.
|
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
|
-
|
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
|
-
|
2
|
-
|
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
|
8
|
-
#
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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('"', """) if value.include?('"')
|
126
|
+
%(#{key}="#{value}")
|
127
|
+
end
|
21
128
|
|
22
|
-
|
23
|
-
|
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
|
-
|
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="["Chicago","IL"]"></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 & 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 & shut.png" />
|
61
262
|
#
|
62
|
-
# tag("img", {src: "open & shut.png"}, false, false)
|
263
|
+
# tag("img", { src: "open & shut.png" }, false, false)
|
63
264
|
# # => <img src="open & 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="["Chicago","IL"]" />
|
67
|
-
|
68
|
-
|
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
|
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(
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
166
|
-
%(#{key}="#{key}")
|
380
|
+
tag_values
|
167
381
|
end
|
382
|
+
module_function :build_tag_values
|
168
383
|
|
169
|
-
def
|
170
|
-
|
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
|