qoobaa-rack 1.0.0.1
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.
- data/COPYING +18 -0
- data/KNOWN-ISSUES +18 -0
- data/RDOX +0 -0
- data/README +353 -0
- data/Rakefile +164 -0
- data/SPEC +164 -0
- data/bin/rackup +176 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +37 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +55 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/auth/openid.rb +487 -0
- data/lib/rack/builder.rb +63 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/commonlogger.rb +52 -0
- data/lib/rack/conditionalget.rb +47 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +153 -0
- data/lib/rack/file.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +88 -0
- data/lib/rack/handler/lsws.rb +60 -0
- data/lib/rack/handler/mongrel.rb +87 -0
- data/lib/rack/handler/scgi.rb +62 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +18 -0
- data/lib/rack/handler/webrick.rb +71 -0
- data/lib/rack/handler.rb +69 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +546 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +204 -0
- data/lib/rack/mock.rb +187 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +107 -0
- data/lib/rack/request.rb +248 -0
- data/lib/rack/response.rb +183 -0
- data/lib/rack/rewindable_input.rb +100 -0
- data/lib/rack/session/abstract/id.rb +142 -0
- data/lib/rack/session/cookie.rb +91 -0
- data/lib/rack/session/memcache.rb +109 -0
- data/lib/rack/session/pool.rb +100 -0
- data/lib/rack/showexceptions.rb +349 -0
- data/lib/rack/showstatus.rb +106 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +528 -0
- data/lib/rack.rb +90 -0
- data/rack.gemspec +60 -0
- data/test/cgi/lighttpd.conf +20 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +7 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/file1.txt +1 -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 +73 -0
- data/test/spec_rack_auth_digest.rb +226 -0
- data/test/spec_rack_auth_openid.rb +84 -0
- data/test/spec_rack_builder.rb +84 -0
- data/test/spec_rack_camping.rb +51 -0
- data/test/spec_rack_cascade.rb +48 -0
- data/test/spec_rack_cgi.rb +89 -0
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_commonlogger.rb +61 -0
- data/test/spec_rack_conditionalget.rb +41 -0
- data/test/spec_rack_content_length.rb +43 -0
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +127 -0
- data/test/spec_rack_directory.rb +61 -0
- data/test/spec_rack_fastcgi.rb +89 -0
- data/test/spec_rack_file.rb +75 -0
- data/test/spec_rack_handler.rb +43 -0
- data/test/spec_rack_head.rb +30 -0
- data/test/spec_rack_lint.rb +521 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_methodoverride.rb +60 -0
- data/test/spec_rack_mock.rb +243 -0
- data/test/spec_rack_mongrel.rb +189 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +504 -0
- data/test/spec_rack_response.rb +218 -0
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_cookie.rb +82 -0
- data/test/spec_rack_session_memcache.rb +250 -0
- data/test/spec_rack_session_pool.rb +172 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_showstatus.rb +72 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_thin.rb +91 -0
- data/test/spec_rack_urlmap.rb +185 -0
- data/test/spec_rack_utils.rb +467 -0
- data/test/spec_rack_webrick.rb +130 -0
- data/test/testrequest.rb +57 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +276 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
require 'rack/response'
|
|
5
|
+
|
|
6
|
+
context "Rack::Response" do
|
|
7
|
+
specify "has sensible default values" do
|
|
8
|
+
response = Rack::Response.new
|
|
9
|
+
status, header, body = response.finish
|
|
10
|
+
status.should.equal 200
|
|
11
|
+
header.should.equal "Content-Type" => "text/html"
|
|
12
|
+
body.each { |part|
|
|
13
|
+
part.should.equal ""
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
response = Rack::Response.new
|
|
17
|
+
status, header, body = *response
|
|
18
|
+
status.should.equal 200
|
|
19
|
+
header.should.equal "Content-Type" => "text/html"
|
|
20
|
+
body.each { |part|
|
|
21
|
+
part.should.equal ""
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
specify "can be written to" do
|
|
26
|
+
response = Rack::Response.new
|
|
27
|
+
|
|
28
|
+
status, header, body = response.finish do
|
|
29
|
+
response.write "foo"
|
|
30
|
+
response.write "bar"
|
|
31
|
+
response.write "baz"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
parts = []
|
|
35
|
+
body.each { |part| parts << part }
|
|
36
|
+
|
|
37
|
+
parts.should.equal ["foo", "bar", "baz"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
specify "can set and read headers" do
|
|
41
|
+
response = Rack::Response.new
|
|
42
|
+
response["Content-Type"].should.equal "text/html"
|
|
43
|
+
response["Content-Type"] = "text/plain"
|
|
44
|
+
response["Content-Type"].should.equal "text/plain"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
specify "can set cookies" do
|
|
48
|
+
response = Rack::Response.new
|
|
49
|
+
|
|
50
|
+
response.set_cookie "foo", "bar"
|
|
51
|
+
response["Set-Cookie"].should.equal "foo=bar"
|
|
52
|
+
response.set_cookie "foo2", "bar2"
|
|
53
|
+
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
|
|
54
|
+
response.set_cookie "foo3", "bar3"
|
|
55
|
+
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
specify "formats the Cookie expiration date accordingly to RFC 2109" do
|
|
59
|
+
response = Rack::Response.new
|
|
60
|
+
|
|
61
|
+
response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
|
|
62
|
+
response["Set-Cookie"].should.match(
|
|
63
|
+
/expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
specify "can set secure cookies" do
|
|
67
|
+
response = Rack::Response.new
|
|
68
|
+
response.set_cookie "foo", {:value => "bar", :secure => true}
|
|
69
|
+
response["Set-Cookie"].should.equal "foo=bar; secure"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
specify "can set http only cookies" do
|
|
73
|
+
response = Rack::Response.new
|
|
74
|
+
response.set_cookie "foo", {:value => "bar", :httponly => true}
|
|
75
|
+
response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
specify "can delete cookies" do
|
|
79
|
+
response = Rack::Response.new
|
|
80
|
+
response.set_cookie "foo", "bar"
|
|
81
|
+
response.set_cookie "foo2", "bar2"
|
|
82
|
+
response.delete_cookie "foo"
|
|
83
|
+
response["Set-Cookie"].should.equal ["foo2=bar2",
|
|
84
|
+
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
specify "can do redirects" do
|
|
88
|
+
response = Rack::Response.new
|
|
89
|
+
response.redirect "/foo"
|
|
90
|
+
status, header, body = response.finish
|
|
91
|
+
|
|
92
|
+
status.should.equal 302
|
|
93
|
+
header["Location"].should.equal "/foo"
|
|
94
|
+
|
|
95
|
+
response = Rack::Response.new
|
|
96
|
+
response.redirect "/foo", 307
|
|
97
|
+
status, header, body = response.finish
|
|
98
|
+
|
|
99
|
+
status.should.equal 307
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
specify "has a useful constructor" do
|
|
103
|
+
r = Rack::Response.new("foo")
|
|
104
|
+
status, header, body = r.finish
|
|
105
|
+
str = ""; body.each { |part| str << part }
|
|
106
|
+
str.should.equal "foo"
|
|
107
|
+
|
|
108
|
+
r = Rack::Response.new(["foo", "bar"])
|
|
109
|
+
status, header, body = r.finish
|
|
110
|
+
str = ""; body.each { |part| str << part }
|
|
111
|
+
str.should.equal "foobar"
|
|
112
|
+
|
|
113
|
+
r = Rack::Response.new(["foo", "bar"].to_set)
|
|
114
|
+
r.write "foo"
|
|
115
|
+
status, header, body = r.finish
|
|
116
|
+
str = ""; body.each { |part| str << part }
|
|
117
|
+
str.should.equal "foobarfoo"
|
|
118
|
+
|
|
119
|
+
r = Rack::Response.new([], 500)
|
|
120
|
+
r.status.should.equal 500
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
specify "has a constructor that can take a block" do
|
|
124
|
+
r = Rack::Response.new { |res|
|
|
125
|
+
res.status = 404
|
|
126
|
+
res.write "foo"
|
|
127
|
+
}
|
|
128
|
+
status, header, body = r.finish
|
|
129
|
+
str = ""; body.each { |part| str << part }
|
|
130
|
+
str.should.equal "foo"
|
|
131
|
+
status.should.equal 404
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
specify "doesn't return invalid responses" do
|
|
135
|
+
r = Rack::Response.new(["foo", "bar"], 204)
|
|
136
|
+
status, header, body = r.finish
|
|
137
|
+
str = ""; body.each { |part| str << part }
|
|
138
|
+
str.should.be.empty
|
|
139
|
+
header["Content-Type"].should.equal nil
|
|
140
|
+
|
|
141
|
+
lambda {
|
|
142
|
+
Rack::Response.new(Object.new)
|
|
143
|
+
}.should.raise(TypeError).
|
|
144
|
+
message.should =~ /stringable or iterable required/
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
specify "knows if it's empty" do
|
|
148
|
+
r = Rack::Response.new
|
|
149
|
+
r.should.be.empty
|
|
150
|
+
r.write "foo"
|
|
151
|
+
r.should.not.be.empty
|
|
152
|
+
|
|
153
|
+
r = Rack::Response.new
|
|
154
|
+
r.should.be.empty
|
|
155
|
+
r.finish
|
|
156
|
+
r.should.be.empty
|
|
157
|
+
|
|
158
|
+
r = Rack::Response.new
|
|
159
|
+
r.should.be.empty
|
|
160
|
+
r.finish { }
|
|
161
|
+
r.should.not.be.empty
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
specify "should provide access to the HTTP status" do
|
|
165
|
+
res = Rack::Response.new
|
|
166
|
+
res.status = 200
|
|
167
|
+
res.should.be.successful
|
|
168
|
+
res.should.be.ok
|
|
169
|
+
|
|
170
|
+
res.status = 404
|
|
171
|
+
res.should.not.be.successful
|
|
172
|
+
res.should.be.client_error
|
|
173
|
+
res.should.be.not_found
|
|
174
|
+
|
|
175
|
+
res.status = 501
|
|
176
|
+
res.should.not.be.successful
|
|
177
|
+
res.should.be.server_error
|
|
178
|
+
|
|
179
|
+
res.status = 307
|
|
180
|
+
res.should.be.redirect
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
specify "should provide access to the HTTP headers" do
|
|
184
|
+
res = Rack::Response.new
|
|
185
|
+
res["Content-Type"] = "text/yaml"
|
|
186
|
+
|
|
187
|
+
res.should.include "Content-Type"
|
|
188
|
+
res.headers["Content-Type"].should.equal "text/yaml"
|
|
189
|
+
res["Content-Type"].should.equal "text/yaml"
|
|
190
|
+
res.content_type.should.equal "text/yaml"
|
|
191
|
+
res.content_length.should.be.nil
|
|
192
|
+
res.location.should.be.nil
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
specify "does not add or change Content-Length when #finish()ing" do
|
|
196
|
+
res = Rack::Response.new
|
|
197
|
+
res.status = 200
|
|
198
|
+
res.finish
|
|
199
|
+
res.headers["Content-Length"].should.be.nil
|
|
200
|
+
|
|
201
|
+
res = Rack::Response.new
|
|
202
|
+
res.status = 200
|
|
203
|
+
res.headers["Content-Length"] = "10"
|
|
204
|
+
res.finish
|
|
205
|
+
res.headers["Content-Length"].should.equal "10"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
specify "updates Content-Length when body appended to using #write" do
|
|
209
|
+
res = Rack::Response.new
|
|
210
|
+
res.status = 200
|
|
211
|
+
res.headers["Content-Length"].should.be.nil
|
|
212
|
+
res.write "Hi"
|
|
213
|
+
res.headers["Content-Length"].should.equal "2"
|
|
214
|
+
res.write " there"
|
|
215
|
+
res.headers["Content-Length"].should.equal "8"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
require 'rack/rewindable_input'
|
|
4
|
+
|
|
5
|
+
shared_context "a rewindable IO object" do
|
|
6
|
+
setup do
|
|
7
|
+
@rio = Rack::RewindableInput.new(@io)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
teardown do
|
|
11
|
+
@rio.close
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
specify "should be able to handle to read()" do
|
|
15
|
+
@rio.read.should.equal "hello world"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
specify "should be able to handle to read(nil)" do
|
|
19
|
+
@rio.read(nil).should.equal "hello world"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
specify "should be able to handle to read(length)" do
|
|
23
|
+
@rio.read(1).should.equal "h"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
specify "should be able to handle to read(length, buffer)" do
|
|
27
|
+
buffer = ""
|
|
28
|
+
result = @rio.read(1, buffer)
|
|
29
|
+
result.should.equal "h"
|
|
30
|
+
result.object_id.should.equal buffer.object_id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
specify "should be able to handle to read(nil, buffer)" do
|
|
34
|
+
buffer = ""
|
|
35
|
+
result = @rio.read(nil, buffer)
|
|
36
|
+
result.should.equal "hello world"
|
|
37
|
+
result.object_id.should.equal buffer.object_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
specify "should rewind to the beginning when #rewind is called" do
|
|
41
|
+
@rio.read(1)
|
|
42
|
+
@rio.rewind
|
|
43
|
+
@rio.read.should.equal "hello world"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
specify "should be able to handle gets" do
|
|
47
|
+
@rio.gets.should == "hello world"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
specify "should be able to handle each" do
|
|
51
|
+
array = []
|
|
52
|
+
@rio.each do |data|
|
|
53
|
+
array << data
|
|
54
|
+
end
|
|
55
|
+
array.should.equal(["hello world"])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
specify "should not buffer into a Tempfile if no data has been read yet" do
|
|
59
|
+
@rio.instance_variable_get(:@rewindable_io).should.be.nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
specify "should buffer into a Tempfile when data has been consumed for the first time" do
|
|
63
|
+
@rio.read(1)
|
|
64
|
+
tempfile = @rio.instance_variable_get(:@rewindable_io)
|
|
65
|
+
tempfile.should.not.be.nil
|
|
66
|
+
@rio.read(1)
|
|
67
|
+
tempfile2 = @rio.instance_variable_get(:@rewindable_io)
|
|
68
|
+
tempfile2.should.equal tempfile
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
specify "should close the underlying tempfile upon calling #close" do
|
|
72
|
+
@rio.read(1)
|
|
73
|
+
tempfile = @rio.instance_variable_get(:@rewindable_io)
|
|
74
|
+
@rio.close
|
|
75
|
+
tempfile.should.be.closed
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
specify "should be possibel to call #close when no data has been buffered yet" do
|
|
79
|
+
@rio.close
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
specify "should be possible to call #close multiple times" do
|
|
83
|
+
@rio.close
|
|
84
|
+
@rio.close
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context "Rack::RewindableInput" do
|
|
89
|
+
context "given an IO object that is already rewindable" do
|
|
90
|
+
setup do
|
|
91
|
+
@io = StringIO.new("hello world")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it_should_behave_like "a rewindable IO object"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context "given an IO object that is not rewindable" do
|
|
98
|
+
setup do
|
|
99
|
+
@io = StringIO.new("hello world")
|
|
100
|
+
@io.instance_eval do
|
|
101
|
+
undef :rewind
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it_should_behave_like "a rewindable IO object"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "given an IO object whose rewind method raises Errno::ESPIPE" do
|
|
109
|
+
setup do
|
|
110
|
+
@io = StringIO.new("hello world")
|
|
111
|
+
def @io.rewind
|
|
112
|
+
raise Errno::ESPIPE, "You can't rewind this!"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it_should_behave_like "a rewindable IO object"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
|
|
3
|
+
require 'rack/session/cookie'
|
|
4
|
+
require 'rack/mock'
|
|
5
|
+
require 'rack/response'
|
|
6
|
+
|
|
7
|
+
context "Rack::Session::Cookie" do
|
|
8
|
+
incrementor = lambda { |env|
|
|
9
|
+
env["rack.session"]["counter"] ||= 0
|
|
10
|
+
env["rack.session"]["counter"] += 1
|
|
11
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
specify "creates a new cookie" do
|
|
15
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
|
|
16
|
+
res["Set-Cookie"].should.match("rack.session=")
|
|
17
|
+
res.body.should.equal '{"counter"=>1}'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
specify "loads from a cookie" do
|
|
21
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
|
|
22
|
+
cookie = res["Set-Cookie"]
|
|
23
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
|
|
24
|
+
get("/", "HTTP_COOKIE" => cookie)
|
|
25
|
+
res.body.should.equal '{"counter"=>2}'
|
|
26
|
+
cookie = res["Set-Cookie"]
|
|
27
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
|
|
28
|
+
get("/", "HTTP_COOKIE" => cookie)
|
|
29
|
+
res.body.should.equal '{"counter"=>3}'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
specify "survives broken cookies" do
|
|
33
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
|
|
34
|
+
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
|
|
35
|
+
res.body.should.equal '{"counter"=>1}'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
bigcookie = lambda { |env|
|
|
39
|
+
env["rack.session"]["cookie"] = "big" * 3000
|
|
40
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
specify "barks on too big cookies" do
|
|
44
|
+
lambda {
|
|
45
|
+
Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
|
|
46
|
+
get("/", :fatal => true)
|
|
47
|
+
}.should.raise(Rack::MockRequest::FatalWarning)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
specify "creates a new cookie with integrity hash" do
|
|
51
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
|
|
52
|
+
if RUBY_VERSION < "1.9"
|
|
53
|
+
res["Set-Cookie"].should.match("rack.session=BAh7BiIMY291bnRlcmkG%0A--1439b4d37b9d4b04c603848382f712d6fcd31088")
|
|
54
|
+
else
|
|
55
|
+
res["Set-Cookie"].should.match("rack.session=BAh7BkkiDGNvdW50ZXIGOg1lbmNvZGluZyINVVMtQVNDSUlpBg%3D%3D%0A--d7a6637b94d2728194a96c18484e1f7ed9074a83")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
specify "loads from a cookie wih integrity hash" do
|
|
60
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
|
|
61
|
+
cookie = res["Set-Cookie"]
|
|
62
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
|
|
63
|
+
get("/", "HTTP_COOKIE" => cookie)
|
|
64
|
+
res.body.should.equal '{"counter"=>2}'
|
|
65
|
+
cookie = res["Set-Cookie"]
|
|
66
|
+
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
|
|
67
|
+
get("/", "HTTP_COOKIE" => cookie)
|
|
68
|
+
res.body.should.equal '{"counter"=>3}'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
specify "ignores tampered with session cookies" do
|
|
72
|
+
app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
|
|
73
|
+
response1 = Rack::MockRequest.new(app).get("/")
|
|
74
|
+
_, digest = response1["Set-Cookie"].split("--")
|
|
75
|
+
tampered_with_cookie = "hackerman-was-here" + "--" + digest
|
|
76
|
+
response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" =>
|
|
77
|
+
tampered_with_cookie)
|
|
78
|
+
|
|
79
|
+
# The tampered-with cookie is ignored, so we get back an identical Set-Cookie
|
|
80
|
+
response2["Set-Cookie"].should.equal(response1["Set-Cookie"])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'rack/session/memcache'
|
|
5
|
+
require 'rack/mock'
|
|
6
|
+
require 'rack/response'
|
|
7
|
+
require 'thread'
|
|
8
|
+
|
|
9
|
+
pool = Rack::Session::Memcache.new(lambda {})
|
|
10
|
+
|
|
11
|
+
context "Rack::Session::Memcache" do
|
|
12
|
+
session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
|
|
13
|
+
session_match = /#{session_key}=[0-9a-fA-F]+;/
|
|
14
|
+
incrementor = lambda do |env|
|
|
15
|
+
env["rack.session"]["counter"] ||= 0
|
|
16
|
+
env["rack.session"]["counter"] += 1
|
|
17
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
|
18
|
+
end
|
|
19
|
+
drop_session = proc do |env|
|
|
20
|
+
env['rack.session.options'][:drop] = true
|
|
21
|
+
incrementor.call(env)
|
|
22
|
+
end
|
|
23
|
+
renew_session = proc do |env|
|
|
24
|
+
env['rack.session.options'][:renew] = true
|
|
25
|
+
incrementor.call(env)
|
|
26
|
+
end
|
|
27
|
+
defer_session = proc do |env|
|
|
28
|
+
env['rack.session.options'][:defer] = true
|
|
29
|
+
incrementor.call(env)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
specify "MemCache can connect to existing server" do
|
|
33
|
+
test_pool = MemCache.new :namespace => 'test:rack:session'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
specify "faults on no connection" do
|
|
37
|
+
if RUBY_VERSION < "1.9"
|
|
38
|
+
lambda do
|
|
39
|
+
Rack::Session::Memcache.new(incrementor, :memcache_server => '')
|
|
40
|
+
end.should.raise
|
|
41
|
+
else
|
|
42
|
+
lambda do
|
|
43
|
+
Rack::Session::Memcache.new(incrementor, :memcache_server => '')
|
|
44
|
+
end.should.raise ArgumentError
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
specify "creates a new cookie" do
|
|
49
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
50
|
+
res = Rack::MockRequest.new(pool).get("/")
|
|
51
|
+
res["Set-Cookie"].should.match("#{session_key}=")
|
|
52
|
+
res.body.should.equal '{"counter"=>1}'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
specify "determines session from a cookie" do
|
|
56
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
57
|
+
req = Rack::MockRequest.new(pool)
|
|
58
|
+
res = req.get("/")
|
|
59
|
+
cookie = res["Set-Cookie"]
|
|
60
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
|
61
|
+
body.should.equal '{"counter"=>2}'
|
|
62
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
|
63
|
+
body.should.equal '{"counter"=>3}'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
specify "survives nonexistant cookies" do
|
|
67
|
+
bad_cookie = "rack.session=blarghfasel"
|
|
68
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
69
|
+
res = Rack::MockRequest.new(pool).
|
|
70
|
+
get("/", "HTTP_COOKIE" => bad_cookie)
|
|
71
|
+
res.body.should.equal '{"counter"=>1}'
|
|
72
|
+
cookie = res["Set-Cookie"][session_match]
|
|
73
|
+
cookie.should.not.match(/#{bad_cookie}/)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
specify "maintains freshness" do
|
|
77
|
+
pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
|
|
78
|
+
res = Rack::MockRequest.new(pool).get('/')
|
|
79
|
+
res.body.should.include '"counter"=>1'
|
|
80
|
+
cookie = res["Set-Cookie"]
|
|
81
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
|
82
|
+
res["Set-Cookie"].should.equal cookie
|
|
83
|
+
res.body.should.include '"counter"=>2'
|
|
84
|
+
puts 'Sleeping to expire session' if $DEBUG
|
|
85
|
+
sleep 4
|
|
86
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
|
87
|
+
res["Set-Cookie"].should.not.equal cookie
|
|
88
|
+
res.body.should.include '"counter"=>1'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
specify "deletes cookies with :drop option" do
|
|
92
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
93
|
+
req = Rack::MockRequest.new(pool)
|
|
94
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
|
95
|
+
dreq = Rack::MockRequest.new(drop)
|
|
96
|
+
|
|
97
|
+
res0 = req.get("/")
|
|
98
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
|
99
|
+
res0.body.should.equal '{"counter"=>1}'
|
|
100
|
+
|
|
101
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
|
102
|
+
res1["Set-Cookie"][session_match].should.equal session
|
|
103
|
+
res1.body.should.equal '{"counter"=>2}'
|
|
104
|
+
|
|
105
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
|
106
|
+
res2["Set-Cookie"].should.equal nil
|
|
107
|
+
res2.body.should.equal '{"counter"=>3}'
|
|
108
|
+
|
|
109
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
|
110
|
+
res3["Set-Cookie"][session_match].should.not.equal session
|
|
111
|
+
res3.body.should.equal '{"counter"=>1}'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
specify "provides new session id with :renew option" do
|
|
115
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
116
|
+
req = Rack::MockRequest.new(pool)
|
|
117
|
+
renew = Rack::Utils::Context.new(pool, renew_session)
|
|
118
|
+
rreq = Rack::MockRequest.new(renew)
|
|
119
|
+
|
|
120
|
+
res0 = req.get("/")
|
|
121
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
|
122
|
+
res0.body.should.equal '{"counter"=>1}'
|
|
123
|
+
|
|
124
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
|
125
|
+
res1["Set-Cookie"][session_match].should.equal session
|
|
126
|
+
res1.body.should.equal '{"counter"=>2}'
|
|
127
|
+
|
|
128
|
+
res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
|
|
129
|
+
new_cookie = res2["Set-Cookie"]
|
|
130
|
+
new_session = new_cookie[session_match]
|
|
131
|
+
new_session.should.not.equal session
|
|
132
|
+
res2.body.should.equal '{"counter"=>3}'
|
|
133
|
+
|
|
134
|
+
res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
|
|
135
|
+
res3["Set-Cookie"][session_match].should.equal new_session
|
|
136
|
+
res3.body.should.equal '{"counter"=>4}'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
specify "omits cookie with :defer option" do
|
|
140
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
141
|
+
req = Rack::MockRequest.new(pool)
|
|
142
|
+
defer = Rack::Utils::Context.new(pool, defer_session)
|
|
143
|
+
dreq = Rack::MockRequest.new(defer)
|
|
144
|
+
|
|
145
|
+
res0 = req.get("/")
|
|
146
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
|
147
|
+
res0.body.should.equal '{"counter"=>1}'
|
|
148
|
+
|
|
149
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
|
150
|
+
res1["Set-Cookie"][session_match].should.equal session
|
|
151
|
+
res1.body.should.equal '{"counter"=>2}'
|
|
152
|
+
|
|
153
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
|
154
|
+
res2["Set-Cookie"].should.equal nil
|
|
155
|
+
res2.body.should.equal '{"counter"=>3}'
|
|
156
|
+
|
|
157
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
|
158
|
+
res3["Set-Cookie"][session_match].should.equal session
|
|
159
|
+
res3.body.should.equal '{"counter"=>4}'
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# anyone know how to do this better?
|
|
163
|
+
specify "multithread: should cleanly merge sessions" do
|
|
164
|
+
next unless $DEBUG
|
|
165
|
+
warn 'Running multithread test for Session::Memcache'
|
|
166
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
|
167
|
+
req = Rack::MockRequest.new(pool)
|
|
168
|
+
|
|
169
|
+
res = req.get('/')
|
|
170
|
+
res.body.should.equal '{"counter"=>1}'
|
|
171
|
+
cookie = res["Set-Cookie"]
|
|
172
|
+
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
|
|
173
|
+
|
|
174
|
+
delta_incrementor = lambda do |env|
|
|
175
|
+
# emulate disconjoinment of threading
|
|
176
|
+
env['rack.session'] = env['rack.session'].dup
|
|
177
|
+
Thread.stop
|
|
178
|
+
env['rack.session'][(Time.now.usec*rand).to_i] = true
|
|
179
|
+
incrementor.call(env)
|
|
180
|
+
end
|
|
181
|
+
tses = Rack::Utils::Context.new pool, delta_incrementor
|
|
182
|
+
treq = Rack::MockRequest.new(tses)
|
|
183
|
+
tnum = rand(7).to_i+5
|
|
184
|
+
r = Array.new(tnum) do
|
|
185
|
+
Thread.new(treq) do |run|
|
|
186
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
|
187
|
+
end
|
|
188
|
+
end.reverse.map{|t| t.run.join.value }
|
|
189
|
+
r.each do |request|
|
|
190
|
+
request['Set-Cookie'].should.equal cookie
|
|
191
|
+
request.body.should.include '"counter"=>2'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
session = pool.pool.get(sess_id)
|
|
195
|
+
session.size.should.be tnum+1 # counter
|
|
196
|
+
session['counter'].should.be 2 # meeeh
|
|
197
|
+
|
|
198
|
+
tnum = rand(7).to_i+5
|
|
199
|
+
r = Array.new(tnum) do |i|
|
|
200
|
+
delta_time = proc do |env|
|
|
201
|
+
env['rack.session'][i] = Time.now
|
|
202
|
+
Thread.stop
|
|
203
|
+
env['rack.session'] = env['rack.session'].dup
|
|
204
|
+
env['rack.session'][i] -= Time.now
|
|
205
|
+
incrementor.call(env)
|
|
206
|
+
end
|
|
207
|
+
app = Rack::Utils::Context.new pool, time_delta
|
|
208
|
+
req = Rack::MockRequest.new app
|
|
209
|
+
Thread.new(req) do |run|
|
|
210
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
|
211
|
+
end
|
|
212
|
+
end.reverse.map{|t| t.run.join.value }
|
|
213
|
+
r.each do |request|
|
|
214
|
+
request['Set-Cookie'].should.equal cookie
|
|
215
|
+
request.body.should.include '"counter"=>3'
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
session = pool.pool.get(sess_id)
|
|
219
|
+
session.size.should.be tnum+1
|
|
220
|
+
session['counter'].should.be 3
|
|
221
|
+
|
|
222
|
+
drop_counter = proc do |env|
|
|
223
|
+
env['rack.session'].delete 'counter'
|
|
224
|
+
env['rack.session']['foo'] = 'bar'
|
|
225
|
+
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
|
226
|
+
end
|
|
227
|
+
tses = Rack::Utils::Context.new pool, drop_counter
|
|
228
|
+
treq = Rack::MockRequest.new(tses)
|
|
229
|
+
tnum = rand(7).to_i+5
|
|
230
|
+
r = Array.new(tnum) do
|
|
231
|
+
Thread.new(treq) do |run|
|
|
232
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
|
233
|
+
end
|
|
234
|
+
end.reverse.map{|t| t.run.join.value }
|
|
235
|
+
r.each do |request|
|
|
236
|
+
request['Set-Cookie'].should.equal cookie
|
|
237
|
+
request.body.should.include '"foo"=>"bar"'
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
session = pool.pool.get(sess_id)
|
|
241
|
+
session.size.should.be r.size+1
|
|
242
|
+
session['counter'].should.be.nil?
|
|
243
|
+
session['foo'].should.equal 'bar'
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
rescue RuntimeError
|
|
247
|
+
$stderr.puts "Skipping Rack::Session::Memcache tests. Start memcached and try again."
|
|
248
|
+
rescue LoadError
|
|
249
|
+
$stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again."
|
|
250
|
+
end
|