rack 2.0.8 → 2.1.2
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 +69 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack.rb +63 -60
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +39 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +32 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +51 -45
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +79 -26
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -18
- data/lib/rack/session/abstract/id.rb +32 -22
- data/lib/rack/session/cookie.rb +10 -9
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +4 -2
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +55 -70
- data/rack.gemspec +17 -7
- metadata +29 -169
- data/HISTORY.md +0 -505
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -510
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/multipart.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/multipart/parser'
|
2
4
|
|
3
5
|
module Rack
|
@@ -14,10 +16,10 @@ module Rack
|
|
14
16
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
15
17
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
16
18
|
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
17
|
-
BROKEN_QUOTED = /^#{CONDISP}.*;\
|
18
|
-
BROKEN_UNQUOTED = /^#{CONDISP}.*;\
|
19
|
+
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
20
|
+
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
19
21
|
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
20
|
-
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition
|
22
|
+
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
21
23
|
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
22
24
|
# Updated definitions from RFC 2231
|
23
25
|
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Multipart
|
3
5
|
class Generator
|
@@ -27,21 +29,18 @@ module Rack
|
|
27
29
|
|
28
30
|
private
|
29
31
|
def multipart?
|
30
|
-
multipart = false
|
31
|
-
|
32
32
|
query = lambda { |value|
|
33
33
|
case value
|
34
34
|
when Array
|
35
|
-
value.
|
35
|
+
value.any?(&query)
|
36
36
|
when Hash
|
37
|
-
value.values.
|
37
|
+
value.values.any?(&query)
|
38
38
|
when Rack::Multipart::UploadedFile
|
39
|
-
|
39
|
+
true
|
40
40
|
end
|
41
41
|
}
|
42
|
-
@params.values.each(&query)
|
43
42
|
|
44
|
-
|
43
|
+
@params.values.any?(&query)
|
45
44
|
end
|
46
45
|
|
47
46
|
def flattened_params
|
@@ -1,16 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/utils'
|
4
|
+
require 'strscan'
|
5
|
+
require 'rack/core_ext/regexp'
|
2
6
|
|
3
7
|
module Rack
|
4
8
|
module Multipart
|
5
9
|
class MultipartPartLimitError < Errno::EMFILE; end
|
6
10
|
|
7
11
|
class Parser
|
8
|
-
|
12
|
+
using ::Rack::RegexpExtensions
|
13
|
+
|
14
|
+
BUFSIZE = 1_048_576
|
9
15
|
TEXT_PLAIN = "text/plain"
|
10
16
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
11
|
-
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0"
|
17
|
+
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
12
18
|
}
|
13
19
|
|
20
|
+
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
21
|
+
|
14
22
|
class BoundedIO # :nodoc:
|
15
23
|
def initialize(io, content_length)
|
16
24
|
@io = io
|
@@ -18,15 +26,15 @@ module Rack
|
|
18
26
|
@cursor = 0
|
19
27
|
end
|
20
28
|
|
21
|
-
def read(size)
|
29
|
+
def read(size, outbuf = nil)
|
22
30
|
return if @cursor >= @content_length
|
23
31
|
|
24
32
|
left = @content_length - @cursor
|
25
33
|
|
26
34
|
str = if left < size
|
27
|
-
@io.read left
|
35
|
+
@io.read left, outbuf
|
28
36
|
else
|
29
|
-
@io.read size
|
37
|
+
@io.read size, outbuf
|
30
38
|
end
|
31
39
|
|
32
40
|
if str
|
@@ -61,13 +69,14 @@ module Rack
|
|
61
69
|
return EMPTY unless boundary
|
62
70
|
|
63
71
|
io = BoundedIO.new(io, content_length) if content_length
|
72
|
+
outbuf = String.new
|
64
73
|
|
65
74
|
parser = new(boundary, tmpfile, bufsize, qp)
|
66
|
-
parser.on_read io.read(bufsize)
|
75
|
+
parser.on_read io.read(bufsize, outbuf)
|
67
76
|
|
68
77
|
loop do
|
69
78
|
break if parser.state == :DONE
|
70
|
-
parser.on_read io.read(bufsize)
|
79
|
+
parser.on_read io.read(bufsize, outbuf)
|
71
80
|
end
|
72
81
|
|
73
82
|
io.rewind
|
@@ -90,14 +99,14 @@ module Rack
|
|
90
99
|
# those which give the lone filename.
|
91
100
|
fn = filename.split(/[\/\\]/).last
|
92
101
|
|
93
|
-
data = {:
|
94
|
-
:
|
102
|
+
data = { filename: fn, type: content_type,
|
103
|
+
name: name, tempfile: body, head: head }
|
95
104
|
elsif !filename && content_type && body.is_a?(IO)
|
96
105
|
body.rewind
|
97
106
|
|
98
107
|
# Generic multipart cases, not coming from a form
|
99
|
-
data = {:
|
100
|
-
:
|
108
|
+
data = { type: content_type,
|
109
|
+
name: name, tempfile: body, head: head }
|
101
110
|
end
|
102
111
|
|
103
112
|
yield data
|
@@ -138,6 +147,7 @@ module Rack
|
|
138
147
|
end
|
139
148
|
|
140
149
|
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
150
|
+
|
141
151
|
check_open_files
|
142
152
|
end
|
143
153
|
|
@@ -163,25 +173,26 @@ module Rack
|
|
163
173
|
attr_reader :state
|
164
174
|
|
165
175
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
166
|
-
@buf = String.new
|
167
|
-
|
168
176
|
@query_parser = query_parser
|
169
177
|
@params = query_parser.make_params
|
170
178
|
@boundary = "--#{boundary}"
|
171
179
|
@bufsize = bufsize
|
172
180
|
|
173
|
-
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
174
|
-
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
175
181
|
@full_boundary = @boundary
|
176
182
|
@end_boundary = @boundary + '--'
|
177
183
|
@state = :FAST_FORWARD
|
178
184
|
@mime_index = 0
|
179
185
|
@collector = Collector.new tempfile
|
186
|
+
|
187
|
+
@sbuf = StringScanner.new("".dup)
|
188
|
+
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
189
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
190
|
+
@head_regex = /(.*?#{EOL})#{EOL}/m
|
180
191
|
end
|
181
192
|
|
182
193
|
def on_read content
|
183
194
|
handle_empty_content!(content)
|
184
|
-
@
|
195
|
+
@sbuf.concat content
|
185
196
|
run_parser
|
186
197
|
end
|
187
198
|
|
@@ -192,7 +203,6 @@ module Rack
|
|
192
203
|
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
193
204
|
end
|
194
205
|
end
|
195
|
-
|
196
206
|
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
197
207
|
end
|
198
208
|
|
@@ -219,7 +229,7 @@ module Rack
|
|
219
229
|
if consume_boundary
|
220
230
|
@state = :MIME_HEAD
|
221
231
|
else
|
222
|
-
raise EOFError, "bad content body" if @
|
232
|
+
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
223
233
|
:want_read
|
224
234
|
end
|
225
235
|
end
|
@@ -227,19 +237,16 @@ module Rack
|
|
227
237
|
def handle_consume_token
|
228
238
|
tok = consume_boundary
|
229
239
|
# break if we're at the end of a buffer, but not if it is the end of a field
|
230
|
-
if tok == :END_BOUNDARY || (@
|
231
|
-
|
240
|
+
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
241
|
+
:DONE
|
232
242
|
else
|
233
|
-
|
243
|
+
:MIME_HEAD
|
234
244
|
end
|
235
245
|
end
|
236
246
|
|
237
247
|
def handle_mime_head
|
238
|
-
if @
|
239
|
-
|
240
|
-
head = @buf.slice!(0, i+2) # First \r\n
|
241
|
-
@buf.slice!(0, 2) # Second \r\n
|
242
|
-
|
248
|
+
if @sbuf.scan_until(@head_regex)
|
249
|
+
head = @sbuf[1]
|
243
250
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
244
251
|
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
245
252
|
name = Rack::Auth::Digest::Params::dequote(name)
|
@@ -250,7 +257,7 @@ module Rack
|
|
250
257
|
filename = get_filename(head)
|
251
258
|
|
252
259
|
if name.nil? || name.empty?
|
253
|
-
name = filename || "#{content_type || TEXT_PLAIN}[]"
|
260
|
+
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
254
261
|
end
|
255
262
|
|
256
263
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
@@ -261,16 +268,19 @@ module Rack
|
|
261
268
|
end
|
262
269
|
|
263
270
|
def handle_mime_body
|
264
|
-
if
|
265
|
-
#
|
266
|
-
@collector.on_mime_body @mime_index,
|
267
|
-
@
|
271
|
+
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
272
|
+
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
273
|
+
@collector.on_mime_body @mime_index, body
|
274
|
+
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
268
275
|
@state = :CONSUME_TOKEN
|
269
276
|
@mime_index += 1
|
270
277
|
else
|
271
|
-
# Save
|
272
|
-
if @rx_max_size < @
|
273
|
-
|
278
|
+
# Save what we have so far
|
279
|
+
if @rx_max_size < @sbuf.rest_size
|
280
|
+
delta = @sbuf.rest_size - @rx_max_size
|
281
|
+
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
282
|
+
@sbuf.pos += delta
|
283
|
+
@sbuf.string = @sbuf.rest
|
274
284
|
end
|
275
285
|
:want_read
|
276
286
|
end
|
@@ -278,16 +288,13 @@ module Rack
|
|
278
288
|
|
279
289
|
def full_boundary; @full_boundary; end
|
280
290
|
|
281
|
-
def rx; @rx; end
|
282
|
-
|
283
291
|
def consume_boundary
|
284
|
-
while @
|
285
|
-
read_buffer = $1
|
292
|
+
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
286
293
|
case read_buffer.strip
|
287
294
|
when full_boundary then return :BOUNDARY
|
288
295
|
when @end_boundary then return :END_BOUNDARY
|
289
296
|
end
|
290
|
-
return if @
|
297
|
+
return if @sbuf.eos?
|
291
298
|
end
|
292
299
|
end
|
293
300
|
|
@@ -308,8 +315,8 @@ module Rack
|
|
308
315
|
|
309
316
|
return unless filename
|
310
317
|
|
311
|
-
if filename.scan(/%.?.?/).all? { |s|
|
312
|
-
filename = Utils.
|
318
|
+
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
319
|
+
filename = Utils.unescape_path(filename)
|
313
320
|
end
|
314
321
|
|
315
322
|
filename.scrub!
|
@@ -325,7 +332,7 @@ module Rack
|
|
325
332
|
filename
|
326
333
|
end
|
327
334
|
|
328
|
-
CHARSET
|
335
|
+
CHARSET = "charset"
|
329
336
|
|
330
337
|
def tag_multipart_encoding(filename, content_type, name, body)
|
331
338
|
name = name.to_s
|
@@ -342,10 +349,10 @@ module Rack
|
|
342
349
|
if TEXT_PLAIN == type_subtype
|
343
350
|
rest = list.drop 1
|
344
351
|
rest.each do |param|
|
345
|
-
k,v = param.split('=', 2)
|
352
|
+
k, v = param.split('=', 2)
|
346
353
|
k.strip!
|
347
354
|
v.strip!
|
348
|
-
v = v[1..-2] if v
|
355
|
+
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
349
356
|
encoding = Encoding.find v if k == CHARSET
|
350
357
|
end
|
351
358
|
end
|
@@ -355,7 +362,6 @@ module Rack
|
|
355
362
|
body.force_encoding(encoding)
|
356
363
|
end
|
357
364
|
|
358
|
-
|
359
365
|
def handle_empty_content!(content)
|
360
366
|
if content.nil? || content.empty?
|
361
367
|
raise EOFError
|
data/lib/rack/null_logger.rb
CHANGED
data/lib/rack/query_parser.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'core_ext/regexp'
|
4
|
+
|
1
5
|
module Rack
|
2
6
|
class QueryParser
|
7
|
+
using ::Rack::RegexpExtensions
|
8
|
+
|
3
9
|
DEFAULT_SEP = /[&;] */n
|
4
10
|
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
5
11
|
|
@@ -36,7 +42,7 @@ module Rack
|
|
36
42
|
|
37
43
|
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
38
44
|
next if p.empty?
|
39
|
-
k, v = p.split('='
|
45
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
40
46
|
|
41
47
|
if cur = params[k]
|
42
48
|
if cur.class == Array
|
@@ -49,7 +55,7 @@ module Rack
|
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
52
|
-
return params.
|
58
|
+
return params.to_h
|
53
59
|
end
|
54
60
|
|
55
61
|
# parse_nested_query expands a query string into structural types. Supported
|
@@ -61,13 +67,13 @@ module Rack
|
|
61
67
|
return {} if qs.nil? || qs.empty?
|
62
68
|
params = make_params
|
63
69
|
|
64
|
-
|
65
|
-
k, v = p.split('='
|
70
|
+
qs.split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
71
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
66
72
|
|
67
73
|
normalize_params(params, k, v, param_depth_limit)
|
68
74
|
end
|
69
75
|
|
70
|
-
return params.
|
76
|
+
return params.to_h
|
71
77
|
rescue ArgumentError => e
|
72
78
|
raise InvalidParameterError, e.message
|
73
79
|
end
|
@@ -79,22 +85,22 @@ module Rack
|
|
79
85
|
raise RangeError if depth <= 0
|
80
86
|
|
81
87
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
82
|
-
k = $1 || ''
|
83
|
-
after = $' || ''
|
88
|
+
k = $1 || ''
|
89
|
+
after = $' || ''
|
84
90
|
|
85
91
|
if k.empty?
|
86
|
-
if !v.nil? && name == "[]"
|
92
|
+
if !v.nil? && name == "[]"
|
87
93
|
return Array(v)
|
88
94
|
else
|
89
95
|
return
|
90
96
|
end
|
91
97
|
end
|
92
98
|
|
93
|
-
if after == ''
|
99
|
+
if after == ''
|
94
100
|
params[k] = v
|
95
|
-
elsif after == "["
|
101
|
+
elsif after == "["
|
96
102
|
params[name] = v
|
97
|
-
elsif after == "[]"
|
103
|
+
elsif after == "[]"
|
98
104
|
params[k] ||= []
|
99
105
|
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
100
106
|
params[k] << v
|
@@ -135,7 +141,7 @@ module Rack
|
|
135
141
|
end
|
136
142
|
|
137
143
|
def params_hash_has_key?(hash, key)
|
138
|
-
return false if
|
144
|
+
return false if /\[\]/.match?(key)
|
139
145
|
|
140
146
|
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
141
147
|
next h if part == ''
|
@@ -171,22 +177,42 @@ module Rack
|
|
171
177
|
@params.key?(key)
|
172
178
|
end
|
173
179
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
180
|
+
# Recursively unwraps nested `Params` objects and constructs an object
|
181
|
+
# of the same shape, but using the objects' internal representations
|
182
|
+
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
183
|
+
# purely of Ruby primitives.
|
184
|
+
#
|
185
|
+
# Mutation warning!
|
186
|
+
#
|
187
|
+
# 1. This method mutates the internal representation of the `Params`
|
188
|
+
# objects in order to save object allocations.
|
189
|
+
#
|
190
|
+
# 2. The value you get back is a reference to the internal hash
|
191
|
+
# representation, not a copy.
|
192
|
+
#
|
193
|
+
# 3. Because the `Params` object's internal representation is mutable
|
194
|
+
# through the `#[]=` method, it is not thread safe. The result of
|
195
|
+
# getting the hash representation while another thread is adding a
|
196
|
+
# key to it is non-deterministic.
|
197
|
+
#
|
198
|
+
def to_h
|
199
|
+
@params.each do |key, value|
|
200
|
+
case value
|
201
|
+
when self
|
202
|
+
# Handle circular references gracefully.
|
203
|
+
@params[key] = @params
|
204
|
+
when Params
|
205
|
+
@params[key] = value.to_h
|
206
|
+
when Array
|
207
|
+
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
208
|
+
else
|
209
|
+
# Ignore anything that is not a `Params` object or
|
210
|
+
# a collection that can contain one.
|
186
211
|
end
|
187
212
|
end
|
188
|
-
|
213
|
+
@params
|
189
214
|
end
|
215
|
+
alias_method :to_params_hash, :to_h
|
190
216
|
end
|
191
217
|
end
|
192
218
|
end
|