rack 0.9.1 → 1.0.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 (79) hide show
  1. data/COPYING +1 -1
  2. data/RDOX +115 -16
  3. data/README +54 -7
  4. data/Rakefile +61 -85
  5. data/SPEC +50 -17
  6. data/bin/rackup +9 -5
  7. data/example/protectedlobster.ru +1 -1
  8. data/lib/rack.rb +7 -3
  9. data/lib/rack/auth/abstract/handler.rb +13 -4
  10. data/lib/rack/auth/digest/md5.rb +1 -1
  11. data/lib/rack/auth/digest/request.rb +2 -2
  12. data/lib/rack/auth/openid.rb +344 -302
  13. data/lib/rack/builder.rb +1 -5
  14. data/lib/rack/chunked.rb +49 -0
  15. data/lib/rack/conditionalget.rb +4 -0
  16. data/lib/rack/content_length.rb +7 -3
  17. data/lib/rack/content_type.rb +23 -0
  18. data/lib/rack/deflater.rb +83 -74
  19. data/lib/rack/directory.rb +5 -2
  20. data/lib/rack/file.rb +4 -1
  21. data/lib/rack/handler.rb +22 -1
  22. data/lib/rack/handler/cgi.rb +7 -3
  23. data/lib/rack/handler/fastcgi.rb +26 -24
  24. data/lib/rack/handler/lsws.rb +7 -4
  25. data/lib/rack/handler/mongrel.rb +5 -3
  26. data/lib/rack/handler/scgi.rb +5 -3
  27. data/lib/rack/handler/thin.rb +3 -0
  28. data/lib/rack/handler/webrick.rb +11 -5
  29. data/lib/rack/lint.rb +138 -66
  30. data/lib/rack/lock.rb +16 -0
  31. data/lib/rack/mime.rb +4 -4
  32. data/lib/rack/mock.rb +3 -3
  33. data/lib/rack/reloader.rb +88 -46
  34. data/lib/rack/request.rb +46 -10
  35. data/lib/rack/response.rb +15 -3
  36. data/lib/rack/rewindable_input.rb +98 -0
  37. data/lib/rack/session/abstract/id.rb +71 -82
  38. data/lib/rack/session/cookie.rb +2 -0
  39. data/lib/rack/session/memcache.rb +59 -47
  40. data/lib/rack/session/pool.rb +56 -29
  41. data/lib/rack/showexceptions.rb +2 -1
  42. data/lib/rack/showstatus.rb +1 -1
  43. data/lib/rack/urlmap.rb +12 -5
  44. data/lib/rack/utils.rb +115 -65
  45. data/rack.gemspec +54 -0
  46. data/test/multipart/binary +0 -0
  47. data/test/multipart/empty +10 -0
  48. data/test/multipart/ie +6 -0
  49. data/test/multipart/nested +10 -0
  50. data/test/multipart/none +9 -0
  51. data/test/multipart/text +10 -0
  52. data/test/spec_rack_auth_basic.rb +5 -1
  53. data/test/spec_rack_auth_digest.rb +93 -36
  54. data/test/spec_rack_auth_openid.rb +47 -100
  55. data/test/spec_rack_builder.rb +2 -2
  56. data/test/spec_rack_chunked.rb +62 -0
  57. data/test/spec_rack_conditionalget.rb +7 -7
  58. data/test/spec_rack_content_type.rb +30 -0
  59. data/test/spec_rack_deflater.rb +36 -14
  60. data/test/spec_rack_directory.rb +1 -1
  61. data/test/spec_rack_file.rb +11 -0
  62. data/test/spec_rack_handler.rb +21 -2
  63. data/test/spec_rack_lint.rb +163 -44
  64. data/test/spec_rack_lock.rb +38 -0
  65. data/test/spec_rack_mock.rb +6 -1
  66. data/test/spec_rack_request.rb +81 -12
  67. data/test/spec_rack_response.rb +46 -2
  68. data/test/spec_rack_rewindable_input.rb +118 -0
  69. data/test/spec_rack_session_memcache.rb +170 -62
  70. data/test/spec_rack_session_pool.rb +129 -41
  71. data/test/spec_rack_static.rb +2 -2
  72. data/test/spec_rack_thin.rb +3 -2
  73. data/test/spec_rack_urlmap.rb +10 -0
  74. data/test/spec_rack_utils.rb +214 -49
  75. data/test/spec_rack_webrick.rb +7 -0
  76. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  77. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  78. metadata +95 -6
  79. data/AUTHORS +0 -8
