rack 1.5.5 → 1.6.0.beta
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/KNOWN-ISSUES +14 -0
- data/README.rdoc +10 -6
- data/Rakefile +3 -4
- data/SPEC +59 -23
- data/lib/rack.rb +2 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +1 -1
- data/lib/rack/builder.rb +19 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/chunked.rb +12 -1
- data/lib/rack/commonlogger.rb +13 -5
- data/lib/rack/conditionalget.rb +14 -2
- data/lib/rack/content_length.rb +5 -1
- data/lib/rack/deflater.rb +52 -13
- data/lib/rack/directory.rb +8 -2
- data/lib/rack/etag.rb +14 -6
- data/lib/rack/file.rb +10 -14
- data/lib/rack/handler.rb +2 -0
- data/lib/rack/handler/fastcgi.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -2
- data/lib/rack/handler/scgi.rb +4 -1
- data/lib/rack/handler/thin.rb +8 -2
- data/lib/rack/handler/webrick.rb +46 -6
- data/lib/rack/head.rb +7 -2
- data/lib/rack/lint.rb +73 -25
- data/lib/rack/lobster.rb +8 -3
- data/lib/rack/methodoverride.rb +14 -3
- data/lib/rack/mime.rb +1 -15
- data/lib/rack/mock.rb +15 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/multipart/parser.rb +107 -53
- data/lib/rack/multipart/uploaded_file.rb +2 -2
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/request.rb +38 -24
- data/lib/rack/response.rb +5 -0
- data/lib/rack/sendfile.rb +10 -5
- data/lib/rack/server.rb +45 -17
- data/lib/rack/session/abstract/id.rb +7 -6
- data/lib/rack/session/cookie.rb +17 -7
- data/lib/rack/session/memcache.rb +4 -4
- data/lib/rack/session/pool.rb +3 -6
- data/lib/rack/showexceptions.rb +20 -11
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/static.rb +27 -30
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +17 -3
- data/lib/rack/utils.rb +78 -47
- data/lib/rack/utils/okjson.rb +90 -91
- data/rack.gemspec +3 -3
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/spec_builder.rb +13 -4
- data/test/spec_chunked.rb +16 -0
- data/test/spec_commonlogger.rb +36 -0
- data/test/spec_content_length.rb +3 -1
- data/test/spec_deflater.rb +283 -148
- data/test/spec_etag.rb +11 -2
- data/test/spec_file.rb +11 -3
- data/test/spec_head.rb +2 -0
- data/test/spec_lobster.rb +1 -1
- data/test/spec_mock.rb +8 -0
- data/test/spec_multipart.rb +111 -49
- data/test/spec_request.rb +109 -25
- data/test/spec_response.rb +30 -0
- data/test/spec_server.rb +20 -5
- data/test/spec_session_cookie.rb +45 -2
- data/test/spec_session_memcache.rb +1 -1
- data/test/spec_showexceptions.rb +29 -36
- data/test/spec_showstatus.rb +19 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/spec_urlmap.rb +23 -0
- data/test/spec_utils.rb +60 -10
- data/test/spec_webrick.rb +41 -0
- metadata +12 -9
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
data/test/spec_etag.rb
CHANGED
@@ -21,13 +21,13 @@ describe Rack::ETag do
|
|
21
21
|
should "set ETag if none is set if status is 200" do
|
22
22
|
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
23
23
|
response = etag(app).call(request)
|
24
|
-
response[1]['ETag'].should.equal "
|
24
|
+
response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
|
25
25
|
end
|
26
26
|
|
27
27
|
should "set ETag if none is set if status is 201" do
|
28
28
|
app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
29
29
|
response = etag(app).call(request)
|
30
|
-
response[1]['ETag'].should.equal "
|
30
|
+
response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
|
31
31
|
end
|
32
32
|
|
33
33
|
should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
|
@@ -95,4 +95,13 @@ describe Rack::ETag do
|
|
95
95
|
response = etag(app).call(request)
|
96
96
|
response[1]['ETag'].should.be.nil
|
97
97
|
end
|
98
|
+
|
99
|
+
should "close the original body" do
|
100
|
+
body = StringIO.new
|
101
|
+
app = lambda { |env| [200, {}, body] }
|
102
|
+
response = etag(app).call(request)
|
103
|
+
body.should.not.be.closed
|
104
|
+
response[2].close
|
105
|
+
body.should.be.closed
|
106
|
+
end
|
98
107
|
end
|
data/test/spec_file.rb
CHANGED
@@ -164,24 +164,32 @@ describe Rack::File do
|
|
164
164
|
heads['Access-Control-Allow-Origin'].should.equal nil
|
165
165
|
end
|
166
166
|
|
167
|
-
should "only support GET and
|
167
|
+
should "only support GET, HEAD, and OPTIONS requests" do
|
168
168
|
req = Rack::MockRequest.new(file(DOCROOT))
|
169
169
|
|
170
170
|
forbidden = %w[post put patch delete]
|
171
171
|
forbidden.each do |method|
|
172
|
-
|
173
172
|
res = req.send(method, "/cgi/test")
|
174
173
|
res.should.be.client_error
|
175
174
|
res.should.be.method_not_allowed
|
175
|
+
res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS)
|
176
176
|
end
|
177
177
|
|
178
|
-
allowed = %w[get head]
|
178
|
+
allowed = %w[get head options]
|
179
179
|
allowed.each do |method|
|
180
180
|
res = req.send(method, "/cgi/test")
|
181
181
|
res.should.be.successful
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
+
should "set Allow correctly for OPTIONS requests" do
|
186
|
+
req = Rack::MockRequest.new(file(DOCROOT))
|
187
|
+
res = req.options('/cgi/test')
|
188
|
+
res.should.be.successful
|
189
|
+
res.headers['Allow'].should.not.equal nil
|
190
|
+
res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS)
|
191
|
+
end
|
192
|
+
|
185
193
|
should "set Content-Length correctly for HEAD requests" do
|
186
194
|
req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
|
187
195
|
res = req.head "/cgi/test"
|
data/test/spec_head.rb
CHANGED
@@ -38,6 +38,8 @@ describe Rack::Head do
|
|
38
38
|
resp[0].should.equal(200)
|
39
39
|
resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
|
40
40
|
resp[2].to_enum.to_a.should.equal([])
|
41
|
+
body.should.not.be.closed
|
42
|
+
resp[2].close
|
41
43
|
body.should.be.closed
|
42
44
|
end
|
43
45
|
end
|
data/test/spec_lobster.rb
CHANGED
data/test/spec_mock.rb
CHANGED
@@ -30,6 +30,14 @@ describe Rack::MockRequest do
|
|
30
30
|
env.should.include "rack.version"
|
31
31
|
end
|
32
32
|
|
33
|
+
should "return an environment with a path" do
|
34
|
+
env = Rack::MockRequest.env_for("http://www.example.com/parse?location[]=1&location[]=2&age_group[]=2")
|
35
|
+
env["QUERY_STRING"].should.equal "location[]=1&location[]=2&age_group[]=2"
|
36
|
+
env["PATH_INFO"].should.equal "/parse"
|
37
|
+
env.should.be.kind_of Hash
|
38
|
+
env.should.include "rack.version"
|
39
|
+
end
|
40
|
+
|
33
41
|
should "provide sensible defaults" do
|
34
42
|
res = Rack::MockRequest.new(app).request
|
35
43
|
|
data/test/spec_multipart.rb
CHANGED
@@ -30,6 +30,44 @@ describe Rack::Multipart do
|
|
30
30
|
params["text"].should.equal "contents"
|
31
31
|
end
|
32
32
|
|
33
|
+
if "<3".respond_to?(:force_encoding)
|
34
|
+
should "set US_ASCII encoding based on charset" do
|
35
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
36
|
+
params = Rack::Multipart.parse_multipart(env)
|
37
|
+
params["text"].encoding.should.equal Encoding::US_ASCII
|
38
|
+
|
39
|
+
# I'm not 100% sure if making the param name encoding match the
|
40
|
+
# Content-Type charset is the right thing to do. We should revisit this.
|
41
|
+
params.keys.each do |key|
|
42
|
+
key.encoding.should.equal Encoding::US_ASCII
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
should "set BINARY encoding on things without content type" do
|
47
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
48
|
+
params = Rack::Multipart.parse_multipart(env)
|
49
|
+
params["submit-name"].encoding.should.equal Encoding::UTF_8
|
50
|
+
end
|
51
|
+
|
52
|
+
should "set UTF8 encoding on names of things without content type" do
|
53
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
54
|
+
params = Rack::Multipart.parse_multipart(env)
|
55
|
+
params.keys.each do |key|
|
56
|
+
key.encoding.should.equal Encoding::UTF_8
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
should "default text to UTF8" do
|
61
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
|
62
|
+
params = Rack::Multipart.parse_multipart(env)
|
63
|
+
params['submit-name'].encoding.should.equal Encoding::UTF_8
|
64
|
+
params['submit-name-with-content'].encoding.should.equal Encoding::UTF_8
|
65
|
+
params.keys.each do |key|
|
66
|
+
key.encoding.should.equal Encoding::UTF_8
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
33
71
|
should "raise RangeError if the key space is exhausted" do
|
34
72
|
env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
|
35
73
|
|
@@ -115,6 +153,18 @@ describe Rack::Multipart do
|
|
115
153
|
params["files"][:tempfile].read.should.equal "contents"
|
116
154
|
end
|
117
155
|
|
156
|
+
should "parse multipart upload with text file with no name field" do
|
157
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name))
|
158
|
+
params = Rack::Multipart.parse_multipart(env)
|
159
|
+
params["file1.txt"][:type].should.equal "text/plain"
|
160
|
+
params["file1.txt"][:filename].should.equal "file1.txt"
|
161
|
+
params["file1.txt"][:head].should.equal "Content-Disposition: form-data; " +
|
162
|
+
"filename=\"file1.txt\"\r\n" +
|
163
|
+
"Content-Type: text/plain\r\n"
|
164
|
+
params["file1.txt"][:name].should.equal "file1.txt"
|
165
|
+
params["file1.txt"][:tempfile].read.should.equal "contents"
|
166
|
+
end
|
167
|
+
|
118
168
|
should "parse multipart upload with nested parameters" do
|
119
169
|
env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
|
120
170
|
params = Rack::Multipart.parse_multipart(env)
|
@@ -166,6 +216,20 @@ describe Rack::Multipart do
|
|
166
216
|
params["files"][:tempfile].read.should.equal "contents"
|
167
217
|
end
|
168
218
|
|
219
|
+
should "parse multipart upload with filename with invalid characters" do
|
220
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:invalid_character))
|
221
|
+
params = Rack::Multipart.parse_multipart(env)
|
222
|
+
params["files"][:type].should.equal "text/plain"
|
223
|
+
params["files"][:filename].should.match(/invalid/)
|
224
|
+
head = "Content-Disposition: form-data; " +
|
225
|
+
"name=\"files\"; filename=\"invalid\xC3.txt\"\r\n" +
|
226
|
+
"Content-Type: text/plain\r\n"
|
227
|
+
head = head.force_encoding("ASCII-8BIT") if head.respond_to?(:force_encoding)
|
228
|
+
params["files"][:head].should.equal head
|
229
|
+
params["files"][:name].should.equal "files"
|
230
|
+
params["files"][:tempfile].read.should.equal "contents"
|
231
|
+
end
|
232
|
+
|
169
233
|
should "not include file params if no file was selected" do
|
170
234
|
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
|
171
235
|
params = Rack::Multipart.parse_multipart(env)
|
@@ -182,6 +246,14 @@ describe Rack::Multipart do
|
|
182
246
|
params["files"].size.should.equal 252
|
183
247
|
end
|
184
248
|
|
249
|
+
should "parse multipart/mixed" do
|
250
|
+
env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files))
|
251
|
+
params = Rack::Utils::Multipart.parse_multipart(env)
|
252
|
+
params["foo"].should.equal "bar"
|
253
|
+
params["files"].should.be.instance_of String
|
254
|
+
params["files"].size.should.equal 252
|
255
|
+
end
|
256
|
+
|
185
257
|
should "parse IE multipart upload and clean up filename" do
|
186
258
|
env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
|
187
259
|
params = Rack::Multipart.parse_multipart(env)
|
@@ -364,56 +436,22 @@ Content-Type: image/jpeg\r
|
|
364
436
|
end
|
365
437
|
|
366
438
|
it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
:input => StringIO.new(data)
|
376
|
-
}
|
377
|
-
env = Rack::MockRequest.env_for("/", options)
|
378
|
-
params = Rack::Multipart.parse_multipart(env)
|
379
|
-
|
380
|
-
params.should.not.equal nil
|
381
|
-
params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
|
382
|
-
params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
|
383
|
-
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
|
384
|
-
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
|
385
|
-
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
|
386
|
-
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
|
387
|
-
ensure
|
388
|
-
Rack::Utils.multipart_part_limit = previous_limit
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
should "not reach a multi-part limit" do
|
393
|
-
begin
|
394
|
-
previous_limit = Rack::Utils.multipart_part_limit
|
395
|
-
Rack::Utils.multipart_part_limit = 4
|
396
|
-
|
397
|
-
env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields)
|
398
|
-
params = Rack::Multipart.parse_multipart(env)
|
399
|
-
params['reply'].should.equal 'yes'
|
400
|
-
params['to'].should.equal 'people'
|
401
|
-
params['from'].should.equal 'others'
|
402
|
-
ensure
|
403
|
-
Rack::Utils.multipart_part_limit = previous_limit
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
should "reach a multipart limit" do
|
408
|
-
begin
|
409
|
-
previous_limit = Rack::Utils.multipart_part_limit
|
410
|
-
Rack::Utils.multipart_part_limit = 3
|
439
|
+
data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n")
|
440
|
+
options = {
|
441
|
+
"CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
|
442
|
+
"CONTENT_LENGTH" => data.length.to_s,
|
443
|
+
:input => StringIO.new(data)
|
444
|
+
}
|
445
|
+
env = Rack::MockRequest.env_for("/", options)
|
446
|
+
params = Rack::Multipart.parse_multipart(env)
|
411
447
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
448
|
+
params.should.not.equal nil
|
449
|
+
params.keys.should.include "AAAAAAAAAAAAAAAAAAA"
|
450
|
+
params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"
|
451
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new"
|
452
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2"
|
453
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id"
|
454
|
+
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
|
417
455
|
end
|
418
456
|
|
419
457
|
should "return nil if no UploadedFiles were used" do
|
@@ -476,4 +514,28 @@ contents\r
|
|
476
514
|
params["file"][:filename].should.equal('long' * 100)
|
477
515
|
end
|
478
516
|
|
517
|
+
should "support mixed case metadata" do
|
518
|
+
file = multipart_file(:text)
|
519
|
+
data = File.open(file, 'rb') { |io| io.read }
|
520
|
+
|
521
|
+
type = "Multipart/Form-Data; Boundary=AaB03x"
|
522
|
+
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
|
523
|
+
|
524
|
+
e = { "CONTENT_TYPE" => type,
|
525
|
+
"CONTENT_LENGTH" => length.to_s,
|
526
|
+
:input => StringIO.new(data) }
|
527
|
+
|
528
|
+
env = Rack::MockRequest.env_for("/", e)
|
529
|
+
params = Rack::Multipart.parse_multipart(env)
|
530
|
+
params["submit-name"].should.equal "Larry"
|
531
|
+
params["submit-name-with-content"].should.equal "Berry"
|
532
|
+
params["files"][:type].should.equal "text/plain"
|
533
|
+
params["files"][:filename].should.equal "file1.txt"
|
534
|
+
params["files"][:head].should.equal "Content-Disposition: form-data; " +
|
535
|
+
"name=\"files\"; filename=\"file1.txt\"\r\n" +
|
536
|
+
"Content-Type: text/plain\r\n"
|
537
|
+
params["files"][:name].should.equal "files"
|
538
|
+
params["files"][:tempfile].read.should.equal "contents"
|
539
|
+
end
|
540
|
+
|
479
541
|
end
|
data/test/spec_request.rb
CHANGED
@@ -2,7 +2,6 @@ require 'stringio'
|
|
2
2
|
require 'cgi'
|
3
3
|
require 'rack/request'
|
4
4
|
require 'rack/mock'
|
5
|
-
require 'securerandom'
|
6
5
|
|
7
6
|
describe Rack::Request do
|
8
7
|
should "wrap the rack variables" do
|
@@ -97,7 +96,10 @@ describe Rack::Request do
|
|
97
96
|
|
98
97
|
req = Rack::Request.new \
|
99
98
|
Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https", "SERVER_PORT" => "80")
|
99
|
+
req.port.should.equal 443
|
100
100
|
|
101
|
+
req = Rack::Request.new \
|
102
|
+
Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https,https", "SERVER_PORT" => "80")
|
101
103
|
req.port.should.equal 443
|
102
104
|
end
|
103
105
|
|
@@ -131,6 +133,14 @@ describe Rack::Request do
|
|
131
133
|
req.params.should.equal "foo" => "bar", "quux" => "bla"
|
132
134
|
end
|
133
135
|
|
136
|
+
should "not truncate query strings containing semi-colons #543" do
|
137
|
+
req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=b;la"))
|
138
|
+
req.query_string.should.equal "foo=bar&quux=b;la"
|
139
|
+
req.GET.should.equal "foo" => "bar", "quux" => "b;la"
|
140
|
+
req.POST.should.be.empty
|
141
|
+
req.params.should.equal "foo" => "bar", "quux" => "b;la"
|
142
|
+
end
|
143
|
+
|
134
144
|
should "limit the keys from the GET query string" do
|
135
145
|
env = Rack::MockRequest.env_for("/?foo=bar")
|
136
146
|
|
@@ -144,7 +154,7 @@ describe Rack::Request do
|
|
144
154
|
end
|
145
155
|
|
146
156
|
should "limit the key size per nested params hash" do
|
147
|
-
nested_query = Rack::MockRequest.env_for("/?foo
|
157
|
+
nested_query = Rack::MockRequest.env_for("/?foo%5Bbar%5D%5Bbaz%5D%5Bqux%5D=1")
|
148
158
|
plain_query = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1")
|
149
159
|
|
150
160
|
old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3
|
@@ -170,6 +180,18 @@ describe Rack::Request do
|
|
170
180
|
req.params.should.equal req.GET.merge(req.POST)
|
171
181
|
end
|
172
182
|
|
183
|
+
should "raise if input params has invalid %-encoding" do
|
184
|
+
mr = Rack::MockRequest.env_for("/?foo=quux",
|
185
|
+
"REQUEST_METHOD" => 'POST',
|
186
|
+
:input => "a%=1"
|
187
|
+
)
|
188
|
+
req = Rack::Request.new mr
|
189
|
+
|
190
|
+
lambda { req.POST }.
|
191
|
+
should.raise(Rack::Utils::InvalidParameterError).
|
192
|
+
message.should.equal "invalid %-encoding (a%)"
|
193
|
+
end
|
194
|
+
|
173
195
|
should "raise if rack.input is missing" do
|
174
196
|
req = Rack::Request.new({})
|
175
197
|
lambda { req.POST }.should.raise(RuntimeError)
|
@@ -604,7 +626,7 @@ describe Rack::Request do
|
|
604
626
|
should "handle multiple media type parameters" do
|
605
627
|
req = Rack::Request.new \
|
606
628
|
Rack::MockRequest.env_for("/",
|
607
|
-
"CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam')
|
629
|
+
"CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam;blong="boo";zump="zoo\"o";weird=lol"')
|
608
630
|
req.should.not.be.form_data
|
609
631
|
req.media_type_params.should.include 'foo'
|
610
632
|
req.media_type_params['foo'].should.equal 'BAR'
|
@@ -613,6 +635,9 @@ describe Rack::Request do
|
|
613
635
|
req.media_type_params.should.not.include 'BLING'
|
614
636
|
req.media_type_params.should.include 'bling'
|
615
637
|
req.media_type_params['bling'].should.equal 'bam'
|
638
|
+
req.media_type_params['blong'].should.equal 'boo'
|
639
|
+
req.media_type_params['zump'].should.equal 'zoo\"o'
|
640
|
+
req.media_type_params['weird'].should.equal 'lol"'
|
616
641
|
end
|
617
642
|
|
618
643
|
should "parse with junk before boundry" do
|
@@ -719,22 +744,6 @@ EOF
|
|
719
744
|
f[:tempfile].size.should.equal 76
|
720
745
|
end
|
721
746
|
|
722
|
-
should "MultipartLimitError when request has too many multipart parts if limit set" do
|
723
|
-
begin
|
724
|
-
data = 10000.times.map { "--AaB03x\r\nContent-Type: text/plain\r\nContent-Disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n")
|
725
|
-
data += "--AaB03x--\r"
|
726
|
-
|
727
|
-
options = {
|
728
|
-
"CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
|
729
|
-
"CONTENT_LENGTH" => data.length.to_s,
|
730
|
-
:input => StringIO.new(data)
|
731
|
-
}
|
732
|
-
|
733
|
-
request = Rack::Request.new Rack::MockRequest.env_for("/", options)
|
734
|
-
lambda { request.POST }.should.raise(Rack::Multipart::MultipartLimitError)
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
747
|
should "parse big multipart form data" do
|
739
748
|
input = <<EOF
|
740
749
|
--AaB03x\r
|
@@ -757,6 +766,31 @@ EOF
|
|
757
766
|
req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
|
758
767
|
end
|
759
768
|
|
769
|
+
should "record tempfiles from multipart form data in env[rack.tempfiles]" do
|
770
|
+
input = <<EOF
|
771
|
+
--AaB03x\r
|
772
|
+
content-disposition: form-data; name="fileupload"; filename="foo.jpg"\r
|
773
|
+
Content-Type: image/jpeg\r
|
774
|
+
Content-Transfer-Encoding: base64\r
|
775
|
+
\r
|
776
|
+
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
|
777
|
+
--AaB03x\r
|
778
|
+
content-disposition: form-data; name="fileupload"; filename="bar.jpg"\r
|
779
|
+
Content-Type: image/jpeg\r
|
780
|
+
Content-Transfer-Encoding: base64\r
|
781
|
+
\r
|
782
|
+
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
|
783
|
+
--AaB03x--\r
|
784
|
+
EOF
|
785
|
+
env = Rack::MockRequest.env_for("/",
|
786
|
+
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
|
787
|
+
"CONTENT_LENGTH" => input.size,
|
788
|
+
:input => input)
|
789
|
+
req = Rack::Request.new(env)
|
790
|
+
req.params
|
791
|
+
env['rack.tempfiles'].size.should.equal(2)
|
792
|
+
end
|
793
|
+
|
760
794
|
should "detect invalid multipart form data" do
|
761
795
|
input = <<EOF
|
762
796
|
--AaB03x\r
|
@@ -796,6 +830,20 @@ EOF
|
|
796
830
|
lambda { req.POST }.should.raise(EOFError)
|
797
831
|
end
|
798
832
|
|
833
|
+
should "consistently raise EOFError on bad multipart form data" do
|
834
|
+
input = <<EOF
|
835
|
+
--AaB03x\r
|
836
|
+
content-disposition: form-data; name="huge"; filename="huge"\r
|
837
|
+
EOF
|
838
|
+
req = Rack::Request.new Rack::MockRequest.env_for("/",
|
839
|
+
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
|
840
|
+
"CONTENT_LENGTH" => input.size,
|
841
|
+
:input => input)
|
842
|
+
|
843
|
+
lambda { req.POST }.should.raise(EOFError)
|
844
|
+
lambda { req.POST }.should.raise(EOFError)
|
845
|
+
end
|
846
|
+
|
799
847
|
should "correctly parse the part name from Content-Id header" do
|
800
848
|
input = <<EOF
|
801
849
|
--AaB03x\r
|
@@ -941,6 +989,23 @@ EOF
|
|
941
989
|
parser.call("gzip ; deflate").should.equal([["gzip", 1.0]])
|
942
990
|
end
|
943
991
|
|
992
|
+
should "parse Accept-Language correctly" do
|
993
|
+
parser = lambda do |x|
|
994
|
+
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_LANGUAGE" => x)).accept_language
|
995
|
+
end
|
996
|
+
|
997
|
+
parser.call(nil).should.equal([])
|
998
|
+
|
999
|
+
parser.call("fr, en").should.equal([["fr", 1.0], ["en", 1.0]])
|
1000
|
+
parser.call("").should.equal([])
|
1001
|
+
parser.call("*").should.equal([["*", 1.0]])
|
1002
|
+
parser.call("fr;q=0.5, en;q=1.0").should.equal([["fr", 0.5], ["en", 1.0]])
|
1003
|
+
parser.call("fr;q=1.0, en; q=0.5, *;q=0").should.equal([["fr", 1.0], ["en", 0.5], ["*", 0] ])
|
1004
|
+
|
1005
|
+
parser.call("fr ; q=0.9").should.equal([["fr", 0.9]])
|
1006
|
+
parser.call("fr").should.equal([["fr", 1.0]])
|
1007
|
+
end
|
1008
|
+
|
944
1009
|
ip_app = lambda { |env|
|
945
1010
|
request = Rack::Request.new(env)
|
946
1011
|
response = Rack::Response.new
|
@@ -1023,12 +1088,6 @@ EOF
|
|
1023
1088
|
'HTTP_CLIENT_IP' => '1.1.1.1'
|
1024
1089
|
res.body.should.equal '1.1.1.1'
|
1025
1090
|
|
1026
|
-
# Spoofing attempt
|
1027
|
-
res = mock.get '/',
|
1028
|
-
'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
|
1029
|
-
'HTTP_CLIENT_IP' => '2.2.2.2'
|
1030
|
-
res.body.should.equal '1.1.1.1'
|
1031
|
-
|
1032
1091
|
res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
|
1033
1092
|
res.body.should.equal '9.9.9.9'
|
1034
1093
|
|
@@ -1047,6 +1106,24 @@ EOF
|
|
1047
1106
|
res.body.should.equal '3.4.5.6'
|
1048
1107
|
end
|
1049
1108
|
|
1109
|
+
should "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do
|
1110
|
+
mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
|
1111
|
+
|
1112
|
+
# IP Spoofing attempt:
|
1113
|
+
# Client sends X-Forwarded-For: 6.6.6.6
|
1114
|
+
# Client-IP: 6.6.6.6
|
1115
|
+
# Load balancer adds X-Forwarded-For: 2.2.2.3, 192.168.0.7
|
1116
|
+
# App receives: X-Forwarded-For: 6.6.6.6
|
1117
|
+
# X-Forwarded-For: 2.2.2.3, 192.168.0.7
|
1118
|
+
# Client-IP: 6.6.6.6
|
1119
|
+
# Rack env: HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7'
|
1120
|
+
# HTTP_CLIENT_IP: '6.6.6.6'
|
1121
|
+
res = mock.get '/',
|
1122
|
+
'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
|
1123
|
+
'HTTP_CLIENT_IP' => '6.6.6.6'
|
1124
|
+
res.body.should.equal '2.2.2.3'
|
1125
|
+
end
|
1126
|
+
|
1050
1127
|
should "regard local addresses as proxies" do
|
1051
1128
|
req = Rack::Request.new(Rack::MockRequest.env_for("/"))
|
1052
1129
|
req.trusted_proxy?('127.0.0.1').should.equal 0
|
@@ -1101,6 +1178,13 @@ EOF
|
|
1101
1178
|
req2.params.should.equal "foo" => "bar"
|
1102
1179
|
end
|
1103
1180
|
|
1181
|
+
should "raise TypeError every time if request parameters are broken" do
|
1182
|
+
broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1")
|
1183
|
+
req = Rack::Request.new(broken_query)
|
1184
|
+
lambda{req.GET}.should.raise(TypeError)
|
1185
|
+
lambda{req.params}.should.raise(TypeError)
|
1186
|
+
end
|
1187
|
+
|
1104
1188
|
(0x20...0x7E).collect { |a|
|
1105
1189
|
b = a.chr
|
1106
1190
|
c = CGI.escape(b)
|