rack 1.5.5 → 1.6.0.beta

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.

Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/KNOWN-ISSUES +14 -0
  3. data/README.rdoc +10 -6
  4. data/Rakefile +3 -4
  5. data/SPEC +59 -23
  6. data/lib/rack.rb +2 -1
  7. data/lib/rack/auth/abstract/request.rb +1 -1
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/md5.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +1 -1
  11. data/lib/rack/builder.rb +19 -4
  12. data/lib/rack/cascade.rb +2 -2
  13. data/lib/rack/chunked.rb +12 -1
  14. data/lib/rack/commonlogger.rb +13 -5
  15. data/lib/rack/conditionalget.rb +14 -2
  16. data/lib/rack/content_length.rb +5 -1
  17. data/lib/rack/deflater.rb +52 -13
  18. data/lib/rack/directory.rb +8 -2
  19. data/lib/rack/etag.rb +14 -6
  20. data/lib/rack/file.rb +10 -14
  21. data/lib/rack/handler.rb +2 -0
  22. data/lib/rack/handler/fastcgi.rb +4 -1
  23. data/lib/rack/handler/mongrel.rb +8 -2
  24. data/lib/rack/handler/scgi.rb +4 -1
  25. data/lib/rack/handler/thin.rb +8 -2
  26. data/lib/rack/handler/webrick.rb +46 -6
  27. data/lib/rack/head.rb +7 -2
  28. data/lib/rack/lint.rb +73 -25
  29. data/lib/rack/lobster.rb +8 -3
  30. data/lib/rack/methodoverride.rb +14 -3
  31. data/lib/rack/mime.rb +1 -15
  32. data/lib/rack/mock.rb +15 -7
  33. data/lib/rack/multipart.rb +2 -2
  34. data/lib/rack/multipart/parser.rb +107 -53
  35. data/lib/rack/multipart/uploaded_file.rb +2 -2
  36. data/lib/rack/nulllogger.rb +21 -2
  37. data/lib/rack/request.rb +38 -24
  38. data/lib/rack/response.rb +5 -0
  39. data/lib/rack/sendfile.rb +10 -5
  40. data/lib/rack/server.rb +45 -17
  41. data/lib/rack/session/abstract/id.rb +7 -6
  42. data/lib/rack/session/cookie.rb +17 -7
  43. data/lib/rack/session/memcache.rb +4 -4
  44. data/lib/rack/session/pool.rb +3 -6
  45. data/lib/rack/showexceptions.rb +20 -11
  46. data/lib/rack/showstatus.rb +1 -1
  47. data/lib/rack/static.rb +27 -30
  48. data/lib/rack/tempfile_reaper.rb +22 -0
  49. data/lib/rack/urlmap.rb +17 -3
  50. data/lib/rack/utils.rb +78 -47
  51. data/lib/rack/utils/okjson.rb +90 -91
  52. data/rack.gemspec +3 -3
  53. data/test/multipart/filename_and_no_name +6 -0
  54. data/test/multipart/invalid_character +6 -0
  55. data/test/spec_builder.rb +13 -4
  56. data/test/spec_chunked.rb +16 -0
  57. data/test/spec_commonlogger.rb +36 -0
  58. data/test/spec_content_length.rb +3 -1
  59. data/test/spec_deflater.rb +283 -148
  60. data/test/spec_etag.rb +11 -2
  61. data/test/spec_file.rb +11 -3
  62. data/test/spec_head.rb +2 -0
  63. data/test/spec_lobster.rb +1 -1
  64. data/test/spec_mock.rb +8 -0
  65. data/test/spec_multipart.rb +111 -49
  66. data/test/spec_request.rb +109 -25
  67. data/test/spec_response.rb +30 -0
  68. data/test/spec_server.rb +20 -5
  69. data/test/spec_session_cookie.rb +45 -2
  70. data/test/spec_session_memcache.rb +1 -1
  71. data/test/spec_showexceptions.rb +29 -36
  72. data/test/spec_showstatus.rb +19 -0
  73. data/test/spec_tempfile_reaper.rb +63 -0
  74. data/test/spec_urlmap.rb +23 -0
  75. data/test/spec_utils.rb +60 -10
  76. data/test/spec_webrick.rb +41 -0
  77. metadata +12 -9
  78. data/test/cgi/lighttpd.errors +0 -1
  79. data/test/multipart/three_files_three_fields +0 -31
