rack 2.2.6.4 → 3.0.8
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.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +205 -80
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +309 -0
- data/SPEC.rdoc +183 -131
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +0 -2
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -54
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +3 -1
- data/lib/rack/builder.rb +83 -63
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +64 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +14 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +783 -682
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +1 -1
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +126 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +119 -61
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +78 -46
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +224 -106
- data/lib/rack/response.rb +136 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +3 -1
- data/lib/rack/utils.rb +202 -176
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +6 -76
- metadata +18 -38
- data/README.rdoc +0 -320
- data/Rakefile +0 -130
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
- data/rack.gemspec +0 -46
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'uploaded_file'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
module Multipart
|
5
7
|
class Generator
|
@@ -74,12 +76,12 @@ module Rack
|
|
74
76
|
|
75
77
|
def content_for_tempfile(io, file, name)
|
76
78
|
length = ::File.stat(file.path).size if file.path
|
77
|
-
filename = "; filename=\"#{Utils.
|
79
|
+
filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\""
|
78
80
|
<<-EOF
|
79
81
|
--#{MULTIPART_BOUNDARY}\r
|
80
|
-
|
81
|
-
|
82
|
-
#{"
|
82
|
+
content-disposition: form-data; name="#{name}"#{filename}\r
|
83
|
+
content-type: #{file.content_type}\r
|
84
|
+
#{"content-length: #{length}\r\n" if length}\r
|
83
85
|
#{io.read}\r
|
84
86
|
EOF
|
85
87
|
end
|
@@ -87,7 +89,7 @@ EOF
|
|
87
89
|
def content_for_other(file, name)
|
88
90
|
<<-EOF
|
89
91
|
--#{MULTIPART_BOUNDARY}\r
|
90
|
-
|
92
|
+
content-disposition: form-data; name="#{name}"\r
|
91
93
|
\r
|
92
94
|
#{file}\r
|
93
95
|
EOF
|
@@ -2,22 +2,54 @@
|
|
2
2
|
|
3
3
|
require 'strscan'
|
4
4
|
|
5
|
+
require_relative '../utils'
|
6
|
+
|
5
7
|
module Rack
|
6
8
|
module Multipart
|
7
9
|
class MultipartPartLimitError < Errno::EMFILE; end
|
10
|
+
|
8
11
|
class MultipartTotalPartLimitError < StandardError; end
|
9
12
|
|
10
|
-
class
|
11
|
-
|
13
|
+
# Use specific error class when parsing multipart request
|
14
|
+
# that ends early.
|
15
|
+
class EmptyContentError < ::EOFError; end
|
16
|
+
|
17
|
+
# Base class for multipart exceptions that do not subclass from
|
18
|
+
# other exception classes for backwards compatibility.
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
EOL = "\r\n"
|
22
|
+
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
|
+
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
28
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
|
29
|
+
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
|
12
45
|
|
46
|
+
class Parser
|
13
47
|
BUFSIZE = 1_048_576
|
14
48
|
TEXT_PLAIN = "text/plain"
|
15
49
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
16
50
|
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
17
51
|
}
|
18
52
|
|
19
|
-
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
20
|
-
|
21
53
|
class BoundedIO # :nodoc:
|
22
54
|
def initialize(io, content_length)
|
23
55
|
@io = io
|
@@ -39,16 +71,12 @@ module Rack
|
|
39
71
|
if str
|
40
72
|
@cursor += str.bytesize
|
41
73
|
else
|
42
|
-
# Raise an error for mismatching
|
74
|
+
# Raise an error for mismatching content-length and actual contents
|
43
75
|
raise EOFError, "bad content body"
|
44
76
|
end
|
45
77
|
|
46
78
|
str
|
47
79
|
end
|
48
|
-
|
49
|
-
def rewind
|
50
|
-
@io.rewind
|
51
|
-
end
|
52
80
|
end
|
53
81
|
|
54
82
|
MultipartInfo = Struct.new :params, :tmp_files
|
@@ -67,18 +95,17 @@ module Rack
|
|
67
95
|
boundary = parse_boundary content_type
|
68
96
|
return EMPTY unless boundary
|
69
97
|
|
98
|
+
if boundary.length > 70
|
99
|
+
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
100
|
+
# Most clients use no more than 55 characters.
|
101
|
+
raise Error, "multipart boundary size too large (#{boundary.length} characters)"
|
102
|
+
end
|
103
|
+
|
70
104
|
io = BoundedIO.new(io, content_length) if content_length
|
71
|
-
outbuf = String.new
|
72
105
|
|
73
106
|
parser = new(boundary, tmpfile, bufsize, qp)
|
74
|
-
parser.
|
107
|
+
parser.parse(io)
|
75
108
|
|
76
|
-
loop do
|
77
|
-
break if parser.state == :DONE
|
78
|
-
parser.on_read io.read(bufsize, outbuf)
|
79
|
-
end
|
80
|
-
|
81
|
-
io.rewind
|
82
109
|
parser.result
|
83
110
|
end
|
84
111
|
|
@@ -178,32 +205,46 @@ module Rack
|
|
178
205
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
179
206
|
@query_parser = query_parser
|
180
207
|
@params = query_parser.make_params
|
181
|
-
@boundary = "--#{boundary}"
|
182
208
|
@bufsize = bufsize
|
183
209
|
|
184
|
-
@full_boundary = @boundary
|
185
|
-
@end_boundary = @boundary + '--'
|
186
210
|
@state = :FAST_FORWARD
|
187
211
|
@mime_index = 0
|
188
212
|
@collector = Collector.new tempfile
|
189
213
|
|
190
214
|
@sbuf = StringScanner.new("".dup)
|
191
|
-
@body_regex = /(?:#{EOL})
|
192
|
-
@rx_max_size =
|
215
|
+
@body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
|
216
|
+
@rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
|
193
217
|
@head_regex = /(.*?#{EOL})#{EOL}/m
|
194
218
|
end
|
195
219
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
220
|
+
def parse(io)
|
221
|
+
outbuf = String.new
|
222
|
+
read_data(io, outbuf)
|
223
|
+
|
224
|
+
loop do
|
225
|
+
status =
|
226
|
+
case @state
|
227
|
+
when :FAST_FORWARD
|
228
|
+
handle_fast_forward
|
229
|
+
when :CONSUME_TOKEN
|
230
|
+
handle_consume_token
|
231
|
+
when :MIME_HEAD
|
232
|
+
handle_mime_head
|
233
|
+
when :MIME_BODY
|
234
|
+
handle_mime_body
|
235
|
+
else # when :DONE
|
236
|
+
return
|
237
|
+
end
|
238
|
+
|
239
|
+
read_data(io, outbuf) if status == :want_read
|
240
|
+
end
|
200
241
|
end
|
201
242
|
|
202
243
|
def result
|
203
244
|
@collector.each do |part|
|
204
245
|
part.get_data do |data|
|
205
246
|
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
206
|
-
@query_parser.normalize_params(@params, part.name, data
|
247
|
+
@query_parser.normalize_params(@params, part.name, data)
|
207
248
|
end
|
208
249
|
end
|
209
250
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
@@ -211,29 +252,38 @@ module Rack
|
|
211
252
|
|
212
253
|
private
|
213
254
|
|
214
|
-
def
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
break if handle_fast_forward == :want_read
|
219
|
-
when :CONSUME_TOKEN
|
220
|
-
break if handle_consume_token == :want_read
|
221
|
-
when :MIME_HEAD
|
222
|
-
break if handle_mime_head == :want_read
|
223
|
-
when :MIME_BODY
|
224
|
-
break if handle_mime_body == :want_read
|
225
|
-
when :DONE
|
226
|
-
break
|
227
|
-
end
|
228
|
-
end
|
255
|
+
def dequote(str) # From WEBrick::HTTPUtils
|
256
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
257
|
+
ret.gsub!(/\\(.)/, "\\1")
|
258
|
+
ret
|
229
259
|
end
|
230
260
|
|
261
|
+
def read_data(io, outbuf)
|
262
|
+
content = io.read(@bufsize, outbuf)
|
263
|
+
handle_empty_content!(content)
|
264
|
+
@sbuf.concat(content)
|
265
|
+
end
|
266
|
+
|
267
|
+
# This handles the initial parser state. We read until we find the starting
|
268
|
+
# boundary, then we can transition to the next state. If we find the ending
|
269
|
+
# boundary, this is an invalid multipart upload, but keep scanning for opening
|
270
|
+
# boundary in that case. If no boundary found, we need to keep reading data
|
271
|
+
# and retry. It's highly unlikely the initial read will not consume the
|
272
|
+
# boundary. The client would have to deliberately craft a response
|
273
|
+
# with the opening boundary beyond the buffer size for that to happen.
|
231
274
|
def handle_fast_forward
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
275
|
+
while true
|
276
|
+
case consume_boundary
|
277
|
+
when :BOUNDARY
|
278
|
+
# found opening boundary, transition to next state
|
279
|
+
@state = :MIME_HEAD
|
280
|
+
return
|
281
|
+
when :END_BOUNDARY
|
282
|
+
# invalid multipart upload, but retry for opening boundary
|
283
|
+
else
|
284
|
+
# no boundary found, keep reading data
|
285
|
+
return :want_read
|
286
|
+
end
|
237
287
|
end
|
238
288
|
end
|
239
289
|
|
@@ -252,7 +302,7 @@ module Rack
|
|
252
302
|
head = @sbuf[1]
|
253
303
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
254
304
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
255
|
-
name =
|
305
|
+
name = dequote(name)
|
256
306
|
else
|
257
307
|
name = head[MULTIPART_CONTENT_ID, 1]
|
258
308
|
end
|
@@ -289,15 +339,16 @@ module Rack
|
|
289
339
|
end
|
290
340
|
end
|
291
341
|
|
292
|
-
|
293
|
-
|
342
|
+
# Scan until the we find the start or end of the boundary.
|
343
|
+
# If we find it, return the appropriate symbol for the start or
|
344
|
+
# end of the boundary. If we don't find the start or end of the
|
345
|
+
# boundary, clear the buffer and return nil.
|
294
346
|
def consume_boundary
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
return if @sbuf.eos?
|
347
|
+
if read_buffer = @sbuf.scan_until(@body_regex)
|
348
|
+
read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY
|
349
|
+
else
|
350
|
+
@sbuf.terminate
|
351
|
+
nil
|
301
352
|
end
|
302
353
|
end
|
303
354
|
|
@@ -307,10 +358,10 @@ module Rack
|
|
307
358
|
when RFC2183
|
308
359
|
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
309
360
|
|
310
|
-
if filename = params['filename']
|
311
|
-
filename = $1 if filename =~ /^"(.*)"$/
|
312
|
-
elsif filename = params['filename*']
|
361
|
+
if filename = params['filename*']
|
313
362
|
encoding, _, filename = filename.split("'", 3)
|
363
|
+
elsif filename = params['filename']
|
364
|
+
filename = $1 if filename =~ /^"(.*)"$/
|
314
365
|
end
|
315
366
|
when BROKEN
|
316
367
|
filename = $1
|
@@ -337,6 +388,7 @@ module Rack
|
|
337
388
|
end
|
338
389
|
|
339
390
|
CHARSET = "charset"
|
391
|
+
deprecate_constant :CHARSET
|
340
392
|
|
341
393
|
def tag_multipart_encoding(filename, content_type, name, body)
|
342
394
|
name = name.to_s
|
@@ -357,7 +409,13 @@ module Rack
|
|
357
409
|
k.strip!
|
358
410
|
v.strip!
|
359
411
|
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
360
|
-
|
412
|
+
if k == "charset"
|
413
|
+
encoding = begin
|
414
|
+
Encoding.find v
|
415
|
+
rescue ArgumentError
|
416
|
+
Encoding::BINARY
|
417
|
+
end
|
418
|
+
end
|
361
419
|
end
|
362
420
|
end
|
363
421
|
end
|
@@ -368,7 +426,7 @@ module Rack
|
|
368
426
|
|
369
427
|
def handle_empty_content!(content)
|
370
428
|
if content.nil? || content.empty?
|
371
|
-
raise
|
429
|
+
raise EmptyContentError
|
372
430
|
end
|
373
431
|
end
|
374
432
|
end
|
data/lib/rack/multipart.rb
CHANGED
@@ -1,64 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'utils'
|
5
|
+
|
3
6
|
require_relative 'multipart/parser'
|
7
|
+
require_relative 'multipart/generator'
|
4
8
|
|
5
9
|
module Rack
|
6
10
|
# A multipart form data parser, adapted from IOWA.
|
7
11
|
#
|
8
12
|
# Usually, Rack::Request#POST takes care of calling this.
|
9
13
|
module Multipart
|
10
|
-
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
11
|
-
autoload :Generator, 'rack/multipart/generator'
|
12
|
-
|
13
|
-
EOL = "\r\n"
|
14
14
|
MULTIPART_BOUNDARY = "AaB03x"
|
15
|
-
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
16
|
-
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
17
|
-
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
18
|
-
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
19
|
-
BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
|
20
|
-
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
21
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
|
22
|
-
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
23
|
-
# Updated definitions from RFC 2231
|
24
|
-
ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
|
25
|
-
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
26
|
-
SECTION = /\*[0-9]+/
|
27
|
-
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
28
|
-
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
29
|
-
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
30
|
-
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
31
|
-
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
32
|
-
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
33
|
-
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
34
|
-
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
35
|
-
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
36
|
-
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
37
|
-
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
38
15
|
|
39
16
|
class << self
|
40
17
|
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
41
|
-
|
42
|
-
|
18
|
+
io = env[RACK_INPUT]
|
19
|
+
|
20
|
+
if content_length = env['CONTENT_LENGTH']
|
21
|
+
content_length = content_length.to_i
|
22
|
+
end
|
43
23
|
|
44
|
-
|
45
|
-
io = req.get_header(RACK_INPUT)
|
46
|
-
io.rewind
|
47
|
-
content_length = req.content_length
|
48
|
-
content_length = content_length.to_i if content_length
|
24
|
+
content_type = env['CONTENT_TYPE']
|
49
25
|
|
50
|
-
tempfile =
|
51
|
-
bufsize =
|
26
|
+
tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
|
27
|
+
bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
|
52
28
|
|
53
|
-
info = Parser.parse
|
54
|
-
|
55
|
-
|
29
|
+
info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
|
30
|
+
env[RACK_TEMPFILES] = info.tmp_files
|
31
|
+
|
32
|
+
return info.params
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_multipart(request, params = Rack::Utils.default_query_parser)
|
36
|
+
parse_multipart(request.env)
|
56
37
|
end
|
57
38
|
|
58
39
|
def build_multipart(params, first = true)
|
59
40
|
Generator.new(params, first).dump
|
60
41
|
end
|
61
42
|
end
|
62
|
-
|
63
43
|
end
|
64
44
|
end
|
data/lib/rack/null_logger.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class NullLogger
|
5
7
|
def initialize(app)
|
@@ -22,6 +24,11 @@ module Rack
|
|
22
24
|
def warn? ; end
|
23
25
|
def error? ; end
|
24
26
|
def fatal? ; end
|
27
|
+
def debug! ; end
|
28
|
+
def error! ; end
|
29
|
+
def fatal! ; end
|
30
|
+
def info! ; end
|
31
|
+
def warn! ; end
|
25
32
|
def level ; end
|
26
33
|
def progname ; end
|
27
34
|
def datetime_format ; end
|
@@ -34,6 +41,8 @@ module Rack
|
|
34
41
|
def sev_threshold=(sev_threshold); end
|
35
42
|
def close ; end
|
36
43
|
def add(severity, message = nil, progname = nil, &block); end
|
44
|
+
def log(severity, message = nil, progname = nil, &block); end
|
37
45
|
def <<(msg); end
|
46
|
+
def reopen(logdev = nil); end
|
38
47
|
end
|
39
48
|
end
|
data/lib/rack/query_parser.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'uri'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class QueryParser
|
5
|
-
|
6
|
-
|
7
|
-
DEFAULT_SEP = /[&;] */n
|
7
|
+
DEFAULT_SEP = /[&] */n
|
8
8
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
9
9
|
|
10
10
|
# ParameterTypeError is the error that is raised when incoming structural
|
@@ -20,29 +20,35 @@ module Rack
|
|
20
20
|
# nested over the specified limit.
|
21
21
|
class ParamsTooDeepError < RangeError; end
|
22
22
|
|
23
|
-
def self.make_default(
|
24
|
-
|
23
|
+
def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
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
|
27
|
+
|
28
|
+
new Params, param_depth_limit
|
25
29
|
end
|
26
30
|
|
27
|
-
attr_reader :
|
31
|
+
attr_reader :param_depth_limit
|
32
|
+
|
33
|
+
def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
|
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
|
28
37
|
|
29
|
-
def initialize(params_class, key_space_limit, param_depth_limit)
|
30
38
|
@params_class = params_class
|
31
|
-
@key_space_limit = key_space_limit
|
32
39
|
@param_depth_limit = param_depth_limit
|
33
40
|
end
|
34
41
|
|
35
42
|
# Stolen from Mongrel, with some small modifications:
|
36
|
-
# Parses a query string by breaking it up at the '&'
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
def parse_query(qs, d = nil, &unescaper)
|
43
|
+
# Parses a query string by breaking it up at the '&'. You can also use this
|
44
|
+
# to parse cookies by changing the characters used in the second parameter
|
45
|
+
# (which defaults to '&').
|
46
|
+
def parse_query(qs, separator = nil, &unescaper)
|
41
47
|
unescaper ||= method(:unescape)
|
42
48
|
|
43
49
|
params = make_params
|
44
50
|
|
45
|
-
(qs || '').split(
|
51
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
46
52
|
next if p.empty?
|
47
53
|
k, v = p.split('=', 2).map!(&unescaper)
|
48
54
|
|
@@ -65,14 +71,14 @@ module Rack
|
|
65
71
|
# query strings with parameters of conflicting types, in this case a
|
66
72
|
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
67
73
|
# case.
|
68
|
-
def parse_nested_query(qs,
|
74
|
+
def parse_nested_query(qs, separator = nil)
|
69
75
|
params = make_params
|
70
76
|
|
71
77
|
unless qs.nil? || qs.empty?
|
72
|
-
(qs || '').split(
|
78
|
+
(qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
|
73
79
|
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
74
80
|
|
75
|
-
|
81
|
+
_normalize_params(params, k, v, 0)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -83,58 +89,87 @@ module Rack
|
|
83
89
|
|
84
90
|
# normalize_params recursively expands parameters into structural types. If
|
85
91
|
# the structural types represented by two different parameter names are in
|
86
|
-
# conflict, a ParameterTypeError is raised.
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
name
|
91
|
-
|
92
|
-
after = $' || ''
|
92
|
+
# conflict, a ParameterTypeError is raised. The depth argument is deprecated
|
93
|
+
# and should no longer be used, it is kept for backwards compatibility with
|
94
|
+
# earlier versions of rack.
|
95
|
+
def normalize_params(params, name, v, _depth=nil)
|
96
|
+
_normalize_params(params, name, v, 0)
|
97
|
+
end
|
93
98
|
|
94
|
-
|
95
|
-
|
96
|
-
|
99
|
+
private def _normalize_params(params, name, v, depth)
|
100
|
+
raise ParamsTooDeepError if depth >= param_depth_limit
|
101
|
+
|
102
|
+
if !name
|
103
|
+
# nil name, treat same as empty string (required by tests)
|
104
|
+
k = after = ''
|
105
|
+
elsif depth == 0
|
106
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
107
|
+
if start = name.index('[', 1)
|
108
|
+
# Start of parameter nesting, use part before brackets as key
|
109
|
+
k = name[0, start]
|
110
|
+
after = name[start, name.length]
|
97
111
|
else
|
98
|
-
|
112
|
+
# Plain parameter with no nesting
|
113
|
+
k = name
|
114
|
+
after = ''
|
99
115
|
end
|
116
|
+
elsif name.start_with?('[]')
|
117
|
+
# Array nesting
|
118
|
+
k = '[]'
|
119
|
+
after = name[2, name.length]
|
120
|
+
elsif name.start_with?('[') && (start = name.index(']', 1))
|
121
|
+
# Hash nesting, use the part inside brackets as the key
|
122
|
+
k = name[1, start-1]
|
123
|
+
after = name[start+1, name.length]
|
124
|
+
else
|
125
|
+
# Probably malformed input, nested but not starting with [
|
126
|
+
# treat full name as key for backwards compatibility.
|
127
|
+
k = name
|
128
|
+
after = ''
|
100
129
|
end
|
101
130
|
|
131
|
+
return if k.empty?
|
132
|
+
|
102
133
|
if after == ''
|
103
|
-
|
134
|
+
if k == '[]' && depth != 0
|
135
|
+
return [v]
|
136
|
+
else
|
137
|
+
params[k] = v
|
138
|
+
end
|
104
139
|
elsif after == "["
|
105
140
|
params[name] = v
|
106
141
|
elsif after == "[]"
|
107
142
|
params[k] ||= []
|
108
143
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
109
144
|
params[k] << v
|
110
|
-
elsif after
|
111
|
-
|
145
|
+
elsif after.start_with?('[]')
|
146
|
+
# Recognize x[][y] (hash inside array) parameters
|
147
|
+
unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
|
148
|
+
# Handle other nested array parameters
|
149
|
+
child_key = after[2, after.length]
|
150
|
+
end
|
112
151
|
params[k] ||= []
|
113
152
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
114
153
|
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
|
115
|
-
|
154
|
+
_normalize_params(params[k].last, child_key, v, depth + 1)
|
116
155
|
else
|
117
|
-
params[k] <<
|
156
|
+
params[k] << _normalize_params(make_params, child_key, v, depth + 1)
|
118
157
|
end
|
119
158
|
else
|
120
159
|
params[k] ||= make_params
|
121
160
|
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
122
|
-
params[k] =
|
161
|
+
params[k] = _normalize_params(params[k], after, v, depth + 1)
|
123
162
|
end
|
124
163
|
|
125
164
|
params
|
126
165
|
end
|
127
166
|
|
128
167
|
def make_params
|
129
|
-
@params_class.new
|
130
|
-
end
|
131
|
-
|
132
|
-
def new_space_limit(key_space_limit)
|
133
|
-
self.class.new @params_class, key_space_limit, param_depth_limit
|
168
|
+
@params_class.new
|
134
169
|
end
|
135
170
|
|
136
171
|
def new_depth_limit(param_depth_limit)
|
137
|
-
self.class.new @params_class,
|
172
|
+
self.class.new @params_class, param_depth_limit
|
138
173
|
end
|
139
174
|
|
140
175
|
private
|
@@ -155,13 +190,12 @@ module Rack
|
|
155
190
|
true
|
156
191
|
end
|
157
192
|
|
158
|
-
def unescape(
|
159
|
-
|
193
|
+
def unescape(string, encoding = Encoding::UTF_8)
|
194
|
+
URI.decode_www_form_component(string, encoding)
|
160
195
|
end
|
161
196
|
|
162
197
|
class Params
|
163
|
-
def initialize
|
164
|
-
@limit = limit
|
198
|
+
def initialize
|
165
199
|
@size = 0
|
166
200
|
@params = {}
|
167
201
|
end
|
@@ -171,8 +205,6 @@ module Rack
|
|
171
205
|
end
|
172
206
|
|
173
207
|
def []=(key, value)
|
174
|
-
@size += key.size if key && !@params.key?(key)
|
175
|
-
raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
|
176
208
|
@params[key] = value
|
177
209
|
end
|
178
210
|
|