kjvarga-rack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES.rdoc +18 -0
  3. data/Manifest +117 -0
  4. data/README.rdoc +357 -0
  5. data/Rakefile +164 -0
  6. data/bin/rackup +176 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/lib/rack.rb +90 -0
  9. data/lib/rack/adapter/camping.rb +22 -0
  10. data/lib/rack/auth/abstract/handler.rb +37 -0
  11. data/lib/rack/auth/abstract/request.rb +37 -0
  12. data/lib/rack/auth/basic.rb +58 -0
  13. data/lib/rack/auth/digest/md5.rb +124 -0
  14. data/lib/rack/auth/digest/nonce.rb +51 -0
  15. data/lib/rack/auth/digest/params.rb +55 -0
  16. data/lib/rack/auth/digest/request.rb +40 -0
  17. data/lib/rack/auth/openid.rb +487 -0
  18. data/lib/rack/builder.rb +63 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +52 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/content_length.rb +29 -0
  24. data/lib/rack/content_type.rb +23 -0
  25. data/lib/rack/deflater.rb +96 -0
  26. data/lib/rack/directory.rb +153 -0
  27. data/lib/rack/file.rb +88 -0
  28. data/lib/rack/handler.rb +69 -0
  29. data/lib/rack/handler/cgi.rb +61 -0
  30. data/lib/rack/handler/evented_mongrel.rb +8 -0
  31. data/lib/rack/handler/fastcgi.rb +88 -0
  32. data/lib/rack/handler/lsws.rb +60 -0
  33. data/lib/rack/handler/mongrel.rb +87 -0
  34. data/lib/rack/handler/scgi.rb +62 -0
  35. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  36. data/lib/rack/handler/thin.rb +18 -0
  37. data/lib/rack/handler/webrick.rb +71 -0
  38. data/lib/rack/head.rb +19 -0
  39. data/lib/rack/lint.rb +546 -0
  40. data/lib/rack/lobster.rb +65 -0
  41. data/lib/rack/lock.rb +16 -0
  42. data/lib/rack/methodoverride.rb +27 -0
  43. data/lib/rack/mime.rb +205 -0
  44. data/lib/rack/mock.rb +187 -0
  45. data/lib/rack/recursive.rb +57 -0
  46. data/lib/rack/reloader.rb +109 -0
  47. data/lib/rack/request.rb +248 -0
  48. data/lib/rack/response.rb +183 -0
  49. data/lib/rack/rewindable_input.rb +100 -0
  50. data/lib/rack/session/abstract/id.rb +142 -0
  51. data/lib/rack/session/cookie.rb +91 -0
  52. data/lib/rack/session/memcache.rb +109 -0
  53. data/lib/rack/session/pool.rb +100 -0
  54. data/lib/rack/showexceptions.rb +349 -0
  55. data/lib/rack/showstatus.rb +106 -0
  56. data/lib/rack/static.rb +38 -0
  57. data/lib/rack/urlmap.rb +55 -0
  58. data/lib/rack/utils.rb +528 -0
  59. data/rack.gemspec +140 -0
  60. data/test/cgi/lighttpd.conf +20 -0
  61. data/test/cgi/test +9 -0
  62. data/test/cgi/test.fcgi +8 -0
  63. data/test/cgi/test.ru +7 -0
  64. data/test/multipart/binary +0 -0
  65. data/test/multipart/empty +10 -0
  66. data/test/multipart/file1.txt +1 -0
  67. data/test/multipart/ie +6 -0
  68. data/test/multipart/nested +10 -0
  69. data/test/multipart/none +9 -0
  70. data/test/multipart/text +10 -0
  71. data/test/spec_rack_auth_basic.rb +73 -0
  72. data/test/spec_rack_auth_digest.rb +226 -0
  73. data/test/spec_rack_auth_openid.rb +84 -0
  74. data/test/spec_rack_builder.rb +84 -0
  75. data/test/spec_rack_camping.rb +51 -0
  76. data/test/spec_rack_cascade.rb +48 -0
  77. data/test/spec_rack_cgi.rb +89 -0
  78. data/test/spec_rack_chunked.rb +62 -0
  79. data/test/spec_rack_commonlogger.rb +61 -0
  80. data/test/spec_rack_conditionalget.rb +41 -0
  81. data/test/spec_rack_content_length.rb +43 -0
  82. data/test/spec_rack_content_type.rb +30 -0
  83. data/test/spec_rack_deflater.rb +127 -0
  84. data/test/spec_rack_directory.rb +61 -0
  85. data/test/spec_rack_fastcgi.rb +89 -0
  86. data/test/spec_rack_file.rb +75 -0
  87. data/test/spec_rack_handler.rb +43 -0
  88. data/test/spec_rack_head.rb +30 -0
  89. data/test/spec_rack_lint.rb +521 -0
  90. data/test/spec_rack_lobster.rb +45 -0
  91. data/test/spec_rack_lock.rb +38 -0
  92. data/test/spec_rack_methodoverride.rb +60 -0
  93. data/test/spec_rack_mock.rb +243 -0
  94. data/test/spec_rack_mongrel.rb +189 -0
  95. data/test/spec_rack_recursive.rb +77 -0
  96. data/test/spec_rack_request.rb +504 -0
  97. data/test/spec_rack_response.rb +218 -0
  98. data/test/spec_rack_rewindable_input.rb +118 -0
  99. data/test/spec_rack_session_cookie.rb +82 -0
  100. data/test/spec_rack_session_memcache.rb +250 -0
  101. data/test/spec_rack_session_pool.rb +172 -0
  102. data/test/spec_rack_showexceptions.rb +21 -0
  103. data/test/spec_rack_showstatus.rb +72 -0
  104. data/test/spec_rack_static.rb +37 -0
  105. data/test/spec_rack_thin.rb +91 -0
  106. data/test/spec_rack_urlmap.rb +185 -0
  107. data/test/spec_rack_utils.rb +467 -0
  108. data/test/spec_rack_webrick.rb +130 -0
  109. data/test/testrequest.rb +57 -0
  110. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  111. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  112. metadata +175 -0