@@ -85,6 +85,18 @@ describe Rack::Response do
85
85
  response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
86
86
  end
87
87
 
88
+ it "can set http only cookies with :http_only" do
89
+ response = Rack::Response.new
90
+ response.set_cookie "foo", {:value => "bar", :http_only => true}
91
+ response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
92
+ end
93
+
94
+ it "can set prefers :httponly for http only cookie setting when :httponly and :http_only provided" do
95
+ response = Rack::Response.new
96
+ response.set_cookie "foo", {:value => "bar", :httponly => false, :http_only => true}
97
+ response["Set-Cookie"].should.equal "foo=bar"
98
+ end
99
+
88
100
  it "can delete cookies" do
89
101
  response = Rack::Response.new
90
102
  response.set_cookie "foo", "bar"
@@ -211,11 +223,24 @@ describe Rack::Response do
211
223
  res.should.be.successful
212
224
  res.should.be.ok
213
225
 
226
+ res.status = 201
227
+ res.should.be.successful
228
+ res.should.be.created
229
+
230
+ res.status = 202
231
+ res.should.be.successful
232
+ res.should.be.accepted
233
+
214
234
  res.status = 400
215
235
  res.should.not.be.successful
216
236
  res.should.be.client_error
217
237
  res.should.be.bad_request
218
238
 
239
+ res.status = 401
240
+ res.should.not.be.successful
241
+ res.should.be.client_error
242
+ res.should.be.unauthorized
243
+
219
244
  res.status = 404
220
245
  res.should.not.be.successful
221
246
  res.should.be.client_error
@@ -226,6 +251,11 @@ describe Rack::Response do
226
251
  res.should.be.client_error
227
252
  res.should.be.method_not_allowed
228
253
 
254
+ res.status = 418
255
+ res.should.not.be.successful
256
+ res.should.be.client_error
257
+ res.should.be.i_m_a_teapot
258
+
229
259
  res.status = 422
230
260
  res.should.not.be.successful
231
261
  res.should.be.client_error
@@ -30,14 +30,24 @@ describe Rack::Server do
30
30
 
31
31
  should "not include Rack::Lint in deployment or none environments" do
32
32
  server = Rack::Server.new(:app => 'foo')
33
- server.middleware['deployment'].flatten.should.not.include(Rack::Lint)
34
- server.middleware['none'].flatten.should.not.include(Rack::Lint)
33
+ server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::Lint)
34
+ server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::Lint)
35
35
  end
36
36
 
37
37
  should "not include Rack::ShowExceptions in deployment or none environments" do
38
38
  server = Rack::Server.new(:app => 'foo')
39
- server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions)
40
- server.middleware['none'].flatten.should.not.include(Rack::ShowExceptions)
39
+ server.default_middleware_by_environment['deployment'].flatten.should.not.include(Rack::ShowExceptions)
40
+ server.default_middleware_by_environment['none'].flatten.should.not.include(Rack::ShowExceptions)
41
+ end
42
+
43
+ should "always return an empty array for unknown environments" do
44
+ server = Rack::Server.new(:app => 'foo')
45
+ server.default_middleware_by_environment['production'].should.equal []
46
+ end
47
+
48
+ should "include Rack::TempfileReaper in deployment environment" do
49
+ server = Rack::Server.new(:app => 'foo')
50
+ server.middleware['deployment'].flatten.should.include(Rack::TempfileReaper)
41
51
  end
42
52
 
43
53
  should "support CGI" do
@@ -51,9 +61,14 @@ describe Rack::Server do
51
61
  end
52
62
  end
53
63
 
64
+ should "be quiet if said so" do
65
+ server = Rack::Server.new(:app => "FOO", :quiet => true)
66
+ Rack::Server.logging_middleware.call(server).should.eql(nil)
67
+ end
68
+
54
69
  should "not force any middleware under the none configuration" do
55
70
  server = Rack::Server.new(:app => 'foo')
56
- server.middleware['none'].should.be.empty
71
+ server.default_middleware_by_environment['none'].should.be.empty
57
72
  end
58
73
 
59
74
  should "use a full path to the pidfile" do
@@ -119,13 +119,35 @@ describe Rack::Session::Cookie do
119
119
  coder.decode('lulz').should.equal nil
120
120
  end
