rack 3.0.0 → 3.2.7
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 +592 -1
- data/CONTRIBUTING.md +11 -9
- data/README.md +120 -29
- data/SPEC.rdoc +206 -288
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +1 -4
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +18 -2
- data/lib/rack/builder.rb +61 -40
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/common_logger.rb +3 -2
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +6 -1
- data/lib/rack/directory.rb +6 -3
- data/lib/rack/etag.rb +3 -0
- data/lib/rack/events.rb +21 -6
- data/lib/rack/files.rb +1 -1
- data/lib/rack/head.rb +2 -3
- data/lib/rack/headers.rb +98 -14
- data/lib/rack/lint.rb +491 -428
- data/lib/rack/media_type.rb +18 -9
- data/lib/rack/method_override.rb +2 -2
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +50 -20
- data/lib/rack/multipart/parser.rb +279 -80
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +88 -80
- data/lib/rack/request.rb +98 -65
- data/lib/rack/response.rb +35 -20
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/sendfile.rb +53 -23
- data/lib/rack/show_exceptions.rb +10 -4
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/static.rb +9 -4
- data/lib/rack/urlmap.rb +1 -1
- data/lib/rack/utils.rb +205 -133
- data/lib/rack/version.rb +3 -20
- data/lib/rack.rb +11 -17
- metadata +6 -15
- data/lib/rack/auth/digest/md5.rb +0 -1
- data/lib/rack/auth/digest/nonce.rb +0 -1
- data/lib/rack/auth/digest/params.rb +0 -1
- data/lib/rack/auth/digest/request.rb +0 -1
- data/lib/rack/auth/digest.rb +0 -256
- data/lib/rack/chunked.rb +0 -120
- data/lib/rack/file.rb +0 -9
- data/lib/rack/logger.rb +0 -22
data/lib/rack/query_parser.rb
CHANGED
|
@@ -1,40 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'bad_request'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
3
6
|
module Rack
|
|
4
7
|
class QueryParser
|
|
5
|
-
DEFAULT_SEP =
|
|
6
|
-
COMMON_SEP = { ";" =>
|
|
8
|
+
DEFAULT_SEP = /& */n
|
|
9
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
|
|
7
10
|
|
|
8
11
|
# ParameterTypeError is the error that is raised when incoming structural
|
|
9
12
|
# parameters (parsed by parse_nested_query) contain conflicting types.
|
|
10
|
-
class ParameterTypeError < TypeError
|
|
13
|
+
class ParameterTypeError < TypeError
|
|
14
|
+
include BadRequest
|
|
15
|
+
end
|
|
11
16
|
|
|
12
17
|
# InvalidParameterError is the error that is raised when incoming structural
|
|
13
18
|
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
|
14
19
|
# sequence.
|
|
15
|
-
class InvalidParameterError < ArgumentError
|
|
20
|
+
class InvalidParameterError < ArgumentError
|
|
21
|
+
include BadRequest
|
|
22
|
+
end
|
|
16
23
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
class
|
|
24
|
+
# QueryLimitError is for errors raised when the query provided exceeds one
|
|
25
|
+
# of the query parser limits.
|
|
26
|
+
class QueryLimitError < RangeError
|
|
27
|
+
include BadRequest
|
|
28
|
+
end
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
# ParamsTooDeepError is the old name for the error that is raised when params
|
|
31
|
+
# are recursively nested over the specified limit. Make it the same as
|
|
32
|
+
# as QueryLimitError, so that code that rescues ParamsTooDeepError error
|
|
33
|
+
# to handle bad query strings also now handles other limits.
|
|
34
|
+
ParamsTooDeepError = QueryLimitError
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
def self.make_default(param_depth_limit, **options)
|
|
37
|
+
new(Params, param_depth_limit, **options)
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
attr_reader :param_depth_limit
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
env_int = lambda do |key, val|
|
|
43
|
+
if str_val = ENV[key]
|
|
44
|
+
begin
|
|
45
|
+
val = Integer(str_val, 10)
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
|
48
|
+
end
|
|
34
49
|
end
|
|
35
50
|
|
|
51
|
+
val
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
|
|
55
|
+
private_constant :BYTESIZE_LIMIT
|
|
56
|
+
|
|
57
|
+
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
|
58
|
+
private_constant :PARAMS_LIMIT
|
|
59
|
+
|
|
60
|
+
attr_reader :bytesize_limit
|
|
61
|
+
|
|
62
|
+
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
|
36
63
|
@params_class = params_class
|
|
37
64
|
@param_depth_limit = param_depth_limit
|
|
65
|
+
@bytesize_limit = bytesize_limit
|
|
66
|
+
@params_limit = params_limit
|
|
38
67
|
end
|
|
39
68
|
|
|
40
69
|
# Stolen from Mongrel, with some small modifications:
|
|
@@ -42,14 +71,9 @@ module Rack
|
|
|
42
71
|
# to parse cookies by changing the characters used in the second parameter
|
|
43
72
|
# (which defaults to '&').
|
|
44
73
|
def parse_query(qs, separator = nil, &unescaper)
|
|
45
|
-
unescaper ||= method(:unescape)
|
|
46
|
-
|
|
47
74
|
params = make_params
|
|
48
75
|
|
|
49
|
-
(qs
|
|
50
|
-
next if p.empty?
|
|
51
|
-
k, v = p.split('=', 2).map!(&unescaper)
|
|
52
|
-
|
|
76
|
+
each_query_pair(qs, separator, unescaper) do |k, v|
|
|
53
77
|
if cur = params[k]
|
|
54
78
|
if cur.class == Array
|
|
55
79
|
params[k] << v
|
|
@@ -64,6 +88,19 @@ module Rack
|
|
|
64
88
|
return params.to_h
|
|
65
89
|
end
|
|
66
90
|
|
|
91
|
+
# Parses a query string by breaking it up at the '&', returning all key-value
|
|
92
|
+
# pairs as an array of [key, value] arrays. Unlike parse_query, this preserves
|
|
93
|
+
# all duplicate keys rather than collapsing them.
|
|
94
|
+
def parse_query_pairs(qs, separator = nil)
|
|
95
|
+
pairs = []
|
|
96
|
+
|
|
97
|
+
each_query_pair(qs, separator) do |k, v|
|
|
98
|
+
pairs << [k, v]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
pairs
|
|
102
|
+
end
|
|
103
|
+
|
|
67
104
|
# parse_nested_query expands a query string into structural types. Supported
|
|
68
105
|
# types are Arrays, Hashes and basic value types. It is possible to supply
|
|
69
106
|
# query strings with parameters of conflicting types, in this case a
|
|
@@ -72,17 +109,11 @@ module Rack
|
|
|
72
109
|
def parse_nested_query(qs, separator = nil)
|
|
73
110
|
params = make_params
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
(
|
|
77
|
-
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
|
78
|
-
|
|
79
|
-
_normalize_params(params, k, v, 0)
|
|
80
|
-
end
|
|
112
|
+
each_query_pair(qs, separator) do |k, v|
|
|
113
|
+
_normalize_params(params, k, v, 0)
|
|
81
114
|
end
|
|
82
115
|
|
|
83
116
|
return params.to_h
|
|
84
|
-
rescue ArgumentError => e
|
|
85
|
-
raise InvalidParameterError, e.message, e.backtrace
|
|
86
117
|
end
|
|
87
118
|
|
|
88
119
|
# normalize_params recursively expands parameters into structural types. If
|
|
@@ -128,8 +159,6 @@ module Rack
|
|
|
128
159
|
|
|
129
160
|
return if k.empty?
|
|
130
161
|
|
|
131
|
-
v ||= String.new
|
|
132
|
-
|
|
133
162
|
if after == ''
|
|
134
163
|
if k == '[]' && depth != 0
|
|
135
164
|
return [v]
|
|
@@ -190,63 +219,42 @@ module Rack
|
|
|
190
219
|
true
|
|
191
220
|
end
|
|
192
221
|
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
class Params
|
|
198
|
-
def initialize
|
|
199
|
-
@size = 0
|
|
200
|
-
@params = {}
|
|
201
|
-
end
|
|
222
|
+
def each_query_pair(qs, separator, unescaper = nil)
|
|
223
|
+
return if !qs || qs.empty?
|
|
202
224
|
|
|
203
|
-
|
|
204
|
-
@
|
|
225
|
+
if qs.bytesize > @bytesize_limit
|
|
226
|
+
raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
|
|
205
227
|
end
|
|
206
228
|
|
|
207
|
-
|
|
208
|
-
@params[key] = value
|
|
209
|
-
end
|
|
229
|
+
pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
|
|
210
230
|
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
if pairs.size > @params_limit
|
|
232
|
+
param_count = pairs.size + pairs.last.count(separator || "&")
|
|
233
|
+
raise QueryLimitError, "total number of query parameters (#{param_count}) exceeds limit (#{@params_limit})"
|
|
213
234
|
end
|
|
214
235
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
# representation, not a copy.
|
|
227
|
-
#
|
|
228
|
-
# 3. Because the `Params` object's internal representation is mutable
|
|
229
|
-
# through the `#[]=` method, it is not thread safe. The result of
|
|
230
|
-
# getting the hash representation while another thread is adding a
|
|
231
|
-
# key to it is non-deterministic.
|
|
232
|
-
#
|
|
233
|
-
def to_h
|
|
234
|
-
@params.each do |key, value|
|
|
235
|
-
case value
|
|
236
|
-
when self
|
|
237
|
-
# Handle circular references gracefully.
|
|
238
|
-
@params[key] = @params
|
|
239
|
-
when Params
|
|
240
|
-
@params[key] = value.to_h
|
|
241
|
-
when Array
|
|
242
|
-
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
|
243
|
-
else
|
|
244
|
-
# Ignore anything that is not a `Params` object or
|
|
245
|
-
# a collection that can contain one.
|
|
246
|
-
end
|
|
236
|
+
if unescaper
|
|
237
|
+
pairs.each do |p|
|
|
238
|
+
next if p.empty?
|
|
239
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
|
240
|
+
yield k, v
|
|
241
|
+
end
|
|
242
|
+
else
|
|
243
|
+
pairs.each do |p|
|
|
244
|
+
next if p.empty?
|
|
245
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
|
246
|
+
yield k, v
|
|
247
247
|
end
|
|
248
|
-
@params
|
|
249
248
|
end
|
|
249
|
+
rescue ArgumentError => e
|
|
250
|
+
raise InvalidParameterError, e.message, e.backtrace
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def unescape(string, encoding = Encoding::UTF_8)
|
|
254
|
+
URI.decode_www_form_component(string, encoding)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class Params < Hash
|
|
250
258
|
alias_method :to_params_hash, :to_h
|
|
251
259
|
end
|
|
252
260
|
end
|
data/lib/rack/request.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Rack
|
|
|
44
44
|
@x_forwarded_proto_priority = [:proto, :scheme]
|
|
45
45
|
|
|
46
46
|
valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
trusted_proxies = Regexp.union(
|
|
49
49
|
/\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330
|
|
50
50
|
/\A::1\z/, # localhost IPv6 ::1
|
|
@@ -54,16 +54,21 @@ module Rack
|
|
|
54
54
|
/\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x
|
|
55
55
|
/\Alocalhost\z|\Aunix(\z|:)/i, # localhost hostname, and unix domain sockets
|
|
56
56
|
)
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
|
|
59
59
|
|
|
60
60
|
ALLOWED_SCHEMES = %w(https http wss ws).freeze
|
|
61
61
|
|
|
62
62
|
def initialize(env)
|
|
63
63
|
@env = env
|
|
64
|
+
@ip = nil
|
|
64
65
|
@params = nil
|
|
65
66
|
end
|
|
66
67
|
|
|
68
|
+
def ip
|
|
69
|
+
@ip ||= super
|
|
70
|
+
end
|
|
71
|
+
|
|
67
72
|
def params
|
|
68
73
|
@params ||= super
|
|
69
74
|
end
|
|
@@ -398,8 +403,8 @@ module Rack
|
|
|
398
403
|
return forwarded.last
|
|
399
404
|
end
|
|
400
405
|
when :x_forwarded
|
|
401
|
-
if value = get_header(HTTP_X_FORWARDED_HOST)
|
|
402
|
-
return wrap_ipv6(
|
|
406
|
+
if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
|
|
407
|
+
return wrap_ipv6(x_forwarded_host)
|
|
403
408
|
end
|
|
404
409
|
end
|
|
405
410
|
end
|
|
@@ -413,10 +418,9 @@ module Rack
|
|
|
413
418
|
|
|
414
419
|
def ip
|
|
415
420
|
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
|
416
|
-
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
|
417
421
|
|
|
418
|
-
|
|
419
|
-
return
|
|
422
|
+
remote_addresses.reverse_each do |ip|
|
|
423
|
+
return ip unless trusted_proxy?(ip)
|
|
420
424
|
end
|
|
421
425
|
|
|
422
426
|
if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
|
|
@@ -424,7 +428,10 @@ module Rack
|
|
|
424
428
|
# So we reject all the trusted addresses (proxy*) and return the
|
|
425
429
|
# last client. Or if we trust everyone, we just return the first
|
|
426
430
|
# address.
|
|
427
|
-
|
|
431
|
+
forwarded_for.reverse_each do |ip|
|
|
432
|
+
return ip unless trusted_proxy?(ip)
|
|
433
|
+
end
|
|
434
|
+
return forwarded_for.first
|
|
428
435
|
end
|
|
429
436
|
|
|
430
437
|
# If all the addresses are trusted, and we aren't forwarded, just return
|
|
@@ -482,12 +489,49 @@ module Rack
|
|
|
482
489
|
|
|
483
490
|
# Returns the data received in the query string.
|
|
484
491
|
def GET
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Returns the form data pairs received in the request body.
|
|
496
|
+
#
|
|
497
|
+
# This method support both application/x-www-form-urlencoded and
|
|
498
|
+
# multipart/form-data.
|
|
499
|
+
def form_pairs
|
|
500
|
+
if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
|
|
501
|
+
return pairs
|
|
502
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
503
|
+
raise error.class, error.message, cause: error.cause
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
begin
|
|
507
|
+
rack_input = get_header(RACK_INPUT)
|
|
508
|
+
|
|
509
|
+
# Otherwise, figure out how to parse the input:
|
|
510
|
+
if rack_input.nil?
|
|
511
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
512
|
+
elsif form_data? || parseable_data?
|
|
513
|
+
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
|
514
|
+
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
|
515
|
+
else
|
|
516
|
+
# Add 2 bytes. One to check whether it is over the limit, and a second
|
|
517
|
+
# in case the slice! call below removes the last byte
|
|
518
|
+
# If read returns nil, use the empty string
|
|
519
|
+
form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
|
|
520
|
+
|
|
521
|
+
# Fix for Safari Ajax postings that always append \0
|
|
522
|
+
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
523
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
524
|
+
|
|
525
|
+
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
526
|
+
pairs = query_parser.parse_query_pairs(form_vars, '&')
|
|
527
|
+
set_header(RACK_REQUEST_FORM_PAIRS, pairs)
|
|
528
|
+
end
|
|
529
|
+
else
|
|
530
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
531
|
+
end
|
|
532
|
+
rescue => error
|
|
533
|
+
set_header(RACK_REQUEST_FORM_ERROR, error)
|
|
534
|
+
raise
|
|
491
535
|
end
|
|
492
536
|
end
|
|
493
537
|
|
|
@@ -496,27 +540,14 @@ module Rack
|
|
|
496
540
|
# This method support both application/x-www-form-urlencoded and
|
|
497
541
|
# multipart/form-data.
|
|
498
542
|
def POST
|
|
499
|
-
if get_header(
|
|
500
|
-
|
|
501
|
-
elsif
|
|
502
|
-
|
|
503
|
-
elsif form_data? || parseable_data?
|
|
504
|
-
unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
|
|
505
|
-
form_vars = get_header(RACK_INPUT).read
|
|
506
|
-
|
|
507
|
-
# Fix for Safari Ajax postings that always append \0
|
|
508
|
-
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
509
|
-
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
510
|
-
|
|
511
|
-
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
512
|
-
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
|
513
|
-
end
|
|
514
|
-
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
|
515
|
-
get_header RACK_REQUEST_FORM_HASH
|
|
516
|
-
else
|
|
517
|
-
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
|
518
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
|
543
|
+
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
|
544
|
+
return form_hash
|
|
545
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
546
|
+
raise error.class, error.message, cause: error.cause
|
|
519
547
|
end
|
|
548
|
+
|
|
549
|
+
pairs = form_pairs
|
|
550
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
|
520
551
|
end
|
|
521
552
|
|
|
522
553
|
# The union of GET and POST data.
|
|
@@ -526,6 +557,10 @@ module Rack
|
|
|
526
557
|
self.GET.merge(self.POST)
|
|
527
558
|
end
|
|
528
559
|
|
|
560
|
+
# Allow overriding the query parser that the receiver will use.
|
|
561
|
+
# By default Rack::Utils.default_query_parser is used.
|
|
562
|
+
attr_writer :query_parser
|
|
563
|
+
|
|
529
564
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
|
530
565
|
#
|
|
531
566
|
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
|
@@ -585,27 +620,6 @@ module Rack
|
|
|
585
620
|
Rack::Request.ip_filter.call(ip)
|
|
586
621
|
end
|
|
587
622
|
|
|
588
|
-
# shortcut for <tt>request.params[key]</tt>
|
|
589
|
-
def [](key)
|
|
590
|
-
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
|
|
591
|
-
|
|
592
|
-
params[key.to_s]
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
# shortcut for <tt>request.params[key] = value</tt>
|
|
596
|
-
#
|
|
597
|
-
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
598
|
-
def []=(key, value)
|
|
599
|
-
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
|
|
600
|
-
|
|
601
|
-
params[key.to_s] = value
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
# like Hash#values_at
|
|
605
|
-
def values_at(*keys)
|
|
606
|
-
keys.map { |key| params[key] }
|
|
607
|
-
end
|
|
608
|
-
|
|
609
623
|
private
|
|
610
624
|
|
|
611
625
|
def default_session; {}; end
|
|
@@ -625,14 +639,26 @@ module Rack
|
|
|
625
639
|
end
|
|
626
640
|
|
|
627
641
|
def parse_http_accept_header(header)
|
|
628
|
-
|
|
629
|
-
|
|
642
|
+
# It would be nice to use filter_map here, but it's Ruby 2.7+
|
|
643
|
+
parts = header.to_s.split(',')
|
|
644
|
+
|
|
645
|
+
parts.map! do |part|
|
|
646
|
+
part.strip!
|
|
647
|
+
next if part.empty?
|
|
648
|
+
|
|
649
|
+
attribute, parameters = part.split(';', 2)
|
|
650
|
+
attribute.strip!
|
|
651
|
+
parameters&.strip!
|
|
630
652
|
quality = 1.0
|
|
631
653
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
|
632
654
|
quality = $1.to_f
|
|
633
655
|
end
|
|
634
656
|
[attribute, quality]
|
|
635
657
|
end
|
|
658
|
+
|
|
659
|
+
parts.compact!
|
|
660
|
+
|
|
661
|
+
parts
|
|
636
662
|
end
|
|
637
663
|
|
|
638
664
|
# Get an array of values set in the RFC 7239 `Forwarded` request header.
|
|
@@ -641,7 +667,7 @@ module Rack
|
|
|
641
667
|
end
|
|
642
668
|
|
|
643
669
|
def query_parser
|
|
644
|
-
Utils.default_query_parser
|
|
670
|
+
@query_parser || Utils.default_query_parser
|
|
645
671
|
end
|
|
646
672
|
|
|
647
673
|
def parse_query(qs, d = '&')
|
|
@@ -649,11 +675,22 @@ module Rack
|
|
|
649
675
|
end
|
|
650
676
|
|
|
651
677
|
def parse_multipart
|
|
678
|
+
warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
|
|
652
679
|
Rack::Multipart.extract_multipart(self, query_parser)
|
|
653
680
|
end
|
|
654
681
|
|
|
682
|
+
def expand_param_pairs(pairs, query_parser = query_parser())
|
|
683
|
+
params = query_parser.make_params
|
|
684
|
+
|
|
685
|
+
pairs.each do |k, v|
|
|
686
|
+
query_parser.normalize_params(params, k, v)
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
params.to_params_hash
|
|
690
|
+
end
|
|
691
|
+
|
|
655
692
|
def split_header(value)
|
|
656
|
-
value ? value.strip.split(/[
|
|
693
|
+
value ? value.strip.split(/[, \t]+/) : []
|
|
657
694
|
end
|
|
658
695
|
|
|
659
696
|
# ipv6 extracted from resolv stdlib, simplified
|
|
@@ -686,8 +723,8 @@ module Rack
|
|
|
686
723
|
# Match IPv6 as a string of hex digits and colons in square brackets
|
|
687
724
|
\[(?<address>#{ipv6})\]
|
|
688
725
|
|
|
|
689
|
-
# Match
|
|
690
|
-
(?<address>[
|
|
726
|
+
# Match characters allowed by RFC 3986 Section 3.2.2
|
|
727
|
+
(?<address>[-a-zA-Z0-9._~%!$&'()*+,;=]*?)
|
|
691
728
|
)
|
|
692
729
|
(:(?<port>\d+))?
|
|
693
730
|
\z
|
|
@@ -701,10 +738,6 @@ module Rack
|
|
|
701
738
|
return match[:host], match[:address], match[:port]&.to_i
|
|
702
739
|
end
|
|
703
740
|
|
|
704
|
-
def reject_trusted_ip_addresses(ip_addresses)
|
|
705
|
-
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
|
706
|
-
end
|
|
707
|
-
|
|
708
741
|
FORWARDED_SCHEME_HEADERS = {
|
|
709
742
|
proto: HTTP_X_FORWARDED_PROTO,
|
|
710
743
|
scheme: HTTP_X_FORWARDED_SCHEME
|
data/lib/rack/response.rb
CHANGED
|
@@ -31,19 +31,12 @@ module Rack
|
|
|
31
31
|
attr_accessor :length, :status, :body
|
|
32
32
|
attr_reader :headers
|
|
33
33
|
|
|
34
|
-
# Deprecated, use headers instead.
|
|
35
|
-
def header
|
|
36
|
-
warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1
|
|
37
|
-
|
|
38
|
-
headers
|
|
39
|
-
end
|
|
40
|
-
|
|
41
34
|
# Initialize the response object with the specified +body+, +status+
|
|
42
35
|
# and +headers+.
|
|
43
36
|
#
|
|
44
37
|
# If the +body+ is +nil+, construct an empty response object with internal
|
|
45
38
|
# buffering.
|
|
46
|
-
#
|
|
39
|
+
#
|
|
47
40
|
# If the +body+ responds to +to_str+, assume it's a string-like object and
|
|
48
41
|
# construct a buffered response object containing using that string as the
|
|
49
42
|
# initial contents of the buffer.
|
|
@@ -62,7 +55,7 @@ module Rack
|
|
|
62
55
|
@status = status.to_i
|
|
63
56
|
|
|
64
57
|
unless headers.is_a?(Hash)
|
|
65
|
-
|
|
58
|
+
raise ArgumentError, "Headers must be a Hash!"
|
|
66
59
|
end
|
|
67
60
|
|
|
68
61
|
@headers = Headers.new
|
|
@@ -79,7 +72,8 @@ module Rack
|
|
|
79
72
|
if body.nil?
|
|
80
73
|
@body = []
|
|
81
74
|
@buffered = true
|
|
82
|
-
|
|
75
|
+
# Body is unspecified - it may be a buffered response, or it may be a HEAD response.
|
|
76
|
+
@length = nil
|
|
83
77
|
elsif body.respond_to?(:to_str)
|
|
84
78
|
@body = [body]
|
|
85
79
|
@buffered = true
|
|
@@ -87,7 +81,7 @@ module Rack
|
|
|
87
81
|
else
|
|
88
82
|
@body = body
|
|
89
83
|
@buffered = nil # undetermined as of yet.
|
|
90
|
-
@length =
|
|
84
|
+
@length = nil
|
|
91
85
|
end
|
|
92
86
|
|
|
93
87
|
yield self if block_given?
|
|
@@ -102,20 +96,30 @@ module Rack
|
|
|
102
96
|
CHUNKED == get_header(TRANSFER_ENCODING)
|
|
103
97
|
end
|
|
104
98
|
|
|
99
|
+
def no_entity_body?
|
|
100
|
+
# The response body is an enumerable body and it is not allowed to have an entity body.
|
|
101
|
+
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
|
102
|
+
end
|
|
103
|
+
|
|
105
104
|
# Generate a response array consistent with the requirements of the SPEC.
|
|
106
105
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
107
106
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
108
107
|
def finish(&block)
|
|
109
|
-
if
|
|
108
|
+
if no_entity_body?
|
|
110
109
|
delete_header CONTENT_TYPE
|
|
111
110
|
delete_header CONTENT_LENGTH
|
|
112
111
|
close
|
|
113
112
|
return [@status, @headers, []]
|
|
114
113
|
else
|
|
115
114
|
if block_given?
|
|
115
|
+
# We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
|
|
116
116
|
@block = block
|
|
117
117
|
return [@status, @headers, self]
|
|
118
118
|
else
|
|
119
|
+
# If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
|
|
120
|
+
if @length && !chunked?
|
|
121
|
+
@headers[CONTENT_LENGTH] = @length.to_s
|
|
122
|
+
end
|
|
119
123
|
return [@status, @headers, @body]
|
|
120
124
|
end
|
|
121
125
|
end
|
|
@@ -133,7 +137,9 @@ module Rack
|
|
|
133
137
|
end
|
|
134
138
|
end
|
|
135
139
|
|
|
136
|
-
# Append to
|
|
140
|
+
# Append a chunk to the response body.
|
|
141
|
+
#
|
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
|
137
143
|
#
|
|
138
144
|
# NOTE: Do not mix #write and direct #body access!
|
|
139
145
|
#
|
|
@@ -315,27 +321,34 @@ module Rack
|
|
|
315
321
|
|
|
316
322
|
protected
|
|
317
323
|
|
|
324
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
|
325
|
+
#
|
|
326
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
|
327
|
+
# * `nil` - The body has not been buffered yet.
|
|
328
|
+
# * `true` - The body is buffered as an Array instance.
|
|
329
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
|
330
|
+
#
|
|
331
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
|
318
332
|
def buffered_body!
|
|
319
333
|
if @buffered.nil?
|
|
320
334
|
if @body.is_a?(Array)
|
|
321
335
|
# The user supplied body was an array:
|
|
322
336
|
@body = @body.compact
|
|
323
|
-
@body.
|
|
324
|
-
|
|
325
|
-
end
|
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
|
338
|
+
@buffered = true
|
|
326
339
|
elsif @body.respond_to?(:each)
|
|
327
340
|
# Turn the user supplied body into a buffered array:
|
|
328
341
|
body = @body
|
|
329
342
|
@body = Array.new
|
|
343
|
+
@buffered = true
|
|
330
344
|
|
|
331
345
|
body.each do |part|
|
|
332
346
|
@writer.call(part.to_s)
|
|
333
347
|
end
|
|
334
348
|
|
|
335
349
|
body.close if body.respond_to?(:close)
|
|
336
|
-
|
|
337
|
-
@buffered = true
|
|
338
350
|
else
|
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
|
339
352
|
@buffered = false
|
|
340
353
|
end
|
|
341
354
|
end
|
|
@@ -344,11 +357,13 @@ module Rack
|
|
|
344
357
|
end
|
|
345
358
|
|
|
346
359
|
def append(chunk)
|
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
|
347
361
|
@body << chunk
|
|
348
362
|
|
|
349
|
-
|
|
363
|
+
if @length
|
|
350
364
|
@length += chunk.bytesize
|
|
351
|
-
|
|
365
|
+
elsif @buffered
|
|
366
|
+
@length = chunk.bytesize
|
|
352
367
|
end
|
|
353
368
|
|
|
354
369
|
return chunk
|