rack 0.9.1 → 1.0.0
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.
- data/COPYING +1 -1
- data/RDOX +115 -16
- data/README +54 -7
- data/Rakefile +61 -85
- data/SPEC +50 -17
- data/bin/rackup +9 -5
- data/example/protectedlobster.ru +1 -1
- data/lib/rack.rb +7 -3
- data/lib/rack/auth/abstract/handler.rb +13 -4
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/auth/digest/request.rb +2 -2
- data/lib/rack/auth/openid.rb +344 -302
- data/lib/rack/builder.rb +1 -5
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/conditionalget.rb +4 -0
- data/lib/rack/content_length.rb +7 -3
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +83 -74
- data/lib/rack/directory.rb +5 -2
- data/lib/rack/file.rb +4 -1
- data/lib/rack/handler.rb +22 -1
- data/lib/rack/handler/cgi.rb +7 -3
- data/lib/rack/handler/fastcgi.rb +26 -24
- data/lib/rack/handler/lsws.rb +7 -4
- data/lib/rack/handler/mongrel.rb +5 -3
- data/lib/rack/handler/scgi.rb +5 -3
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +11 -5
- data/lib/rack/lint.rb +138 -66
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/mime.rb +4 -4
- data/lib/rack/mock.rb +3 -3
- data/lib/rack/reloader.rb +88 -46
- data/lib/rack/request.rb +46 -10
- data/lib/rack/response.rb +15 -3
- data/lib/rack/rewindable_input.rb +98 -0
- data/lib/rack/session/abstract/id.rb +71 -82
- data/lib/rack/session/cookie.rb +2 -0
- data/lib/rack/session/memcache.rb +59 -47
- data/lib/rack/session/pool.rb +56 -29
- data/lib/rack/showexceptions.rb +2 -1
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/urlmap.rb +12 -5
- data/lib/rack/utils.rb +115 -65
- data/rack.gemspec +54 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +5 -1
- data/test/spec_rack_auth_digest.rb +93 -36
- data/test/spec_rack_auth_openid.rb +47 -100
- data/test/spec_rack_builder.rb +2 -2
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_conditionalget.rb +7 -7
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +36 -14
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_file.rb +11 -0
- data/test/spec_rack_handler.rb +21 -2
- data/test/spec_rack_lint.rb +163 -44
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_mock.rb +6 -1
- data/test/spec_rack_request.rb +81 -12
- data/test/spec_rack_response.rb +46 -2
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_memcache.rb +170 -62
- data/test/spec_rack_session_pool.rb +129 -41
- data/test/spec_rack_static.rb +2 -2
- data/test/spec_rack_thin.rb +3 -2
- data/test/spec_rack_urlmap.rb +10 -0
- data/test/spec_rack_utils.rb +214 -49
- data/test/spec_rack_webrick.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +95 -6
- 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
|
-
|
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
|
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
|
-
|
25
|
-
cookie =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
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" => "
|
49
|
+
get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
|
36
50
|
res.body.should.equal '{"counter"=>1}'
|
37
51
|
end
|
38
52
|
|
39
|
-
specify "
|
40
|
-
pool = Rack::Session::Pool.new(incrementor
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
data/test/spec_rack_static.rb
CHANGED
data/test/spec_rack_thin.rb
CHANGED
@@ -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=
|
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 "
|
35
|
+
response["SERVER_PORT"].should.equal "9204"
|
35
36
|
response["SERVER_NAME"].should.equal "0.0.0.0"
|
36
37
|
end
|
37
38
|
|
data/test/spec_rack_urlmap.rb
CHANGED
@@ -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"
|
data/test/spec_rack_utils.rb
CHANGED
@@ -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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
-
|
130
|
-
c1.
|
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(
|
133
|
-
c2.for.should.equal
|
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(
|
145
|
-
c1.
|
146
|
-
|
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.
|
152
|
-
c3.for.should.equal
|
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 =
|
163
|
-
c2 = c1.
|
164
|
-
c3 = c2.
|
165
|
-
c4 = c3.
|
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
|