rack 3.0.11 → 3.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -4
- 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 +116 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +19 -14
- data/lib/rack/mock_response.rb +14 -16
- data/lib/rack/multipart/parser.rb +123 -62
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +15 -68
- data/lib/rack/request.rb +28 -21
- data/lib/rack/response.rb +20 -17
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +68 -96
- 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,102 @@ 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 = $1 if filename =~ /^"(.*)"$/
|
398
|
+
filename = normalize_filename(filename)
|
399
|
+
end
|
319
400
|
|
320
401
|
if name.nil? || name.empty?
|
321
402
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
@@ -330,7 +411,7 @@ module Rack
|
|
330
411
|
|
331
412
|
def handle_mime_body
|
332
413
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
333
|
-
body = body_with_boundary.sub(
|
414
|
+
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
334
415
|
@collector.on_mime_body @mime_index, body
|
335
416
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
336
417
|
@state = :CONSUME_TOKEN
|
@@ -360,39 +441,14 @@ module Rack
|
|
360
441
|
end
|
361
442
|
end
|
362
443
|
|
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
|
-
|
444
|
+
def normalize_filename(filename)
|
381
445
|
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
382
446
|
filename = Utils.unescape_path(filename)
|
383
447
|
end
|
384
448
|
|
385
449
|
filename.scrub!
|
386
450
|
|
387
|
-
|
388
|
-
filename = filename.gsub(/\\(.)/, '\1')
|
389
|
-
end
|
390
|
-
|
391
|
-
if encoding
|
392
|
-
filename.force_encoding ::Encoding.find(encoding)
|
393
|
-
end
|
394
|
-
|
395
|
-
filename
|
451
|
+
filename.split(/[\/\\]/).last || String.new
|
396
452
|
end
|
397
453
|
|
398
454
|
CHARSET = "charset"
|
@@ -418,11 +474,7 @@ module Rack
|
|
418
474
|
v.strip!
|
419
475
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
420
476
|
if k == "charset"
|
421
|
-
encoding =
|
422
|
-
Encoding.find v
|
423
|
-
rescue ArgumentError
|
424
|
-
Encoding::BINARY
|
425
|
-
end
|
477
|
+
encoding = find_encoding(v)
|
426
478
|
end
|
427
479
|
end
|
428
480
|
end
|
@@ -432,6 +484,15 @@ module Rack
|
|
432
484
|
body.force_encoding(encoding)
|
433
485
|
end
|
434
486
|
|
487
|
+
# Return the related Encoding object. However, because
|
488
|
+
# enc is submitted by the user, it may be invalid, so
|
489
|
+
# use a binary encoding in that case.
|
490
|
+
def find_encoding(enc)
|
491
|
+
Encoding.find enc
|
492
|
+
rescue ArgumentError
|
493
|
+
Encoding::BINARY
|
494
|
+
end
|
495
|
+
|
435
496
|
def handle_empty_content!(content)
|
436
497
|
if content.nil? || content.empty?
|
437
498
|
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,8 +642,8 @@ module Rack
|
|
645
642
|
end
|
646
643
|
|
647
644
|
def parse_http_accept_header(header)
|
648
|
-
header.to_s.split(
|
649
|
-
attribute, parameters = part.split(
|
645
|
+
header.to_s.split(/\s*,\s*/).map do |part|
|
646
|
+
attribute, parameters = part.split(/\s*;\s*/, 2)
|
650
647
|
quality = 1.0
|
651
648
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
652
649
|
quality = $1.to_f
|
@@ -672,6 +669,16 @@ module Rack
|
|
672
669
|
Rack::Multipart.extract_multipart(self, query_parser)
|
673
670
|
end
|
674
671
|
|
672
|
+
def expand_param_pairs(pairs, query_parser = query_parser())
|
673
|
+
params = query_parser.make_params
|
674
|
+
|
675
|
+
pairs.each do |k, v|
|
676
|
+
query_parser.normalize_params(params, k, v)
|
677
|
+
end
|
678
|
+
|
679
|
+
params.to_params_hash
|
680
|
+
end
|
681
|
+
|
675
682
|
def split_header(value)
|
676
683
|
value ? value.strip.split(/[,\s]+/) : []
|
677
684
|
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
|
@@ -106,7 +99,7 @@ module Rack
|
|
106
99
|
# The response body is an enumerable body and it is not allowed to have an entity body.
|
107
100
|
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
108
101
|
end
|
109
|
-
|
102
|
+
|
110
103
|
# Generate a response array consistent with the requirements of the SPEC.
|
111
104
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
112
105
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
@@ -117,6 +110,10 @@ module Rack
|
|
117
110
|
close
|
118
111
|
return [@status, @headers, []]
|
119
112
|
else
|
113
|
+
if @length && @length > 0 && !chunked?
|
114
|
+
set_header CONTENT_LENGTH, @length.to_s
|
115
|
+
end
|
116
|
+
|
120
117
|
if block_given?
|
121
118
|
@block = block
|
122
119
|
return [@status, @headers, self]
|
@@ -320,20 +317,26 @@ module Rack
|
|
320
317
|
|
321
318
|
protected
|
322
319
|
|
320
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
321
|
+
#
|
322
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
323
|
+
# * `nil` - The body has not been buffered yet.
|
324
|
+
# * `true` - The body is buffered as an Array instance.
|
325
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
326
|
+
#
|
327
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
323
328
|
def buffered_body!
|
324
329
|
if @buffered.nil?
|
325
330
|
if @body.is_a?(Array)
|
326
331
|
# The user supplied body was an array:
|
327
332
|
@body = @body.compact
|
328
|
-
@body.
|
329
|
-
@length += part.to_s.bytesize
|
330
|
-
end
|
331
|
-
|
333
|
+
@length = @body.sum{|part| part.bytesize}
|
332
334
|
@buffered = true
|
333
335
|
elsif @body.respond_to?(:each)
|
334
336
|
# Turn the user supplied body into a buffered array:
|
335
337
|
body = @body
|
336
338
|
@body = Array.new
|
339
|
+
@length = 0
|
337
340
|
|
338
341
|
body.each do |part|
|
339
342
|
@writer.call(part.to_s)
|
@@ -341,8 +344,10 @@ module Rack
|
|
341
344
|
|
342
345
|
body.close if body.respond_to?(:close)
|
343
346
|
|
347
|
+
# We have converted the body into an Array:
|
344
348
|
@buffered = true
|
345
349
|
else
|
350
|
+
# We don't know how to buffer the user-supplied body:
|
346
351
|
@buffered = false
|
347
352
|
end
|
348
353
|
end
|
@@ -351,12 +356,10 @@ module Rack
|
|
351
356
|
end
|
352
357
|
|
353
358
|
def append(chunk)
|
359
|
+
chunk = chunk.dup unless chunk.frozen?
|
354
360
|
@body << chunk
|
355
361
|
|
356
|
-
|
357
|
-
@length += chunk.bytesize
|
358
|
-
set_header(CONTENT_LENGTH, @length.to_s)
|
359
|
-
end
|
362
|
+
@length += chunk.bytesize
|
360
363
|
|
361
364
|
return chunk
|
362
365
|
end
|
data/lib/rack/show_exceptions.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'ostruct'
|
4
3
|
require 'erb'
|
5
4
|
|
6
5
|
require_relative 'constants'
|
@@ -19,6 +18,11 @@ module Rack
|
|
19
18
|
class ShowExceptions
|
20
19
|
CONTEXT = 7
|
21
20
|
|
21
|
+
Frame = Struct.new(:filename, :lineno, :function,
|
22
|
+
:pre_context_lineno, :pre_context,
|
23
|
+
:context_line, :post_context_lineno,
|
24
|
+
:post_context)
|
25
|
+
|
22
26
|
def initialize(app)
|
23
27
|
@app = app
|
24
28
|
end
|
@@ -79,7 +83,7 @@ module Rack
|
|
79
83
|
# This double assignment is to prevent an "unused variable" warning.
|
80
84
|
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
81
85
|
frames = frames = exception.backtrace.map { |line|
|
82
|
-
frame =
|
86
|
+
frame = Frame.new
|
83
87
|
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
84
88
|
frame.filename = $1
|
85
89
|
frame.lineno = $2.to_i
|