actionpack 7.2.3 → 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 +198 -119
- 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 +58 -10
- data/lib/action_controller/metal/rate_limiting.rb +13 -4
- data/lib/action_controller/metal/renderers.rb +2 -3
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -89
- data/lib/action_controller/railtie.rb +6 -7
- data/lib/action_controller/test_case.rb +12 -2
- 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/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 +60 -16
- data/lib/action_dispatch/http/response.rb +34 -13
- 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 +17 -4
- data/lib/action_dispatch/middleware/debug_view.rb +0 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -6
- 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 +91 -62
- 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/assertions/response.rb +12 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
- data/lib/action_dispatch/testing/integration.rb +18 -7
- data/lib/action_dispatch.rb +6 -4
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +15 -48
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -9,6 +9,8 @@ module ActionDispatch
|
|
|
9
9
|
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
|
10
10
|
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
|
11
11
|
|
|
12
|
+
mattr_accessor :strict_freshness, default: false
|
|
13
|
+
|
|
12
14
|
def if_modified_since
|
|
13
15
|
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
|
14
16
|
Time.rfc2822(since) rescue nil
|
|
@@ -34,19 +36,32 @@ module ActionDispatch
|
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
# Check response freshness (`Last-Modified` and ETag) against request
|
|
38
|
-
# `If-Modified-Since` and `If-None-Match` conditions.
|
|
39
|
-
# supplied,
|
|
39
|
+
# Check response freshness (`Last-Modified` and `ETag`) against request
|
|
40
|
+
# `If-Modified-Since` and `If-None-Match` conditions.
|
|
41
|
+
# If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified`
|
|
42
|
+
# or both are considered equally. You can adjust the preference with
|
|
43
|
+
# `config.action_dispatch.strict_freshness`.
|
|
44
|
+
# Reference: http://tools.ietf.org/html/rfc7232#section-6
|
|
40
45
|
def fresh?(response)
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
if Request.strict_freshness
|
|
47
|
+
if if_none_match
|
|
48
|
+
etag_matches?(response.etag)
|
|
49
|
+
elsif if_modified_since
|
|
50
|
+
not_modified?(response.last_modified)
|
|
51
|
+
else
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
last_modified = if_modified_since
|
|
56
|
+
etag = if_none_match
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
return false unless last_modified || etag
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
success = true
|
|
61
|
+
success &&= not_modified?(response.last_modified) if last_modified
|
|
62
|
+
success &&= etag_matches?(response.etag) if etag
|
|
63
|
+
success
|
|
64
|
+
end
|
|
50
65
|
end
|
|
51
66
|
end
|
|
52
67
|
|
|
@@ -171,6 +186,7 @@ module ActionDispatch
|
|
|
171
186
|
PUBLIC = "public"
|
|
172
187
|
PRIVATE = "private"
|
|
173
188
|
MUST_REVALIDATE = "must-revalidate"
|
|
189
|
+
IMMUTABLE = "immutable"
|
|
174
190
|
|
|
175
191
|
def handle_conditional_get!
|
|
176
192
|
# Normally default cache control setting is handled by ETag middleware. But, if
|
|
@@ -221,6 +237,7 @@ module ActionDispatch
|
|
|
221
237
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
|
222
238
|
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
|
223
239
|
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
|
240
|
+
options << IMMUTABLE if control[:immutable]
|
|
224
241
|
options.concat(extras) if extras
|
|
225
242
|
end
|
|
226
243
|
|
|
@@ -128,6 +128,7 @@ module ActionDispatch # :nodoc:
|
|
|
128
128
|
MAPPINGS = {
|
|
129
129
|
self: "'self'",
|
|
130
130
|
unsafe_eval: "'unsafe-eval'",
|
|
131
|
+
wasm_unsafe_eval: "'wasm-unsafe-eval'",
|
|
131
132
|
unsafe_hashes: "'unsafe-hashes'",
|
|
132
133
|
unsafe_inline: "'unsafe-inline'",
|
|
133
134
|
none: "'none'",
|
|
@@ -170,6 +171,8 @@ module ActionDispatch # :nodoc:
|
|
|
170
171
|
worker_src: "worker-src"
|
|
171
172
|
}.freeze
|
|
172
173
|
|
|
174
|
+
HASH_SOURCE_ALGORITHM_PREFIXES = ["sha256-", "sha384-", "sha512-"].freeze
|
|
175
|
+
|
|
173
176
|
DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
|
|
174
177
|
|
|
175
178
|
private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
|
|
@@ -304,7 +307,13 @@ module ActionDispatch # :nodoc:
|
|
|
304
307
|
case source
|
|
305
308
|
when Symbol
|
|
306
309
|
apply_mapping(source)
|
|
307
|
-
when String
|
|
310
|
+
when String
|
|
311
|
+
if hash_source?(source)
|
|
312
|
+
"'#{source}'"
|
|
313
|
+
else
|
|
314
|
+
source
|
|
315
|
+
end
|
|
316
|
+
when Proc
|
|
308
317
|
source
|
|
309
318
|
else
|
|
310
319
|
raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
|
|
@@ -373,5 +382,9 @@ module ActionDispatch # :nodoc:
|
|
|
373
382
|
def nonce_directive?(directive, nonce_directives)
|
|
374
383
|
nonce_directives.include?(directive)
|
|
375
384
|
end
|
|
385
|
+
|
|
386
|
+
def hash_source?(source)
|
|
387
|
+
source.start_with?(*HASH_SOURCE_ALGORITHM_PREFIXES)
|
|
388
|
+
end
|
|
376
389
|
end
|
|
377
390
|
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
class ParamBuilder
|
|
5
|
+
# --
|
|
6
|
+
# This implementation is based on Rack::QueryParser,
|
|
7
|
+
# Copyright (C) 2007-2021 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
|
|
8
|
+
|
|
9
|
+
def self.make_default(param_depth_limit)
|
|
10
|
+
new param_depth_limit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :param_depth_limit
|
|
14
|
+
|
|
15
|
+
def initialize(param_depth_limit)
|
|
16
|
+
@param_depth_limit = param_depth_limit
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
cattr_accessor :ignore_leading_brackets
|
|
20
|
+
|
|
21
|
+
LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.")
|
|
22
|
+
|
|
23
|
+
cattr_accessor :default
|
|
24
|
+
self.default = make_default(100)
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
delegate :from_query_string, :from_pairs, :from_hash, to: :default
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def from_query_string(qs, separator: nil, encoding_template: nil)
|
|
31
|
+
from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def from_pairs(pairs, encoding_template: nil)
|
|
35
|
+
params = make_params
|
|
36
|
+
|
|
37
|
+
pairs.each do |k, v|
|
|
38
|
+
if Hash === v
|
|
39
|
+
v = ActionDispatch::Http::UploadedFile.new(v)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
store_nested_param(params, k, v, 0, encoding_template)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
params
|
|
46
|
+
rescue ArgumentError => e
|
|
47
|
+
raise InvalidParameterError, e.message, e.backtrace
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def from_hash(hash, encoding_template: nil)
|
|
51
|
+
# Force encodings from encoding template
|
|
52
|
+
hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template)
|
|
53
|
+
|
|
54
|
+
# Assert valid encoding
|
|
55
|
+
Request::Utils.check_param_encoding(hash)
|
|
56
|
+
|
|
57
|
+
# Convert hashes to HWIA (or UploadedFile), and deep-munge nils
|
|
58
|
+
# out of arrays
|
|
59
|
+
hash = Request::Utils.normalize_encode_params(hash)
|
|
60
|
+
|
|
61
|
+
hash
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
def store_nested_param(params, name, v, depth, encoding_template = nil)
|
|
66
|
+
raise ParamsTooDeepError if depth >= param_depth_limit
|
|
67
|
+
|
|
68
|
+
if !name
|
|
69
|
+
# nil name, treat same as empty string (required by tests)
|
|
70
|
+
k = after = ""
|
|
71
|
+
elsif depth == 0
|
|
72
|
+
if ignore_leading_brackets || (ignore_leading_brackets.nil? && LEADING_BRACKETS_COMPAT)
|
|
73
|
+
# Rack 2 compatible behavior, ignore leading brackets
|
|
74
|
+
if name =~ /\A[\[\]]*([^\[\]]+)\]*/
|
|
75
|
+
k = $1
|
|
76
|
+
after = $' || ""
|
|
77
|
+
|
|
78
|
+
if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("["))
|
|
79
|
+
ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.")
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
k = name
|
|
83
|
+
after = ""
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
|
87
|
+
if start = name.index("[", 1)
|
|
88
|
+
# Start of parameter nesting, use part before brackets as key
|
|
89
|
+
k = name[0, start]
|
|
90
|
+
after = name[start, name.length]
|
|
91
|
+
else
|
|
92
|
+
# Plain parameter with no nesting
|
|
93
|
+
k = name
|
|
94
|
+
after = ""
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
elsif name.start_with?("[]")
|
|
98
|
+
# Array nesting
|
|
99
|
+
k = "[]"
|
|
100
|
+
after = name[2, name.length]
|
|
101
|
+
elsif name.start_with?("[") && (start = name.index("]", 1))
|
|
102
|
+
# Hash nesting, use the part inside brackets as the key
|
|
103
|
+
k = name[1, start - 1]
|
|
104
|
+
after = name[start + 1, name.length]
|
|
105
|
+
else
|
|
106
|
+
# Probably malformed input, nested but not starting with [
|
|
107
|
+
# treat full name as key for backwards compatibility.
|
|
108
|
+
k = name
|
|
109
|
+
after = ""
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return if k.empty?
|
|
113
|
+
|
|
114
|
+
if depth == 0 && String === v
|
|
115
|
+
# We have to wait until we've found the top part of the name,
|
|
116
|
+
# because that's what the encoding template is configured with
|
|
117
|
+
if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen?
|
|
118
|
+
v.force_encoding(designated_encoding)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# ... and we can't validate the encoding until after we've
|
|
122
|
+
# applied any template override
|
|
123
|
+
unless v.valid_encoding?
|
|
124
|
+
raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if after == ""
|
|
129
|
+
if k == "[]" && depth != 0
|
|
130
|
+
return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : []
|
|
131
|
+
else
|
|
132
|
+
params[k] = v
|
|
133
|
+
end
|
|
134
|
+
elsif after == "["
|
|
135
|
+
params[name] = v
|
|
136
|
+
elsif after == "[]"
|
|
137
|
+
params[k] ||= []
|
|
138
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
139
|
+
params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge
|
|
140
|
+
elsif after.start_with?("[]")
|
|
141
|
+
# Recognize x[][y] (hash inside array) parameters
|
|
142
|
+
unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]")
|
|
143
|
+
# Handle other nested array parameters
|
|
144
|
+
child_key = after[2, after.length]
|
|
145
|
+
end
|
|
146
|
+
params[k] ||= []
|
|
147
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
148
|
+
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
|
|
149
|
+
store_nested_param(params[k].last, child_key, v, depth + 1)
|
|
150
|
+
else
|
|
151
|
+
params[k] << store_nested_param(make_params, child_key, v, depth + 1)
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
params[k] ||= make_params
|
|
155
|
+
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
|
156
|
+
params[k] = store_nested_param(params[k], after, v, depth + 1)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
params
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def make_params
|
|
163
|
+
ActiveSupport::HashWithIndifferentAccess.new
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def new_depth_limit(param_depth_limit)
|
|
167
|
+
self.class.new @params_class, param_depth_limit
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def params_hash_type?(obj)
|
|
171
|
+
Hash === obj
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def params_hash_has_key?(hash, key)
|
|
175
|
+
return false if key.include?("[]")
|
|
176
|
+
|
|
177
|
+
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
|
178
|
+
next h if part == ""
|
|
179
|
+
return false unless params_hash_type?(h) && h.key?(part)
|
|
180
|
+
h[part]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
true
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
class ParamError < ActionDispatch::Http::Parameters::ParseError
|
|
5
|
+
def initialize(message = nil)
|
|
6
|
+
super
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.===(other)
|
|
10
|
+
super || (
|
|
11
|
+
defined?(Rack::Utils::ParameterTypeError) && Rack::Utils::ParameterTypeError === other ||
|
|
12
|
+
defined?(Rack::Utils::InvalidParameterError) && Rack::Utils::InvalidParameterError === other ||
|
|
13
|
+
defined?(Rack::QueryParser::ParamsTooDeepError) && Rack::QueryParser::ParamsTooDeepError === other
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ParameterTypeError < ParamError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class InvalidParameterError < ParamError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class ParamsTooDeepError < ParamError
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -86,12 +86,14 @@ module ActionDispatch # :nodoc:
|
|
|
86
86
|
ambient_light_sensor: "ambient-light-sensor",
|
|
87
87
|
autoplay: "autoplay",
|
|
88
88
|
camera: "camera",
|
|
89
|
+
display_capture: "display-capture",
|
|
89
90
|
encrypted_media: "encrypted-media",
|
|
90
91
|
fullscreen: "fullscreen",
|
|
91
92
|
geolocation: "geolocation",
|
|
92
93
|
gyroscope: "gyroscope",
|
|
93
94
|
hid: "hid",
|
|
94
95
|
idle_detection: "idle-detection",
|
|
96
|
+
keyboard_map: "keyboard-map",
|
|
95
97
|
magnetometer: "magnetometer",
|
|
96
98
|
microphone: "microphone",
|
|
97
99
|
midi: "midi",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "rack"
|
|
5
|
+
|
|
6
|
+
module ActionDispatch
|
|
7
|
+
class QueryParser
|
|
8
|
+
DEFAULT_SEP = /& */n
|
|
9
|
+
COMPAT_SEP = /[&;] */n
|
|
10
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
|
|
11
|
+
|
|
12
|
+
cattr_accessor :strict_query_string_separator
|
|
13
|
+
|
|
14
|
+
SEMICOLON_COMPAT = defined?(::Rack::QueryParser::DEFAULT_SEP) && ::Rack::QueryParser::DEFAULT_SEP.to_s.include?(";")
|
|
15
|
+
|
|
16
|
+
#--
|
|
17
|
+
# Note this departs from WHATWG's specified parsing algorithm by
|
|
18
|
+
# giving a nil value for keys that do not use '='. Callers that need
|
|
19
|
+
# the standard's interpretation can use `v.to_s`.
|
|
20
|
+
def self.each_pair(s, separator = nil)
|
|
21
|
+
return enum_for(:each_pair, s, separator) unless block_given?
|
|
22
|
+
|
|
23
|
+
s ||= ""
|
|
24
|
+
|
|
25
|
+
splitter =
|
|
26
|
+
if separator
|
|
27
|
+
COMMON_SEP[separator] || /[#{separator}] */n
|
|
28
|
+
elsif strict_query_string_separator
|
|
29
|
+
DEFAULT_SEP
|
|
30
|
+
elsif SEMICOLON_COMPAT && s.include?(";")
|
|
31
|
+
if strict_query_string_separator.nil?
|
|
32
|
+
ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.")
|
|
33
|
+
end
|
|
34
|
+
COMPAT_SEP
|
|
35
|
+
else
|
|
36
|
+
DEFAULT_SEP
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
s.split(splitter).each do |part|
|
|
40
|
+
next if part.empty?
|
|
41
|
+
|
|
42
|
+
k, v = part.split("=", 2)
|
|
43
|
+
|
|
44
|
+
k = URI.decode_www_form_component(k)
|
|
45
|
+
v &&= URI.decode_www_form_component(v)
|
|
46
|
+
|
|
47
|
+
yield k, v
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -55,12 +55,17 @@ module ActionDispatch
|
|
|
55
55
|
METHOD
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
|
|
59
|
+
|
|
58
60
|
def self.empty
|
|
59
61
|
new({})
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
def initialize(env)
|
|
63
65
|
super
|
|
66
|
+
|
|
67
|
+
@rack_request = Rack::Request.new(env)
|
|
68
|
+
|
|
64
69
|
@method = nil
|
|
65
70
|
@request_method = nil
|
|
66
71
|
@remote_ip = nil
|
|
@@ -69,6 +74,8 @@ module ActionDispatch
|
|
|
69
74
|
@ip = nil
|
|
70
75
|
end
|
|
71
76
|
|
|
77
|
+
attr_reader :rack_request
|
|
78
|
+
|
|
72
79
|
def commit_cookie_jar! # :nodoc:
|
|
73
80
|
end
|
|
74
81
|
|
|
@@ -283,7 +290,7 @@ module ActionDispatch
|
|
|
283
290
|
|
|
284
291
|
# Returns the content length of the request as an integer.
|
|
285
292
|
def content_length
|
|
286
|
-
return raw_post.bytesize if
|
|
293
|
+
return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
|
|
287
294
|
super.to_i
|
|
288
295
|
end
|
|
289
296
|
|
|
@@ -387,15 +394,12 @@ module ActionDispatch
|
|
|
387
394
|
# Override Rack's GET method to support indifferent access.
|
|
388
395
|
def GET
|
|
389
396
|
fetch_header("action_dispatch.request.query_parameters") do |k|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
# Check for non UTF-8 parameter values, which would cause errors later
|
|
395
|
-
Request::Utils.check_param_encoding(rack_query_params)
|
|
396
|
-
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
|
|
397
|
+
encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
|
|
398
|
+
rack_query_params = ActionDispatch::ParamBuilder.from_query_string(rack_request.query_string, encoding_template: encoding_template)
|
|
399
|
+
|
|
400
|
+
set_header k, rack_query_params
|
|
397
401
|
end
|
|
398
|
-
rescue
|
|
402
|
+
rescue ActionDispatch::ParamError => e
|
|
399
403
|
raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
|
|
400
404
|
end
|
|
401
405
|
alias :query_parameters :GET
|
|
@@ -403,18 +407,54 @@ module ActionDispatch
|
|
|
403
407
|
# Override Rack's POST method to support indifferent access.
|
|
404
408
|
def POST
|
|
405
409
|
fetch_header("action_dispatch.request.request_parameters") do
|
|
406
|
-
|
|
407
|
-
|
|
410
|
+
encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
|
|
411
|
+
|
|
412
|
+
param_list = nil
|
|
413
|
+
pr = parse_formatted_parameters(params_parsers) do
|
|
414
|
+
if param_list = request_parameters_list
|
|
415
|
+
ActionDispatch::ParamBuilder.from_pairs(param_list, encoding_template: encoding_template)
|
|
416
|
+
else
|
|
417
|
+
# We're not using a version of Rack that provides raw form
|
|
418
|
+
# pairs; we must use its hash (and thus post-process it below).
|
|
419
|
+
fallback_request_parameters
|
|
420
|
+
end
|
|
408
421
|
end
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
422
|
+
|
|
423
|
+
# If the request body was parsed by a custom parser like JSON
|
|
424
|
+
# (and thus the above block was not run), we need to
|
|
425
|
+
# post-process the result hash.
|
|
426
|
+
if param_list.nil?
|
|
427
|
+
pr = ActionDispatch::ParamBuilder.from_hash(pr, encoding_template: encoding_template)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
self.request_parameters = pr
|
|
412
431
|
end
|
|
413
|
-
rescue
|
|
432
|
+
rescue ActionDispatch::ParamError, EOFError => e
|
|
414
433
|
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
|
|
415
434
|
end
|
|
416
435
|
alias :request_parameters :POST
|
|
417
436
|
|
|
437
|
+
def request_parameters_list
|
|
438
|
+
# We don't use Rack's parse result, but we must call it so Rack
|
|
439
|
+
# can populate the rack.request.* keys we need.
|
|
440
|
+
rack_post = rack_request.POST
|
|
441
|
+
|
|
442
|
+
if form_pairs = get_header("rack.request.form_pairs")
|
|
443
|
+
# Multipart
|
|
444
|
+
form_pairs
|
|
445
|
+
elsif form_vars = get_header("rack.request.form_vars")
|
|
446
|
+
# URL-encoded
|
|
447
|
+
ActionDispatch::QueryParser.each_pair(form_vars)
|
|
448
|
+
elsif rack_post && !rack_post.empty?
|
|
449
|
+
# It was multipart, but Rack did not preserve a pair list
|
|
450
|
+
# (probably too old). Flat parameter list is not available.
|
|
451
|
+
nil
|
|
452
|
+
else
|
|
453
|
+
# No request body, or not a format Rack knows
|
|
454
|
+
[]
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
418
458
|
# Returns the authorization header regardless of whether it was specified
|
|
419
459
|
# directly or through one of the proxy alternatives.
|
|
420
460
|
def authorization
|
|
@@ -469,7 +509,7 @@ module ActionDispatch
|
|
|
469
509
|
def read_body_stream
|
|
470
510
|
if body_stream
|
|
471
511
|
reset_stream(body_stream) do
|
|
472
|
-
if
|
|
512
|
+
if has_header?(TRANSFER_ENCODING)
|
|
473
513
|
body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
|
|
474
514
|
else
|
|
475
515
|
body_stream.read(content_length)
|
|
@@ -491,6 +531,10 @@ module ActionDispatch
|
|
|
491
531
|
yield
|
|
492
532
|
end
|
|
493
533
|
end
|
|
534
|
+
|
|
535
|
+
def fallback_request_parameters
|
|
536
|
+
rack_request.POST
|
|
537
|
+
end
|
|
494
538
|
end
|
|
495
539
|
end
|
|
496
540
|
|
|
@@ -119,10 +119,22 @@ module ActionDispatch # :nodoc:
|
|
|
119
119
|
@str_body = nil
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
BODY_METHODS = { to_ary: true }
|
|
123
|
+
|
|
124
|
+
def respond_to?(method, include_private = false)
|
|
125
|
+
if BODY_METHODS.key?(method)
|
|
126
|
+
@buf.respond_to?(method)
|
|
127
|
+
else
|
|
128
|
+
super
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
122
132
|
def to_ary
|
|
123
|
-
@
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
if @str_body
|
|
134
|
+
[body]
|
|
135
|
+
else
|
|
136
|
+
@buf = @buf.to_ary
|
|
137
|
+
end
|
|
126
138
|
end
|
|
127
139
|
|
|
128
140
|
def body
|
|
@@ -342,7 +354,13 @@ module ActionDispatch # :nodoc:
|
|
|
342
354
|
# Returns the content of the response as a string. This contains the contents of
|
|
343
355
|
# any calls to `render`.
|
|
344
356
|
def body
|
|
345
|
-
@stream.
|
|
357
|
+
if @stream.respond_to?(:to_ary)
|
|
358
|
+
@stream.to_ary.join
|
|
359
|
+
elsif @stream.respond_to?(:body)
|
|
360
|
+
@stream.body
|
|
361
|
+
else
|
|
362
|
+
@stream
|
|
363
|
+
end
|
|
346
364
|
end
|
|
347
365
|
|
|
348
366
|
def write(string)
|
|
@@ -351,11 +369,16 @@ module ActionDispatch # :nodoc:
|
|
|
351
369
|
|
|
352
370
|
# Allows you to manually set or override the response body.
|
|
353
371
|
def body=(body)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
372
|
+
# Prevent ActionController::Metal::Live::Response from committing the response prematurely.
|
|
373
|
+
synchronize do
|
|
374
|
+
if body.respond_to?(:to_str)
|
|
375
|
+
@stream = build_buffer(self, [body])
|
|
376
|
+
elsif body.respond_to?(:to_path)
|
|
377
|
+
@stream = body
|
|
378
|
+
elsif body.respond_to?(:to_ary)
|
|
379
|
+
@stream = build_buffer(self, body)
|
|
380
|
+
else
|
|
381
|
+
@stream = body
|
|
359
382
|
end
|
|
360
383
|
end
|
|
361
384
|
end
|
|
@@ -496,10 +519,6 @@ module ActionDispatch # :nodoc:
|
|
|
496
519
|
Buffer.new response, body
|
|
497
520
|
end
|
|
498
521
|
|
|
499
|
-
def munge_body_object(body)
|
|
500
|
-
body.respond_to?(:each) ? body : [body]
|
|
501
|
-
end
|
|
502
|
-
|
|
503
522
|
def assign_default_content_type_and_charset!
|
|
504
523
|
return if media_type
|
|
505
524
|
|
|
@@ -513,6 +532,8 @@ module ActionDispatch # :nodoc:
|
|
|
513
532
|
@response = response
|
|
514
533
|
end
|
|
515
534
|
|
|
535
|
+
attr :response
|
|
536
|
+
|
|
516
537
|
def close
|
|
517
538
|
# Rack "close" maps to Response#abort, and **not** Response#close (which is used
|
|
518
539
|
# when the controller's finished writing)
|