rack 1.3.10 → 1.4.0

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 (83) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +0 -9
  3. data/README.rdoc +4 -118
  4. data/Rakefile +15 -0
  5. data/SPEC +3 -5
  6. data/lib/rack.rb +0 -12
  7. data/lib/rack/auth/abstract/request.rb +1 -5
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/nonce.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +28 -14
  11. data/lib/rack/backports/uri/common_192.rb +17 -14
  12. data/lib/rack/body_proxy.rb +0 -10
  13. data/lib/rack/builder.rb +26 -18
  14. data/lib/rack/cascade.rb +1 -12
  15. data/lib/rack/chunked.rb +2 -0
  16. data/lib/rack/content_type.rb +7 -1
  17. data/lib/rack/deflater.rb +1 -5
  18. data/lib/rack/directory.rb +5 -1
  19. data/lib/rack/file.rb +26 -9
  20. data/lib/rack/handler.rb +2 -2
  21. data/lib/rack/head.rb +0 -1
  22. data/lib/rack/lint.rb +3 -5
  23. data/lib/rack/methodoverride.rb +10 -4
  24. data/lib/rack/mime.rb +606 -171
  25. data/lib/rack/mock.rb +2 -1
  26. data/lib/rack/multipart.rb +2 -2
  27. data/lib/rack/multipart/parser.rb +3 -10
  28. data/lib/rack/reloader.rb +1 -1
  29. data/lib/rack/request.rb +45 -13
  30. data/lib/rack/response.rb +15 -14
  31. data/lib/rack/sendfile.rb +8 -6
  32. data/lib/rack/server.rb +4 -30
  33. data/lib/rack/session/abstract/id.rb +25 -6
  34. data/lib/rack/session/cookie.rb +12 -16
  35. data/lib/rack/static.rb +21 -8
  36. data/lib/rack/urlmap.rb +28 -13
  37. data/lib/rack/utils.rb +22 -28
  38. data/rack.gemspec +5 -5
  39. data/test/builder/end.ru +2 -0
  40. data/test/cgi/lighttpd.conf +1 -0
  41. data/test/cgi/sample_rackup.ru +1 -1
  42. data/test/cgi/test+directory/test+file +1 -0
  43. data/test/cgi/test.ru +1 -1
  44. data/test/gemloader.rb +6 -2
  45. data/test/spec_auth_basic.rb +4 -9
  46. data/test/spec_auth_digest.rb +3 -16
  47. data/test/spec_body_proxy.rb +0 -4
  48. data/test/spec_builder.rb +63 -20
  49. data/test/spec_cascade.rb +10 -13
  50. data/test/spec_cgi.rb +1 -1
  51. data/test/spec_chunked.rb +39 -12
  52. data/test/spec_commonlogger.rb +4 -3
  53. data/test/spec_conditionalget.rb +16 -12
  54. data/test/spec_content_length.rb +1 -1
  55. data/test/spec_content_type.rb +6 -0
  56. data/test/spec_deflater.rb +2 -2
  57. data/test/spec_directory.rb +12 -0
  58. data/test/spec_fastcgi.rb +1 -1
  59. data/test/spec_file.rb +58 -8
  60. data/test/spec_head.rb +6 -18
  61. data/test/spec_lint.rb +2 -2
  62. data/test/spec_methodoverride.rb +15 -0
  63. data/test/spec_mock.rb +6 -2
  64. data/test/spec_mongrel.rb +8 -8
  65. data/test/spec_multipart.rb +10 -63
  66. data/test/spec_request.rb +94 -21
  67. data/test/spec_response.rb +22 -24
  68. data/test/spec_sendfile.rb +3 -0
  69. data/test/spec_server.rb +2 -49
  70. data/test/spec_session_cookie.rb +58 -22
  71. data/test/spec_session_memcache.rb +31 -1
  72. data/test/spec_session_pool.rb +10 -4
  73. data/test/spec_static.rb +8 -0
  74. data/test/spec_thin.rb +2 -2
  75. data/test/spec_utils.rb +38 -35
  76. data/test/spec_webrick.rb +5 -3
  77. data/test/static/index.html +1 -0
  78. metadata +13 -18
  79. data/contrib/rack.png +0 -0
  80. data/contrib/rack.svg +0 -150
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/test/builder/line.ru +0 -1
  83. data/test/spec_auth.rb +0 -57
