view_component 3.24.0 → 3.25.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e50fc0d9c23700e9ca13233ffda7dd983deeaf93200325e81a25852866c115f5
4
- data.tar.gz: c326352446cf7303cf0a5fb4967ffb391fdf363a272f31d88a9a074619230668
3
+ metadata.gz: 17958e9ab2fbfbce6d2d397332fb0683315d805bb2ca94920a570fcf544c43e8
4
+ data.tar.gz: 76b1b5f122715ac0086e78b8010dc5f03e9fad57180ce444c68f556b148d1a59
5
5
  SHA512:
6
- metadata.gz: 61d508fd6b1ad6b4c53d85e55e972567b3cd08139380d48966f39be19e04803f480b368f3bdf82f9597ab4f487d2f9141bcf7ffff25b2542fbe4312d7c0d53e8
7
- data.tar.gz: 546ec854a8ec8f8fbb611a4d45d4d645fcae74f4a5f467a4531d7492ac05695270eeb369f0eca180d67b7b29a427e0053ad456fc037d5fbc9e950d4012aa4d66
6
+ metadata.gz: 1ef7030f4690849614463924c9f10953609ef1a74508718cbe5d5f5049256ffcc51c67671a1b95a770bd96c0cd0b3b1f0f438bf56c3ba2265b252d1072f3faad
7
+ data.tar.gz: b54825e954ceef4e0f45bbc71cc59f92689275f02da9707e46c3fb30b30eb280b9e01b0b3585b3e6996da92dd5ce2c822d9dd839373e8a89660203a665a0ebb3
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "view_component/errors"
4
+
3
5
  class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
4
6
  before_action :validate_test_env
5
7
  before_action :validate_file_path
6
8
 
9
+ rescue_from ViewComponent::SystemTestControllerNefariousPathError, with: :render_not_found
10
+
7
11
  def self.temp_dir
8
12
  @_tmpdir ||= FileUtils.mkdir_p("./tmp/view_components/").first
9
13
  end
@@ -14,6 +18,10 @@ class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
14
18
 
15
19
  private
16
20
 
21
+ def render_not_found
22
+ head :not_found
23
+ end
24
+
17
25
  def validate_test_env
18
26
  raise ViewComponent::SystemTestControllerOnlyAllowedInTestError unless Rails.env.test?
19
27
  end
@@ -23,7 +31,8 @@ class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
23
31
  def validate_file_path
24
32
  base_path = ::File.realpath(self.class.temp_dir)
25
33
  @path = ::File.realpath(params.permit(:file)[:file], base_path)
26
- unless @path.start_with?(base_path)
34
+ allowed_prefix = "#{base_path}#{::File::SEPARATOR}"
35
+ unless @path == base_path || @path.start_with?(allowed_prefix)
27
36
  raise ViewComponent::SystemTestControllerNefariousPathError
28
37
  end