121
121
  end
122
+
123
+ describe 'ZipJSON' do
124
+ it 'jsons, deflates, and base64 encodes' do
125
+ coder = Rack::Session::Cookie::Base64::ZipJSON.new
126
+ obj = %w[fuuuuu]
127
+ json = Rack::Utils::OkJson.encode(obj)
128
+ coder.encode(obj).should.equal [Zlib::Deflate.deflate(json)].pack('m')
129
+ end
130
+
131
+ it 'base64 decodes, inflates, and decodes json' do
132
+ coder = Rack::Session::Cookie::Base64::ZipJSON.new
133
+ obj = %w[fuuuuu]
134
+ json = Rack::Utils::OkJson.encode(obj)
135
+ b64 = [Zlib::Deflate.deflate(json)].pack('m')
136
+ coder.decode(b64).should.equal obj
137
+ end
138
+
139
+ it 'rescues failures on decode' do
140
+ coder = Rack::Session::Cookie::Base64::ZipJSON.new
141
+ coder.decode('lulz').should.equal nil
142
+ end
143
+ end
122
144
  end
123
145
 
124
146
  it "warns if no secret is given" do
125
- cookie = Rack::Session::Cookie.new(incrementor)
147
+ Rack::Session::Cookie.new(incrementor)
126
148
  @warnings.first.should =~ /no secret/i
127
149
  @warnings.clear
128
- cookie = Rack::Session::Cookie.new(incrementor, :secret => 'abc')
150
+ Rack::Session::Cookie.new(incrementor, :secret => 'abc')
129
151
  @warnings.should.be.empty?
130
152
  end
131
153
 
@@ -364,4 +386,25 @@ describe Rack::Session::Cookie do
364
386
  response.body.should.match(/counter/)
365
387
  response.body.should.match(/foo/)
366
388
  end
389
+
390
+ it "allows more than one '--' in the cookie when calculating digests" do
391
+ @counter = 0
392
+ app = lambda do |env|
393
+ env["rack.session"]["message"] ||= ""
394
+ env["rack.session"]["message"] << "#{(@counter += 1).to_s}--"
395
+ hash = env["rack.session"].dup
396
+ hash.delete("session_id")
397
+ Rack::Response.new(hash["message"]).to_a
398
+ end
399
+ # another example of an unsafe coder is Base64.urlsafe_encode64
400
+ unsafe_coder = Class.new {
401
+ def encode(hash); hash.inspect end
402
+ def decode(str); eval(str) if str; end
403
+ }.new
404
+ _app = [ app, { :secret => "test", :coder => unsafe_coder } ]
405
+ response = response_for(:app => _app)
406
+ response.body.should.equal "1--"
407
+ response = response_for(:app => _app, :cookie => response)
408
+ response.body.should.equal "1--2--"
409
+ end
367
410
  end
@@ -274,7 +274,7 @@ begin
274
274
  session['counter'].should.equal 2 # meeeh
275
275
 
276
276
  tnum = rand(7).to_i+5
277
- r = Array.new(tnum) do |i|
277
+ r = Array.new(tnum) do
278
278
  app = Rack::Utils::Context.new pool, time_delta
279
279
  req = Rack::MockRequest.new app
280
280
  Thread.new(req) do |run|
@@ -16,7 +16,7 @@ describe Rack::ShowExceptions do
16
16
  ))
17
17
 
18
18
  lambda{
19
- res = req.get("/")
19
+ res = req.get("/", "HTTP_ACCEPT" => "text/html")
20
20
  }.should.not.raise
21
21
 
22
22
  res.should.be.a.server_error
@@ -26,7 +26,7 @@ describe Rack::ShowExceptions do
26
26
  res.should =~ /ShowExceptions/
27
27
  end
28
28
 
29
- it "responds with plain text on AJAX requests accepting anything but HTML" do
29
+ it "responds with HTML only to requests accepting HTML" do
30
30
  res = nil
31
31
 
32
32
  req = Rack::MockRequest.new(
@@ -34,39 +34,32 @@ describe Rack::ShowExceptions do
34
34
  lambda{|env| raise RuntimeError, "It was never supposed to work" }
35
35
  ))
36
36
 
