kastner-rack 0.3.171

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.
Files changed (94) hide show
  1. data/AUTHORS +8 -0
  2. data/COPYING +18 -0
  3. data/KNOWN-ISSUES +18 -0
  4. data/README +273 -0
  5. data/Rakefile +185 -0
  6. data/bin/rackup +172 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +85 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +28 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +437 -0
  21. data/lib/rack/builder.rb +67 -0
  22. data/lib/rack/cascade.rb +36 -0
  23. data/lib/rack/commonlogger.rb +61 -0
  24. data/lib/rack/conditionalget.rb +42 -0
  25. data/lib/rack/deflater.rb +63 -0
  26. data/lib/rack/directory.rb +149 -0
  27. data/lib/rack/file.rb +84 -0
  28. data/lib/rack/handler.rb +46 -0
  29. data/lib/rack/handler/cgi.rb +57 -0
  30. data/lib/rack/handler/evented_mongrel.rb +8 -0
  31. data/lib/rack/handler/fastcgi.rb +86 -0
  32. data/lib/rack/handler/lsws.rb +52 -0
  33. data/lib/rack/handler/mongrel.rb +78 -0
  34. data/lib/rack/handler/scgi.rb +57 -0
  35. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  36. data/lib/rack/handler/webrick.rb +61 -0
  37. data/lib/rack/head.rb +19 -0
  38. data/lib/rack/lint.rb +463 -0
  39. data/lib/rack/lobster.rb +65 -0
  40. data/lib/rack/methodoverride.rb +21 -0
  41. data/lib/rack/mime.rb +204 -0
  42. data/lib/rack/mock.rb +160 -0
  43. data/lib/rack/recursive.rb +57 -0
  44. data/lib/rack/reloader.rb +64 -0
  45. data/lib/rack/request.rb +217 -0
  46. data/lib/rack/response.rb +171 -0
  47. data/lib/rack/session/abstract/id.rb +140 -0
  48. data/lib/rack/session/cookie.rb +89 -0
  49. data/lib/rack/session/memcache.rb +97 -0
  50. data/lib/rack/session/pool.rb +73 -0
  51. data/lib/rack/showexceptions.rb +348 -0
  52. data/lib/rack/showstatus.rb +105 -0
  53. data/lib/rack/static.rb +38 -0
  54. data/lib/rack/urlmap.rb +48 -0
  55. data/lib/rack/utils.rb +318 -0
  56. data/rack.gemspec +31 -0
  57. data/test/cgi/lighttpd.conf +20 -0
  58. data/test/cgi/test +9 -0
  59. data/test/cgi/test.fcgi +8 -0
  60. data/test/cgi/test.ru +7 -0
  61. data/test/spec_rack_auth_basic.rb +69 -0
  62. data/test/spec_rack_auth_digest.rb +169 -0
  63. data/test/spec_rack_auth_openid.rb +137 -0
  64. data/test/spec_rack_builder.rb +84 -0
  65. data/test/spec_rack_camping.rb +51 -0
  66. data/test/spec_rack_cascade.rb +50 -0
  67. data/test/spec_rack_cgi.rb +89 -0
  68. data/test/spec_rack_commonlogger.rb +32 -0
  69. data/test/spec_rack_conditionalget.rb +41 -0
  70. data/test/spec_rack_deflater.rb +70 -0
  71. data/test/spec_rack_directory.rb +56 -0
  72. data/test/spec_rack_fastcgi.rb +89 -0
  73. data/test/spec_rack_file.rb +57 -0
  74. data/test/spec_rack_handler.rb +24 -0
  75. data/test/spec_rack_head.rb +30 -0
  76. data/test/spec_rack_lint.rb +371 -0
  77. data/test/spec_rack_lobster.rb +45 -0
  78. data/test/spec_rack_methodoverride.rb +31 -0
  79. data/test/spec_rack_mock.rb +152 -0
  80. data/test/spec_rack_mongrel.rb +170 -0
  81. data/test/spec_rack_recursive.rb +77 -0
  82. data/test/spec_rack_request.rb +426 -0
  83. data/test/spec_rack_response.rb +173 -0
  84. data/test/spec_rack_session_cookie.rb +78 -0
  85. data/test/spec_rack_session_memcache.rb +132 -0
  86. data/test/spec_rack_session_pool.rb +84 -0
  87. data/test/spec_rack_showexceptions.rb +21 -0
  88. data/test/spec_rack_showstatus.rb +72 -0
  89. data/test/spec_rack_static.rb +37 -0
  90. data/test/spec_rack_urlmap.rb +175 -0
  91. data/test/spec_rack_utils.rb +174 -0
  92. data/test/spec_rack_webrick.rb +123 -0
  93. data/test/testrequest.rb +45 -0
  94. metadata +177 -0