29
38
  end
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,24 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 3.25.0
14
+
15
+ * Support Rails `render_in` options signature. Rails [#50623](https://github.com/rails/rails/pull/50623) changed the `render_in` signature from `render_in(view_context, &block)` to `render_in(view_context, **options, &block)`. `ViewComponent::Base#render_in`, `ViewComponent::Collection#render_in`, and `ViewComponent::Instrumentation#render_in` now accept `**options`, restoring compatibility with Rails main and silencing the deprecation warning.
16
+
17
+ *Joel Hawksley*
18
+
19
+ * Fix stale render context on reused component instances. A `ViewComponent::Base` instance memoized its controller, helpers, request, view context, lookup context, view flow, and requested format/variant on first render via `||=`. Rendering the same instance a second time (intentionally or via aliasing) reused that stale context, which could leak data across requests, sessions, or users. `#render_in` now resets these ivars on every call so each render derives its context from the current view.
20
+
21
+ *Joel Hawksley*
22
+
23
+ * Fix path traversal vulnerability in `ViewComponentsSystemTestController` where sibling directories sharing a string prefix with the allowed temp directory could bypass the path containment check. The `start_with?` check has been replaced with a separator-aware prefix check, and nefarious path errors now return a 404 instead of an unhandled exception.
24
+
25
+ *Joel Hawksley*
26
+
27
+ * Fix preview route vulnerability where inherited methods on `ViewComponent::Preview` (such as `render_with_template`) could be invoked via the preview URL, allowing arbitrary internal Rails templates to be rendered with attacker-controlled locals and request parameters. `render_args` now raises `AbstractController::ActionNotFound` for any example not explicitly declared on the preview subclass.
28
+
29
+ *Joel Hawksley*
30
+
13
31
  ## 3.24.0
14
32
 
15
33
  * Add Rails 8.1 support to ViewComponent v3.
@@ -65,38 +65,49 @@ module ViewComponent
65
65
  # @param view_context [ActionView::Base] The original view context.
66
66
  # @return [void]
67
67
  def set_original_view_context(view_context)
68
- self.__vc_original_view_context = view_context
68
+ # Stash on a separate ivar that survives `__vc_reset_render_state!` so a
69
+ # parent component can call `set_original_view_context` immediately before
70
+ # `render_in` without having that value clobbered by the per-render reset.
71
+ @__vc_pending_original_view_context = view_context
69
72
  end
70
73
 
71
74
  # Entrypoint for rendering components.
72
75
  #
73
76
  # - `view_context`: ActionView context from calling view
77
+ # - `options`: optional render options (e.g., locals)
74
78
  # - `block`: optional block to be captured within the view context
75
79
  #
76
80
  # Returns HTML that has been escaped by the respective template handler.
77
81
  #
78
82
  # @return [String]
79
- def render_in(view_context, &block)
83
+ def render_in(view_context, **options, &block)
80
84
  self.class.compile(raise_errors: true)
81
85
 
86
+ __vc_reset_render_state!
87
+
82
88
  @view_context = view_context
83
- self.__vc_original_view_context ||= view_context
89
+ self.__vc_original_view_context =
90
+ if instance_variable_defined?(:@__vc_pending_original_view_context)
91
+ remove_instance_variable(:@__vc_pending_original_view_context)
92
+ else
93
+ view_context
94
+ end
84
95
 
85
96
  @output_buffer = ActionView::OutputBuffer.new
86
97
 
87
- @lookup_context ||= view_context.lookup_context
98
+ @lookup_context = view_context.lookup_context
88
99
 
89
100
  # required for path helpers in older Rails versions
90
- @view_renderer ||= view_context.view_renderer
101
+ @view_renderer = view_context.view_renderer
91
102
 
92
103
  # For content_for
93
- @view_flow ||= view_context.view_flow
104
+ @view_flow = view_context.view_flow
94
105
 
95
106
  # For i18n
96
107
  @virtual_path ||= virtual_path
97
108
 
98
109
  # For template variants (+phone, +desktop, etc.)
99
- @__vc_variant ||= @lookup_context.variants.first
110
+ @__vc_variant = @lookup_context.variants.first
100
111
 
101
112
  # For caching, such as #cache_if
102
113
  @current_template = nil unless defined?(@current_template)
@@ -211,7 +222,7 @@ module ViewComponent
211
222
  # @private
212
223
  def render(options = {}, args = {}, &block)
213
224
  if options.respond_to?(:set_original_view_context)
214
- options.set_original_view_context(self.__vc_original_view_context)
225
+ options.set_original_view_context(__vc_original_view_context)
215
226
  super
216
227
  else
217
228
  __vc_original_view_context.render(options, args, &block)
@@ -394,6 +405,23 @@ module ViewComponent
394
405
  # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
395
406
  #
396
407
 
408
+ # Resets every render-scoped instance variable derived from the calling view
409
+ # context so a reused instance cannot leak controller/helper/request/format
410
+ # state from a previous render. Slot state (`@__vc_set_slots`,
411
+ # `@__vc_content_set_by_with_content`) is intentionally preserved because it
412
+ # is populated by callers _before_ `render_in` runs (e.g. via `with_*`
413
+ # slot setters or `with_content`).
414
+ def __vc_reset_render_state!
415
+ %i[
416
+ @__vc_controller
417
+ @__vc_helpers
418
+ @__vc_request
419
+ @__vc_original_view_context
420
+ ].each do |ivar|
421
+ remove_instance_variable(ivar) if instance_variable_defined?(ivar)
422
+ end
423
+ end
424
+
397
425
  # Configuration for generators.
398
426
  #
399
427
  # All options under this namespace default to `false` unless otherwise
@@ -16,27 +16,13 @@ module ViewComponent
16
16
  self.__vc_original_view_context = view_context
17
17
  end
18
18
 
19
- def render_in(view_context, &block)
19
+ def render_in(view_context, **options, &block)
20
20
  components.map do |component|
21
21
  component.set_original_view_context(__vc_original_view_context)
22
- component.render_in(view_context, &block)
22
+ component.render_in(view_context, **options, &block)
23
23
  end.join(rendered_spacer(view_context)).html_safe
24
24
  end
25
25
 
26
- def components
27
- return @components if defined? @components
28
-
29
- iterator = ActionView::PartialIteration.new(@collection.size)
30
-
31
- component.validate_collection_parameter!(validate_default: true)
32
-
33
- @components = @collection.map do |item|
34
- component.new(**component_options(item, iterator)).tap do |component|
35
- iterator.iterate!
36
- end
37
- end
38
- end
39
-
40
26
  def each(&block)
41
27
  components.each(&block)
42
28
  end
@@ -49,6 +35,20 @@ module ViewComponent
49
35
 
50
36
  private
51
37
 
38
+ # Always rebuild child component instances per render to avoid leaking
39
+ # request-scoped state from a previous render into a later one.
40
+ def components
41
+ iterator = ActionView::PartialIteration.new(@collection.size)
42
+
43
+ component.validate_collection_parameter!(validate_default: true)
44
+
45
+ @collection.map do |item|
46
+ component.new(**component_options(item, iterator)).tap do |_|
47
+ iterator.iterate!
48
+ end
49
+ end
50
+ end
51
+
52
52
  def initialize(component, object, spacer_component, **options)
53
53
  @component = component
54
54
  @collection = collection_variable(object || [])
@@ -74,7 +74,7 @@ module ViewComponent
74
74
 
75
75
  def rendered_spacer(view_context)
76
76
  if @spacer_component
77
- @spacer_component.set_original_view_context(__vc_original_view_context)
77
+ @spacer_component.set_original_view_context(__vc_original_view_context) if @spacer_component.respond_to?(:set_original_view_context)
78
78
  @spacer_component.render_in(view_context)
79
79
  else
80
80
  ""
@@ -8,7 +8,7 @@ module ViewComponent # :nodoc:
8
8
  mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation)