37
- lambda{
38
- res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
39
- }.should.not.raise
40
-
41
- res.should.be.a.server_error
42
- res.status.should.equal 500
43
-
44
- res.content_type.should.equal "text/plain"
45
-
46
- res.body.should.include "RuntimeError: It was never supposed to work\n"
47
- res.body.should.include __FILE__
48
- end
49
-
50
- it "responds with HTML on AJAX requests accepting HTML" do
51
- res = nil
52
-
53
- req = Rack::MockRequest.new(
54
- show_exceptions(
55
- lambda{|env| raise RuntimeError, "It was never supposed to work" }
56
- ))
57
-
58
- lambda{
59
- res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/html")
60
- }.should.not.raise
61
-
62
- res.should.be.a.server_error
63
- res.status.should.equal 500
64
-
65
- res.content_type.should.equal "text/html"
66
-
67
- res.body.should.include "RuntimeError"
68
- res.body.should.include "It was never supposed to work"
69
- res.body.should.include Rack::Utils.escape_html(__FILE__)
37
+ [
38
+ # Serve text/html when the client accepts text/html
39
+ ["text/html", ["/", {"HTTP_ACCEPT" => "text/html"}]],
40
+ ["text/html", ["/", {"HTTP_ACCEPT" => "*/*"}]],
41
+ # Serve text/plain when the client does not accept text/html
42
+ ["text/plain", ["/"]],
43
+ ["text/plain", ["/", {"HTTP_ACCEPT" => "application/json"}]]
44
+ ].each do |exmime, rargs|
45
+ lambda{
46
+ res = req.get(*rargs)
47
+ }.should.not.raise
48
+
49
+ res.should.be.a.server_error
50
+ res.status.should.equal 500
51
+
52
+ res.content_type.should.equal exmime
53
+
54
+ res.body.should.include "RuntimeError"
55
+ res.body.should.include "It was never supposed to work"
56
+
57
+ if exmime == "text/html"
58
+ res.body.should.include '</html>'
59
+ else
60
+ res.body.should.not.include '</html>'
61
+ end
62
+ end
70
63
  end
71
64
 
72
65
  it "handles exceptions without a backtrace" do
@@ -79,7 +72,7 @@ describe Rack::ShowExceptions do
79
72
  )
80
73
 
81
74
  lambda{
82
- res = req.get("/")
75
+ res = req.get("/", "HTTP_ACCEPT" => "text/html")
83
76
  }.should.not.raise
84
77
 
85
78
  res.should.be.a.server_error
@@ -1,6 +1,7 @@
1
1
  require 'rack/showstatus'
2
2
  require 'rack/lint'
3
3
  require 'rack/mock'
4
+ require 'rack/utils'
4
5
 
5
6
  describe Rack::ShowStatus do
6
7
  def show_status(app)
@@ -40,6 +41,24 @@ describe Rack::ShowStatus do
40
41
  res.should =~ /too meta/
41
42
  end
42
43
 
