actionpack 7.2.3 → 8.1.3
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 +394 -119
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +11 -5
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/callbacks.rb +6 -0
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +3 -2
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +4 -4
- data/lib/action_controller/log_subscriber.rb +22 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/conditional_get.rb +30 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -5
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +65 -25
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +39 -9
- data/lib/action_controller/metal/redirecting.rb +105 -13
- data/lib/action_controller/metal/renderers.rb +29 -9
- data/lib/action_controller/metal/rendering.rb +7 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
- data/lib/action_controller/metal/rescue.rb +9 -0
- 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 +33 -15
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/test_case.rb +12 -2
- data/lib/action_dispatch/http/cache.rb +138 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +6 -0
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/request.rb +70 -21
- data/lib/action_dispatch/http/response.rb +50 -16
- data/lib/action_dispatch/http/url.rb +110 -14
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -41
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/scanner.rb +44 -42
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +8 -4
- data/lib/action_dispatch/middleware/debug_exceptions.rb +24 -5
- data/lib/action_dispatch/middleware/debug_view.rb +11 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -11
- data/lib/action_dispatch/middleware/executor.rb +12 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +21 -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 +80 -57
- data/lib/action_dispatch/routing/mapper.rb +404 -223
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +21 -12
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertions/response.rb +26 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
- data/lib/action_dispatch/testing/integration.rb +18 -7
- data/lib/action_dispatch.rb +14 -4
- data/lib/action_pack/gem_version.rb +2 -2
- metadata +18 -48
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -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
|
|
@@ -65,14 +65,14 @@ module ActionDispatch
|
|
|
65
65
|
alias :params :parameters
|
|
66
66
|
|
|
67
67
|
def path_parameters=(parameters) # :nodoc:
|
|
68
|
-
|
|
68
|
+
@env.delete("action_dispatch.request.parameters")
|
|
69
69
|
|
|
70
70
|
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
|
|
71
71
|
# If any of the path parameters has an invalid encoding then raise since it's
|
|
72
72
|
# likely to trigger errors further on.
|
|
73
73
|
Request::Utils.check_param_encoding(parameters)
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
@env[PARAMETERS_KEY] = parameters
|
|
76
76
|
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
|
77
77
|
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
|
|
78
78
|
end
|
|
@@ -82,7 +82,7 @@ module ActionDispatch
|
|
|
82
82
|
#
|
|
83
83
|
# { action: "my_action", controller: "my_controller" }
|
|
84
84
|
def path_parameters
|
|
85
|
-
|
|
85
|
+
@env[PARAMETERS_KEY] ||= {}
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
private
|
|
@@ -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",
|
|
@@ -184,4 +186,8 @@ module ActionDispatch # :nodoc:
|
|
|
184
186
|
end
|
|
185
187
|
end
|
|
186
188
|
end
|
|
189
|
+
|
|
190
|
+
ActiveSupport.on_load(:action_dispatch_request) do
|
|
191
|
+
include ActionDispatch::PermissionsPolicy::Request
|
|
192
|
+
end
|
|
187
193
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "rack"
|
|
5
|
+
|
|
6
|
+
module ActionDispatch
|
|
7
|
+
class QueryParser
|
|
8
|
+
DEFAULT_SEP = /& */n
|
|
9
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
|
|
10
|
+
|
|
11
|
+
def self.strict_query_string_separator
|
|
12
|
+
ActionDispatch.deprecator.warn <<~MSG
|
|
13
|
+
The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
|
|
14
|
+
MSG
|
|
15
|
+
@strict_query_string_separator
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.strict_query_string_separator=(value)
|
|
19
|
+
ActionDispatch.deprecator.warn <<~MSG
|
|
20
|
+
The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
|
|
21
|
+
MSG
|
|
22
|
+
@strict_query_string_separator = value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#--
|
|
26
|
+
# Note this departs from WHATWG's specified parsing algorithm by
|
|
27
|
+
# giving a nil value for keys that do not use '='. Callers that need
|
|
28
|
+
# the standard's interpretation can use `v.to_s`.
|
|
29
|
+
def self.each_pair(s, separator = nil)
|
|
30
|
+
return enum_for(:each_pair, s, separator) unless block_given?
|
|
31
|
+
|
|
32
|
+
s ||= ""
|
|
33
|
+
|
|
34
|
+
splitter =
|
|
35
|
+
if separator
|
|
36
|
+
COMMON_SEP[separator] || /[#{separator}] */n
|
|
37
|
+
else
|
|
38
|
+
DEFAULT_SEP
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
s.split(splitter).each do |part|
|
|
42
|
+
next if part.empty?
|
|
43
|
+
|
|
44
|
+
k, v = part.split("=", 2)
|
|
45
|
+
|
|
46
|
+
k = URI.decode_www_form_component(k)
|
|
47
|
+
v &&= URI.decode_www_form_component(v)
|
|
48
|
+
|
|
49
|
+
yield k, v
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -25,7 +25,6 @@ module ActionDispatch
|
|
|
25
25
|
include ActionDispatch::Http::FilterParameters
|
|
26
26
|
include ActionDispatch::Http::URL
|
|
27
27
|
include ActionDispatch::ContentSecurityPolicy::Request
|
|
28
|
-
include ActionDispatch::PermissionsPolicy::Request
|
|
29
28
|
include Rack::Request::Env
|
|
30
29
|
|
|
31
30
|
autoload :Session, "action_dispatch/request/session"
|
|
@@ -55,12 +54,17 @@ module ActionDispatch
|
|
|
55
54
|
METHOD
|
|
56
55
|
end
|
|
57
56
|
|
|
57
|
+
TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
|
|
58
|
+
|
|
58
59
|
def self.empty
|
|
59
60
|
new({})
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def initialize(env)
|
|
63
64
|
super
|
|
65
|
+
|
|
66
|
+
@rack_request = Rack::Request.new(env)
|
|
67
|
+
|
|
64
68
|
@method = nil
|
|
65
69
|
@request_method = nil
|
|
66
70
|
@remote_ip = nil
|
|
@@ -69,6 +73,8 @@ module ActionDispatch
|
|
|
69
73
|
@ip = nil
|
|
70
74
|
end
|
|
71
75
|
|
|
76
|
+
attr_reader :rack_request
|
|
77
|
+
|
|
72
78
|
def commit_cookie_jar! # :nodoc:
|
|
73
79
|
end
|
|
74
80
|
|
|
@@ -132,7 +138,7 @@ module ActionDispatch
|
|
|
132
138
|
|
|
133
139
|
# Populate the HTTP method lookup cache.
|
|
134
140
|
HTTP_METHODS.each { |method|
|
|
135
|
-
HTTP_METHOD_LOOKUP[method] = method.downcase.
|
|
141
|
+
HTTP_METHOD_LOOKUP[method] = method.downcase.tap { |m| m.tr!("-", "_") }.to_sym
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
alias raw_request_method request_method # :nodoc:
|
|
@@ -151,11 +157,17 @@ module ActionDispatch
|
|
|
151
157
|
#
|
|
152
158
|
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
|
|
153
159
|
def route_uri_pattern
|
|
154
|
-
get_header("action_dispatch.route_uri_pattern")
|
|
160
|
+
unless pattern = get_header("action_dispatch.route_uri_pattern")
|
|
161
|
+
route = get_header("action_dispatch.route")
|
|
162
|
+
return if route.nil?
|
|
163
|
+
pattern = route.path.spec.to_s
|
|
164
|
+
set_header("action_dispatch.route_uri_pattern", pattern)
|
|
165
|
+
end
|
|
166
|
+
pattern
|
|
155
167
|
end
|
|
156
168
|
|
|
157
|
-
def
|
|
158
|
-
|
|
169
|
+
def route=(route) # :nodoc:
|
|
170
|
+
@env["action_dispatch.route"] = route
|
|
159
171
|
end
|
|
160
172
|
|
|
161
173
|
def routes # :nodoc:
|
|
@@ -283,7 +295,7 @@ module ActionDispatch
|
|
|
283
295
|
|
|
284
296
|
# Returns the content length of the request as an integer.
|
|
285
297
|
def content_length
|
|
286
|
-
return raw_post.bytesize if
|
|
298
|
+
return raw_post.bytesize if has_header?(TRANSFER_ENCODING)
|
|
287
299
|
super.to_i
|
|
288
300
|
end
|
|
289
301
|
|
|
@@ -387,15 +399,12 @@ module ActionDispatch
|
|
|
387
399
|
# Override Rack's GET method to support indifferent access.
|
|
388
400
|
def GET
|
|
389
401
|
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)
|
|
402
|
+
encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
|
|
403
|
+
rack_query_params = ActionDispatch::ParamBuilder.from_query_string(rack_request.query_string, encoding_template: encoding_template)
|
|
404
|
+
|
|
405
|
+
set_header k, rack_query_params
|
|
397
406
|
end
|
|
398
|
-
rescue
|
|
407
|
+
rescue ActionDispatch::ParamError => e
|
|
399
408
|
raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
|
|
400
409
|
end
|
|
401
410
|
alias :query_parameters :GET
|
|
@@ -403,18 +412,54 @@ module ActionDispatch
|
|
|
403
412
|
# Override Rack's POST method to support indifferent access.
|
|
404
413
|
def POST
|
|
405
414
|
fetch_header("action_dispatch.request.request_parameters") do
|
|
406
|
-
|
|
407
|
-
|
|
415
|
+
encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action])
|
|
416
|
+
|
|
417
|
+
param_list = nil
|
|
418
|
+
pr = parse_formatted_parameters(params_parsers) do
|
|
419
|
+
if param_list = request_parameters_list
|
|
420
|
+
ActionDispatch::ParamBuilder.from_pairs(param_list, encoding_template: encoding_template)
|
|
421
|
+
else
|
|
422
|
+
# We're not using a version of Rack that provides raw form
|
|
423
|
+
# pairs; we must use its hash (and thus post-process it below).
|
|
424
|
+
fallback_request_parameters
|
|
425
|
+
end
|
|
408
426
|
end
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
427
|
+
|
|
428
|
+
# If the request body was parsed by a custom parser like JSON
|
|
429
|
+
# (and thus the above block was not run), we need to
|
|
430
|
+
# post-process the result hash.
|
|
431
|
+
if param_list.nil?
|
|
432
|
+
pr = ActionDispatch::ParamBuilder.from_hash(pr, encoding_template: encoding_template)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
self.request_parameters = pr
|
|
412
436
|
end
|
|
413
|
-
rescue
|
|
437
|
+
rescue ActionDispatch::ParamError, EOFError => e
|
|
414
438
|
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
|
|
415
439
|
end
|
|
416
440
|
alias :request_parameters :POST
|
|
417
441
|
|
|
442
|
+
def request_parameters_list
|
|
443
|
+
# We don't use Rack's parse result, but we must call it so Rack
|
|
444
|
+
# can populate the rack.request.* keys we need.
|
|
445
|
+
rack_post = rack_request.POST
|
|
446
|
+
|
|
447
|
+
if form_pairs = get_header("rack.request.form_pairs")
|
|
448
|
+
# Multipart
|
|
449
|
+
form_pairs
|
|
450
|
+
elsif form_vars = get_header("rack.request.form_vars")
|
|
451
|
+
# URL-encoded
|
|
452
|
+
ActionDispatch::QueryParser.each_pair(form_vars)
|
|
453
|
+
elsif rack_post && !rack_post.empty?
|
|
454
|
+
# It was multipart, but Rack did not preserve a pair list
|
|
455
|
+
# (probably too old). Flat parameter list is not available.
|
|
456
|
+
nil
|
|
457
|
+
else
|
|
458
|
+
# No request body, or not a format Rack knows
|
|
459
|
+
[]
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
418
463
|
# Returns the authorization header regardless of whether it was specified
|
|
419
464
|
# directly or through one of the proxy alternatives.
|
|
420
465
|
def authorization
|
|
@@ -469,7 +514,7 @@ module ActionDispatch
|
|
|
469
514
|
def read_body_stream
|
|
470
515
|
if body_stream
|
|
471
516
|
reset_stream(body_stream) do
|
|
472
|
-
if
|
|
517
|
+
if has_header?(TRANSFER_ENCODING)
|
|
473
518
|
body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
|
|
474
519
|
else
|
|
475
520
|
body_stream.read(content_length)
|
|
@@ -491,6 +536,10 @@ module ActionDispatch
|
|
|
491
536
|
yield
|
|
492
537
|
end
|
|
493
538
|
end
|
|
539
|
+
|
|
540
|
+
def fallback_request_parameters
|
|
541
|
+
rack_request.POST
|
|
542
|
+
end
|
|
494
543
|
end
|
|
495
544
|
end
|
|
496
545
|
|
|
@@ -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
|
|
@@ -265,14 +277,27 @@ module ActionDispatch # :nodoc:
|
|
|
265
277
|
# Sets the HTTP response's content MIME type. For example, in the controller you
|
|
266
278
|
# could write this:
|
|
267
279
|
#
|
|
268
|
-
# response.content_type = "text/
|
|
280
|
+
# response.content_type = "text/html"
|
|
281
|
+
#
|
|
282
|
+
# This method also accepts a symbol with the extension of the MIME type:
|
|
283
|
+
#
|
|
284
|
+
# response.content_type = :html
|
|
269
285
|
#
|
|
270
286
|
# If a character set has been defined for this response (see #charset=) then the
|
|
271
287
|
# character set information will also be included in the content type
|
|
272
288
|
# information.
|
|
273
289
|
def content_type=(content_type)
|
|
274
|
-
|
|
275
|
-
|
|
290
|
+
case content_type
|
|
291
|
+
when NilClass
|
|
292
|
+
return
|
|
293
|
+
when Symbol
|
|
294
|
+
mime_type = Mime[content_type]
|
|
295
|
+
raise ArgumentError, "Unknown MIME type #{content_type}" unless mime_type
|
|
296
|
+
new_header_info = ContentTypeHeader.new(mime_type.to_s)
|
|
297
|
+
else
|
|
298
|
+
new_header_info = parse_content_type(content_type.to_s)
|
|
299
|
+
end
|
|
300
|
+
|
|
276
301
|
prev_header_info = parsed_content_type_header
|
|
277
302
|
charset = new_header_info.charset || prev_header_info.charset
|
|
278
303
|
charset ||= self.class.default_charset unless prev_header_info.mime_type
|
|
@@ -342,7 +367,13 @@ module ActionDispatch # :nodoc:
|
|
|
342
367
|
# Returns the content of the response as a string. This contains the contents of
|
|
343
368
|
# any calls to `render`.
|
|
344
369
|
def body
|
|
345
|
-
@stream.
|
|
370
|
+
if @stream.respond_to?(:to_ary)
|
|
371
|
+
@stream.to_ary.join
|
|
372
|
+
elsif @stream.respond_to?(:body)
|
|
373
|
+
@stream.body
|
|
374
|
+
else
|
|
375
|
+
@stream
|
|
376
|
+
end
|
|
346
377
|
end
|
|
347
378
|
|
|
348
379
|
def write(string)
|
|
@@ -351,11 +382,16 @@ module ActionDispatch # :nodoc:
|
|
|
351
382
|
|
|
352
383
|
# Allows you to manually set or override the response body.
|
|
353
384
|
def body=(body)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
385
|
+
# Prevent ActionController::Metal::Live::Response from committing the response prematurely.
|
|
386
|
+
synchronize do
|
|
387
|
+
if body.respond_to?(:to_str)
|
|
388
|
+
@stream = build_buffer(self, [body])
|
|
389
|
+
elsif body.respond_to?(:to_path)
|
|
390
|
+
@stream = body
|
|
391
|
+
elsif body.respond_to?(:to_ary)
|
|
392
|
+
@stream = build_buffer(self, body)
|
|
393
|
+
else
|
|
394
|
+
@stream = body
|
|
359
395
|
end
|
|
360
396
|
end
|
|
361
397
|
end
|
|
@@ -496,10 +532,6 @@ module ActionDispatch # :nodoc:
|
|
|
496
532
|
Buffer.new response, body
|
|
497
533
|
end
|
|
498
534
|
|
|
499
|
-
def munge_body_object(body)
|
|
500
|
-
body.respond_to?(:each) ? body : [body]
|
|
501
|
-
end
|
|
502
|
-
|
|
503
535
|
def assign_default_content_type_and_charset!
|
|
504
536
|
return if media_type
|
|
505
537
|
|
|
@@ -513,6 +545,8 @@ module ActionDispatch # :nodoc:
|
|
|
513
545
|
@response = response
|
|
514
546
|
end
|
|
515
547
|
|
|
548
|
+
attr :response
|
|
549
|
+
|
|
516
550
|
def close
|
|
517
551
|
# Rack "close" maps to Response#abort, and **not** Response#close (which is used
|
|
518
552
|
# when the controller's finished writing)
|
|
@@ -11,8 +11,105 @@ module ActionDispatch
|
|
|
11
11
|
HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
|
|
12
12
|
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
|
|
13
13
|
|
|
14
|
+
# DomainExtractor provides utility methods for extracting domain and subdomain
|
|
15
|
+
# information from host strings. This module is used internally by Action Dispatch
|
|
16
|
+
# to parse host names and separate the domain from subdomains based on the
|
|
17
|
+
# top-level domain (TLD) length.
|
|
18
|
+
#
|
|
19
|
+
# The module assumes a standard domain structure where domains consist of:
|
|
20
|
+
# - Subdomains (optional, can be multiple levels)
|
|
21
|
+
# - Domain name
|
|
22
|
+
# - Top-level domain (TLD, can be multiple levels like .co.uk)
|
|
23
|
+
#
|
|
24
|
+
# For example, in "api.staging.example.co.uk":
|
|
25
|
+
# - Subdomains: ["api", "staging"]
|
|
26
|
+
# - Domain: "example.co.uk" (with tld_length=2)
|
|
27
|
+
# - TLD: "co.uk"
|
|
28
|
+
module DomainExtractor
|
|
29
|
+
extend self
|
|
30
|
+
|
|
31
|
+
# Extracts the domain part from a host string, including the specified
|
|
32
|
+
# number of top-level domain components.
|
|
33
|
+
#
|
|
34
|
+
# The domain includes the main domain name plus the TLD components.
|
|
35
|
+
# The +tld_length+ parameter specifies how many components from the right
|
|
36
|
+
# should be considered part of the TLD.
|
|
37
|
+
#
|
|
38
|
+
# ==== Parameters
|
|
39
|
+
#
|
|
40
|
+
# [+host+]
|
|
41
|
+
# The host string to extract the domain from.
|
|
42
|
+
#
|
|
43
|
+
# [+tld_length+]
|
|
44
|
+
# The number of domain components that make up the TLD. For example,
|
|
45
|
+
# use 1 for ".com" or 2 for ".co.uk".
|
|
46
|
+
#
|
|
47
|
+
# ==== Examples
|
|
48
|
+
#
|
|
49
|
+
# # Standard TLD (tld_length = 1)
|
|
50
|
+
# DomainExtractor.domain_from("www.example.com", 1)
|
|
51
|
+
# # => "example.com"
|
|
52
|
+
#
|
|
53
|
+
# # Country-code TLD (tld_length = 2)
|
|
54
|
+
# DomainExtractor.domain_from("www.example.co.uk", 2)
|
|
55
|
+
# # => "example.co.uk"
|
|
56
|
+
#
|
|
57
|
+
# # Multiple subdomains
|
|
58
|
+
# DomainExtractor.domain_from("api.staging.myapp.herokuapp.com", 1)
|
|
59
|
+
# # => "herokuapp.com"
|
|
60
|
+
#
|
|
61
|
+
# # Single component (returns the host itself)
|
|
62
|
+
# DomainExtractor.domain_from("localhost", 1)
|
|
63
|
+
# # => "localhost"
|
|
64
|
+
def domain_from(host, tld_length)
|
|
65
|
+
host.split(".").last(1 + tld_length).join(".")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Extracts the subdomain components from a host string as an Array.
|
|
69
|
+
#
|
|
70
|
+
# Returns all the components that come before the domain and TLD parts.
|
|
71
|
+
# The +tld_length+ parameter is used to determine where the domain begins
|
|
72
|
+
# so that everything before it is considered a subdomain.
|
|
73
|
+
#
|
|
74
|
+
# ==== Parameters
|
|
75
|
+
#
|
|
76
|
+
# [+host+]
|
|
77
|
+
# The host string to extract subdomains from.
|
|
78
|
+
#
|
|
79
|
+
# [+tld_length+]
|
|
80
|
+
# The number of domain components that make up the TLD. This affects
|
|
81
|
+
# where the domain boundary is calculated.
|
|
82
|
+
#
|
|
83
|
+
# ==== Examples
|
|
84
|
+
#
|
|
85
|
+
# # Standard TLD (tld_length = 1)
|
|
86
|
+
# DomainExtractor.subdomains_from("www.example.com", 1)
|
|
87
|
+
# # => ["www"]
|
|
88
|
+
#
|
|
89
|
+
# # Country-code TLD (tld_length = 2)
|
|
90
|
+
# DomainExtractor.subdomains_from("api.staging.example.co.uk", 2)
|
|
91
|
+
# # => ["api", "staging"]
|
|
92
|
+
#
|
|
93
|
+
# # No subdomains
|
|
94
|
+
# DomainExtractor.subdomains_from("example.com", 1)
|
|
95
|
+
# # => []
|
|
96
|
+
#
|
|
97
|
+
# # Single subdomain with complex TLD
|
|
98
|
+
# DomainExtractor.subdomains_from("www.mysite.co.uk", 2)
|
|
99
|
+
# # => ["www"]
|
|
100
|
+
#
|
|
101
|
+
# # Multiple levels of subdomains
|
|
102
|
+
# DomainExtractor.subdomains_from("dev.api.staging.example.com", 1)
|
|
103
|
+
# # => ["dev", "api", "staging"]
|
|
104
|
+
def subdomains_from(host, tld_length)
|
|
105
|
+
parts = host.split(".")
|
|
106
|
+
parts[0..-(tld_length + 2)]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
14
110
|
mattr_accessor :secure_protocol, default: false
|
|
15
111
|
mattr_accessor :tld_length, default: 1
|
|
112
|
+
mattr_accessor :domain_extractor, default: DomainExtractor
|
|
16
113
|
|
|
17
114
|
class << self
|
|
18
115
|
# Returns the domain part of a host given the domain level.
|
|
@@ -96,34 +193,33 @@ module ActionDispatch
|
|
|
96
193
|
end
|
|
97
194
|
|
|
98
195
|
def extract_domain_from(host, tld_length)
|
|
99
|
-
|
|
196
|
+
domain_extractor.domain_from(host, tld_length)
|
|
100
197
|
end
|
|
101
198
|
|
|
102
199
|
def extract_subdomains_from(host, tld_length)
|
|
103
|
-
|
|
104
|
-
parts[0..-(tld_length + 2)]
|
|
200
|
+
domain_extractor.subdomains_from(host, tld_length)
|
|
105
201
|
end
|
|
106
202
|
|
|
107
203
|
def build_host_url(host, port, protocol, options, path)
|
|
108
204
|
if match = host.match(HOST_REGEXP)
|
|
109
|
-
|
|
110
|
-
host
|
|
111
|
-
port
|
|
205
|
+
protocol_from_host = match[1] if protocol.nil?
|
|
206
|
+
host = match[2]
|
|
207
|
+
port = match[3] unless options.key? :port
|
|
112
208
|
end
|
|
113
209
|
|
|
114
|
-
protocol = normalize_protocol
|
|
210
|
+
protocol = protocol_from_host || normalize_protocol(protocol).dup
|
|
115
211
|
host = normalize_host(host, options)
|
|
212
|
+
port = normalize_port(port, protocol)
|
|
116
213
|
|
|
117
|
-
result = protocol
|
|
214
|
+
result = protocol
|
|
118
215
|
|
|
119
216
|
if options[:user] && options[:password]
|
|
120
217
|
result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
|
|
121
218
|
end
|
|
122
219
|
|
|
123
220
|
result << host
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
221
|
+
|
|
222
|
+
result << ":" << port.to_s if port
|
|
127
223
|
|
|
128
224
|
result.concat path
|
|
129
225
|
end
|
|
@@ -169,11 +265,11 @@ module ActionDispatch
|
|
|
169
265
|
return unless port
|
|
170
266
|
|
|
171
267
|
case protocol
|
|
172
|
-
when "//" then
|
|
268
|
+
when "//" then port
|
|
173
269
|
when "https://"
|
|
174
|
-
|
|
270
|
+
port unless port.to_i == 443
|
|
175
271
|
else
|
|
176
|
-
|
|
272
|
+
port unless port.to_i == 80
|
|
177
273
|
end
|
|
178
274
|
end
|
|
179
275
|
end
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
|
-
require "strscan"
|
|
6
|
-
|
|
7
5
|
module ActionDispatch
|
|
8
6
|
module Journey # :nodoc:
|
|
9
7
|
module GTG # :nodoc:
|
|
@@ -16,7 +14,13 @@ module ActionDispatch
|
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
class Simulator # :nodoc:
|
|
19
|
-
|
|
17
|
+
STATIC_TOKENS = Array.new(64)
|
|
18
|
+
STATIC_TOKENS[".".ord] = "."
|
|
19
|
+
STATIC_TOKENS["/".ord] = "/"
|
|
20
|
+
STATIC_TOKENS["?".ord] = "?"
|
|
21
|
+
STATIC_TOKENS.freeze
|
|
22
|
+
|
|
23
|
+
INITIAL_STATE = [0, nil].freeze
|
|
20
24
|
|
|
21
25
|
attr_reader :tt
|
|
22
26
|
|
|
@@ -25,21 +29,38 @@ module ActionDispatch
|
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def memos(string)
|
|
28
|
-
input = StringScanner.new(string)
|
|
29
32
|
state = INITIAL_STATE
|
|
30
|
-
start_index = 0
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
pos = 0
|
|
35
|
+
eos = string.bytesize
|
|
36
|
+
|
|
37
|
+
while pos < eos
|
|
38
|
+
start_index = pos
|
|
39
|
+
pos += 1
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
if (token = STATIC_TOKENS[string.getbyte(start_index)])
|
|
42
|
+
state = tt.move(state, string, token, start_index, false)
|
|
43
|
+
else
|
|
44
|
+
while pos < eos && STATIC_TOKENS[string.getbyte(pos)].nil?
|
|
45
|
+
pos += 1
|
|
46
|
+
end
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
token = string.byteslice(start_index, pos - start_index)
|
|
49
|
+
state = tt.move(state, string, token, start_index, true)
|
|
50
|
+
end
|
|
38
51
|
end
|
|
39
52
|
|
|
40
|
-
acceptance_states =
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
acceptance_states = []
|
|
54
|
+
states_count = state.size
|
|
55
|
+
i = 0
|
|
56
|
+
while i < states_count
|
|
57
|
+
if state[i + 1].nil?
|
|
58
|
+
s = state[i]
|
|
59
|
+
if tt.accepting?(s)
|
|
60
|
+
acceptance_states.concat(tt.memo(s))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
i += 2
|
|
43
64
|
end
|
|
44
65
|
|
|
45
66
|
acceptance_states.empty? ? yield : acceptance_states
|