@@ -6,51 +6,145 @@ require 'rack/response'
6
6
  require 'thread'
7
7
 
8
8
  context "Rack::Session::Pool" do
9
- incrementor = lambda { |env|
9
+ session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
10
+ session_match = /#{session_key}=[0-9a-fA-F]+;/
11
+ incrementor = lambda do |env|
10
12
  env["rack.session"]["counter"] ||= 0
11
13
  env["rack.session"]["counter"] += 1
12
14
  Rack::Response.new(env["rack.session"].inspect).to_a
13
- }
15
+ end
16
+ drop_session = proc do |env|
17
+ env['rack.session.options'][:drop] = true
18
+ incrementor.call(env)
19
+ end
20
+ renew_session = proc do |env|
21
+ env['rack.session.options'][:renew] = true
22
+ incrementor.call(env)
23
+ end
24
+ defer_session = proc do |env|
25
+ env['rack.session.options'][:defer] = true
26
+ incrementor.call(env)
27
+ end
14
28
 
15
29
  specify "creates a new cookie" do
16
30
  pool = Rack::Session::Pool.new(incrementor)
17
31
  res = Rack::MockRequest.new(pool).get("/")
18
- res["Set-Cookie"].should.match("rack.session=")
32
+ res["Set-Cookie"].should.match session_match
19
33
  res.body.should.equal '{"counter"=>1}'
20
34
  end
21
35
 
22
36
  specify "determines session from a cookie" do
23
37
  pool = Rack::Session::Pool.new(incrementor)
24
- res = Rack::MockRequest.new(pool).get("/")
25
- cookie = res["Set-Cookie"]
26
- res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
27
- res.body.should.equal '{"counter"=>2}'
28
- res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
29
- res.body.should.equal '{"counter"=>3}'
38
+ req = Rack::MockRequest.new(pool)
39
+ cookie = req.get("/")["Set-Cookie"]
40
+ req.get("/", "HTTP_COOKIE" => cookie).
41
+ body.should.equal '{"counter"=>2}'
42
+ req.get("/", "HTTP_COOKIE" => cookie).
43
+ body.should.equal '{"counter"=>3}'
30
44
  end
31
45
 
32
- specify "survives broken cookies" do
46
+ specify "survives nonexistant cookies" do
33
47
  pool = Rack::Session::Pool.new(incrementor)
34
48
  res = Rack::MockRequest.new(pool).
35
- get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
49
+ get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
36
50
  res.body.should.equal '{"counter"=>1}'
37
51
  end
38
52
 