44
+ should "escape error" do
45
+ detail = "<script>alert('hi \"')</script>"
46
+ req = Rack::MockRequest.new(
47
+ show_status(
48
+ lambda{|env|
49
+ env["rack.showstatus.detail"] = detail
50
+ [500, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
51
+ }))
52
+
53
+ res = req.get("/", :lint => true)
54
+ res.should.be.not.empty
55
+
56
+ res["Content-Type"].should.equal("text/html")
57
+ res.should =~ /500/
58
+ res.should.not.include detail
59
+ res.body.should.include Rack::Utils.escape_html(detail)
60
+ end
61
+
43
62
  should "not replace existing messages" do
44
63
  req = Rack::MockRequest.new(
45
64
  show_status(
@@ -0,0 +1,63 @@
1
+ require 'rack/tempfile_reaper'
2
+ require 'rack/lint'
3
+ require 'rack/mock'
4
+
5
+ describe Rack::TempfileReaper do
6
+ class MockTempfile
7
+ attr_reader :closed
8
+
9
+ def initialize
10
+ @closed = false
11
+ end
12
+
13
+ def close!
14
+ @closed = true
15
+ end
16
+ end
17
+
18
+ before do
19
+ @env = Rack::MockRequest.env_for
20
+ end
21
+
22
+ def call(app)
23
+ Rack::Lint.new(Rack::TempfileReaper.new(app)).call(@env)
24
+ end
25
+
26
+ should 'do nothing (i.e. not bomb out) without env[rack.tempfiles]' do
27
+ app = lambda { |_| [200, {}, ['Hello, World!']] }
28
+ response = call(app)
29
+ response[2].close
30
+ response[0].should.equal(200)
31
+ end
32
+
33
+ should 'close env[rack.tempfiles] when body is closed' do
34
+ tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new
35
+ @env['rack.tempfiles'] = [ tempfile1, tempfile2 ]
36
+ app = lambda { |_| [200, {}, ['Hello, World!']] }
37
+ call(app)[2].close
38
+ tempfile1.closed.should.equal true
39
+ tempfile2.closed.should.equal true
40
+ end
41
+
42
+ should 'initialize env[rack.tempfiles] when not already present' do
43
+ tempfile = MockTempfile.new
44
+ app = lambda do |env|
45
+ env['rack.tempfiles'] << tempfile
46
+ [200, {}, ['Hello, World!']]
47
+ end
48
+ call(app)[2].close
49
+ tempfile.closed.should.equal true
50
+ end
51
+
52
+ should 'append env[rack.tempfiles] when already present' do
53
+ tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new
54
+ @env['rack.tempfiles'] = [ tempfile1 ]
55
+ app = lambda do |env|
56
+ env['rack.tempfiles'] << tempfile2
57
+ [200, {}, ['Hello, World!']]
58
+ end
59
+ call(app)[2].close
60
+ tempfile1.closed.should.equal true
61
+ tempfile2.closed.should.equal true
62
+ end
63
+ end
@@ -210,4 +210,27 @@ describe Rack::URLMap do
210
210
  res["X-PathInfo"].should.equal "/http://example.org/bar"
211
211
  res["X-ScriptName"].should.equal ""
212
212
  end
213
+
214
+ should "not be case sensitive with hosts" do
215
+ map = Rack::Lint.new(Rack::URLMap.new("http://example.org/" => lambda { |env|
216
+ [200,
217
+ { "Content-Type" => "text/plain",
218
+ "X-Position" => "root",
219
+ "X-PathInfo" => env["PATH_INFO"],
220
+ "X-ScriptName" => env["SCRIPT_NAME"]
221
+ }, [""]]}
222
+ ))
223
+
224
+ res = Rack::MockRequest.new(map).get("http://example.org/")
225
+ res.should.be.ok
226
+ res["X-Position"].should.equal "root"
227
+ res["X-PathInfo"].should.equal "/"
228
+ res["X-ScriptName"].should.equal ""
229
+
230
+ res = Rack::MockRequest.new(map).get("http://EXAMPLE.ORG/")
231
+ res.should.be.ok
232
+ res["X-Position"].should.equal "root"
233
+ res["X-PathInfo"].should.equal "/"
234
+ res["X-ScriptName"].should.equal ""
235
+ end
213
236
  end
@@ -123,15 +123,14 @@ describe Rack::Utils do
123
123
  Rack::Utils.parse_query(",foo=bar;,", ";,").should.equal "foo" => "bar"
124
124
  end
125
125
 
126
- should "raise an exception if the params are too deep" do
127
- len = Rack::Utils.param_depth_limit
126
+ should "not create infinite loops with cycle structures" do
127
+ ex = { "foo" => nil }
128
+ ex["foo"] = ex
128
129
 
130
+ params = Rack::Utils::KeySpaceConstrainedParams.new
131
+ params['foo'] = params
129
132
  lambda {
130
- Rack::Utils.parse_nested_query("foo#{"[a]" * len}=bar")
131
- }.should.raise(RangeError)
132
-
133
- lambda {
134
- Rack::Utils.parse_nested_query("foo#{"[a]" * (len - 1)}=bar")
133
+ params.to_params_hash.to_s.should.equal ex.to_s
135
134
  }.should.not.raise
136
135
  end
137
136
 
@@ -169,6 +168,16 @@ describe Rack::Utils do
169
168
  should.equal "foo" => [""]
170
169
  Rack::Utils.parse_nested_query("foo[]=bar").
171
170
  should.equal "foo" => ["bar"]
171
+ Rack::Utils.parse_nested_query("foo[]=bar&foo").
172
+ should.equal "foo" => nil
173
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[").
174
+ should.equal "foo" => ["bar"], "foo[" => nil
175
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz").
176
+ should.equal "foo" => ["bar"], "foo[" => "baz"
177
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[]").
178
+ should.equal "foo" => ["bar", nil]
179
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[]=").
180
+ should.equal "foo" => ["bar", ""]
172
181
 
173
182
  Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
174
183
  should.equal "foo" => ["1", "2"]
@@ -204,16 +213,22 @@ describe Rack::Utils do
204
213
  should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
205
214
 
206
215
  lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
207
- should.raise(TypeError).
216
+ should.raise(Rack::Utils::ParameterTypeError).
208
217
  message.should.equal "expected Hash (got String) for param `y'"
209
218
 
210
219
  lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
211
- should.raise(TypeError).
220
+ should.raise(Rack::Utils::ParameterTypeError).
212
221
  message.should.match(/expected Array \(got [^)]*\) for param `x'/)
213
222
 
214
223
  lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
215
- should.raise(TypeError).
224
+ should.raise(Rack::Utils::ParameterTypeError).
216
225
  message.should.equal "expected Array (got String) for param `y'"
226
+
227
+ if RUBY_VERSION.to_f > 1.9
228
+ lambda { Rack::Utils.parse_nested_query("foo%81E=1") }.
229
+ should.raise(Rack::Utils::InvalidParameterError).
230
+ message.should.equal "invalid byte sequence in UTF-8"
231
+ end
217
232
  end
218
233
 
219
234
  should "build query strings correctly" do
@@ -233,6 +248,8 @@ describe Rack::Utils do
233
248
 
234
249
  Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
235
250
  should.be equal_query_to("foo=1&bar=2")
251
+ Rack::Utils.build_nested_query("foo" => 1, "bar" => 2).
252
+ should.be equal_query_to("foo=1&bar=2")
236
253
  Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
237
254
  should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
238
255
 
@@ -242,6 +259,14 @@ describe Rack::Utils do
242
259
  should.equal "foo[]="
243
260
  Rack::Utils.build_nested_query("foo" => ["bar"]).
244
261
  should.equal "foo[]=bar"
262
+ Rack::Utils.build_nested_query('foo' => []).
263
+ should.equal ''
264
+ Rack::Utils.build_nested_query('foo' => {}).
265
+ should.equal ''
266
+ Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).
267
+ should.equal 'foo=bar'
268
+ Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).
269
+ should.equal 'foo=bar'
245
270
 
246
271
  # The ordering of the output query string is unpredictable with 1.8's
247
272
  # unordered hash. Test that build_nested_query performs the inverse
@@ -302,9 +327,15 @@ describe Rack::Utils do
302
327
  # Higher quality matches are preferred
303
328
  Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).should.equal "text/plain"
304
329
 
330
+ # Respect requested content type
331
+ Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).should.equal "application/json"
332
+
305
333
  # All else equal, the available mimes are preferred in order