@@ -0,0 +1,43 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/handler'
4
+
5
+ class Rack::Handler::Lobster; end
6
+ class RockLobster; end
7
+
8
+ context "Rack::Handler" do
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 "handler that doesn't exist should raise a NameError" do
17
+ lambda {
18
+ Rack::Handler.get('boom')
19
+ }.should.raise(NameError)
20
+ end
21
+
22
+ specify "should get unregistered, but already required, handler by name" do
23
+ Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
24
+ end
25
+
26
+ specify "should register custom handler" do
27
+ Rack::Handler.register('rock_lobster', 'RockLobster')
28
+ Rack::Handler.get('rock_lobster').should.equal RockLobster
29
+ end
30
+
31
+ specify "should not need registration for properly coded handlers even if not already required" do
32
+ begin
33
+ $:.push "test/unregistered_handler"
34
+ Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
35
+ lambda {
36
+ Rack::Handler.get('UnRegistered')
37
+ }.should.raise(NameError)
38
+ Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
39
+ ensure
40
+ $:.delete "test/unregistered_handler"
41
+ end
42
+ end
43
+ 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,521 @@
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("rack.session" => []))
71
+ }.should.raise(Rack::Lint::LintError).
72
+ message.should.equal("session [] must respond to store and []=")
73
+
74
+ lambda {
75
+ Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
76
+ }.should.raise(Rack::Lint::LintError).
77
+ message.should.match(/REQUEST_METHOD/)
78
+
79
+ lambda {
80
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
81
+ }.should.raise(Rack::Lint::LintError).
82
+ message.should.match(/must start with/)
83
+
84
+ lambda {
85
+ Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
86
+ }.should.raise(Rack::Lint::LintError).
87
+ message.should.match(/must start with/)
88
+
89
+ lambda {
90
+ Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
91
+ }.should.raise(Rack::Lint::LintError).
92
+ message.should.match(/Invalid CONTENT_LENGTH/)
93
+
94
+ lambda {
95
+ e = env
96
+ e.delete("PATH_INFO")
97
+ e.delete("SCRIPT_NAME")
98
+ Rack::Lint.new(nil).call(e)
99
+ }.should.raise(Rack::Lint::LintError).
100
+ message.should.match(/One of .* must be set/)
101
+
102
+ lambda {
103
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
104
+ }.should.raise(Rack::Lint::LintError).
105
+ message.should.match(/cannot be .* make it ''/)
106
+ end
107
+
108
+ specify "notices input errors" do
109
+ lambda {
110
+ Rack::Lint.new(nil).call(env("rack.input" => ""))
111
+ }.should.raise(Rack::Lint::LintError).
112
+ message.should.match(/does not respond to #gets/)
113
+
114
+ lambda {
115
+ input = Object.new
116
+ def input.binmode?
117
+ false
118
+ end
119
+ Rack::Lint.new(nil).call(env("rack.input" => input))
120
+ }.should.raise(Rack::Lint::LintError).
121
+ message.should.match(/is not opened in binary mode/)
122
+
123
+ lambda {
124
+ input = Object.new
125
+ def input.external_encoding
126
+ result = Object.new
127
+ def result.name
128
+ "US-ASCII"
129
+ end
130
+ result
131
+ end
132
+ Rack::Lint.new(nil).call(env("rack.input" => input))
133
+ }.should.raise(Rack::Lint::LintError).
134
+ message.should.match(/does not have ASCII-8BIT as its external encoding/);puts RUBY_VERSION
135
+ end
136
+
137
+ specify "notices error errors" do
138
+ lambda {
139
+ Rack::Lint.new(nil).call(env("rack.errors" => ""))
140
+ }.should.raise(Rack::Lint::LintError).
141
+ message.should.match(/does not respond to #puts/)
142
+ end
143
+
144
+ specify "notices status errors" do
145
+ lambda {
146
+ Rack::Lint.new(lambda { |env|
147
+ ["cc", {}, ""]
148
+ }).call(env({}))
149
+ }.should.raise(Rack::Lint::LintError).
150
+ message.should.match(/must be >=100 seen as integer/)
151
+
152
+ lambda {
153
+ Rack::Lint.new(lambda { |env|
154
+ [42, {}, ""]
155
+ }).call(env({}))
156
+ }.should.raise(Rack::Lint::LintError).
157
+ message.should.match(/must be >=100 seen as integer/)
158
+ end
159
+
160
+ specify "notices header errors" do
161
+ lambda {
162
+ Rack::Lint.new(lambda { |env|
163
+ [200, Object.new, []]
164
+ }).call(env({}))
165
+ }.should.raise(Rack::Lint::LintError).
166
+ message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
167
+
168
+ lambda {
169
+ Rack::Lint.new(lambda { |env|
170
+ [200, {true=>false}, []]
171
+ }).call(env({}))
172
+ }.should.raise(Rack::Lint::LintError).
173
+ message.should.equal("header key must be a string, was TrueClass")
174
+
175
+ lambda {
176
+ Rack::Lint.new(lambda { |env|
177
+ [200, {"Status" => "404"}, []]
178
+ }).call(env({}))
179
+ }.should.raise(Rack::Lint::LintError).
180
+ message.should.match(/must not contain Status/)
181
+
182
+ lambda {
183
+ Rack::Lint.new(lambda { |env|
184
+ [200, {"Content-Type:" => "text/plain"}, []]
185
+ }).call(env({}))
186
+ }.should.raise(Rack::Lint::LintError).
187
+ message.should.match(/must not contain :/)
188
+
189
+ lambda {
190
+ Rack::Lint.new(lambda { |env|
191
+ [200, {"Content-" => "text/plain"}, []]
192
+ }).call(env({}))
193
+ }.should.raise(Rack::Lint::LintError).
194
+ message.should.match(/must not end/)
195
+
196
+ lambda {
197
+ Rack::Lint.new(lambda { |env|
198
+ [200, {"..%%quark%%.." => "text/plain"}, []]
199
+ }).call(env({}))
200
+ }.should.raise(Rack::Lint::LintError).
201
+ message.should.equal("invalid header name: ..%%quark%%..")
202
+
203
+ lambda {
204
+ Rack::Lint.new(lambda { |env|
205
+ [200, {"Foo" => Object.new}, []]
206
+ }).call(env({}))
207
+ }.should.raise(Rack::Lint::LintError).
208
+ message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
209
+
210
+ lambda {
211
+ Rack::Lint.new(lambda { |env|
212
+ [200, {"Foo" => [1, 2, 3]}, []]
213
+ }).call(env({}))
214
+ }.should.raise(Rack::Lint::LintError).
215
+ message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
216
+
217
+
218
+ lambda {
219
+ Rack::Lint.new(lambda { |env|
220
+ [200, {"Foo-Bar" => "text\000plain"}, []]
221
+ }).call(env({}))
222
+ }.should.raise(Rack::Lint::LintError).
223
+ message.should.match(/invalid header/)
224
+
225
+ # line ends (010) should be allowed in header values.
226
+ lambda {
227
+ Rack::Lint.new(lambda { |env|
228
+ [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
229
+ }).call(env({}))
230
+ }.should.not.raise(Rack::Lint::LintError)
231
+ end
232
+
233
+ specify "notices content-type errors" do
234
+ lambda {
235
+ Rack::Lint.new(lambda { |env|
236
+ [200, {"Content-length" => "0"}, []]
237
+ }).call(env({}))
238
+ }.should.raise(Rack::Lint::LintError).
239
+ message.should.match(/No Content-Type/)
240
+
241
+ [100, 101, 204, 304].each do |status|
242
+ lambda {
243
+ Rack::Lint.new(lambda { |env|
244
+ [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
245
+ }).call(env({}))
246
+ }.should.raise(Rack::Lint::LintError).
247
+ message.should.match(/Content-Type header found/)
248
+ end
249
+ end
250
+
251
+ specify "notices content-length errors" do
252
+ [100, 101, 204, 304].each do |status|
253
+ lambda {
254
+ Rack::Lint.new(lambda { |env|
255
+ [status, {"Content-length" => "0"}, []]
256
+ }).call(env({}))
257
+ }.should.raise(Rack::Lint::LintError).
258
+ message.should.match(/Content-Length header found/)
259
+ end
260
+
261
+ lambda {
262
+ Rack::Lint.new(lambda { |env|
263
+ [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
264
+ }).call(env({}))
265
+ }.should.raise(Rack::Lint::LintError).
266
+ message.should.match(/Content-Length header was 1, but should be 0/)
267
+ end
268
+
269
+ specify "notices body errors" do
270
+ lambda {
271
+ status, header, body = Rack::Lint.new(lambda { |env|
272
+ [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
273
+ }).call(env({}))
274
+ body.each { |part| }
275
+ }.should.raise(Rack::Lint::LintError).
276
+ message.should.match(/yielded non-string/)
277
+ end
278
+
279
+ specify "notices input handling errors" do
280
+ lambda {
281
+ Rack::Lint.new(lambda { |env|
282
+ env["rack.input"].gets("\r\n")
283
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
284
+ }).call(env({}))
285
+ }.should.raise(Rack::Lint::LintError).
286
+ message.should.match(/gets called with arguments/)
287
+
288
+ lambda {
289
+ Rack::Lint.new(lambda { |env|
290
+ env["rack.input"].read(1, 2, 3)
291
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
292
+ }).call(env({}))
293
+ }.should.raise(Rack::Lint::LintError).
294
+ message.should.match(/read called with too many arguments/)
295
+
296
+ lambda {
297
+ Rack::Lint.new(lambda { |env|
298
+ env["rack.input"].read("foo")
299
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
300
+ }).call(env({}))
301
+ }.should.raise(Rack::Lint::LintError).
302
+ message.should.match(/read called with non-integer and non-nil length/)
303
+
304
+ lambda {
305
+ Rack::Lint.new(lambda { |env|
306
+ env["rack.input"].read(-1)
307
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
308
+ }).call(env({}))
309
+ }.should.raise(Rack::Lint::LintError).
310
+ message.should.match(/read called with a negative length/)
311
+
312
+ lambda {
313
+ Rack::Lint.new(lambda { |env|
314
+ env["rack.input"].read(nil, nil)
315
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
316
+ }).call(env({}))
317
+ }.should.raise(Rack::Lint::LintError).
318
+ message.should.match(/read called with non-String buffer/)
319
+
320
+ lambda {
321
+ Rack::Lint.new(lambda { |env|
322
+ env["rack.input"].read(nil, 1)
323
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
324
+ }).call(env({}))
325
+ }.should.raise(Rack::Lint::LintError).
326
+ message.should.match(/read called with non-String buffer/)
327
+
328
+ lambda {
329
+ Rack::Lint.new(lambda { |env|
330
+ env["rack.input"].rewind(0)
331
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
332
+ }).call(env({}))
333
+ }.should.raise(Rack::Lint::LintError).
334
+ message.should.match(/rewind called with arguments/)
335
+
336
+ weirdio = Object.new
337
+ class << weirdio
338
+ def gets
339
+ 42
340
+ end
341
+
342
+ def read
343
+ 23
344
+ end
345
+
346
+ def each
347
+ yield 23
348
+ yield 42
349
+ end
350
+
351
+ def rewind
352
+ raise Errno::ESPIPE, "Errno::ESPIPE"
353
+ end
354
+ end
355
+
356
+ eof_weirdio = Object.new
357
+ class << eof_weirdio
358
+ def gets
359
+ nil
360
+ end
361
+
362
+ def read(*args)
363
+ nil
364
+ end
365
+
366
+ def each
367
+ end
368
+
369
+ def rewind
370
+ end
371
+ end
372
+
373
+ lambda {
374
+ Rack::Lint.new(lambda { |env|
375
+ env["rack.input"].gets
376
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
377
+ }).call(env("rack.input" => weirdio))
378
+ }.should.raise(Rack::Lint::LintError).
379
+ message.should.match(/gets didn't return a String/)
380
+
381
+ lambda {
382
+ Rack::Lint.new(lambda { |env|
383
+ env["rack.input"].each { |x| }
384
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
385
+ }).call(env("rack.input" => weirdio))
386
+ }.should.raise(Rack::Lint::LintError).
387
+ message.should.match(/each didn't yield a String/)
388
+
389
+ lambda {
390
+ Rack::Lint.new(lambda { |env|
391
+ env["rack.input"].read
392
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
393
+ }).call(env("rack.input" => weirdio))
394
+ }.should.raise(Rack::Lint::LintError).
395
+ message.should.match(/read didn't return nil or a String/)
396
+
397
+ lambda {
398
+ Rack::Lint.new(lambda { |env|
399
+ env["rack.input"].read
400
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
401
+ }).call(env("rack.input" => eof_weirdio))
402
+ }.should.raise(Rack::Lint::LintError).
403
+ message.should.match(/read\(nil\) returned nil on EOF/)
404
+
405
+ lambda {
406
+ Rack::Lint.new(lambda { |env|
407
+ env["rack.input"].rewind
408
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
409
+ }).call(env("rack.input" => weirdio))
410
+ }.should.raise(Rack::Lint::LintError).
411
+ message.should.match(/rewind raised Errno::ESPIPE/)
412
+
413
+
414
+ lambda {
415
+ Rack::Lint.new(lambda { |env|
416
+ env["rack.input"].close
417
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
418
+ }).call(env({}))
419
+ }.should.raise(Rack::Lint::LintError).
420
+ message.should.match(/close must not be called/)
421
+ end
422
+
423
+ specify "notices error handling errors" do
424
+ lambda {
425
+ Rack::Lint.new(lambda { |env|
426
+ env["rack.errors"].write(42)
427
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
428
+ }).call(env({}))
429
+ }.should.raise(Rack::Lint::LintError).
430
+ message.should.match(/write not called with a String/)
431
+
432
+ lambda {
433
+ Rack::Lint.new(lambda { |env|
434
+ env["rack.errors"].close
435
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
436
+ }).call(env({}))
437
+ }.should.raise(Rack::Lint::LintError).
438
+ message.should.match(/close must not be called/)
439
+ end
440
+
441
+ specify "notices HEAD errors" do
442
+ lambda {
443
+ Rack::Lint.new(lambda { |env|
444
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
445
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
446
+ }.should.not.raise
447
+
448
+ lambda {
449
+ Rack::Lint.new(lambda { |env|
450
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
451
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
452
+ }.should.raise(Rack::Lint::LintError).
453
+ message.should.match(/body was given for HEAD/)
454
+ end
455
+
456
+ specify "passes valid read calls" do
457
+ lambda {
458
+ Rack::Lint.new(lambda { |env|
459
+ env["rack.input"].read
460
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
461
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
462
+ }.should.not.raise(Rack::Lint::LintError)
463
+
464
+ lambda {
465
+ Rack::Lint.new(lambda { |env|
466
+ env["rack.input"].read(0)
467
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
468
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
469
+ }.should.not.raise(Rack::Lint::LintError)
470
+
471
+ lambda {
472
+ Rack::Lint.new(lambda { |env|
473
+ env["rack.input"].read(1)
474
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
475
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
476
+ }.should.not.raise(Rack::Lint::LintError)
477
+
478
+ lambda {
479
+ Rack::Lint.new(lambda { |env|
480
+ env["rack.input"].read(nil)
481
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
482
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
483
+ }.should.not.raise(Rack::Lint::LintError)
484
+
485
+ lambda {
486
+ Rack::Lint.new(lambda { |env|
487
+ env["rack.input"].read(nil, '')
488
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
489
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
490
+ }.should.not.raise(Rack::Lint::LintError)
491
+
492
+ lambda {
493
+ Rack::Lint.new(lambda { |env|
494
+ env["rack.input"].read(1, '')
495
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
496
+ }).call(env({"rack.input" => StringIO.new("hello world")}))
497
+ }.should.not.raise(Rack::Lint::LintError)
498
+ end
499
+ end
500
+
501
+ context "Rack::Lint::InputWrapper" do
502
+ specify "delegates :size to underlying IO object" do
503
+ class IOMock
504
+ def size
505
+ 101
506
+ end
507
+ end
508
+
509
+ wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
510
+ wrapper.size.should == 101
511
+ end
512
+
513
+ specify "delegates :rewind to underlying IO object" do
514
+ io = StringIO.new("123")
515
+ wrapper = Rack::Lint::InputWrapper.new(io)
516
+ wrapper.read.should.equal "123"
517
+ wrapper.read.should.equal ""
518
+ wrapper.rewind
519
+ wrapper.read.should.equal "123"
520
+ end
521
+ end