@@ -2,41 +2,29 @@ require 'rack/head'
2
2
  require 'rack/mock'
3
3
 
4
4
  describe Rack::Head do
5
-
6
5
  def test_response(headers = {})
7
- body = StringIO.new "foo"
8
- app = lambda do |env|
9
- [200, {"Content-type" => "test/plain", "Content-length" => "3"}, body]
10
- end
6
+ app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
11
7
  request = Rack::MockRequest.env_for("/", headers)
12
8
  response = Rack::Head.new(app).call(request)
13
9
 
14
- return response, body
10
+ return response
15
11
  end
16
12
 
17
13
  should "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
18
14
  %w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
19
- resp, _ = test_response("REQUEST_METHOD" => type)
15
+ resp = test_response("REQUEST_METHOD" => type)
20
16
 
21
17
  resp[0].should.equal(200)
22
18
  resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
23
- resp[2].each { |b| b.should.equal("foo") }
19
+ resp[2].should.equal(["foo"])
24
20
  end
25
21
  end
26
22
 
27
23
  should "remove body from HEAD requests" do
28
- resp, _ = test_response("REQUEST_METHOD" => "HEAD")
29
-
30
- resp[0].should.equal(200)
31
- resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
32
- resp[2].each { |b| flunk "body should be empty" }
33
- end
24
+ resp = test_response("REQUEST_METHOD" => "HEAD")
34
25
 
35
- should "close the body when it is removed" do
36
- resp, body = test_response("REQUEST_METHOD" => "HEAD")
37
26
  resp[0].should.equal(200)
38
27
  resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
39
- resp[2].each { |b| flunk "body should be empty" }
40
- body.should.be.closed
28
+ resp[2].should.equal([])
41
29
  end
42
30
  end
@@ -241,7 +241,7 @@ describe Rack::Lint do
241
241
  }.should.raise(Rack::Lint::LintError).
242
242
  message.should.match(/No Content-Type/)
243
243
 
