view_component 3.24.0 → 3.26.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/app/helpers/preview_helper.rb +9 -1
- data/docs/CHANGELOG.md +28 -0
- data/lib/view_component/base.rb +36 -8
- data/lib/view_component/collection.rb +17 -17
- data/lib/view_component/compiler.rb +9 -1
- 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 +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7646c2f512d91517ac31ba0f2f039ef7c486ce4e5eec74d1fe5ca742a24f48b5
|
|
4
|
+
data.tar.gz: 548a7e762a5e2b2970a79d7debba10747adffe62c1a946eb12eb689d0bbb04de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c880684d390ecb0c5a8b62ed329fe2883623bea9e61371666ba5e7aa1eca31f92af9d2e6558a728e195cd756a3390b4154cb024b1d613fd47a111f7b5452f60f
|
|
7
|
+
data.tar.gz: be6a18ae9ed76ed5c2f71641e43b56fb32d67f8655f044d15f02e36da168539155e2de84e1ee6f2e1f195247832e1b6e11fc9d5bc75b1d02ec3cf92bfa8ce266
|
|
@@ -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
|
|
@@ -23,7 +23,7 @@ module PreviewHelper
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def find_template_data_for_preview_source(lookup_context:, template_identifier:)
|
|
26
|
-
template = lookup_context
|
|
26
|
+
template = find_template_for_preview_source(lookup_context, template_identifier)
|
|
27
27
|
|
|
28
28
|
if Rails.version.to_f >= 6.1 || template.source.present?
|
|
29
29
|
{
|
|
@@ -61,6 +61,14 @@ module PreviewHelper
|
|
|
61
61
|
|
|
62
62
|
private
|
|
63
63
|
|
|
64
|
+
def find_template_for_preview_source(lookup_context, template_identifier)
|
|
65
|
+
if lookup_context.respond_to?(:find_template)
|
|
66
|
+
lookup_context.find_template(template_identifier)
|
|
67
|
+
else
|
|
68
|
+
lookup_context.find(template_identifier)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
64
72
|
def prism_language_name_by_template(template:)
|
|
65
73
|
language = template.identifier.split(".").last
|
|
66
74
|
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,34 @@ nav_order: 6
|
|
|
10
10
|
|
|
11
11
|
## main
|
|
12
12
|
|
|
13
|
+
## 3.26.0
|
|
14
|
+
|
|
15
|
+
* Fix rendering partials from components rendered as a collection when the original view context is explicitly stashed as `nil`.
|
|
16
|
+
|
|
17
|
+
*Justin Coyne, Joel Hawksley*
|
|
18
|
+
|
|
19
|
+
* Restore compatibility with Rails main after `ActionView::Template.template_handler_extensions` and `ActionView::LookupContext#find_template` were removed.
|
|
20
|
+
|
|
21
|
+
*Joel Hawksley*
|
|
22
|
+
|
|
23
|
+
## 3.25.0
|
|
24
|
+
|
|
25
|
+
* 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.
|
|
26
|
+
|
|
27
|
+
*Joel Hawksley*
|
|
28
|
+
|
|
29
|
+
* 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.
|
|
30
|
+
|
|
31
|
+
*Joel Hawksley*
|
|
32
|
+
|
|
33
|
+
* 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.
|
|
34
|
+
|
|
35
|
+
*Joel Hawksley*
|
|
36
|
+
|
|
37
|
+
* 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.
|
|
38
|
+
|
|
39
|
+
*Joel Hawksley*
|
|
40
|
+
|
|
13
41
|
## 3.24.0
|
|
14
42
|
|
|
15
43
|
* 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) || 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
|
""
|
|
@@ -170,7 +170,7 @@ module ViewComponent
|
|
|
170
170
|
@templates ||=
|
|
171
171
|
begin
|
|
172
172
|
templates = @component.sidecar_files(
|
|
173
|
-
|
|
173
|
+
template_handler_extensions
|
|
174
174
|
).map do |path|
|
|
175
175
|
# Extract format and variant from template filename
|
|
176
176
|
this_format, variant =
|
|
@@ -225,5 +225,13 @@ module ViewComponent
|
|
|
225
225
|
templates
|
|
226
226
|
end
|
|
227
227
|
end
|
|
228
|
+
|
|
229
|
+
def template_handler_extensions
|
|
230
|
+
if ActionView::Template.respond_to?(:template_handler_extensions)
|
|
231
|
+
ActionView::Template.template_handler_extensions
|
|
232
|
+
else
|
|
233
|
+
ActionView::Template::Handlers.extensions.map(&:to_s)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
228
236
|
end
|
|
229
237
|
end
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: view_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ViewComponent Team
|
|
@@ -655,7 +655,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
655
655
|
- !ruby/object:Gem::Version
|
|
656
656
|
version: '0'
|
|
657
657
|
requirements: []
|
|
658
|
-
rubygems_version: 3.
|
|
658
|
+
rubygems_version: 3.6.9
|
|
659
659
|
specification_version: 4
|
|
660
660
|
summary: A framework for building reusable, testable & encapsulated view components
|
|
661
661
|
in Ruby on Rails.
|