rack 3.0.11 → 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -3
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -15
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/constants.rb +3 -0
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +118 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +14 -16
- data/lib/rack/multipart/parser.rb +122 -62
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +15 -68
- data/lib/rack/request.rb +40 -21
- data/lib/rack/response.rb +28 -20
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +71 -98
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +10 -16
- metadata +4 -10
- 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
@@ -3,51 +3,46 @@
|
|
3
3
|
require 'strscan'
|
4
4
|
|
5
5
|
require_relative '../utils'
|
6
|
+
require_relative '../bad_request'
|
6
7
|
|
7
8
|
module Rack
|
8
9
|
module Multipart
|
9
|
-
class MultipartPartLimitError < Errno::EMFILE
|
10
|
+
class MultipartPartLimitError < Errno::EMFILE
|
11
|
+
include BadRequest
|
12
|
+
end
|
10
13
|
|
11
|
-
class MultipartTotalPartLimitError < StandardError
|
14
|
+
class MultipartTotalPartLimitError < StandardError
|
15
|
+
include BadRequest
|
16
|
+
end
|
12
17
|
|
13
18
|
# Use specific error class when parsing multipart request
|
14
19
|
# that ends early.
|
15
|
-
class EmptyContentError < ::EOFError
|
20
|
+
class EmptyContentError < ::EOFError
|
21
|
+
include BadRequest
|
22
|
+
end
|
16
23
|
|
17
24
|
# Base class for multipart exceptions that do not subclass from
|
18
25
|
# other exception classes for backwards compatibility.
|
19
|
-
class
|
26
|
+
class BoundaryTooLongError < StandardError
|
27
|
+
include BadRequest
|
28
|
+
end
|
29
|
+
|
30
|
+
# Prefer to use the BoundaryTooLongError class or Rack::BadRequest.
|
31
|
+
Error = BoundaryTooLongError
|
20
32
|
|
21
33
|
EOL = "\r\n"
|
22
34
|
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
23
|
-
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
24
|
-
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
25
|
-
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
26
|
-
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
27
35
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
28
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:
|
36
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:(.*)(?=#{EOL}(\S|\z))/ni
|
29
37
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
30
|
-
# Updated definitions from RFC 2231
|
31
|
-
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
32
|
-
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
33
|
-
SECTION = /\*[0-9]+/
|
34
|
-
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
35
|
-
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
36
|
-
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
37
|
-
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
38
|
-
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
39
|
-
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
40
|
-
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
41
|
-
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
42
|
-
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
43
|
-
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
44
|
-
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
45
38
|
|
46
39
|
class Parser
|
47
40
|
BUFSIZE = 1_048_576
|
48
41
|
TEXT_PLAIN = "text/plain"
|
49
42
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
50
|
-
|
43
|
+
extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
|
44
|
+
|
45
|
+
Tempfile.new(["RackMultipart", extension])
|
51
46
|
}
|
52
47
|
|
53
48
|
class BoundedIO # :nodoc:
|
@@ -98,7 +93,7 @@ module Rack
|
|
98
93
|
if boundary.length > 70
|
99
94
|
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
100
95
|
# Most clients use no more than 55 characters.
|
101
|
-
raise
|
96
|
+
raise BoundaryTooLongError, "multipart boundary size too large (#{boundary.length} characters)"
|
102
97
|
end
|
103
98
|
|
104
99
|
io = BoundedIO.new(io, content_length) if content_length
|
@@ -213,6 +208,7 @@ module Rack
|
|
213
208
|
|
214
209
|
@sbuf = StringScanner.new("".dup)
|
215
210
|
@body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
|
211
|
+
@body_regex_at_end = /#{@body_regex}\z/m
|
216
212
|
@end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish)
|
217
213
|
@rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
|
218
214
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
@@ -305,17 +301,101 @@ module Rack
|
|
305
301
|
end
|
306
302
|
end
|
307
303
|
|
304
|
+
CONTENT_DISPOSITION_MAX_PARAMS = 16
|
305
|
+
CONTENT_DISPOSITION_MAX_BYTES = 1536
|
308
306
|
def handle_mime_head
|
309
307
|
if @sbuf.scan_until(@head_regex)
|
310
308
|
head = @sbuf[1]
|
311
309
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
312
|
-
if
|
313
|
-
|
310
|
+
if (disposition = head[MULTIPART_CONTENT_DISPOSITION, 1]) &&
|
311
|
+
disposition.bytesize <= CONTENT_DISPOSITION_MAX_BYTES
|
312
|
+
|
313
|
+
# ignore actual content-disposition value (should always be form-data)
|
314
|
+
i = disposition.index(';')
|
315
|
+
disposition.slice!(0, i+1)
|
316
|
+
param = nil
|
317
|
+
num_params = 0
|
318
|
+
|
319
|
+
# Parse parameter list
|
320
|
+
while i = disposition.index('=')
|
321
|
+
# Only parse up to max parameters, to avoid potential denial of service
|
322
|
+
num_params += 1
|
323
|
+
break if num_params > CONTENT_DISPOSITION_MAX_PARAMS
|
324
|
+
|
325
|
+
# Found end of parameter name, ensure forward progress in loop
|
326
|
+
param = disposition.slice!(0, i+1)
|
327
|
+
|
328
|
+
# Remove ending equals and preceding whitespace from parameter name
|
329
|
+
param.chomp!('=')
|
330
|
+
param.lstrip!
|
331
|
+
|
332
|
+
if disposition[0] == '"'
|
333
|
+
# Parameter value is quoted, parse it, handling backslash escapes
|
334
|
+
disposition.slice!(0, 1)
|
335
|
+
value = String.new
|
336
|
+
|
337
|
+
while i = disposition.index(/(["\\])/)
|
338
|
+
c = $1
|
339
|
+
|
340
|
+
# Append all content until ending quote or escape
|
341
|
+
value << disposition.slice!(0, i)
|
342
|
+
|
343
|
+
# Remove either backslash or ending quote,
|
344
|
+
# ensures forward progress in loop
|
345
|
+
disposition.slice!(0, 1)
|
346
|
+
|
347
|
+
# stop parsing parameter value if found ending quote
|
348
|
+
break if c == '"'
|
349
|
+
|
350
|
+
escaped_char = disposition.slice!(0, 1)
|
351
|
+
if param == 'filename' && escaped_char != '"'
|
352
|
+
# Possible IE uploaded filename, append both escape backslash and value
|
353
|
+
value << c << escaped_char
|
354
|
+
else
|
355
|
+
# Other only append escaped value
|
356
|
+
value << escaped_char
|
357
|
+
end
|
358
|
+
end
|
359
|
+
else
|
360
|
+
if i = disposition.index(';')
|
361
|
+
# Parameter value unquoted (which may be invalid), value ends at semicolon
|
362
|
+
value = disposition.slice!(0, i)
|
363
|
+
else
|
364
|
+
# If no ending semicolon, assume remainder of line is value and stop
|
365
|
+
# parsing
|
366
|
+
disposition.strip!
|
367
|
+
value = disposition
|
368
|
+
disposition = ''
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
case param
|
373
|
+
when 'name'
|
374
|
+
name = value
|
375
|
+
when 'filename'
|
376
|
+
filename = value
|
377
|
+
when 'filename*'
|
378
|
+
filename_star = value
|
379
|
+
# else
|
380
|
+
# ignore other parameters
|
381
|
+
end
|
382
|
+
|
383
|
+
# skip trailing semicolon, to proceed to next parameter
|
384
|
+
if i = disposition.index(';')
|
385
|
+
disposition.slice!(0, i+1)
|
386
|
+
end
|
387
|
+
end
|
314
388
|
else
|
315
389
|
name = head[MULTIPART_CONTENT_ID, 1]
|
316
390
|
end
|
317
391
|
|
318
|
-
|
392
|
+
if filename_star
|
393
|
+
encoding, _, filename = filename_star.split("'", 3)
|
394
|
+
filename = normalize_filename(filename || '')
|
395
|
+
filename.force_encoding(find_encoding(encoding))
|
396
|
+
elsif filename
|
397
|
+
filename = normalize_filename(filename)
|
398
|
+
end
|
319
399
|
|
320
400
|
if name.nil? || name.empty?
|
321
401
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
@@ -330,7 +410,7 @@ module Rack
|
|
330
410
|
|
331
411
|
def handle_mime_body
|
332
412
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
333
|
-
body = body_with_boundary.sub(
|
413
|
+
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
334
414
|
@collector.on_mime_body @mime_index, body
|
335
415
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
336
416
|
@state = :CONSUME_TOKEN
|
@@ -360,39 +440,14 @@ module Rack
|
|
360
440
|
end
|
361
441
|
end
|
362
442
|
|
363
|
-
def
|
364
|
-
filename = nil
|
365
|
-
case head
|
366
|
-
when RFC2183
|
367
|
-
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
368
|
-
|
369
|
-
if filename = params['filename*']
|
370
|
-
encoding, _, filename = filename.split("'", 3)
|
371
|
-
elsif filename = params['filename']
|
372
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
373
|
-
end
|
374
|
-
when BROKEN
|
375
|
-
filename = $1
|
376
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
377
|
-
end
|
378
|
-
|
379
|
-
return unless filename
|
380
|
-
|
443
|
+
def normalize_filename(filename)
|
381
444
|
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
382
445
|
filename = Utils.unescape_path(filename)
|
383
446
|
end
|
384
447
|
|
385
448
|
filename.scrub!
|
386
449
|
|
387
|
-
|
388
|
-
filename = filename.gsub(/\\(.)/, '\1')
|
389
|
-
end
|
390
|
-
|
391
|
-
if encoding
|
392
|
-
filename.force_encoding ::Encoding.find(encoding)
|
393
|
-
end
|
394
|
-
|
395
|
-
filename
|
450
|
+
filename.split(/[\/\\]/).last || String.new
|
396
451
|
end
|
397
452
|
|
398
453
|
CHARSET = "charset"
|
@@ -418,11 +473,7 @@ module Rack
|
|
418
473
|
v.strip!
|
419
474
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
420
475
|
if k == "charset"
|
421
|
-
encoding =
|
422
|
-
Encoding.find v
|
423
|
-
rescue ArgumentError
|
424
|
-
Encoding::BINARY
|
425
|
-
end
|
476
|
+
encoding = find_encoding(v)
|
426
477
|
end
|
427
478
|
end
|
428
479
|
end
|
@@ -432,6 +483,15 @@ module Rack
|
|
432
483
|
body.force_encoding(encoding)
|
433
484
|
end
|
434
485
|
|
486
|
+
# Return the related Encoding object. However, because
|
487
|
+
# enc is submitted by the user, it may be invalid, so
|
488
|
+
# use a binary encoding in that case.
|
489
|
+
def find_encoding(enc)
|
490
|
+
Encoding.find enc
|
491
|
+
rescue ArgumentError
|
492
|
+
Encoding::BINARY
|
493
|
+
end
|
494
|
+
|
435
495
|
def handle_empty_content!(content)
|
436
496
|
if content.nil? || content.empty?
|
437
497
|
raise EmptyContentError
|
data/lib/rack/multipart.rb
CHANGED
@@ -6,6 +6,8 @@ require_relative 'utils'
|
|
6
6
|
require_relative 'multipart/parser'
|
7
7
|
require_relative 'multipart/generator'
|
8
8
|
|
9
|
+
require_relative 'bad_request'
|
10
|
+
|
9
11
|
module Rack
|
10
12
|
# A multipart form data parser, adapted from IOWA.
|
11
13
|
#
|
@@ -13,9 +15,40 @@ module Rack
|
|
13
15
|
module Multipart
|
14
16
|
MULTIPART_BOUNDARY = "AaB03x"
|
15
17
|
|
18
|
+
class MissingInputError < StandardError
|
19
|
+
include BadRequest
|
20
|
+
end
|
21
|
+
|
22
|
+
# Accumulator for multipart form data, conforming to the QueryParser API.
|
23
|
+
# In future, the Parser could return the pair list directly, but that would
|
24
|
+
# change its API.
|
25
|
+
class ParamList # :nodoc:
|
26
|
+
def self.make_params
|
27
|
+
new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.normalize_params(params, key, value)
|
31
|
+
params << [key, value]
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@pairs = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def <<(pair)
|
39
|
+
@pairs << pair
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_params_hash
|
43
|
+
@pairs
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
16
47
|
class << self
|
17
48
|
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
18
|
-
io = env[RACK_INPUT]
|
49
|
+
unless io = env[RACK_INPUT]
|
50
|
+
raise MissingInputError, "Missing input stream!"
|
51
|
+
end
|
19
52
|
|
20
53
|
if content_length = env['CONTENT_LENGTH']
|
21
54
|
content_length = content_length.to_i
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,40 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'bad_request'
|
3
4
|
require 'uri'
|
4
5
|
|
5
6
|
module Rack
|
6
7
|
class QueryParser
|
7
|
-
DEFAULT_SEP =
|
8
|
-
COMMON_SEP = { ";" =>
|
8
|
+
DEFAULT_SEP = /& */n
|
9
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
|
9
10
|
|
10
11
|
# ParameterTypeError is the error that is raised when incoming structural
|
11
12
|
# parameters (parsed by parse_nested_query) contain conflicting types.
|
12
|
-
class ParameterTypeError < TypeError
|
13
|
+
class ParameterTypeError < TypeError
|
14
|
+
include BadRequest
|
15
|
+
end
|
13
16
|
|
14
17
|
# InvalidParameterError is the error that is raised when incoming structural
|
15
18
|
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
16
19
|
# sequence.
|
17
|
-
class InvalidParameterError < ArgumentError
|
20
|
+
class InvalidParameterError < ArgumentError
|
21
|
+
include BadRequest
|
22
|
+
end
|
18
23
|
|
19
24
|
# ParamsTooDeepError is the error that is raised when params are recursively
|
20
25
|
# nested over the specified limit.
|
21
|
-
class ParamsTooDeepError < RangeError
|
22
|
-
|
23
|
-
|
24
|
-
unless not_deprecated
|
25
|
-
warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
|
26
|
-
end
|
26
|
+
class ParamsTooDeepError < RangeError
|
27
|
+
include BadRequest
|
28
|
+
end
|
27
29
|
|
30
|
+
def self.make_default(param_depth_limit)
|
28
31
|
new Params, param_depth_limit
|
29
32
|
end
|
30
33
|
|
31
34
|
attr_reader :param_depth_limit
|
32
35
|
|
33
|
-
def initialize(params_class,
|
34
|
-
unless not_deprecated
|
35
|
-
warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
|
36
|
-
end
|
37
|
-
|
36
|
+
def initialize(params_class, param_depth_limit)
|
38
37
|
@params_class = params_class
|
39
38
|
@param_depth_limit = param_depth_limit
|
40
39
|
end
|
@@ -194,59 +193,7 @@ module Rack
|
|
194
193
|
URI.decode_www_form_component(string, encoding)
|
195
194
|
end
|
196
195
|
|
197
|
-
class Params
|
198
|
-
def initialize
|
199
|
-
@size = 0
|
200
|
-
@params = {}
|
201
|
-
end
|
202
|
-
|
203
|
-
def [](key)
|
204
|
-
@params[key]
|
205
|
-
end
|
206
|
-
|
207
|
-
def []=(key, value)
|
208
|
-
@params[key] = value
|
209
|
-
end
|
210
|
-
|
211
|
-
def key?(key)
|
212
|
-
@params.key?(key)
|
213
|
-
end
|
214
|
-
|
215
|
-
# Recursively unwraps nested `Params` objects and constructs an object
|
216
|
-
# of the same shape, but using the objects' internal representations
|
217
|
-
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
218
|
-
# purely of Ruby primitives.
|
219
|
-
#
|
220
|
-
# Mutation warning!
|
221
|
-
#
|
222
|
-
# 1. This method mutates the internal representation of the `Params`
|
223
|
-
# objects in order to save object allocations.
|
224
|
-
#
|
225
|
-
# 2. The value you get back is a reference to the internal hash
|
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
|
247
|
-
end
|
248
|
-
@params
|
249
|
-
end
|
196
|
+
class Params < Hash
|
250
197
|
alias_method :to_params_hash, :to_h
|
251
198
|
end
|
252
199
|
end
|
data/lib/rack/request.rb
CHANGED
@@ -482,9 +482,14 @@ module Rack
|
|
482
482
|
|
483
483
|
# Returns the data received in the query string.
|
484
484
|
def GET
|
485
|
-
|
485
|
+
rr_query_string = get_header(RACK_REQUEST_QUERY_STRING)
|
486
|
+
query_string = self.query_string
|
487
|
+
if rr_query_string == query_string
|
486
488
|
get_header(RACK_REQUEST_QUERY_HASH)
|
487
489
|
else
|
490
|
+
if rr_query_string
|
491
|
+
warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1
|
492
|
+
end
|
488
493
|
query_hash = parse_query(query_string, '&')
|
489
494
|
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
490
495
|
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
@@ -505,9 +510,12 @@ module Rack
|
|
505
510
|
|
506
511
|
# If the form hash was already memoized:
|
507
512
|
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
513
|
+
form_input = get_header(RACK_REQUEST_FORM_INPUT)
|
508
514
|
# And it was memoized from the same input:
|
509
|
-
if
|
515
|
+
if form_input.equal?(rack_input)
|
510
516
|
return form_hash
|
517
|
+
elsif form_input
|
518
|
+
warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1
|
511
519
|
end
|
512
520
|
end
|
513
521
|
|
@@ -516,7 +524,10 @@ module Rack
|
|
516
524
|
set_header RACK_REQUEST_FORM_INPUT, nil
|
517
525
|
set_header(RACK_REQUEST_FORM_HASH, {})
|
518
526
|
elsif form_data? || parseable_data?
|
519
|
-
|
527
|
+
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
528
|
+
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
529
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
530
|
+
else
|
520
531
|
form_vars = get_header(RACK_INPUT).read
|
521
532
|
|
522
533
|
# Fix for Safari Ajax postings that always append \0
|
@@ -605,24 +616,10 @@ module Rack
|
|
605
616
|
Rack::Request.ip_filter.call(ip)
|
606
617
|
end
|
607
618
|
|
608
|
-
# shortcut for <tt>request.params[key]</tt>
|
609
|
-
def [](key)
|
610
|
-
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
|
611
|
-
|
612
|
-
params[key.to_s]
|
613
|
-
end
|
614
|
-
|
615
|
-
# shortcut for <tt>request.params[key] = value</tt>
|
616
|
-
#
|
617
|
-
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
618
|
-
def []=(key, value)
|
619
|
-
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
|
620
|
-
|
621
|
-
params[key.to_s] = value
|
622
|
-
end
|
623
|
-
|
624
619
|
# like Hash#values_at
|
625
620
|
def values_at(*keys)
|
621
|
+
warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1)
|
622
|
+
|
626
623
|
keys.map { |key| params[key] }
|
627
624
|
end
|
628
625
|
|
@@ -645,14 +642,26 @@ module Rack
|
|
645
642
|
end
|
646
643
|
|
647
644
|
def parse_http_accept_header(header)
|
648
|
-
|
649
|
-
|
645
|
+
# It would be nice to use filter_map here, but it's Ruby 2.7+
|
646
|
+
parts = header.to_s.split(',')
|
647
|
+
|
648
|
+
parts.map! do |part|
|
649
|
+
part.strip!
|
650
|
+
next if part.empty?
|
651
|
+
|
652
|
+
attribute, parameters = part.split(';', 2)
|
653
|
+
attribute.strip!
|
654
|
+
parameters&.strip!
|
650
655
|
quality = 1.0
|
651
656
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
652
657
|
quality = $1.to_f
|
653
658
|
end
|
654
659
|
[attribute, quality]
|
655
660
|
end
|
661
|
+
|
662
|
+
parts.compact!
|
663
|
+
|
664
|
+
parts
|
656
665
|
end
|
657
666
|
|
658
667
|
# Get an array of values set in the RFC 7239 `Forwarded` request header.
|
@@ -672,6 +681,16 @@ module Rack
|
|
672
681
|
Rack::Multipart.extract_multipart(self, query_parser)
|
673
682
|
end
|
674
683
|
|
684
|
+
def expand_param_pairs(pairs, query_parser = query_parser())
|
685
|
+
params = query_parser.make_params
|
686
|
+
|
687
|
+
pairs.each do |k, v|
|
688
|
+
query_parser.normalize_params(params, k, v)
|
689
|
+
end
|
690
|
+
|
691
|
+
params.to_params_hash
|
692
|
+
end
|
693
|
+
|
675
694
|
def split_header(value)
|
676
695
|
value ? value.strip.split(/[,\s]+/) : []
|
677
696
|
end
|
data/lib/rack/response.rb
CHANGED
@@ -31,13 +31,6 @@ 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, use #headers instead', 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
|
#
|
@@ -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?
|
@@ -106,7 +100,7 @@ module Rack
|
|
106
100
|
# The response body is an enumerable body and it is not allowed to have an entity body.
|
107
101
|
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
108
102
|
end
|
109
|
-
|
103
|
+
|
110
104
|
# Generate a response array consistent with the requirements of the SPEC.
|
111
105
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
112
106
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
@@ -118,9 +112,14 @@ module Rack
|
|
118
112
|
return [@status, @headers, []]
|
119
113
|
else
|
120
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.
|
121
116
|
@block = block
|
122
117
|
return [@status, @headers, self]
|
123
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
|
124
123
|
return [@status, @headers, @body]
|
125
124
|
end
|
126
125
|
end
|
@@ -138,7 +137,9 @@ module Rack
|
|
138
137
|
end
|
139
138
|
end
|
140
139
|
|
141
|
-
# Append to
|
140
|
+
# Append a chunk to the response body.
|
141
|
+
#
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
142
143
|
#
|
143
144
|
# NOTE: Do not mix #write and direct #body access!
|
144
145
|
#
|
@@ -320,29 +321,34 @@ module Rack
|
|
320
321
|
|
321
322
|
protected
|
322
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.
|
323
332
|
def buffered_body!
|
324
333
|
if @buffered.nil?
|
325
334
|
if @body.is_a?(Array)
|
326
335
|
# The user supplied body was an array:
|
327
336
|
@body = @body.compact
|
328
|
-
@body.
|
329
|
-
@length += part.to_s.bytesize
|
330
|
-
end
|
331
|
-
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
332
338
|
@buffered = true
|
333
339
|
elsif @body.respond_to?(:each)
|
334
340
|
# Turn the user supplied body into a buffered array:
|
335
341
|
body = @body
|
336
342
|
@body = Array.new
|
343
|
+
@buffered = true
|
337
344
|
|
338
345
|
body.each do |part|
|
339
346
|
@writer.call(part.to_s)
|
340
347
|
end
|
341
348
|
|
342
349
|
body.close if body.respond_to?(:close)
|
343
|
-
|
344
|
-
@buffered = true
|
345
350
|
else
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
346
352
|
@buffered = false
|
347
353
|
end
|
348
354
|
end
|
@@ -351,11 +357,13 @@ module Rack
|
|
351
357
|
end
|
352
358
|
|
353
359
|
def append(chunk)
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
354
361
|
@body << chunk
|
355
362
|
|
356
|
-
|
363
|
+
if @length
|
357
364
|
@length += chunk.bytesize
|
358
|
-
|
365
|
+
elsif @buffered
|
366
|
+
@length = chunk.bytesize
|
359
367
|
end
|
360
368
|
|
361
369
|
return chunk
|