244
- [100, 101, 204, 304].each do |status|
244
+ [100, 101, 204, 205, 304].each do |status|
245
245
  lambda {
246
246
  Rack::Lint.new(lambda { |env|
247
247
  [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
@@ -252,7 +252,7 @@ describe Rack::Lint do
252
252
  end
253
253
 
254
254
  should "notice content-length errors" do
255
- [100, 101, 204, 304].each do |status|
255
+ [100, 101, 204, 205, 304].each do |status|
256
256
  lambda {
257
257
  Rack::Lint.new(lambda { |env|
258
258
  [status, {"Content-length" => "0"}, []]
@@ -55,4 +55,19 @@ describe Rack::MethodOverride do
55
55
 
56
56
  req.env["rack.methodoverride.original_method"].should.equal "POST"
57
57
  end
58
+
59
+ should "not modify REQUEST_METHOD when given invalid multipart form data" do
60
+ input = <<EOF
61
+ --AaB03x\r
62
+ content-disposition: form-data; name="huge"; filename="huge"\r
63
+ EOF
64
+ env = Rack::MockRequest.env_for("/",
65
+ "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
66
+ "CONTENT_LENGTH" => input.size,
67
+ :method => "POST", :input => input)
68
+ app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
69
+ req = app.call(env)
70
+
71
+ req.env["REQUEST_METHOD"].should.equal "POST"
72
+ end
58
73
  end
@@ -42,7 +42,7 @@ describe Rack::MockRequest do
42
42
  env["mock.postdata"].should.be.empty
43
43
  end
44
44
 
45
- should "allow GET/POST/PUT/DELETE" do
45
+ should "allow GET/POST/PUT/DELETE/HEAD" do
46
46
  res = Rack::MockRequest.new(app).get("", :input => "foo")
47
47
  env = YAML.load(res.body)
48
48
  env["REQUEST_METHOD"].should.equal "GET"
@@ -59,6 +59,10 @@ describe Rack::MockRequest do
59
59
  env = YAML.load(res.body)
60
60
  env["REQUEST_METHOD"].should.equal "DELETE"
61
61
 
62
+ res = Rack::MockRequest.new(app).head("", :input => "foo")
63
+ env = YAML.load(res.body)
64
+ env["REQUEST_METHOD"].should.equal "HEAD"
65
+
62
66
  Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
63
67
  should.equal "OPTIONS"
64
68
  end
@@ -189,7 +193,7 @@ describe Rack::MockRequest do
189
193
  should "call close on the original body object" do
190
194
  called = false
191
195
  body = Rack::BodyProxy.new(['hi']) { called = true }
192
- capp = proc { |e| [200, {'Content-Type' => 'text/plain '}, body] }
196
+ capp = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] }
193
197
  called.should.equal false
194
198
  Rack::MockRequest.new(capp).get('/', :lint => true)
195
199
  called.should.equal true
@@ -11,7 +11,7 @@ $tcp_cork_opts = nil
11
11
  describe Rack::Handler::Mongrel do
12
12
  extend TestRequest::Helpers
13
13
 
14
- @server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
14
+ @server = Mongrel::HttpServer.new(@host='127.0.0.1', @port=9201)
15
15
  @server.register('/test',
16
16
  Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
17
17
  @server.register('/stream',
@@ -31,7 +31,7 @@ describe Rack::Handler::Mongrel do
31
31
  response["HTTP_VERSION"].should.equal "HTTP/1.1"
32
32
  response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
33
33
  response["SERVER_PORT"].should.equal "9201"
34
- response["SERVER_NAME"].should.equal "0.0.0.0"
34
+ response["SERVER_NAME"].should.equal "127.0.0.1"
35
35
  end
36
36
 
37
37
  should "have rack headers" do
@@ -84,7 +84,7 @@ describe Rack::Handler::Mongrel do
84
84
  should "provide a .run" do
85
85
  block_ran = false
86
86
  Thread.new {
87
- Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
87
+ Rack::Handler::Mongrel.run(lambda {}, {:Host => '127.0.0.1', :Port => 9211}) { |server|
88
88
  server.should.be.kind_of Mongrel::HttpServer
89
89
  block_ran = true
90
90
  }
@@ -97,7 +97,7 @@ describe Rack::Handler::Mongrel do
97
97
  block_ran = false
98
98
  Thread.new {
99
99
  map = {'/'=>lambda{},'/foo'=>lambda{}}
100
- Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
100
+ Rack::Handler::Mongrel.run(map, :map => true, :Host => '127.0.0.1', :Port => 9221) { |server|
101
101
  server.should.be.kind_of Mongrel::HttpServer
102
102
  server.classifier.uris.size.should.equal 2
103
103
  server.classifier.uris.should.not.include '/arf'
@@ -114,7 +114,7 @@ describe Rack::Handler::Mongrel do
114
114
  block_ran = false
115
115
  Thread.new {
116
116
  map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
117
- Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
117
+ Rack::Handler::Mongrel.run(map, {:map => true, :Host => '127.0.0.1', :Port => 9231}) { |server|
118
118
  server.should.be.kind_of Mongrel::HttpServer
119
119
  server.classifier.uris.size.should.equal 2
120
120
  server.classifier.uris.should.not.include '/arf'
@@ -134,12 +134,12 @@ describe Rack::Handler::Mongrel do
134
134
  '/' => lambda{},
135
135
  '/foo' => lambda{},
136
136
  '/bar' => lambda{},
137
- 'http://localhost/' => lambda{},
138
- 'http://localhost/bar' => lambda{},
137
+ 'http://127.0.0.1/' => lambda{},
138
+ 'http://127.0.0.1/bar' => lambda{},
139
139
  'http://falsehost/arf' => lambda{},
140
140
  'http://falsehost/qux' => lambda{}
141
141
  })
142
- opt = {:map => true, :Port => 9241, :Host => 'localhost'}
142
+ opt = {:map => true, :Port => 9241, :Host => '127.0.0.1'}
143
143
  Rack::Handler::Mongrel.run(map, opt) { |server|
144
144
  server.should.be.kind_of Mongrel::HttpServer
145
145
  server.classifier.uris.should.include '/'
@@ -48,59 +48,6 @@ describe Rack::Multipart do
48
48
  params['profile']['bio'].should.include 'hello'
49
49
  end
50
50
 
51
- should "reject insanely long boundaries" do
52
- # using a pipe since a tempfile can use up too much space
53
- rd, wr = IO.pipe
54
-
55
- # we only call rewind once at start, so make sure it succeeds
56
- # and doesn't hit ESPIPE
57
- def rd.rewind; end
58
- wr.sync = true
59
-
60
- # mock out length to make this pipe look like a Tempfile
61
- def rd.length
62
- 1024 * 1024 * 8
63
- end
64
-
65
- # write to a pipe in a background thread, this will write a lot
66
- # unless Rack (properly) shuts down the read end
67
- thr = Thread.new do
68
- begin
69
- wr.write("--AaB03x")
70
-
71
- # make the initial boundary a few gigs long
72
- longer = "0123456789" * 1024 * 1024
73
- (1024 * 1024).times { wr.write(longer) }
74
-
75
- wr.write("\r\n")
76
- wr.write('Content-Disposition: form-data; name="a"; filename="a.txt"')
77
- wr.write("\r\n")
78
- wr.write("Content-Type: text/plain\r\n")
79
- wr.write("\r\na")
80
- wr.write("--AaB03x--\r\n")
81
- wr.close
82
- rescue => err # this is EPIPE if Rack shuts us down
83
- err
84
- end
85
- end
86
-
87
- fixture = {
88
- "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
89
- "CONTENT_LENGTH" => rd.length.to_s,
90
- :input => rd,
91
- }
92
-
93
- env = Rack::MockRequest.env_for '/', fixture
94
- lambda {
95
- Rack::Multipart.parse_multipart(env)
96
- }.should.raise(EOFError)
97
- rd.close
98
-
99
- err = thr.value
100
- err.should.be.instance_of Errno::EPIPE
101
- wr.close
102
- end
103
-
104
51
  should "parse multipart upload with text file" do
105
52
  env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
106
53
  params = Rack::Multipart.parse_multipart(env)
@@ -348,24 +295,24 @@ describe Rack::Multipart do
348
295
  message.should.equal "value must be a Hash"
349
296
  end
350
297
 
351
- should "parse very long unquoted multipart file names" do
298
+ it "can parse fields with a content type" do
352
299
  data = <<-EOF
353
- --AaB03x\r
354
- Content-Type: text/plain\r
355
- Content-Disposition: attachment; name=file; filename=#{'long' * 100}\r
300
+ --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r
301
+ Content-Disposition: form-data; name="description"\r
302
+ Content-Type: text/plain"\r
356
303
  \r
357
- contents\r
358
- --AaB03x--\r
359
- EOF
360
-
304
+ Very very blue\r
305
+ --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r
306
+ EOF
361
307
  options = {
362
- "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
308
+ "CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon",
363
309
  "CONTENT_LENGTH" => data.length.to_s,
364
310
  :input => StringIO.new(data)
365
311
  }
366
312
  env = Rack::MockRequest.env_for("/", options)
367
313
  params = Rack::Utils::Multipart.parse_multipart(env)
368
314
 
369
- params["file"][:filename].should.equal('long' * 100)
315
+ params.should.equal({"description"=>"Very very blue"})
370
316
  end
317
+
371
318
  end
@@ -359,13 +359,17 @@ describe Rack::Request do
359
359
  request.scheme.should.equal "https"
360
360
  request.should.be.ssl?
361
361
 
362
+ request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https'))
363
+ request.scheme.should.equal "https"
364
+ request.should.be.ssl?
365
+
362
366
  request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https'))
363
367
  request.scheme.should.equal "https"
364
368
  request.should.be.ssl?
365
369
 
366
370
  request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http'))
367
371
  request.scheme.should.equal "https"
368
- request.should.be.ssl
372
+ request.should.be.ssl?
369
373
  end
370
374
 
371
375
  should "parse cookies" do
@@ -383,6 +387,15 @@ describe Rack::Request do
383
387
  hash = req.cookies
384
388
  req.env.delete("HTTP_COOKIE")
385
389
  req.cookies.should.equal(hash)
390
+ req.env["HTTP_COOKIE"] = "zoo=m"
391
+ req.cookies.should.equal(hash)
392
+ end
393
+
394
+ should "modify the cookies hash in place" do
395
+ req = Rack::Request.new(Rack::MockRequest.env_for(""))
396
+ req.cookies.should.equal({})
397
+ req.cookies['foo'] = 'bar'
398
+ req.cookies.should.equal 'foo' => 'bar'
386
399
  end
387
400
 
388
401
  should "raise any errors on every request" do
@@ -774,38 +787,98 @@ EOF
774
787
  parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]])
775
788
  parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ])
776
789
 
777
- lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
790
+ parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]])
791
+ parser.call("gzip ; deflate").should.equal([["gzip", 1.0]])
778
792
  end
779
793
 
794
+ ip_app = lambda { |env|
795
+ request = Rack::Request.new(env)
796
+ response = Rack::Response.new
797
+ response.write request.ip
798
+ response.finish
799
+ }
800
+
780
801
  should 'provide ip information' do
781
- app = lambda { |env|
782
- request = Rack::Request.new(env)
783
- response = Rack::Response.new
784
- response.write request.ip
785
- response.finish
786
- }
802
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
787
803
 
788
- mock = Rack::MockRequest.new(Rack::Lint.new(app))
789
- res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
790
- res.body.should.equal '123.123.123.123'
804
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4'
805
+ res.body.should.equal '1.2.3.4'
791
806
 
792
- res = mock.get '/',
793
- 'REMOTE_ADDR' => '123.123.123.123',
794
- 'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
807
+ res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329'
808
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
809
+
810
+ res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
811
+ res.body.should.equal '1.2.3.4'
812
+ end
795
813
 
796
- res.body.should.equal '234.234.234.234'
814
+ should 'deals with proxies' do
815
+ mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
797
816
 
798
817
  res = mock.get '/',
799
- 'REMOTE_ADDR' => '123.123.123.123',
800
- 'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
818
+ 'REMOTE_ADDR' => '1.2.3.4',
819
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
820
+ res.body.should.equal '1.2.3.4'
801
821
 
802
- res.body.should.equal '234.234.234.234'
822
+ res = mock.get '/',
823
+ 'REMOTE_ADDR' => '127.0.0.1',
824
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
825
+ res.body.should.equal '3.4.5.6'
826
+
827
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
828
+ res.body.should.equal '3.4.5.6'
829
+
830
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
831
+ res.body.should.equal '3.4.5.6'
832
+
833
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
834
+ res.body.should.equal '3.4.5.6'
835
+
836
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
837
+ res.body.should.equal '3.4.5.6'
838
+
839
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6'
840
+ res.body.should.equal '3.4.5.6'
841
+
842
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
843
+ res.body.should.equal 'unknown'
844
+
845
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1'
846
+ res.body.should.equal 'unknown'
847
+
848
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1'
849
+ res.body.should.equal 'unknown'
803
850
 
851
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
852
+ res.body.should.equal '3.4.5.6'
853
+
854
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11'
855
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
856
+
857
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1'
858
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
859
+
860
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11'
861
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
862
+
863
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000'
864
+ res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11'
865
+
866
+ res = mock.get '/',
867
+ 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1',
868
+ 'HTTP_CLIENT_IP' => '1.1.1.1'
869
+ res.body.should.equal '1.1.1.1'
870
+
871
+ # Spoofing attempt
804
872
  res = mock.get '/',
805
- 'REMOTE_ADDR' => '123.123.123.123',
806
- 'HTTP_X_FORWARDED_FOR' => 'unknown,234.234.234.234,212.212.212.212'
873
+ 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
874
+ 'HTTP_CLIENT_IP' => '2.2.2.2'
875
+ res.body.should.equal '1.1.1.1'
876
+
877
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
878
+ res.body.should.equal '9.9.9.9'
807
879
 
808
- res.body.should.equal '234.234.234.234'
880
+ res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329'
881
+ res.body.should.equal 'fe80::202:b3ff:fe1e:8329'
809
882
  end
810
883
 
811
884
  class MyRequest < Rack::Request
@@ -109,6 +109,18 @@ describe Rack::Response do
109
109
  "foo=; domain=sample.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
110
110
  end
111
111
 
112
+ it "can delete cookies with the same name with different paths" do
113
+ response = Rack::Response.new
114
+ response.set_cookie "foo", {:value => "bar", :path => "/"}
115
+ response.set_cookie "foo", {:value => "bar", :path => "/path"}
116
+ response["Set-Cookie"].should.equal ["foo=bar; path=/",
117
+ "foo=bar; path=/path"].join("\n")
118
+
119
+ response.delete_cookie "foo", :path => "/path"
120
+ response["Set-Cookie"].should.equal ["foo=bar; path=/",
121
+ "foo=; path=/path; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
122
+ end
123
+
112
124
  it "can do redirects" do
113
125
  response = Rack::Response.new
114
126
  response.redirect "/foo"
@@ -196,11 +208,21 @@ describe Rack::Response do
196
208
  res.should.be.successful
197
209
  res.should.be.ok
198
210
 
211
+ res.status = 400
212
+ res.should.not.be.successful
213
+ res.should.be.client_error
214
+ res.should.be.bad_request
215
+
199
216
  res.status = 404
200
217
  res.should.not.be.successful
201
218
  res.should.be.client_error
202
219
  res.should.be.not_found
203
220
 
221
+ res.status = 422
222
+ res.should.not.be.successful
223
+ res.should.be.client_error
224
+ res.should.be.unprocessable
225
+
204
226
  res.status = 501
205
227
  res.should.not.be.successful
206
228
  res.should.be.server_error
@@ -250,28 +272,4 @@ describe Rack::Response do
250
272
  res.close
251
273
  res.body.should.be.closed
252
274
  end
253
-
254
- it "calls close on #body when 204, 205, or 304" do
255
- res = Rack::Response.new
256
- res.body = StringIO.new
257
- res.finish
258
- res.body.should.not.be.closed
259
-
260
- res.status = 204
261
- _, _, b = res.finish
262
- res.body.should.be.closed
263
- b.should.not == res.body
264
-
265
- res.body = StringIO.new
266
- res.status = 304
267
- _, _, b = res.finish
268
- res.body.should.be.closed
269
- b.should.not == res.body
270
- end
271
-
272
- it "wraps the body from #to_ary to prevent infinite loops" do
273
- res = Rack::Response.new
274
- res.finish.last.should.not.respond_to?(:to_ary)
275
- lambda { res.finish.last.to_ary }.should.raise(NoMethodError)
276
- end
277
275
  end