39
- specify "maintains freshness" do
40
- pool = Rack::Session::Pool.new(incrementor, :expire_after => 3)
41
- res = Rack::MockRequest.new(pool).get('/')
42
- res.body.should.include '"counter"=>1'
43
- cookie = res["Set-Cookie"]
44
- res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
45
- res["Set-Cookie"].should.equal cookie
46
- res.body.should.include '"counter"=>2'
47
- sleep 4
48
- res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
49
- res["Set-Cookie"].should.not.equal cookie
50
- res.body.should.include '"counter"=>1'
53
+ specify "deletes cookies with :drop option" do
54
+ pool = Rack::Session::Pool.new(incrementor)
55
+ req = Rack::MockRequest.new(pool)
56
+ drop = Rack::Utils::Context.new(pool, drop_session)
57
+ dreq = Rack::MockRequest.new(drop)
58
+
59
+ res0 = req.get("/")
60
+ session = (cookie = res0["Set-Cookie"])[session_match]
61
+ res0.body.should.equal '{"counter"=>1}'
62
+ pool.pool.size.should.be 1
63
+
64
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
65
+ res1["Set-Cookie"][session_match].should.equal session
66
+ res1.body.should.equal '{"counter"=>2}'
67
+ pool.pool.size.should.be 1
68
+
69
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
70
+ res2["Set-Cookie"].should.equal nil
71
+ res2.body.should.equal '{"counter"=>3}'
72
+ pool.pool.size.should.be 0
73
+
74
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
75
+ res3["Set-Cookie"][session_match].should.not.equal session
76
+ res3.body.should.equal '{"counter"=>1}'
77
+ pool.pool.size.should.be 1
78
+ end
79
+
80
+ specify "provides new session id with :renew option" do
81
+ pool = Rack::Session::Pool.new(incrementor)
82
+ req = Rack::MockRequest.new(pool)
83
+ renew = Rack::Utils::Context.new(pool, renew_session)
84
+ rreq = Rack::MockRequest.new(renew)
85
+
86
+ res0 = req.get("/")
87
+ session = (cookie = res0["Set-Cookie"])[session_match]
88
+ res0.body.should.equal '{"counter"=>1}'
89
+ pool.pool.size.should.be 1
90
+
91
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
92
+ res1["Set-Cookie"][session_match].should.equal session
93
+ res1.body.should.equal '{"counter"=>2}'
94
+ pool.pool.size.should.be 1
95
+
96
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
97
+ new_cookie = res2["Set-Cookie"]
98
+ new_session = new_cookie[session_match]
99
+ new_session.should.not.equal session
100
+ res2.body.should.equal '{"counter"=>3}'
101
+ pool.pool.size.should.be 1
102
+
103
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
104
+ res3["Set-Cookie"][session_match].should.equal new_session
105
+ res3.body.should.equal '{"counter"=>4}'
106
+ pool.pool.size.should.be 1
107
+ end
108
+
109
+ specify "omits cookie with :defer option" do
110
+ pool = Rack::Session::Pool.new(incrementor)
111
+ req = Rack::MockRequest.new(pool)
112
+ defer = Rack::Utils::Context.new(pool, defer_session)
113
+ dreq = Rack::MockRequest.new(defer)
114
+
115
+ res0 = req.get("/")
116
+ session = (cookie = res0["Set-Cookie"])[session_match]
117
+ res0.body.should.equal '{"counter"=>1}'
118
+ pool.pool.size.should.be 1
119
+
120
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
121
+ res1["Set-Cookie"][session_match].should.equal session
122
+ res1.body.should.equal '{"counter"=>2}'
123
+ pool.pool.size.should.be 1
124
+
125
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
126
+ res2["Set-Cookie"].should.equal nil
127
+ res2.body.should.equal '{"counter"=>3}'
128
+ pool.pool.size.should.be 1
129
+
130
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
131
+ res3["Set-Cookie"][session_match].should.equal session
132
+ res3.body.should.equal '{"counter"=>4}'
133
+ pool.pool.size.should.be 1
51
134
  end
52
135
 
136
+ # anyone know how to do this better?
53
137
  specify "multithread: should merge sessions" do
138
+ next unless $DEBUG
139
+ warn 'Running multithread tests for Session::Pool'
140
+ pool = Rack::Session::Pool.new(incrementor)
141
+ req = Rack::MockRequest.new(pool)
142
+
143
+ res = req.get('/')
144
+ res.body.should.equal '{"counter"=>1}'
145
+ cookie = res["Set-Cookie"]
146
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
147
+
54
148
  delta_incrementor = lambda do |env|
55
149
  # emulate disconjoinment of threading
56
150
  env['rack.session'] = env['rack.session'].dup
@@ -58,27 +152,21 @@ context "Rack::Session::Pool" do
58
152
  env['rack.session'][(Time.now.usec*rand).to_i] = true
59
153
  incrementor.call(env)
60
154
  end
