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 +4 -4
- data/app/controllers/view_components_system_test_controller.rb +10 -1
- data/docs/CHANGELOG.md +18 -0
- data/lib/view_component/base.rb +36 -8
- data/lib/view_component/collection.rb +17 -17
- data/lib/view_component/instrumentation.rb +1 -1
- data/lib/view_component/preview.rb +2 -0
- data/lib/view_component/slot.rb +1 -1
- data/lib/view_component/version.rb +1 -1
- metadata +8 -115
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17958e9ab2fbfbce6d2d397332fb0683315d805bb2ca94920a570fcf544c43e8
|
|
4
|
+
data.tar.gz: 76b1b5f122715ac0086e78b8010dc5f03e9fad57180ce444c68f556b148d1a59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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.
|
data/lib/view_component/base.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
98
|
+
@lookup_context = view_context.lookup_context
|
|
88
99
|
|
|
89
100
|
# required for path helpers in older Rails versions
|
|
90
|
-
@view_renderer
|
|
101
|
+
@view_renderer = view_context.view_renderer
|
|
91
102
|
|
|
92
103
|
# For content_for
|
|
93
|
-
@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
|
|
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(
|
|
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)
|
data/lib/view_component/slot.rb
CHANGED
|
@@ -53,7 +53,7 @@ module ViewComponent
|
|
|
53
53
|
|
|
54
54
|
@content =
|
|
55
55
|
if __vc_component_instance?
|
|
56
|
-
@__vc_component_instance.
|
|
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`
|
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.
|
|
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:
|
|
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
|
-
|
|
453
|
-
|
|
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.
|
|
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.
|