rack 3.1.16 → 3.2.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 +142 -14
- data/README.md +49 -20
- data/SPEC.rdoc +199 -306
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/builder.rb +6 -0
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +1 -0
- data/lib/rack/events.rb +21 -6
- data/lib/rack/head.rb +2 -3
- data/lib/rack/lint.rb +430 -457
- data/lib/rack/media_type.rb +6 -7
- data/lib/rack/mock_response.rb +19 -25
- data/lib/rack/multipart/parser.rb +85 -9
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/query_parser.rb +43 -24
- data/lib/rack/request.rb +49 -55
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/sendfile.rb +50 -20
- data/lib/rack/show_exceptions.rb +4 -2
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/utils.rb +14 -23
- data/lib/rack/version.rb +4 -8
- data/lib/rack.rb +0 -1
- metadata +5 -5
- data/lib/rack/logger.rb +0 -23
data/lib/rack/media_type.rb
CHANGED
@@ -14,12 +14,11 @@ module Rack
|
|
14
14
|
# For more information on the use of media types in HTTP, see:
|
15
15
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
16
16
|
def type(content_type)
|
17
|
-
return nil unless content_type
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
17
|
+
return nil unless content_type && !content_type.empty?
|
18
|
+
type = content_type.split(SPLIT_PATTERN, 2).first
|
19
|
+
type.rstrip!
|
20
|
+
type.downcase!
|
21
|
+
type
|
23
22
|
end
|
24
23
|
|
25
24
|
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
@@ -33,7 +32,7 @@ module Rack
|
|
33
32
|
# and "text/plain;charset" will return { 'charset' => '' }, similarly to
|
34
33
|
# the query params parser (barring the latter case, which returns nil instead)).
|
35
34
|
def params(content_type)
|
36
|
-
return {} if content_type.nil?
|
35
|
+
return {} if content_type.nil? || content_type.empty?
|
37
36
|
|
38
37
|
content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
|
39
38
|
s.strip!
|
data/lib/rack/mock_response.rb
CHANGED
@@ -10,33 +10,27 @@ module Rack
|
|
10
10
|
# MockRequest.
|
11
11
|
|
12
12
|
class MockResponse < Rack::Response
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@path = args["path"]
|
25
|
-
@domain = args["domain"]
|
26
|
-
@expires = args["expires"]
|
27
|
-
@secure = args["secure"]
|
28
|
-
end
|
13
|
+
class Cookie
|
14
|
+
attr_reader :name, :value, :path, :domain, :expires, :secure
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
@name = args["name"]
|
18
|
+
@value = args["value"]
|
19
|
+
@path = args["path"]
|
20
|
+
@domain = args["domain"]
|
21
|
+
@expires = args["expires"]
|
22
|
+
@secure = args["secure"]
|
23
|
+
end
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
def method_missing(method_name, *args, &block)
|
26
|
+
@value.send(method_name, *args, &block)
|
27
|
+
end
|
28
|
+
# :nocov:
|
29
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
30
|
+
# :nocov:
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
32
|
+
def respond_to_missing?(method_name, include_all = false)
|
33
|
+
@value.respond_to?(method_name, include_all) || super
|
40
34
|
end
|
41
35
|
end
|
42
36
|
|
@@ -38,6 +38,18 @@ module Rack
|
|
38
38
|
MULTIPART_CONTENT_DISPOSITION = /^Content-Disposition:#{FWS}?(#{HEADER_VALUE})/ni
|
39
39
|
MULTIPART_CONTENT_ID = /^Content-ID:#{FWS}?(#{HEADER_VALUE})/ni
|
40
40
|
|
41
|
+
# Rack::Multipart::Parser handles parsing of multipart/form-data requests.
|
42
|
+
#
|
43
|
+
# File Parameter Contents
|
44
|
+
#
|
45
|
+
# When processing file uploads, the parser returns a hash containing
|
46
|
+
# information about uploaded files. For +file+ parameters, the hash includes:
|
47
|
+
#
|
48
|
+
# * +:filename+ - The original filename, already URL decoded by the parser
|
49
|
+
# * +:type+ - The content type of the uploaded file
|
50
|
+
# * +:name+ - The parameter name from the form
|
51
|
+
# * +:tempfile+ - A Tempfile object containing the uploaded data
|
52
|
+
# * +:head+ - The raw header content for this part
|
41
53
|
class Parser
|
42
54
|
BUFSIZE = 1_048_576
|
43
55
|
TEXT_PLAIN = "text/plain"
|
@@ -47,6 +59,27 @@ module Rack
|
|
47
59
|
Tempfile.new(["RackMultipart", extension])
|
48
60
|
}
|
49
61
|
|
62
|
+
BOUNDARY_START_LIMIT = 16 * 1024
|
63
|
+
private_constant :BOUNDARY_START_LIMIT
|
64
|
+
|
65
|
+
MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
|
66
|
+
private_constant :MIME_HEADER_BYTESIZE_LIMIT
|
67
|
+
|
68
|
+
env_int = lambda do |key, val|
|
69
|
+
if str_val = ENV[key]
|
70
|
+
begin
|
71
|
+
val = Integer(str_val, 10)
|
72
|
+
rescue ArgumentError
|
73
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
val
|
78
|
+
end
|
79
|
+
|
80
|
+
BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
|
81
|
+
private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
82
|
+
|
50
83
|
class BoundedIO # :nodoc:
|
51
84
|
def initialize(io, content_length)
|
52
85
|
@io = io
|
@@ -206,6 +239,8 @@ module Rack
|
|
206
239
|
|
207
240
|
@state = :FAST_FORWARD
|
208
241
|
@mime_index = 0
|
242
|
+
@body_retained = nil
|
243
|
+
@retained_size = 0
|
209
244
|
@collector = Collector.new tempfile
|
210
245
|
|
211
246
|
@sbuf = StringScanner.new("".dup)
|
@@ -243,7 +278,8 @@ module Rack
|
|
243
278
|
@collector.each do |part|
|
244
279
|
part.get_data do |data|
|
245
280
|
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
246
|
-
|
281
|
+
name, data = handle_dummy_encoding(part.name, data)
|
282
|
+
@query_parser.normalize_params(@params, name, data)
|
247
283
|
end
|
248
284
|
end
|
249
285
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
@@ -251,12 +287,6 @@ module Rack
|
|
251
287
|
|
252
288
|
private
|
253
289
|
|
254
|
-
def dequote(str) # From WEBrick::HTTPUtils
|
255
|
-
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
256
|
-
ret.gsub!(/\\(.)/, "\\1")
|
257
|
-
ret
|
258
|
-
end
|
259
|
-
|
260
290
|
def read_data(io, outbuf)
|
261
291
|
content = io.read(@bufsize, outbuf)
|
262
292
|
handle_empty_content!(content)
|
@@ -287,6 +317,10 @@ module Rack
|
|
287
317
|
|
288
318
|
# retry for opening boundary
|
289
319
|
else
|
320
|
+
# We raise if we don't find the multipart boundary, to avoid unbounded memory
|
321
|
+
# buffering. Note that the actual limit is the higher of 16KB and the buffer size (1MB by default)
|
322
|
+
raise Error, "multipart boundary not found within limit" if @sbuf.string.bytesize > BOUNDARY_START_LIMIT
|
323
|
+
|
290
324
|
# no boundary found, keep reading data
|
291
325
|
return :want_read
|
292
326
|
end
|
@@ -403,16 +437,30 @@ module Rack
|
|
403
437
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
404
438
|
end
|
405
439
|
|
440
|
+
# Mime part head data is retained for both TempfilePart and BufferPart
|
441
|
+
# for the entireity of the parse, even though it isn't used for BufferPart.
|
442
|
+
update_retained_size(head.bytesize)
|
443
|
+
|
444
|
+
# If a filename is given, a TempfilePart will be used, so the body will
|
445
|
+
# not be buffered in memory. However, if a filename is not given, a BufferPart
|
446
|
+
# will be used, and the body will be buffered in memory.
|
447
|
+
@body_retained = !filename
|
448
|
+
|
406
449
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
407
450
|
@state = :MIME_BODY
|
408
451
|
else
|
409
|
-
|
452
|
+
# We raise if the mime part header is too large, to avoid unbounded memory
|
453
|
+
# buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
|
454
|
+
raise Error, "multipart mime part header too large" if @sbuf.string.bytesize > MIME_HEADER_BYTESIZE_LIMIT
|
455
|
+
|
456
|
+
return :want_read
|
410
457
|
end
|
411
458
|
end
|
412
459
|
|
413
460
|
def handle_mime_body
|
414
461
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
415
462
|
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
463
|
+
update_retained_size(body.bytesize) if @body_retained
|
416
464
|
@collector.on_mime_body @mime_index, body
|
417
465
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
418
466
|
@state = :CONSUME_TOKEN
|
@@ -421,7 +469,9 @@ module Rack
|
|
421
469
|
# Save what we have so far
|
422
470
|
if @rx_max_size < @sbuf.rest_size
|
423
471
|
delta = @sbuf.rest_size - @rx_max_size
|
424
|
-
|
472
|
+
body = @sbuf.peek(delta)
|
473
|
+
update_retained_size(body.bytesize) if @body_retained
|
474
|
+
@collector.on_mime_body @mime_index, body
|
425
475
|
@sbuf.pos += delta
|
426
476
|
@sbuf.string = @sbuf.rest
|
427
477
|
end
|
@@ -429,6 +479,13 @@ module Rack
|
|
429
479
|
end
|
430
480
|
end
|
431
481
|
|
482
|
+
def update_retained_size(size)
|
483
|
+
@retained_size += size
|
484
|
+
if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
485
|
+
raise Error, "multipart data over retained size limit"
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
432
489
|
# Scan until the we find the start or end of the boundary.
|
433
490
|
# If we find it, return the appropriate symbol for the start or
|
434
491
|
# end of the boundary. If we don't find the start or end of the
|
@@ -494,6 +551,25 @@ module Rack
|
|
494
551
|
Encoding::BINARY
|
495
552
|
end
|
496
553
|
|
554
|
+
REENCODE_DUMMY_ENCODINGS = {
|
555
|
+
# ISO-2022-JP is a legacy but still widely used encoding in Japan
|
556
|
+
# Here we convert ISO-2022-JP to UTF-8 so that it can be handled.
|
557
|
+
Encoding::ISO_2022_JP => true
|
558
|
+
|
559
|
+
# Other dummy encodings are rarely used and have not been supported yet.
|
560
|
+
# Adding support for them will require careful considerations.
|
561
|
+
}
|
562
|
+
|
563
|
+
def handle_dummy_encoding(name, body)
|
564
|
+
# A string object with a 'dummy' encoding does not have full functionality and can cause errors.
|
565
|
+
# So here we covert it to UTF-8 so that it can be handled properly.
|
566
|
+
if name.encoding.dummy? && REENCODE_DUMMY_ENCODINGS[name.encoding]
|
567
|
+
name = name.encode(Encoding::UTF_8)
|
568
|
+
body = body.encode(Encoding::UTF_8)
|
569
|
+
end
|
570
|
+
return name, body
|
571
|
+
end
|
572
|
+
|
497
573
|
def handle_empty_content!(content)
|
498
574
|
if content.nil? || content.empty?
|
499
575
|
raise EmptyContentError
|
@@ -5,14 +5,47 @@ require 'fileutils'
|
|
5
5
|
|
6
6
|
module Rack
|
7
7
|
module Multipart
|
8
|
+
# Despite the misleading name, UploadedFile is designed for use for
|
9
|
+
# preparing multipart file upload bodies, generally for use in tests.
|
10
|
+
# It is not designed for and should not be used for handling uploaded
|
11
|
+
# files (there is no need for that, since Rack's multipart parser
|
12
|
+
# already creates Tempfiles for that). Using this with non-trusted
|
13
|
+
# filenames can create a security vulnerability.
|
14
|
+
#
|
15
|
+
# You should only use this class if you plan on passing the instances
|
16
|
+
# to Rack::MockRequest for use in creating multipart request bodies.
|
17
|
+
#
|
18
|
+
# UploadedFile delegates most methods to the tempfile it contains.
|
8
19
|
class UploadedFile
|
9
|
-
|
10
|
-
#
|
20
|
+
# The provided name of the file. This generally is the basename of
|
21
|
+
# path provided during initialization, but it can contain slashes if they
|
22
|
+
# were present in the filename argument when the instance was created.
|
11
23
|
attr_reader :original_filename
|
12
24
|
|
13
|
-
# The content type of the
|
25
|
+
# The content type of the instance.
|
14
26
|
attr_accessor :content_type
|
15
27
|
|
28
|
+
# Create a new UploadedFile. For backwards compatibility, this accepts
|
29
|
+
# both positional and keyword versions of the same arguments:
|
30
|
+
#
|
31
|
+
# filepath/path :: The path to the file
|
32
|
+
# ct/content_type :: The content_type of the file
|
33
|
+
# bin/binary :: Whether to set binmode on the file before copying data into it.
|
34
|
+
#
|
35
|
+
# If both positional and keyword arguments are present, the keyword arguments
|
36
|
+
# take precedence.
|
37
|
+
#
|
38
|
+
# The following keyword-only arguments are also accepted:
|
39
|
+
#
|
40
|
+
# filename :: Override the filename to use for the file. This is so the
|
41
|
+
# filename for the upload does not need to match the basename of
|
42
|
+
# the file path. This should not contain slashes, unless you are
|
43
|
+
# trying to test how an application handles invalid filenames in
|
44
|
+
# multipart upload bodies.
|
45
|
+
# io :: Use the given IO-like instance as the tempfile, instead of creating
|
46
|
+
# a Tempfile instance. This is useful for building multipart file
|
47
|
+
# upload bodies without a file being present on the filesystem. If you are
|
48
|
+
# providing this, you should also provide the filename argument.
|
16
49
|
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
17
50
|
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
18
51
|
if io
|
@@ -28,15 +61,19 @@ module Rack
|
|
28
61
|
@content_type = content_type
|
29
62
|
end
|
30
63
|
|
64
|
+
# The path of the tempfile for the instance, if the tempfile has a path.
|
65
|
+
# nil if the tempfile does not have a path.
|
31
66
|
def path
|
32
67
|
@tempfile.path if @tempfile.respond_to?(:path)
|
33
68
|
end
|
34
69
|
alias_method :local_path, :path
|
35
70
|
|
36
|
-
|
37
|
-
|
71
|
+
# Return true if the tempfile responds to the method.
|
72
|
+
def respond_to_missing?(*args)
|
73
|
+
@tempfile.respond_to?(*args)
|
38
74
|
end
|
39
75
|
|
76
|
+
# Delegate method missing calls to the tempfile.
|
40
77
|
def method_missing(method_name, *args, &block) #:nodoc:
|
41
78
|
@tempfile.__send__(method_name, *args, &block)
|
42
79
|
end
|
data/lib/rack/query_parser.rb
CHANGED
@@ -57,6 +57,8 @@ module Rack
|
|
57
57
|
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
58
58
|
private_constant :PARAMS_LIMIT
|
59
59
|
|
60
|
+
attr_reader :bytesize_limit
|
61
|
+
|
60
62
|
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
61
63
|
@params_class = params_class
|
62
64
|
@param_depth_limit = param_depth_limit
|
@@ -69,14 +71,9 @@ module Rack
|
|
69
71
|
# to parse cookies by changing the characters used in the second parameter
|
70
72
|
# (which defaults to '&').
|
71
73
|
def parse_query(qs, separator = nil, &unescaper)
|
72
|
-
unescaper ||= method(:unescape)
|
73
|
-
|
74
74
|
params = make_params
|
75
75
|
|
76
|
-
|
77
|
-
next if p.empty?
|
78
|
-
k, v = p.split('=', 2).map!(&unescaper)
|
79
|
-
|
76
|
+
each_query_pair(qs, separator, unescaper) do |k, v|
|
80
77
|
if cur = params[k]
|
81
78
|
if cur.class == Array
|
82
79
|
params[k] << v
|
@@ -91,6 +88,19 @@ module Rack
|
|
91
88
|
return params.to_h
|
92
89
|
end
|
93
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
|
+
|
94
104
|
# parse_nested_query expands a query string into structural types. Supported
|
95
105
|
# types are Arrays, Hashes and basic value types. It is possible to supply
|
96
106
|
# query strings with parameters of conflicting types, in this case a
|
@@ -99,17 +109,11 @@ module Rack
|
|
99
109
|
def parse_nested_query(qs, separator = nil)
|
100
110
|
params = make_params
|
101
111
|
|
102
|
-
|
103
|
-
|
104
|
-
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
105
|
-
|
106
|
-
_normalize_params(params, k, v, 0)
|
107
|
-
end
|
112
|
+
each_query_pair(qs, separator) do |k, v|
|
113
|
+
_normalize_params(params, k, v, 0)
|
108
114
|
end
|
109
115
|
|
110
116
|
return params.to_h
|
111
|
-
rescue ArgumentError => e
|
112
|
-
raise InvalidParameterError, e.message, e.backtrace
|
113
117
|
end
|
114
118
|
|
115
119
|
# normalize_params recursively expands parameters into structural types. If
|
@@ -215,20 +219,35 @@ module Rack
|
|
215
219
|
true
|
216
220
|
end
|
217
221
|
|
218
|
-
def
|
219
|
-
if qs
|
220
|
-
if qs.bytesize > @bytesize_limit
|
221
|
-
raise QueryLimitError, "total query size (#{qs.bytesize}) exceeds limit (#{@bytesize_limit})"
|
222
|
-
end
|
222
|
+
def each_query_pair(qs, separator, unescaper = nil)
|
223
|
+
return if !qs || qs.empty?
|
223
224
|
|
224
|
-
|
225
|
-
|
226
|
-
|
225
|
+
if qs.bytesize > @bytesize_limit
|
226
|
+
raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
|
227
|
+
end
|
228
|
+
|
229
|
+
pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
|
230
|
+
|
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})"
|
234
|
+
end
|
227
235
|
|
228
|
-
|
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
|
229
242
|
else
|
230
|
-
|
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
|
+
end
|
231
248
|
end
|
249
|
+
rescue ArgumentError => e
|
250
|
+
raise InvalidParameterError, e.message, e.backtrace
|
232
251
|
end
|
233
252
|
|
234
253
|
def unescape(string, encoding = Encoding::UTF_8)
|
data/lib/rack/request.rb
CHANGED
@@ -61,9 +61,14 @@ module Rack
|
|
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,67 +489,45 @@ module Rack
|
|
482
489
|
|
483
490
|
# Returns the data received in the query string.
|
484
491
|
def GET
|
485
|
-
|
486
|
-
query_string = self.query_string
|
487
|
-
if rr_query_string == query_string
|
488
|
-
get_header(RACK_REQUEST_QUERY_HASH)
|
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
|
493
|
-
query_hash = parse_query(query_string, '&')
|
494
|
-
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
495
|
-
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
496
|
-
end
|
492
|
+
get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
|
497
493
|
end
|
498
494
|
|
499
|
-
# Returns the data received in the request body.
|
495
|
+
# Returns the form data pairs received in the request body.
|
500
496
|
#
|
501
497
|
# This method support both application/x-www-form-urlencoded and
|
502
498
|
# multipart/form-data.
|
503
|
-
def
|
504
|
-
if
|
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)
|
505
503
|
raise error.class, error.message, cause: error.cause
|
506
504
|
end
|
507
505
|
|
508
506
|
begin
|
509
507
|
rack_input = get_header(RACK_INPUT)
|
510
508
|
|
511
|
-
# If the form hash was already memoized:
|
512
|
-
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
513
|
-
form_input = get_header(RACK_REQUEST_FORM_INPUT)
|
514
|
-
# And it was memoized from the same input:
|
515
|
-
if form_input.equal?(rack_input)
|
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
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
509
|
# Otherwise, figure out how to parse the input:
|
523
510
|
if rack_input.nil?
|
524
|
-
set_header
|
525
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
511
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
526
512
|
elsif form_data? || parseable_data?
|
527
513
|
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
528
514
|
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
529
|
-
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
530
515
|
else
|
531
|
-
|
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) || ''
|
532
520
|
|
533
521
|
# Fix for Safari Ajax postings that always append \0
|
534
522
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
535
523
|
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
536
524
|
|
537
525
|
set_header RACK_REQUEST_FORM_VARS, form_vars
|
538
|
-
|
526
|
+
pairs = query_parser.parse_query_pairs(form_vars, '&')
|
527
|
+
set_header(RACK_REQUEST_FORM_PAIRS, pairs)
|
539
528
|
end
|
540
|
-
|
541
|
-
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
542
|
-
get_header RACK_REQUEST_FORM_HASH
|
543
529
|
else
|
544
|
-
set_header
|
545
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
530
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
546
531
|
end
|
547
532
|
rescue => error
|
548
533
|
set_header(RACK_REQUEST_FORM_ERROR, error)
|
@@ -550,6 +535,21 @@ module Rack
|
|
550
535
|
end
|
551
536
|
end
|
552
537
|
|
538
|
+
# Returns the data received in the request body.
|
539
|
+
#
|
540
|
+
# This method support both application/x-www-form-urlencoded and
|
541
|
+
# multipart/form-data.
|
542
|
+
def POST
|
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
|
547
|
+
end
|
548
|
+
|
549
|
+
pairs = form_pairs
|
550
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
551
|
+
end
|
552
|
+
|
553
553
|
# The union of GET and POST data.
|
554
554
|
#
|
555
555
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
@@ -557,6 +557,10 @@ module Rack
|
|
557
557
|
self.GET.merge(self.POST)
|
558
558
|
end
|
559
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
|
+
|
560
564
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
561
565
|
#
|
562
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.
|
@@ -616,13 +620,6 @@ module Rack
|
|
616
620
|
Rack::Request.ip_filter.call(ip)
|
617
621
|
end
|
618
622
|
|
619
|
-
# like Hash#values_at
|
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
|
-
|
623
|
-
keys.map { |key| params[key] }
|
624
|
-
end
|
625
|
-
|
626
623
|
private
|
627
624
|
|
628
625
|
def default_session; {}; end
|
@@ -670,7 +667,7 @@ module Rack
|
|
670
667
|
end
|
671
668
|
|
672
669
|
def query_parser
|
673
|
-
Utils.default_query_parser
|
670
|
+
@query_parser || Utils.default_query_parser
|
674
671
|
end
|
675
672
|
|
676
673
|
def parse_query(qs, d = '&')
|
@@ -678,6 +675,7 @@ module Rack
|
|
678
675
|
end
|
679
676
|
|
680
677
|
def parse_multipart
|
678
|
+
warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
|
681
679
|
Rack::Multipart.extract_multipart(self, query_parser)
|
682
680
|
end
|
683
681
|
|
@@ -692,7 +690,7 @@ module Rack
|
|
692
690
|
end
|
693
691
|
|
694
692
|
def split_header(value)
|
695
|
-
value ? value.strip.split(/[
|
693
|
+
value ? value.strip.split(/[, \t]+/) : []
|
696
694
|
end
|
697
695
|
|
698
696
|
# ipv6 extracted from resolv stdlib, simplified
|
@@ -740,10 +738,6 @@ module Rack
|
|
740
738
|
return match[:host], match[:address], match[:port]&.to_i
|
741
739
|
end
|
742
740
|
|
743
|
-
def reject_trusted_ip_addresses(ip_addresses)
|
744
|
-
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
745
|
-
end
|
746
|
-
|
747
741
|
FORWARDED_SCHEME_HEADERS = {
|
748
742
|
proto: HTTP_X_FORWARDED_PROTO,
|
749
743
|
scheme: HTTP_X_FORWARDED_SCHEME
|