61
- pool = Rack::Session::Pool.new(incrementor)
62
- res = Rack::MockRequest.new(pool).get('/')
63
- res.body.should.equal '{"counter"=>1}'
64
- cookie = res["Set-Cookie"]
65
- sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
66
-
67
- pool = pool.context(delta_incrementor)
68
- r = Array.new(rand(7).to_i+3).
69
- map! do
70
- Thread.new do
71
- Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
72
- end
73
- end.
74
- reverse!.
75
- map!{|t| t.run.join.value }
76
- session = pool.for.pool[sess_id] # for is needed by Utils::Context
77
- session.size.should.be r.size+1 # counter
78
- session['counter'].should.be 2 # meeeh
155
+ tses = Rack::Utils::Context.new pool, delta_incrementor
156
+ treq = Rack::MockRequest.new(tses)
157
+ tnum = rand(7).to_i+5
158
+ r = Array.new(tnum) do
159
+ Thread.new(treq) do |run|
160
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
161
+ end
162
+ end.reverse.map{|t| t.run.join.value }
79
163
  r.each do |res|
80
164
  res['Set-Cookie'].should.equal cookie
81
165
  res.body.should.include '"counter"=>2'
82
166
  end
167
+
168
+ session = pool.pool[sess_id]
169
+ session.size.should.be tnum+1 # counter
170
+ session['counter'].should.be 2 # meeeh
83
171
  end
84
172
  end
@@ -5,7 +5,7 @@ require 'rack/mock'
5
5
 
6
6
  class DummyApp
7
7
  def call(env)
8
- [200, {}, "Hello World"]
8
+ [200, {}, ["Hello World"]]
9
9
  end
10
10
  end
11
11
 
@@ -34,4 +34,4 @@ context "Rack::Static" do
34
34
  res.body.should == "Hello World"
35
35
  end
36
36
 
37
- end
37
+ end
@@ -10,9 +10,10 @@ context "Rack::Handler::Thin" do
10
10
 
11
11
  setup do
12
12
  @app = Rack::Lint.new(TestRequest.new)
13
+ @server = nil
13
14
  Thin::Logging.silent = true
14
15
  @thread = Thread.new do
15
- Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9201) do |server|
16
+ Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server|
16
17
  @server = server
17
18
  end
18
19
  end
@@ -31,7 +32,7 @@ context "Rack::Handler::Thin" do
31
32
  response["SERVER_SOFTWARE"].should =~ /thin/
32
33
  response["HTTP_VERSION"].should.equal "HTTP/1.1"
33
34
  response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
34
- response["SERVER_PORT"].should.equal "9201"
35
+ response["SERVER_PORT"].should.equal "9204"
35
36
  response["SERVER_NAME"].should.equal "0.0.0.0"
36
37
  end
37
38
 
@@ -68,6 +68,12 @@ context "Rack::URLMap" do
68
68
  "X-Position" => "foo.org",
69
69
  "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
70
70
  }, [""]]},
