view_component 4.10.0 → 4.12.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/docs/CHANGELOG.md +20 -0
- data/lib/view_component/base.rb +45 -10
- data/lib/view_component/collection.rb +15 -12
- data/lib/view_component/instrumentation.rb +1 -1
- data/lib/view_component/slotable.rb +4 -2
- 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: b2f1a7d34956fc9ae629c0f0e710f4a7335b33ebc99f1c3eccafc41d19713524
|
|
4
|
+
data.tar.gz: efd4bec7d2425da171f89f28fbc1bfaf9f2cba94fd0f2a9cbce674830281938e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d0f5024d419a56b5e178f0cce3d77ac4a1ae2d4d3efc0ac5334e7b55a4dcb6cc7460aa4d60a47bce63918b0a452b53c5425297ef03da2ce074793407acef570
|
|
7
|
+
data.tar.gz: be1d649efec64995de234eb70998001d42cd9b0197bdce00ec5011d68cb2a2784925abf283b3f51c3986a55a446db3ba0c17cedf406c919e31b178ece2be6b51
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,26 @@ nav_order: 6
|
|
|
10
10
|
|
|
11
11
|
## main
|
|
12
12
|
|
|
13
|
+
## 4.12.0
|
|
14
|
+
|
|
15
|
+
* 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 details 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.
|
|
16
|
+
|
|
17
|
+
*Joel Hawksley*
|
|
18
|
+
|
|
19
|
+
* Fix HTML-safety bypass in `around_render`. `ViewComponent::Base#around_render` could return HTML-unsafe strings that bypassed the escaping applied to normal `#call` return values, creating an XSS risk. The vulnerability was amplified in `ViewComponent::Collection#render_in`, which joined per-item results and unconditionally marked the output `html_safe`. HTML-unsafe strings returned from `around_render` are now escaped (with a warning) and `Collection#render_in` now uses `safe_join` so unsafe per-item output is escaped instead of laundered into a `SafeBuffer`.
|
|
20
|
+
|
|
21
|
+
*Joel Hawksley*
|
|
22
|
+
|
|
23
|
+
## 4.11.0
|
|
24
|
+
|
|
25
|
+
* Update `render_in` signature to accept `**_` for compatibility with Rails [#50623](https://github.com/rails/rails/pull/50623).
|
|
26
|
+
|
|
27
|
+
*Joel Hawksley*
|
|
28
|
+
|
|
29
|
+
* Fix translation scope resolution in nested lambda-backed slots. Relative `t(".key")` calls inside lambda-backed slots were resolving against an intermediate component's scope instead of the original partial's scope where the block was defined.
|
|
30
|
+
|
|
31
|
+
*Artin Boghosian*
|
|
32
|
+
|
|
13
33
|
## 4.10.0
|
|
14
34
|
|
|
15
35
|
* Fix `NameError: uninitialized constant ViewComponent::SystemTestControllerNefariousPathError` when booting in the test environment with `eager_load = true`.
|
data/lib/view_component/base.rb
CHANGED
|
@@ -103,25 +103,27 @@ module ViewComponent
|
|
|
103
103
|
# Returns HTML that has been escaped by the respective template handler.
|
|
104
104
|
#
|
|
105
105
|
# @return [String]
|
|
106
|
-
def render_in(view_context, &block)
|
|
106
|
+
def render_in(view_context, **_, &block)
|
|
107
107
|
self.class.__vc_compile(raise_errors: true)
|
|
108
108
|
|
|
109
|
+
__vc_reset_render_state!
|
|
110
|
+
|
|
109
111
|
@view_context = view_context
|
|
110
112
|
@old_virtual_path = view_context.instance_variable_get(:@virtual_path)
|
|
111
|
-
self.__vc_original_view_context
|
|
113
|
+
self.__vc_original_view_context = view_context
|
|
112
114
|
|
|
113
115
|
@output_buffer = view_context.output_buffer
|
|
114
116
|
|
|
115
|
-
@lookup_context
|
|
117
|
+
@lookup_context = view_context.lookup_context
|
|
116
118
|
|
|
117
119
|
# For content_for
|
|
118
|
-
@view_flow
|
|
120
|
+
@view_flow = view_context.view_flow
|
|
119
121
|
|
|
120
122
|
# For i18n
|
|
121
123
|
@virtual_path ||= virtual_path
|
|
122
124
|
|
|
123
125
|
# Describes the inferred request constraints (locales, formats, variants)
|
|
124
|
-
@__vc_requested_details
|
|
126
|
+
@__vc_requested_details = @lookup_context.vc_requested_details
|
|
125
127
|
|
|
126
128
|
# For caching, such as #cache_if
|
|
127
129
|
@current_template = nil unless defined?(@current_template)
|
|
@@ -142,10 +144,21 @@ module ViewComponent
|
|
|
142
144
|
value = nil
|
|
143
145
|
|
|
144
146
|
@output_buffer.with_buffer do
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
inner_rendered_template = nil
|
|
148
|
+
around_rendered_template = around_render do
|
|
149
|
+
inner_rendered_template = render_template_for(@__vc_requested_details).to_s
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# If `around_render` returned the same object the block yielded, the inner
|
|
153
|
+
# template's escaping is authoritative and we can trust the result. If the
|
|
154
|
+
# user replaced/wrapped the value, re-check HTML safety to prevent
|
|
155
|
+
# bypassing the escaping applied to normal `#call` return values
|
|
156
|
+
# (GHSA-97jw-64cj-jc58).
|
|
157
|
+
rendered_template = if around_rendered_template.equal?(inner_rendered_template)
|
|
158
|
+
around_rendered_template
|
|
159
|
+
else
|
|
160
|
+
__vc_safe_around_render_output(around_rendered_template)
|
|
161
|
+
end
|
|
149
162
|
|
|
150
163
|
# Avoid allocating new string when output_preamble and output_postamble are blank
|
|
151
164
|
value = if output_preamble.blank? && output_postamble.blank?
|
|
@@ -255,7 +268,7 @@ module ViewComponent
|
|
|
255
268
|
# @private
|
|
256
269
|
def render(options = {}, args = {}, &block)
|
|
257
270
|
if options.respond_to?(:set_original_view_context)
|
|
258
|
-
options.set_original_view_context(
|
|
271
|
+
options.set_original_view_context(__vc_original_view_context)
|
|
259
272
|
|
|
260
273
|
# We assume options is a component, so there's no need to evaluate the
|
|
261
274
|
# block in the view context as we do below.
|
|
@@ -444,6 +457,28 @@ module ViewComponent
|
|
|
444
457
|
end
|
|
445
458
|
end
|
|
446
459
|
|
|
460
|
+
def __vc_safe_around_render_output(output)
|
|
461
|
+
__vc_maybe_escape_html(output) do
|
|
462
|
+
Kernel.warn("WARNING: The #{self.class} component's around_render returned an HTML-unsafe string. The output will be automatically escaped, but you may want to investigate.")
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Resets every render-scoped instance variable derived from the calling view
|
|
467
|
+
# context so a reused instance cannot leak controller/helper/request/format
|
|
468
|
+
# state from a previous render. Slot state (`@__vc_set_slots`,
|
|
469
|
+
# `@__vc_content_set_by_with_content`) is intentionally preserved because it
|
|
470
|
+
# is populated by callers _before_ `render_in` runs (e.g. via `with_*`
|
|
471
|
+
# slot setters or `with_content`).
|
|
472
|
+
def __vc_reset_render_state!
|
|
473
|
+
%i[
|
|
474
|
+
@__vc_controller
|
|
475
|
+
@__vc_helpers
|
|
476
|
+
@__vc_request
|
|
477
|
+
].each do |ivar|
|
|
478
|
+
remove_instance_variable(ivar) if instance_variable_defined?(ivar)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
447
482
|
# Configuration for generators.
|
|
448
483
|
#
|
|
449
484
|
# All options under this namespace default to `false` unless otherwise
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "action_view/renderer/collection_renderer"
|
|
4
|
+
require "action_view/helpers/output_safety_helper"
|
|
4
5
|
|
|
5
6
|
module ViewComponent
|
|
6
7
|
class Collection
|
|
7
8
|
include Enumerable
|
|
9
|
+
include ActionView::Helpers::OutputSafetyHelper
|
|
8
10
|
|
|
9
11
|
attr_reader :component
|
|
10
12
|
|
|
11
13
|
delegate :size, to: :@collection
|
|
12
14
|
|
|
13
|
-
def render_in(view_context, &block)
|
|
14
|
-
components.map do |component|
|
|
15
|
+
def render_in(view_context, **_, &block)
|
|
16
|
+
rendered = components.map do |component|
|
|
15
17
|
component.render_in(view_context, &block)
|
|
16
|
-
end
|
|
18
|
+
end
|
|
19
|
+
safe_join(rendered, rendered_spacer(view_context))
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def each(&block)
|
|
@@ -30,15 +33,15 @@ module ViewComponent
|
|
|
30
33
|
|
|
31
34
|
private
|
|
32
35
|
|
|
36
|
+
# Always rebuild child component instances per render to avoid leaking
|
|
37
|
+
# request-scoped state from a previous render into a later one (GHSA).
|
|
33
38
|
def components
|
|
34
|
-
return @components if defined? @components
|
|
35
|
-
|
|
36
39
|
iterator = ActionView::PartialIteration.new(@collection.size)
|
|
37
40
|
|
|
38
41
|
component.__vc_validate_collection_parameter!(validate_default: true)
|
|
39
42
|
|
|
40
|
-
@
|
|
41
|
-
component.new(**component_options(item, iterator)).tap do |
|
|
43
|
+
@collection.map do |item|
|
|
44
|
+
component.new(**component_options(item, iterator)).tap do |_|
|
|
42
45
|
iterator.iterate!
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -67,12 +70,12 @@ module ViewComponent
|
|
|
67
70
|
@options.merge(item_options)
|
|
68
71
|
end
|
|
69
72
|
|
|
73
|
+
# Render the spacer through a fresh `dup` so a collection rendered multiple
|
|
74
|
+
# times always gets a clean spacer instance.
|
|
70
75
|
def rendered_spacer(view_context)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
""
|
|
75
|
-
end
|
|
76
|
+
return "" unless @spacer_component
|
|
77
|
+
|
|
78
|
+
@spacer_component.dup.render_in(view_context)
|
|
76
79
|
end
|
|
77
80
|
end
|
|
78
81
|
end
|
|
@@ -8,7 +8,7 @@ module ViewComponent # :nodoc:
|
|
|
8
8
|
mod.prepend(self) unless self <= ViewComponent::Instrumentation
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def render_in(view_context, &block)
|
|
11
|
+
def render_in(view_context, **_, &block)
|
|
12
12
|
return super if !Rails.application.config.view_component.instrumentation_enabled.present?
|
|
13
13
|
|
|
14
14
|
payload = {
|
|
@@ -380,6 +380,7 @@ module ViewComponent
|
|
|
380
380
|
def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
|
|
381
381
|
slot_definition ||= self.class.registered_slots[slot_name]
|
|
382
382
|
slot = Slot.new(self)
|
|
383
|
+
captured_block_virtual_path = nil
|
|
383
384
|
|
|
384
385
|
# Passing the block to the sub-component wrapper like this has two
|
|
385
386
|
# benefits:
|
|
@@ -394,7 +395,8 @@ module ViewComponent
|
|
|
394
395
|
slot.__vc_content_block = block
|
|
395
396
|
# Capture the virtual path at the time the block is defined, so that
|
|
396
397
|
# translations resolve relative to where the block was created, not where it's rendered
|
|
397
|
-
|
|
398
|
+
captured_block_virtual_path = view_context.instance_variable_get(:@virtual_path)
|
|
399
|
+
slot.__vc_content_block_virtual_path = captured_block_virtual_path
|
|
398
400
|
end
|
|
399
401
|
|
|
400
402
|
# If class
|
|
@@ -413,7 +415,7 @@ module ViewComponent
|
|
|
413
415
|
renderable_value =
|
|
414
416
|
if block
|
|
415
417
|
renderable_function.call(*args, **kwargs) do |*rargs|
|
|
416
|
-
with_captured_virtual_path(
|
|
418
|
+
with_captured_virtual_path(captured_block_virtual_path) do
|
|
417
419
|
view_context.capture(*rargs, &block)
|
|
418
420
|
end
|
|
419
421
|
end
|
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: 4.
|
|
4
|
+
version: 4.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ViewComponent Team
|
|
@@ -135,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
135
135
|
- !ruby/object:Gem::Version
|
|
136
136
|
version: '0'
|
|
137
137
|
requirements: []
|
|
138
|
-
rubygems_version: 4.0.
|
|
138
|
+
rubygems_version: 4.0.10
|
|
139
139
|
specification_version: 4
|
|
140
140
|
summary: A framework for building reusable, testable & encapsulated view components
|
|
141
141
|
in Ruby on Rails.
|