9
9
  end
10
10
 
11
- def render_in(view_context, &block)
11
+ def render_in(view_context, **options, &block)
12
12
  ActiveSupport::Notifications.instrument(
13
13
  notification_name,
14
14
  {
@@ -42,6 +42,8 @@ module ViewComponent # :nodoc:
42
42
 
43
43
  # Returns the arguments for rendering of the component in its layout
44
44
  def render_args(example, params: {})
45
+ raise AbstractController::ActionNotFound, "#{example} is not a valid preview example" unless examples.include?(example.to_s)
46
+
45
47
  example_params_names = instance_method(example).parameters.map(&:last)
46
48
  provided_params = params.slice(*example_params_names).to_h.symbolize_keys
47
49
  result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params)
@@ -53,7 +53,7 @@ module ViewComponent
53
53
 
54
54
  @content =
55
55
  if __vc_component_instance?
56
- @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
56
+ @__vc_component_instance.set_original_view_context(@parent.__vc_original_view_context)
57
57
 
58
58
  if defined?(@__vc_content_block)
59
59
  # render_in is faster than `parent.render`
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 3
6
- MINOR = 24
6
+ MINOR = 25
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.24.0
4
+ version: 3.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: activesupport
@@ -449,118 +450,8 @@ dependencies:
449
450
  - - "~>"
450
451
  - !ruby/object:Gem::Version
451
452
  version: 0.0.1
452
- - !ruby/object:Gem::Dependency
453
- name: net-imap
454
- requirement: !ruby/object:Gem::Requirement
455
- requirements:
456
- - - ">="
457
- - !ruby/object:Gem::Version
458
- version: '0'
459
- type: :development
460
- prerelease: false
461
- version_requirements: !ruby/object:Gem::Requirement
462
- requirements:
463
- - - ">="
464
- - !ruby/object:Gem::Version
465
- version: '0'
466
- - !ruby/object:Gem::Dependency
467
- name: net-pop
468
- requirement: !ruby/object:Gem::Requirement
469
- requirements:
470
- - - ">="
471
- - !ruby/object:Gem::Version
472
- version: '0'
473
- type: :development
474
- prerelease: false
475
- version_requirements: !ruby/object:Gem::Requirement
476
- requirements:
477
- - - ">="
478
- - !ruby/object:Gem::Version
479
- version: '0'
480
- - !ruby/object:Gem::Dependency
481
- name: net-smtp
482
- requirement: !ruby/object:Gem::Requirement
483
- requirements:
484
- - - ">="
485
- - !ruby/object:Gem::Version
486
- version: '0'
487
- type: :development
488
- prerelease: false
489
- version_requirements: !ruby/object:Gem::Requirement
490
- requirements:
491
- - - ">="
492
- - !ruby/object:Gem::Version
493
- version: '0'
494
- - !ruby/object:Gem::Dependency
495
- name: base64
496
- requirement: !ruby/object:Gem::Requirement
497
- requirements:
498
- - - ">="
499
- - !ruby/object:Gem::Version
500
- version: '0'
501
- type: :development
502
- prerelease: false
503
- version_requirements: !ruby/object:Gem::Requirement
504
- requirements:
505
- - - ">="
506
- - !ruby/object:Gem::Version
507
- version: '0'
508
- - !ruby/object:Gem::Dependency
509
- name: bigdecimal
510
- requirement: !ruby/object:Gem::Requirement
511
- requirements:
512
- - - ">="
513
- - !ruby/object:Gem::Version
514
- version: '0'
515
- type: :development
516
- prerelease: false
517
- version_requirements: !ruby/object:Gem::Requirement
518
- requirements:
519
- - - ">="
520
- - !ruby/object:Gem::Version
521
- version: '0'
522
- - !ruby/object:Gem::Dependency
523
- name: drb
524
- requirement: !ruby/object:Gem::Requirement
525
- requirements:
526
- - - ">="
527
- - !ruby/object:Gem::Version
528
- version: '0'
529
- type: :development
530
- prerelease: false
531
- version_requirements: !ruby/object:Gem::Requirement
532
- requirements:
533
- - - ">="
534
- - !ruby/object:Gem::Version
535
- version: '0'
536
- - !ruby/object:Gem::Dependency
537
- name: mutex_m
538
- requirement: !ruby/object:Gem::Requirement
539
- requirements:
540
- - - ">="
541
- - !ruby/object:Gem::Version
542
- version: '0'
543
- type: :development
544
- prerelease: false
545
- version_requirements: !ruby/object:Gem::Requirement
546
- requirements:
547
- - - ">="
548
- - !ruby/object:Gem::Version
549
- version: '0'
550
- - !ruby/object:Gem::Dependency
551
- name: propshaft
552
- requirement: !ruby/object:Gem::Requirement
553
- requirements:
554
- - - "~>"
555
- - !ruby/object:Gem::Version
556
- version: 1.1.0
557
- type: :development
558
- prerelease: false
559
- version_requirements: !ruby/object:Gem::Requirement
560
- requirements:
561
- - - "~>"
562
- - !ruby/object:Gem::Version
563
- version: 1.1.0
453
+ description:
454
+ email:
564
455
  executables: []
565
456
  extensions: []
566
457
  extra_rdoc_files: []
@@ -641,6 +532,7 @@ metadata:
641
532
  allowed_push_host: https://rubygems.org
642
533
  source_code_uri: https://github.com/viewcomponent/view_component
643
534
  changelog_uri: https://github.com/ViewComponent/view_component/blob/main/docs/CHANGELOG.md
535
+ post_install_message:
644
536
  rdoc_options: []
645
537
  require_paths:
646
538
  - lib
@@ -655,7 +547,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
655
547
  - !ruby/object:Gem::Version
656
548
  version: '0'
657
549
  requirements: []
658
- rubygems_version: 3.7.2
550
+ rubygems_version: 3.0.3.1
551
+ signing_key:
659
552
  specification_version: 4
660
553
  summary: A framework for building reusable, testable & encapsulated view components
661
554
  in Ruby on Rails.