actionpack 7.2.2.1 → 8.0.5
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/CHANGELOG.md +228 -101
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +1 -12
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/base.rb +1 -1
- data/lib/action_controller/form_builder.rb +3 -3
- data/lib/action_controller/metal/allow_browser.rb +11 -1
- data/lib/action_controller/metal/conditional_get.rb +5 -1
- data/lib/action_controller/metal/data_streaming.rb +4 -2
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +59 -11
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/redirecting.rb +4 -3
- data/lib/action_controller/metal/renderers.rb +2 -3
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +3 -1
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -92
- data/lib/action_controller/railtie.rb +6 -7
- data/lib/action_controller/renderer.rb +0 -1
- data/lib/action_controller/test_case.rb +12 -2
- data/lib/action_dispatch/constants.rb +6 -0
- data/lib/action_dispatch/http/cache.rb +27 -10
- data/lib/action_dispatch/http/content_security_policy.rb +14 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +8 -3
- data/lib/action_dispatch/http/param_builder.rb +186 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/permissions_policy.rb +2 -0
- data/lib/action_dispatch/http/query_parser.rb +53 -0
- data/lib/action_dispatch/http/request.rb +64 -19
- data/lib/action_dispatch/http/response.rb +49 -14
- data/lib/action_dispatch/http/url.rb +2 -2
- data/lib/action_dispatch/journey/formatter.rb +8 -3
- data/lib/action_dispatch/journey/gtg/transition_table.rb +4 -4
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/scanner.rb +44 -42
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +19 -4
- data/lib/action_dispatch/middleware/debug_view.rb +0 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +3 -9
- data/lib/action_dispatch/middleware/executor.rb +5 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +5 -1
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +0 -3
- data/lib/action_dispatch/railtie.rb +8 -0
- data/lib/action_dispatch/request/session.rb +1 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +96 -67
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/route_set.rb +21 -10
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +12 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
- data/lib/action_dispatch/testing/integration.rb +20 -10
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +1 -2
- data/lib/action_dispatch.rb +6 -4
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +16 -38
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -53,12 +53,53 @@ module ActionController
|
|
|
53
53
|
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
|
|
54
54
|
# ...
|
|
55
55
|
# end
|
|
56
|
+
#
|
|
57
|
+
# ## Streaming and Execution State
|
|
58
|
+
#
|
|
59
|
+
# When streaming, the action is executed in a separate thread. By default, this thread
|
|
60
|
+
# shares execution state from the parent thread.
|
|
61
|
+
#
|
|
62
|
+
# You can configure which execution state keys should be excluded from being shared
|
|
63
|
+
# using the `config.action_controller.live_streaming_excluded_keys` configuration:
|
|
64
|
+
#
|
|
65
|
+
# # config/application.rb
|
|
66
|
+
# config.action_controller.live_streaming_excluded_keys = [:active_record_connected_to_stack]
|
|
67
|
+
#
|
|
68
|
+
# This is useful when using ActionController::Live inside a `connected_to` block. For example,
|
|
69
|
+
# if the parent request is reading from a replica using `connected_to(role: :reading)`, you may
|
|
70
|
+
# want the streaming thread to use its own connection context instead of inheriting the read-only
|
|
71
|
+
# context:
|
|
72
|
+
#
|
|
73
|
+
# # Without configuration, streaming thread inherits read-only connection
|
|
74
|
+
# ActiveRecord::Base.connected_to(role: :reading) do
|
|
75
|
+
# @posts = Post.all
|
|
76
|
+
# render stream: true # Streaming thread cannot write to database
|
|
77
|
+
# end
|
|
78
|
+
#
|
|
79
|
+
# # With configuration, streaming thread gets fresh connection context
|
|
80
|
+
# # config.action_controller.live_streaming_excluded_keys = [:active_record_connected_to_stack]
|
|
81
|
+
# ActiveRecord::Base.connected_to(role: :reading) do
|
|
82
|
+
# @posts = Post.all
|
|
83
|
+
# render stream: true # Streaming thread can write to database if needed
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# Common keys you might want to exclude:
|
|
87
|
+
# - `:active_record_connected_to_stack` - Database connection routing and roles
|
|
88
|
+
# - `:active_record_prohibit_shard_swapping` - Shard swapping restrictions
|
|
89
|
+
#
|
|
90
|
+
# By default, no keys are excluded to maintain backward compatibility.
|
|
56
91
|
module Live
|
|
57
92
|
extend ActiveSupport::Concern
|
|
58
93
|
|
|
94
|
+
mattr_accessor :live_streaming_excluded_keys, default: []
|
|
95
|
+
|
|
96
|
+
included do
|
|
97
|
+
class_attribute :live_streaming_excluded_keys, instance_accessor: false, default: Live.live_streaming_excluded_keys
|
|
98
|
+
end
|
|
99
|
+
|
|
59
100
|
module ClassMethods
|
|
60
101
|
def make_response!(request)
|
|
61
|
-
if request.get_header("HTTP_VERSION") == "HTTP/1.0"
|
|
102
|
+
if (request.get_header("SERVER_PROTOCOL") || request.get_header("HTTP_VERSION")) == "HTTP/1.0"
|
|
62
103
|
super
|
|
63
104
|
else
|
|
64
105
|
Live::Response.new.tap do |res|
|
|
@@ -171,12 +212,6 @@ module ActionController
|
|
|
171
212
|
@ignore_disconnect = false
|
|
172
213
|
end
|
|
173
214
|
|
|
174
|
-
# ActionDispatch::Response delegates #to_ary to the internal
|
|
175
|
-
# ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the
|
|
176
|
-
# response body can be buffered and/or cached by Rack middlewares, this is not
|
|
177
|
-
# the case for Live responses so we undefine it for this Buffer subclass.
|
|
178
|
-
undef_method :to_ary
|
|
179
|
-
|
|
180
215
|
def write(string)
|
|
181
216
|
unless @response.committed?
|
|
182
217
|
@response.headers["Cache-Control"] ||= "no-cache"
|
|
@@ -288,7 +323,7 @@ module ActionController
|
|
|
288
323
|
# Since we're processing the view in a different thread, copy the thread locals
|
|
289
324
|
# from the main thread to the child thread. :'(
|
|
290
325
|
locals.each { |k, v| t2[k] = v }
|
|
291
|
-
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
|
326
|
+
ActiveSupport::IsolatedExecutionState.share_with(t1, except: self.class.live_streaming_excluded_keys)
|
|
292
327
|
|
|
293
328
|
begin
|
|
294
329
|
super(name)
|
|
@@ -307,6 +342,9 @@ module ActionController
|
|
|
307
342
|
error = e
|
|
308
343
|
end
|
|
309
344
|
ensure
|
|
345
|
+
ActiveSupport::IsolatedExecutionState.clear
|
|
346
|
+
clean_up_thread_locals(locals, t2)
|
|
347
|
+
|
|
310
348
|
@_response.commit!
|
|
311
349
|
end
|
|
312
350
|
end
|
|
@@ -328,7 +366,8 @@ module ActionController
|
|
|
328
366
|
# or other running data where you don't want the entire file buffered in memory
|
|
329
367
|
# first. Similar to send_data, but where the data is generated live.
|
|
330
368
|
#
|
|
331
|
-
# Options:
|
|
369
|
+
# #### Options:
|
|
370
|
+
#
|
|
332
371
|
# * `:filename` - suggests a filename for the browser to use.
|
|
333
372
|
# * `:type` - specifies an HTTP content type. You can specify either a string
|
|
334
373
|
# or a symbol for a registered type with `Mime::Type.register`, for example
|
|
@@ -371,11 +410,20 @@ module ActionController
|
|
|
371
410
|
# data from the response bodies. Nobody should call this method except in Rails
|
|
372
411
|
# internals. Seriously!
|
|
373
412
|
def new_controller_thread # :nodoc:
|
|
374
|
-
|
|
413
|
+
ActionController::Live.live_thread_pool_executor.post do
|
|
375
414
|
t2 = Thread.current
|
|
376
415
|
t2.abort_on_exception = true
|
|
377
416
|
yield
|
|
378
|
-
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Ensure we clean up any thread locals we copied so that the thread can reused.
|
|
421
|
+
def clean_up_thread_locals(locals, thread) # :nodoc:
|
|
422
|
+
locals.each { |k, _| thread[k] = nil }
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def self.live_thread_pool_executor
|
|
426
|
+
@live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live")
|
|
379
427
|
end
|
|
380
428
|
|
|
381
429
|
def log_error(exception)
|
|
@@ -198,14 +198,14 @@ module ActionController
|
|
|
198
198
|
# # enables the parameter wrapper for XML format
|
|
199
199
|
#
|
|
200
200
|
# wrap_parameters :person
|
|
201
|
-
# # wraps parameters into
|
|
201
|
+
# # wraps parameters into params[:person] hash
|
|
202
202
|
#
|
|
203
203
|
# wrap_parameters Person
|
|
204
204
|
# # wraps parameters by determining the wrapper key from Person class
|
|
205
|
-
# # (
|
|
205
|
+
# # (:person, in this case) and the list of attribute names
|
|
206
206
|
#
|
|
207
207
|
# wrap_parameters include: [:username, :title]
|
|
208
|
-
# # wraps only
|
|
208
|
+
# # wraps only :username and :title attributes from parameters.
|
|
209
209
|
#
|
|
210
210
|
# wrap_parameters false
|
|
211
211
|
# # disables parameters wrapping for this controller altogether.
|
|
@@ -29,6 +29,9 @@ module ActionController # :nodoc:
|
|
|
29
29
|
# datastore as your general caches, you can pass a custom store in the `store`
|
|
30
30
|
# parameter.
|
|
31
31
|
#
|
|
32
|
+
# If you want to use multiple rate limits per controller, you need to give each of
|
|
33
|
+
# them an explicit name via the `name:` option.
|
|
34
|
+
#
|
|
32
35
|
# Examples:
|
|
33
36
|
#
|
|
34
37
|
# class SessionsController < ApplicationController
|
|
@@ -44,14 +47,20 @@ module ActionController # :nodoc:
|
|
|
44
47
|
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
|
|
45
48
|
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
|
|
46
49
|
# end
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
#
|
|
51
|
+
# class SessionsController < ApplicationController
|
|
52
|
+
# rate_limit to: 3, within: 2.seconds, name: "short-term"
|
|
53
|
+
# rate_limit to: 10, within: 5.minutes, name: "long-term"
|
|
54
|
+
# end
|
|
55
|
+
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, name: nil, **options)
|
|
56
|
+
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store, name: name) }, **options
|
|
49
57
|
end
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
private
|
|
53
|
-
def rate_limiting(to:, within:, by:, with:, store:)
|
|
54
|
-
|
|
61
|
+
def rate_limiting(to:, within:, by:, with:, store:, name:)
|
|
62
|
+
cache_key = ["rate-limit", controller_path, name, instance_exec(&by)].compact.join(":")
|
|
63
|
+
count = store.increment(cache_key, 1, expires_in: within)
|
|
55
64
|
if count && count > to
|
|
56
65
|
ActiveSupport::Notifications.instrument("rate_limit.action_controller", request: request) do
|
|
57
66
|
instance_exec(&with)
|
|
@@ -106,13 +106,14 @@ module ActionController
|
|
|
106
106
|
|
|
107
107
|
allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host }
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
proposed_status = _extract_redirect_to_status(options, response_options)
|
|
110
110
|
|
|
111
111
|
redirect_to_location = _compute_redirect_to_location(request, options)
|
|
112
112
|
_ensure_url_is_http_header_safe(redirect_to_location)
|
|
113
113
|
|
|
114
114
|
self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host)
|
|
115
115
|
self.response_body = ""
|
|
116
|
+
self.status = proposed_status
|
|
116
117
|
end
|
|
117
118
|
|
|
118
119
|
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
|
|
@@ -211,9 +212,9 @@ module ActionController
|
|
|
211
212
|
|
|
212
213
|
def _extract_redirect_to_status(options, response_options)
|
|
213
214
|
if options.is_a?(Hash) && options.key?(:status)
|
|
214
|
-
|
|
215
|
+
ActionDispatch::Response.rack_status_code(options.delete(:status))
|
|
215
216
|
elsif response_options.key?(:status)
|
|
216
|
-
|
|
217
|
+
ActionDispatch::Response.rack_status_code(response_options[:status])
|
|
217
218
|
else
|
|
218
219
|
302
|
|
219
220
|
end
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
|
-
require "set"
|
|
6
|
-
|
|
7
5
|
module ActionController
|
|
8
6
|
# See Renderers.add
|
|
9
7
|
def self.add_renderer(key, &block)
|
|
@@ -154,7 +152,8 @@ module ActionController
|
|
|
154
152
|
end
|
|
155
153
|
|
|
156
154
|
add :json do |json, options|
|
|
157
|
-
|
|
155
|
+
json_options = options.except(:callback, :content_type, :status)
|
|
156
|
+
json = json.to_json(json_options) unless json.kind_of?(String)
|
|
158
157
|
|
|
159
158
|
if options[:callback].present?
|
|
160
159
|
if media_type.nil? || media_type == Mime[:json]
|
|
@@ -142,6 +142,7 @@ module ActionController # :nodoc:
|
|
|
142
142
|
#
|
|
143
143
|
#
|
|
144
144
|
# Built-in unverified request handling methods are:
|
|
145
|
+
#
|
|
145
146
|
# * `:exception` - Raises ActionController::InvalidAuthenticityToken
|
|
146
147
|
# exception.
|
|
147
148
|
# * `:reset_session` - Resets the session.
|
|
@@ -170,6 +171,7 @@ module ActionController # :nodoc:
|
|
|
170
171
|
#
|
|
171
172
|
#
|
|
172
173
|
# Built-in session token strategies are:
|
|
174
|
+
#
|
|
173
175
|
# * `:session` - Store the CSRF token in the session. Used as default if
|
|
174
176
|
# `:store` option is not specified.
|
|
175
177
|
# * `:cookie` - Store the CSRF token in an encrypted cookie.
|
|
@@ -498,7 +500,7 @@ module ActionController # :nodoc:
|
|
|
498
500
|
# Checks the client's masked token to see if it matches the session token.
|
|
499
501
|
# Essentially the inverse of `masked_authenticity_token`.
|
|
500
502
|
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
|
501
|
-
if encoded_masked_token.
|
|
503
|
+
if !encoded_masked_token.is_a?(String) || encoded_masked_token.empty?
|
|
502
504
|
return false
|
|
503
505
|
end
|
|
504
506
|
|
|
@@ -165,95 +165,16 @@ module ActionController # :nodoc:
|
|
|
165
165
|
#
|
|
166
166
|
# ## Web server support
|
|
167
167
|
#
|
|
168
|
-
#
|
|
169
|
-
# instructions for each of them.
|
|
170
|
-
#
|
|
171
|
-
# #### Unicorn
|
|
172
|
-
#
|
|
173
|
-
# Unicorn supports streaming but it needs to be configured. For this, you need
|
|
174
|
-
# to create a config file as follow:
|
|
175
|
-
#
|
|
176
|
-
# # unicorn.config.rb
|
|
177
|
-
# listen 3000, tcp_nopush: false
|
|
178
|
-
#
|
|
179
|
-
# And use it on initialization:
|
|
180
|
-
#
|
|
181
|
-
# unicorn_rails --config-file unicorn.config.rb
|
|
182
|
-
#
|
|
183
|
-
# You may also want to configure other parameters like `:tcp_nodelay`.
|
|
184
|
-
#
|
|
185
|
-
# For more information, please check the
|
|
186
|
-
# [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method-
|
|
187
|
-
# i-listen).
|
|
188
|
-
#
|
|
189
|
-
# If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming
|
|
190
|
-
# should work out of the box on Rainbows.
|
|
191
|
-
#
|
|
192
|
-
# #### Passenger
|
|
193
|
-
#
|
|
194
|
-
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
|
195
|
-
#
|
|
196
|
-
# 1. NGINX response buffering mechanism which is dependent on the value of
|
|
197
|
-
# `passenger_buffer_response` option (default is "off").
|
|
198
|
-
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
|
199
|
-
# of `passenger_buffer_response`.
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
# When `passenger_buffer_response` is turned "on", then streaming would be done
|
|
203
|
-
# at the NGINX level which waits until the application is done sending the
|
|
204
|
-
# response back to the client.
|
|
205
|
-
#
|
|
206
|
-
# For more information, please check the [documentation]
|
|
207
|
-
# (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response).
|
|
168
|
+
# Rack 3+ compatible servers all support streaming.
|
|
208
169
|
module Streaming
|
|
209
|
-
class Body # :nodoc:
|
|
210
|
-
TERM = "\r\n"
|
|
211
|
-
TAIL = "0#{TERM}"
|
|
212
|
-
|
|
213
|
-
# Store the response body to be chunked.
|
|
214
|
-
def initialize(body)
|
|
215
|
-
@body = body
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# For each element yielded by the response body, yield the element in chunked
|
|
219
|
-
# encoding.
|
|
220
|
-
def each(&block)
|
|
221
|
-
term = TERM
|
|
222
|
-
@body.each do |chunk|
|
|
223
|
-
size = chunk.bytesize
|
|
224
|
-
next if size == 0
|
|
225
|
-
|
|
226
|
-
yield [size.to_s(16), term, chunk.b, term].join
|
|
227
|
-
end
|
|
228
|
-
yield TAIL
|
|
229
|
-
yield term
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Close the response body if the response body supports it.
|
|
233
|
-
def close
|
|
234
|
-
@body.close if @body.respond_to?(:close)
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
170
|
private
|
|
239
|
-
# Set proper cache control and transfer encoding when streaming
|
|
240
|
-
def _process_options(options)
|
|
241
|
-
super
|
|
242
|
-
if options[:stream]
|
|
243
|
-
if request.version == "HTTP/1.0"
|
|
244
|
-
options.delete(:stream)
|
|
245
|
-
else
|
|
246
|
-
headers["Cache-Control"] ||= "no-cache"
|
|
247
|
-
headers["Transfer-Encoding"] = "chunked"
|
|
248
|
-
headers.delete("Content-Length")
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
171
|
# Call render_body if we are streaming instead of usual `render`.
|
|
254
172
|
def _render_template(options)
|
|
255
173
|
if options.delete(:stream)
|
|
256
|
-
|
|
174
|
+
# It shouldn't be necessary to set this.
|
|
175
|
+
headers["cache-control"] ||= "no-cache"
|
|
176
|
+
|
|
177
|
+
view_renderer.render_body(view_context, options)
|
|
257
178
|
else
|
|
258
179
|
super
|
|
259
180
|
end
|