@@ -0,0 +1,89 @@
1
+ require 'test/spec'
2
+ require 'testrequest'
3
+
4
+ context "Rack::Handler::FastCGI" do
5
+ include TestRequest::Helpers
6
+
7
+ setup do
8
+ @host = '0.0.0.0'
9
+ @port = 9203
10
+ end
11
+
12
+ # Keep this first.
13
+ specify "startup" do
14
+ $pid = fork {
15
+ Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
16
+ exec "lighttpd -D -f lighttpd.conf"
17
+ }
18
+ end
19
+
20
+ specify "should respond" do
21
+ sleep 1
22
+ lambda {
23
+ GET("/test.fcgi")
24
+ }.should.not.raise
25
+ end
26
+
27
+ specify "should be a lighttpd" do
28
+ GET("/test.fcgi")
29
+ status.should.be 200
30
+ response["SERVER_SOFTWARE"].should =~ /lighttpd/
31
+ response["HTTP_VERSION"].should.equal "HTTP/1.1"
32
+ response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
33
+ response["SERVER_PORT"].should.equal @port.to_s
34
+ response["SERVER_NAME"].should =~ @host
35
+ end
36
+
37
+ specify "should have rack headers" do
38
+ GET("/test.fcgi")
39
+ response["rack.version"].should.equal [0,1]
40
+ response["rack.multithread"].should.be false
41
+ response["rack.multiprocess"].should.be true
42
+ response["rack.run_once"].should.be false
43
+ end
44
+
45
+ specify "should have CGI headers on GET" do
46
+ GET("/test.fcgi")
47
+ response["REQUEST_METHOD"].should.equal "GET"
48
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
49
+ response["REQUEST_PATH"].should.equal "/"
50
+ response["PATH_INFO"].should.be.nil
51
+ response["QUERY_STRING"].should.equal ""
52
+ response["test.postdata"].should.equal ""
53
+
54
+ GET("/test.fcgi/foo?quux=1")
55
+ response["REQUEST_METHOD"].should.equal "GET"
56
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
57
+ response["REQUEST_PATH"].should.equal "/"
58
+ response["PATH_INFO"].should.equal "/foo"
59
+ response["QUERY_STRING"].should.equal "quux=1"
60
+ end
61
+
62
+ specify "should have CGI headers on POST" do
63
+ POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
64
+ status.should.equal 200
65
+ response["REQUEST_METHOD"].should.equal "POST"
66
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
67
+ response["REQUEST_PATH"].should.equal "/"
68
+ response["QUERY_STRING"].should.equal ""
69
+ response["HTTP_X_TEST_HEADER"].should.equal "42"
70
+ response["test.postdata"].should.equal "rack-form-data=23"
71
+ end
72
+
73
+ specify "should support HTTP auth" do
74
+ GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
75
+ response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
76
+ end
77
+
78
+ specify "should set status" do
79
+ GET("/test.fcgi?secret")
80
+ status.should.equal 403
81
+ response["rack.url_scheme"].should.equal "http"
82
+ end
83
+
84
+ # Keep this last.
85
+ specify "shutdown" do
86
+ Process.kill 15, $pid
87
+ Process.wait($pid).should.equal $pid
88
+ end
89
+ end
@@ -0,0 +1,57 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/file'
4
+ require 'rack/lint'
5
+
6
+ require 'rack/mock'
7
+
8
+ context "Rack::File" do
9
+ DOCROOT = File.expand_path(File.dirname(__FILE__))
10
+
11
+ specify "serves files" do
12
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
13
+ get("/cgi/test")
14
+
15
+ res.should.be.ok
16
+ res.should =~ /ruby/
17
+ end
18
+
19
+ specify "sets Last-Modified header" do
20
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
21
+ get("/cgi/test")
22
+
23
+ path = File.join(DOCROOT, "/cgi/test")
24
+
25
+ res.should.be.ok
26
+ res["Last-Modified"].should.equal File.mtime(path).httpdate
27
+ end
28
+
29
+ specify "serves files with URL encoded filenames" do
30
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
31
+ get("/cgi/%74%65%73%74") # "/cgi/test"
32
+
33
+ res.should.be.ok
34
+ res.should =~ /ruby/
35
+ end
36
+
37
+ specify "does not allow directory traversal" do
38
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
39
+ get("/cgi/../test")
40
+
41
+ res.should.be.forbidden
42
+ end
43
+
44
+ specify "404s if it can't find the file" do
45
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
46
+ get("/cgi/blubb")
47
+
48
+ res.should.be.not_found
49
+ end
50
+
51
+ specify "detects SystemCallErrors" do
52
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
53
+ get("/cgi")
54
+
55
+ res.should.be.not_found
56
+ end
57
+ end
@@ -0,0 +1,24 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/handler'
4
+
5
+ context "Rack::Handler" do
6
+ class Rack::Handler::Lobster; end
7
+ class RockLobster; end
8
+
9
+ specify "has registered default handlers" do
10
+ Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
11
+ Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
12
+ Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
13
+ Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
14
+ end
15
+
16
+ specify "should get unregistered handler by name" do
17
+ Rack::Handler.get('lobster').should.equal Rack::Handler::Lobster
18
+ end
19
+
20
+ specify "should register custom handler" do
21
+ Rack::Handler.register('rock_lobster', 'RockLobster')
22
+ Rack::Handler.get('rock_lobster').should.equal RockLobster
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ require 'rack/head'
2
+ require 'rack/mock'
3
+
4
+ context "Rack::Head" do
5
+ def test_response(headers = {})
6
+ app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
7
+ request = Rack::MockRequest.env_for("/", headers)
8
+ response = Rack::Head.new(app).call(request)
9
+
10
+ return response
11
+ end
12
+
13
+ specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
14
+ %w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
15
+ resp = test_response("REQUEST_METHOD" => type)
16
+
17
+ resp[0].should.equal(200)
18
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
19
+ resp[2].should.equal(["foo"])
20
+ end
21
+ end
22
+
23
+ specify "removes body from HEAD requests" do
24
+ resp = test_response("REQUEST_METHOD" => "HEAD")
25
+
26
+ resp[0].should.equal(200)
27
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
28
+ resp[2].should.equal([])
29
+ end
30
+ end
@@ -0,0 +1,371 @@
1
+ require 'test/spec'
2
+ require 'stringio'
3
+
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+
7
+ context "Rack::Lint" do
8
+ def env(*args)
9
+ Rack::MockRequest.env_for("/", *args)
10
+ end
11
+
12
+ specify "passes valid request" do
13
+ lambda {
14
+ Rack::Lint.new(lambda { |env|
15
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
16
+ }).call(env({}))
17
+ }.should.not.raise
18
+ end
19
+
20
+ specify "notices fatal errors" do
21
+ lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
22
+ message.should.match(/No env given/)
23
+ end
24
+
25
+ specify "notices environment errors" do
26
+ lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
27
+ message.should.match(/not a Hash/)
28
+
29
+ lambda {
30
+ e = env
31
+ e.delete("REQUEST_METHOD")
32
+ Rack::Lint.new(nil).call(e)
33
+ }.should.raise(Rack::Lint::LintError).
34
+ message.should.match(/missing required key REQUEST_METHOD/)
35
+
36
+ lambda {
37
+ e = env
38
+ e.delete("SERVER_NAME")
39
+ Rack::Lint.new(nil).call(e)
40
+ }.should.raise(Rack::Lint::LintError).
41
+ message.should.match(/missing required key SERVER_NAME/)
42
+
43
+
44
+ lambda {
45
+ Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
46
+ }.should.raise(Rack::Lint::LintError).
47
+ message.should.match(/contains HTTP_CONTENT_TYPE/)
48
+
49
+ lambda {
50
+ Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
51
+ }.should.raise(Rack::Lint::LintError).
52
+ message.should.match(/contains HTTP_CONTENT_LENGTH/)
53
+
54
+ lambda {
55
+ Rack::Lint.new(nil).call(env("FOO" => Object.new))
56
+ }.should.raise(Rack::Lint::LintError).
57
+ message.should.match(/non-string value/)
58
+
59
+ lambda {
60
+ Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
61
+ }.should.raise(Rack::Lint::LintError).
62
+ message.should.match(/must be an Array/)
63
+
64
+ lambda {
65
+ Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
66
+ }.should.raise(Rack::Lint::LintError).
67
+ message.should.match(/url_scheme unknown/)
68
+
69
+ lambda {
70
+ Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
71
+ }.should.raise(Rack::Lint::LintError).
72
+ message.should.match(/REQUEST_METHOD/)
73
+
74
+ lambda {
75
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
76
+ }.should.raise(Rack::Lint::LintError).
77
+ message.should.match(/must start with/)
78
+
79
+ lambda {
80
+ Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
81
+ }.should.raise(Rack::Lint::LintError).
82
+ message.should.match(/must start with/)
83
+
84
+ lambda {
85
+ Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
86
+ }.should.raise(Rack::Lint::LintError).
87
+ message.should.match(/Invalid CONTENT_LENGTH/)
88
+
89
+ lambda {
90
+ e = env
91
+ e.delete("PATH_INFO")
92
+ e.delete("SCRIPT_NAME")
93
+ Rack::Lint.new(nil).call(e)
94
+ }.should.raise(Rack::Lint::LintError).
95
+ message.should.match(/One of .* must be set/)
96
+
97
+ lambda {
98
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
99
+ }.should.raise(Rack::Lint::LintError).
100
+ message.should.match(/cannot be .* make it ''/)
101
+ end
102
+
103
+ specify "notices input errors" do
104
+ lambda {
105
+ Rack::Lint.new(nil).call(env("rack.input" => ""))
106
+ }.should.raise(Rack::Lint::LintError).
107
+ message.should.match(/does not respond to #gets/)
108
+ end
109
+
110
+ specify "notices error errors" do
111
+ lambda {
112
+ Rack::Lint.new(nil).call(env("rack.errors" => ""))
113
+ }.should.raise(Rack::Lint::LintError).
114
+ message.should.match(/does not respond to #puts/)
115
+ end
116
+
117
+ specify "notices status errors" do
118
+ lambda {
119
+ Rack::Lint.new(lambda { |env|
120
+ ["cc", {}, ""]
121
+ }).call(env({}))
122
+ }.should.raise(Rack::Lint::LintError).
123
+ message.should.match(/must be >=100 seen as integer/)
124
+
125
+ lambda {
126
+ Rack::Lint.new(lambda { |env|
127
+ [42, {}, ""]
128
+ }).call(env({}))
129
+ }.should.raise(Rack::Lint::LintError).
130
+ message.should.match(/must be >=100 seen as integer/)
131
+ end
132
+
133
+ specify "notices header errors" do
134
+ lambda {
135
+ Rack::Lint.new(lambda { |env|
136
+ [200, Object.new, ""]
137
+ }).call(env({}))
138
+ }.should.raise(Rack::Lint::LintError).
139
+ message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
140
+
141
+ lambda {
142
+ Rack::Lint.new(lambda { |env|
143
+ [200, {true=>false}, ""]
144
+ }).call(env({}))
145
+ }.should.raise(Rack::Lint::LintError).
146
+ message.should.equal("header key must be a string, was TrueClass")
147
+
148
+ lambda {
149
+ Rack::Lint.new(lambda { |env|
150
+ [200, {"Status" => "404"}, ""]
151
+ }).call(env({}))
152
+ }.should.raise(Rack::Lint::LintError).
153
+ message.should.match(/must not contain Status/)
154
+
155
+ lambda {
156
+ Rack::Lint.new(lambda { |env|
157
+ [200, {"Content-Type:" => "text/plain"}, ""]
158
+ }).call(env({}))
159
+ }.should.raise(Rack::Lint::LintError).
160
+ message.should.match(/must not contain :/)
161
+
162
+ lambda {
163
+ Rack::Lint.new(lambda { |env|
164
+ [200, {"Content-" => "text/plain"}, ""]
165
+ }).call(env({}))
166
+ }.should.raise(Rack::Lint::LintError).
167
+ message.should.match(/must not end/)
168
+
169
+ lambda {
170
+ Rack::Lint.new(lambda { |env|
171
+ [200, {"..%%quark%%.." => "text/plain"}, ""]
172
+ }).call(env({}))
173
+ }.should.raise(Rack::Lint::LintError).
174
+ message.should.equal("invalid header name: ..%%quark%%..")
175
+
176
+ lambda {
177
+ Rack::Lint.new(lambda { |env|
178
+ [200, {"Foo" => Object.new}, ""]
179
+ }).call(env({}))
180
+ }.should.raise(Rack::Lint::LintError).
181
+ message.should.equal("header values must respond to #each, but the value of 'Foo' doesn't (is Object)")
182
+
183
+ lambda {
184
+ Rack::Lint.new(lambda { |env|
185
+ [200, {"Foo" => [1,2,3]}, ""]
186
+ }).call(env({}))
187
+ }.should.raise(Rack::Lint::LintError).
188
+ message.should.equal("header values must consist of Strings, but 'Foo' also contains a Fixnum")
189
+
190
+
191
+ lambda {
192
+ Rack::Lint.new(lambda { |env|
193
+ [200, {"Foo-Bar" => "text\000plain"}, ""]
194
+ }).call(env({}))
195
+ }.should.raise(Rack::Lint::LintError).
196
+ message.should.match(/invalid header/)
197
+ end
198
+
199
+ specify "notices content-type errors" do
200
+ lambda {
201
+ Rack::Lint.new(lambda { |env|
202
+ [200, {"Content-length" => "0"}, ""]
203
+ }).call(env({}))
204
+ }.should.raise(Rack::Lint::LintError).
205
+ message.should.match(/No Content-Type/)
206
+
207
+ [100, 101, 204, 304].each do |status|
208
+ lambda {
209
+ Rack::Lint.new(lambda { |env|
210
+ [status, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
211
+ }).call(env({}))
212
+ }.should.raise(Rack::Lint::LintError).
213
+ message.should.match(/Content-Type header found/)
214
+ end
215
+ end
216
+
217
+ specify "notices content-length errors" do
218
+ lambda {
219
+ Rack::Lint.new(lambda { |env|
220
+ [200, {"Content-type" => "text/plain"}, ""]
221
+ }).call(env({}))
222
+ }.should.raise(Rack::Lint::LintError).
223
+ message.should.match(/No Content-Length/)
224
+
225
+ [100, 101, 204, 304].each do |status|
226
+ lambda {
227
+ Rack::Lint.new(lambda { |env|
228
+ [status, {"Content-length" => "0"}, ""]
229
+ }).call(env({}))
230
+ }.should.raise(Rack::Lint::LintError).
231
+ message.should.match(/Content-Length header found/)
232
+ end
233
+
234
+ lambda {
235
+ Rack::Lint.new(lambda { |env|
236
+ [200, {"Content-type" => "text/plain", "Content-Length" => "0", "Transfer-Encoding" => "chunked"}, ""]
237
+ }).call(env({}))
238
+ }.should.raise(Rack::Lint::LintError).
239
+ message.should.match(/Content-Length header should not be used/)
240
+
241
+ lambda {
242
+ Rack::Lint.new(lambda { |env|
243
+ [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, ""]
244
+ }).call(env({}))
245
+ }.should.raise(Rack::Lint::LintError).
246
+ message.should.match(/Content-Length header was 1, but should be 0/)
247
+ end
248
+
249
+ specify "notices body errors" do
250
+ lambda {
251
+ status, header, body = Rack::Lint.new(lambda { |env|
252
+ [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
253
+ }).call(env({}))
254
+ body.each { |part| }
255
+ }.should.raise(Rack::Lint::LintError).
256
+ message.should.match(/yielded non-string/)
257
+ end
258
+
259
+ specify "notices input handling errors" do
260
+ lambda {
261
+ Rack::Lint.new(lambda { |env|
262
+ env["rack.input"].gets("\r\n")
263
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
264
+ }).call(env({}))
265
+ }.should.raise(Rack::Lint::LintError).
266
+ message.should.match(/gets called with arguments/)
267
+
268
+ lambda {
269
+ Rack::Lint.new(lambda { |env|
270
+ env["rack.input"].read("foo")
271
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
272
+ }).call(env({}))
273
+ }.should.raise(Rack::Lint::LintError).
274
+ message.should.match(/read called with non-integer argument/)
275
+
276
+ weirdio = Object.new
277
+ class << weirdio
278
+ def gets
279
+ 42
280
+ end
281
+
282
+ def read
283
+ 23
284
+ end
285
+
286
+ def each
287
+ yield 23
288
+ yield 42
289
+ end
290
+ end
291
+
292
+ lambda {
293
+ Rack::Lint.new(lambda { |env|
294
+ env["rack.input"].gets
295
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
296
+ }).call(env("rack.input" => weirdio))
297
+ }.should.raise(Rack::Lint::LintError).
298
+ message.should.match(/gets didn't return a String/)
299
+
300
+ lambda {
301
+ Rack::Lint.new(lambda { |env|
302
+ env["rack.input"].each { |x| }
303
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
304
+ }).call(env("rack.input" => weirdio))
305
+ }.should.raise(Rack::Lint::LintError).
306
+ message.should.match(/each didn't yield a String/)
307
+
308
+ lambda {
309
+ Rack::Lint.new(lambda { |env|
310
+ env["rack.input"].read
311
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
312
+ }).call(env("rack.input" => weirdio))
313
+ }.should.raise(Rack::Lint::LintError).
314
+ message.should.match(/read didn't return a String/)
315
+
316
+
317
+ lambda {
318
+ Rack::Lint.new(lambda { |env|
319
+ env["rack.input"].close
320
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
321
+ }).call(env({}))
322
+ }.should.raise(Rack::Lint::LintError).
323
+ message.should.match(/close must not be called/)
324
+ end
325
+
326
+ specify "notices error handling errors" do
327
+ lambda {
328
+ Rack::Lint.new(lambda { |env|
329
+ env["rack.errors"].write(42)
330
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
331
+ }).call(env({}))
332
+ }.should.raise(Rack::Lint::LintError).
333
+ message.should.match(/write not called with a String/)
334
+
335
+ lambda {
336
+ Rack::Lint.new(lambda { |env|
337
+ env["rack.errors"].close
338
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
339
+ }).call(env({}))
340
+ }.should.raise(Rack::Lint::LintError).
341
+ message.should.match(/close must not be called/)
342
+ end
343
+
344
+ specify "notices HEAD errors" do
345
+ lambda {
346
+ Rack::Lint.new(lambda { |env|
347
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
348
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
349
+ }.should.not.raise
350
+
351
+ lambda {
352
+ Rack::Lint.new(lambda { |env|
353
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
354
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
355
+ }.should.raise(Rack::Lint::LintError).
356
+ message.should.match(/body was given for HEAD/)
357
+ end
358
+ end
359
+
360
+ context "Rack::Lint::InputWrapper" do
361
+ specify "delegates :size to underlying IO object" do
362
+ class IOMock
363
+ def size
364
+ 101
365
+ end
366
+ end
367
+
368
+ wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
369
+ wrapper.size.should == 101
370
+ end
371
+ end