306
334
  Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).should.equal "text/html"
307
335
  Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).should.equal "text/html"
336
+
337
+ # When there are no matches, return nil:
338
+ Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).should.equal nil
308
339
  end
309
340
 
310
341
  should "escape html entities [&><'\"/]" do
@@ -390,6 +421,25 @@ describe Rack::Utils do
390
421
  should "return rfc2109 format from rfc2109 helper" do
391
422
  Rack::Utils.rfc2109(Time.at(0).gmtime).should == "Thu, 01-Jan-1970 00:00:00 GMT"
392
423
  end
424
+
425
+ should "clean directory traversal" do
426
+ Rack::Utils.clean_path_info("/cgi/../cgi/test").should.equal "/cgi/test"
427
+ Rack::Utils.clean_path_info(".").should.empty
428
+ Rack::Utils.clean_path_info("test/..").should.empty
429
+ end
430
+
431
+ should "clean unsafe directory traversal to safe path" do
432
+ Rack::Utils.clean_path_info("/../README.rdoc").should.equal "/README.rdoc"
433
+ Rack::Utils.clean_path_info("../test/spec_utils.rb").should.equal "test/spec_utils.rb"
434
+ end
435
+
436
+ should "not clean directory traversal with encoded periods" do
437
+ Rack::Utils.clean_path_info("/%2E%2E/README").should.equal "/%2E%2E/README"
438
+ end
439
+
440
+ should "clean slash only paths" do
441
+ Rack::Utils.clean_path_info("/").should.equal "/"
442
+ end
393
443
  end
394
444
 
395
445
  describe Rack::Utils, "byte_range" do