71
+ "http://subdomain.foo.org/" => lambda { |env|
72
+ [200,
73
+ { "Content-Type" => "text/plain",
74
+ "X-Position" => "subdomain.foo.org",
75
+ "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
76
+ }, [""]]},
71
77
  "http://bar.org/" => lambda { |env|
72
78
  [200,
73
79
  { "Content-Type" => "text/plain",
@@ -94,6 +100,10 @@ context "Rack::URLMap" do
94
100
  res.should.be.ok
95
101
  res["X-Position"].should.equal "foo.org"
96
102
 
103
+ res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org")
104
+ res.should.be.ok
105
+ res["X-Position"].should.equal "subdomain.foo.org"
106
+
97
107
  res = Rack::MockRequest.new(map).get("http://foo.org/")
98
108
  res.should.be.ok
99
109
  res["X-Position"].should.equal "default.org"
@@ -29,7 +29,83 @@ context "Rack::Utils" do
29
29
  Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
30
30
  should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
31
31
  end
32
-
32
+
33
+ specify "should parse nested query strings correctly" do
34
+ Rack::Utils.parse_nested_query("foo").
35
+ should.equal "foo" => nil
36
+ Rack::Utils.parse_nested_query("foo=").
37
+ should.equal "foo" => ""
38
+ Rack::Utils.parse_nested_query("foo=bar").
39
+ should.equal "foo" => "bar"
40
+
41
+ Rack::Utils.parse_nested_query("foo=bar&foo=quux").
42
+ should.equal "foo" => "quux"
43
+ Rack::Utils.parse_nested_query("foo&foo=").
44
+ should.equal "foo" => ""
45
+ Rack::Utils.parse_nested_query("foo=1&bar=2").
46
+ should.equal "foo" => "1", "bar" => "2"
47
+ Rack::Utils.parse_nested_query("&foo=1&&bar=2").
48
+ should.equal "foo" => "1", "bar" => "2"
49
+ Rack::Utils.parse_nested_query("foo&bar=").
50
+ should.equal "foo" => nil, "bar" => ""
51
+ Rack::Utils.parse_nested_query("foo=bar&baz=").
52
+ should.equal "foo" => "bar", "baz" => ""
53
+ Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
54
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
55
+
56
+ Rack::Utils.parse_nested_query("foo[]").
57
+ should.equal "foo" => [nil]
58
+ Rack::Utils.parse_nested_query("foo[]=").
59
+ should.equal "foo" => [""]
60
+ Rack::Utils.parse_nested_query("foo[]=bar").
61
+ should.equal "foo" => ["bar"]
62
+
63
+ Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
64
+ should.equal "foo" => ["1", "2"]
65
+ Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
66
+ should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
67
+ Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
68
+ should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
69
+
70
+ Rack::Utils.parse_nested_query("x[y][z]=1").
71
+ should.equal "x" => {"y" => {"z" => "1"}}
72
+ Rack::Utils.parse_nested_query("x[y][z][]=1").
73
+ should.equal "x" => {"y" => {"z" => ["1"]}}
74
+ Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
75
+ should.equal "x" => {"y" => {"z" => "2"}}
76
+ Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
77
+ should.equal "x" => {"y" => {"z" => ["1", "2"]}}
78
+
79
+ Rack::Utils.parse_nested_query("x[y][][z]=1").
80
+ should.equal "x" => {"y" => [{"z" => "1"}]}
81
+ Rack::Utils.parse_nested_query("x[y][][z][]=1").
82
+ should.equal "x" => {"y" => [{"z" => ["1"]}]}
83
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
84
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
85
+
86
+ Rack::Utils.parse_nested_query("x[y][][v][w]=1").
87
+ should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
88
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
89
+ should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
90
+
91
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
92
+ should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
93
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
94
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
95
+
96
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
97
+ should.raise(TypeError).
98
+ message.should.equal "expected Hash (got String) for param `y'"
99
+
100
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
101
+ should.raise(TypeError).
102
+ message.should.equal "expected Array (got Hash) for param `x'"
103
+
104
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
105
+ should.raise(TypeError).
106
+ message.should.equal "expected Array (got String) for param `y'"
107
+ end
108
+
33
109
  specify "should build query strings correctly" do
34
110
  Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
35
111
  Rack::Utils.build_query("foo" => ["bar", "quux"]).
@@ -62,6 +138,10 @@ context "Rack::Utils" do
62
138
  helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
63
139
  helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
64
140
  end
141
+
142
+ specify "should return the bytesize of String" do
143
+ Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
144
+ end
65
145
  end
66
146
 
67
147
  context "Rack::Utils::HeaderHash" do
@@ -95,75 +175,53 @@ context "Rack::Utils::HeaderHash" do
95
175
  h = Rack::Utils::HeaderHash.new("foo" => "bar")
96
176
  h.to_hash.should.be.instance_of Hash
97
177
  end
178
+
179
+ specify "should convert Array values to Strings when converting to Hash" do
180
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
181
+ h.to_hash.should.equal({ "foo" => "bar\nbaz" })
182
+ end
98
183
  end
99
184
 
100
185
  context "Rack::Utils::Context" do
101
- test_app1 = Object.new
102
- def test_app1.context app
103
- Rack::Utils::Context.new self, app do |env|
104
- app.call env
105
- end
186
+ class ContextTest
187
+ attr_reader :app
188
+ def initialize app; @app=app; end
189
+ def call env; context env; end
190
+ def context env, app=@app; app.call(env); end
106
191
  end
107
- test_app2 = Object.new
108
- def test_app2.context env; end
109
- test_app3 = Object.new
110
192
  test_target1 = proc{|e| e.to_s+' world' }
111
193
  test_target2 = proc{|e| e.to_i+2 }
112
194
  test_target3 = proc{|e| nil }
113
195
  test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
114
- test_target5 = Object.new
115
-
116
- specify "should perform checks on both arguments" do
117
- lambda { Rack::Utils::Context.new(nil, nil){} }.should.raise
118
- lambda { Rack::Utils::Context.new(test_app1, nil){} }.should.raise
119
- lambda { Rack::Utils::Context.new(nil, test_target1){} }.should.raise
120
- lambda { Rack::Utils::Context.new(test_app1, test_target1){} }.should.not.raise
121
- lambda { Rack::Utils::Context.new(test_app3, test_target1){} }.should.raise
122
- lambda { Rack::Utils::Context.new(test_app1, test_target5){} }.should.raise
123
- lambda { test_app1.context(nil){} }.should.raise
124
- lambda { test_app1.context(test_target1){} }.should.not.raise
125
- lambda { test_app1.context(test_target5){} }.should.raise
126
- end
196
+ test_app = ContextTest.new test_target4
127
197
 
128
198
  specify "should set context correctly" do
129
- c1 = Rack::Utils::Context.new(test_app1, test_target1){}
130
- c1.for.should.equal test_app1
199
+ test_app.app.should.equal test_target4
200
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
201
+ c1.for.should.equal test_app
131
202
  c1.app.should.equal test_target1
132
- c2 = Rack::Utils::Context.new(test_app1, test_target2){}
133
- c2.for.should.equal test_app1
203
+ c2 = Rack::Utils::Context.new(test_app, test_target2)
204
+ c2.for.should.equal test_app
134
205
  c2.app.should.equal test_target2
135
- c3 = Rack::Utils::Context.new(test_app2, test_target3){}
136
- c3.for.should.equal test_app2
137
- c3.app.should.equal test_target3
138
- c4 = Rack::Utils::Context.new(test_app2, test_target4){}
139
- c4.for.should.equal test_app2
140
- c4.app.should.equal test_target4
141
206
  end
142
207
 
143
208
  specify "should alter app on recontexting" do
144
- c1 = Rack::Utils::Context.new(test_app1, test_target1){}
145
- c1.for.should.equal test_app1
146
- c1.app.should.equal test_target1
147
- c2 = c1.context(test_target2)
148
- c2.for.should.equal test_app1
149
- c2.app.should.not.equal test_target1
209
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
210
+ c2 = c1.recontext(test_target2)
211
+ c2.for.should.equal test_app
150
212
  c2.app.should.equal test_target2
151
- c3 = c2.context(test_target3)
152
- c3.for.should.equal test_app1
153
- c3.app.should.not.equal test_target2
213
+ c3 = c2.recontext(test_target3)
214
+ c3.for.should.equal test_app
154
215
  c3.app.should.equal test_target3
155
- c4 = c3.context(test_target4)
156
- c4.for.should.equal test_app1
157
- c4.app.should.not.equal test_target3
158
- c4.app.should.equal test_target4
159
216
  end
160
217
 
161
218
  specify "should run different apps" do
162
- c1 = test_app1.context(test_target1)
163
- c2 = c1.context test_target2
164
- c3 = c2.context test_target3
165
- c4 = c3.context test_target4
219
+ c1 = Rack::Utils::Context.new test_app, test_target1
220
+ c2 = c1.recontext test_target2
221
+ c3 = c2.recontext test_target3
222
+ c4 = c3.recontext test_target4
166
223
  a4 = Rack::Lint.new c4
224
+ a5 = Rack::Lint.new test_app
167
225
  r1 = c1.call('hello')
168
226
  r1.should.equal 'hello world'
169
227
  r2 = c2.call(2)
@@ -172,5 +230,112 @@ context "Rack::Utils::Context" do
172
230
  r3.should.be.nil
173
231
  r4 = Rack::MockRequest.new(a4).get('/')
174
232
  r4.status.should.be 200
233
+ r5 = Rack::MockRequest.new(a5).get('/')
234
+ r5.status.should.be 200
235
+ r4.body.should.equal r5.body
236
+ end
237
+ end
238
+
239
+ context "Rack::Utils::Multipart" do
240
+ specify "should return nil if content type is not multipart" do
241
+ env = Rack::MockRequest.env_for("/",
242
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
243
+ Rack::Utils::Multipart.parse_multipart(env).should.equal nil
244
+ end
245
+
246
+ specify "should parse multipart upload with text file" do
247
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
248
+ params = Rack::Utils::Multipart.parse_multipart(env)
249
+ params["submit-name"].should.equal "Larry"
250
+ params["files"][:type].should.equal "text/plain"
251
+ params["files"][:filename].should.equal "file1.txt"
252
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
253
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
254
+ "Content-Type: text/plain\r\n"
255
+ params["files"][:name].should.equal "files"
256
+ params["files"][:tempfile].read.should.equal "contents"
257
+ end
258
+
259
+ specify "should parse multipart upload with nested parameters" do
260
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
261
+ params = Rack::Utils::Multipart.parse_multipart(env)
262
+ params["foo"]["submit-name"].should.equal "Larry"
263
+ params["foo"]["files"][:type].should.equal "text/plain"
264
+ params["foo"]["files"][:filename].should.equal "file1.txt"
265
+ params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
266
+ "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
267
+ "Content-Type: text/plain\r\n"
268
+ params["foo"]["files"][:name].should.equal "foo[files]"
269
+ params["foo"]["files"][:tempfile].read.should.equal "contents"
175
270
  end
271
+
272
+ specify "should parse multipart upload with binary file" do
273
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
274
+ params = Rack::Utils::Multipart.parse_multipart(env)
275
+ params["submit-name"].should.equal "Larry"
276
+ params["files"][:type].should.equal "image/png"
277
+ params["files"][:filename].should.equal "rack-logo.png"
278
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
279
+ "name=\"files\"; filename=\"rack-logo.png\"\r\n" +
280
+ "Content-Type: image/png\r\n"
281
+ params["files"][:name].should.equal "files"
282
+ params["files"][:tempfile].read.length.should.equal 26473
283
+ end
284
+
285
+ specify "should parse multipart upload with empty file" do
286
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
287
+ params = Rack::Utils::Multipart.parse_multipart(env)
288
+ params["submit-name"].should.equal "Larry"
289
+ params["files"][:type].should.equal "text/plain"
290
+ params["files"][:filename].should.equal "file1.txt"
291
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
292
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
293
+ "Content-Type: text/plain\r\n"
294
+ params["files"][:name].should.equal "files"
295
+ params["files"][:tempfile].read.should.equal ""
296
+ end
297
+
298
+ specify "should not include file params if no file was selected" do
299
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
300
+ params = Rack::Utils::Multipart.parse_multipart(env)
301
+ params["submit-name"].should.equal "Larry"
302
+ params["files"].should.equal nil
303
+ params.keys.should.not.include "files"
304
+ end
305
+
306
+ specify "should parse IE multipart upload and clean up filename" do
307
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
308
+ params = Rack::Utils::Multipart.parse_multipart(env)
309
+ params["files"][:type].should.equal "text/plain"
310
+ params["files"][:filename].should.equal "file1.txt"
311
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
312
+ "name=\"files\"; " +
313
+ 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
314
+ "\r\nContent-Type: text/plain\r\n"
315
+ params["files"][:name].should.equal "files"
316
+ params["files"][:tempfile].read.should.equal "contents"
317
+ end
318
+
319
+ specify "rewinds input after parsing upload" do
320
+ options = multipart_fixture(:text)
321
+ input = options[:input]
322
+ env = Rack::MockRequest.env_for("/", options)
323
+ params = Rack::Utils::Multipart.parse_multipart(env)
324
+ params["submit-name"].should.equal "Larry"
325
+ params["files"][:filename].should.equal "file1.txt"
326
+ input.read.length.should.equal 197
327
+ end
328
+
329
+ private
330
+ def multipart_fixture(name)
331
+ file = File.join(File.dirname(__FILE__), "multipart", name.to_s)
332
+ data = File.open(file, 'rb') { |io| io.read }
333
+
334
+ type = "multipart/form-data; boundary=AaB03x"
335
+ length = data.respond_to?(:bytesize) ? data.bytesize : data.size
336
+
337
+ { "CONTENT_TYPE" => type,
338
+ "CONTENT_LENGTH" => length.to_s,
339
+ :input => StringIO.new(data) }
340
+ end
176
341
  end