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,467 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/utils'
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+
7
+ context "Rack::Utils" do
8
+ specify "should escape correctly" do
9
+ Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
10
+ Rack::Utils.escape("a space").should.equal "a+space"
11
+ Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
12
+ should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
13
+ end
14
+
15
+ specify "should unescape correctly" do
16
+ Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
17
+ Rack::Utils.unescape("a+space").should.equal "a space"
18
+ Rack::Utils.unescape("a%20space").should.equal "a space"
19
+ Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
20
+ should.equal "q1!2\"'w$5&7/z8)?\\"
21
+ end
22
+
23
+ specify "should parse query strings correctly" do
24
+ Rack::Utils.parse_query("foo=bar").should.equal "foo" => "bar"
25
+ Rack::Utils.parse_query("foo=bar&foo=quux").
26
+ should.equal "foo" => ["bar", "quux"]
27
+ Rack::Utils.parse_query("foo=1&bar=2").
28
+ should.equal "foo" => "1", "bar" => "2"
29
+ Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
30
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
31
+ Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
32
+ end
33
+
34
+ specify "should parse nested query strings correctly" do
35
+ Rack::Utils.parse_nested_query("foo").
36
+ should.equal "foo" => nil
37
+ Rack::Utils.parse_nested_query("foo=").
38
+ should.equal "foo" => ""
39
+ Rack::Utils.parse_nested_query("foo=bar").
40
+ should.equal "foo" => "bar"
41
+
42
+ Rack::Utils.parse_nested_query("foo=bar&foo=quux").
43
+ should.equal "foo" => "quux"
44
+ Rack::Utils.parse_nested_query("foo&foo=").
45
+ should.equal "foo" => ""
46
+ Rack::Utils.parse_nested_query("foo=1&bar=2").
47
+ should.equal "foo" => "1", "bar" => "2"
48
+ Rack::Utils.parse_nested_query("&foo=1&&bar=2").
49
+ should.equal "foo" => "1", "bar" => "2"
50
+ Rack::Utils.parse_nested_query("foo&bar=").
51
+ should.equal "foo" => nil, "bar" => ""
52
+ Rack::Utils.parse_nested_query("foo=bar&baz=").
53
+ should.equal "foo" => "bar", "baz" => ""
54
+ Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
55
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
56
+
57
+ Rack::Utils.parse_nested_query("foo[]").
58
+ should.equal "foo" => [nil]
59
+ Rack::Utils.parse_nested_query("foo[]=").
60
+ should.equal "foo" => [""]
61
+ Rack::Utils.parse_nested_query("foo[]=bar").
62
+ should.equal "foo" => ["bar"]
63
+
64
+ Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
65
+ should.equal "foo" => ["1", "2"]
66
+ Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
67
+ should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
68
+ Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
69
+ should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
70
+
71
+ Rack::Utils.parse_nested_query("x[y][z]=1").
72
+ should.equal "x" => {"y" => {"z" => "1"}}
73
+ Rack::Utils.parse_nested_query("x[y][z][]=1").
74
+ should.equal "x" => {"y" => {"z" => ["1"]}}
75
+ Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
76
+ should.equal "x" => {"y" => {"z" => "2"}}
77
+ Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
78
+ should.equal "x" => {"y" => {"z" => ["1", "2"]}}
79
+
80
+ Rack::Utils.parse_nested_query("x[y][][z]=1").
81
+ should.equal "x" => {"y" => [{"z" => "1"}]}
82
+ Rack::Utils.parse_nested_query("x[y][][z][]=1").
83
+ should.equal "x" => {"y" => [{"z" => ["1"]}]}
84
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
85
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
86
+
87
+ Rack::Utils.parse_nested_query("x[y][][v][w]=1").
88
+ should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
89
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
90
+ should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
91
+
92
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
93
+ should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
94
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
95
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
96
+
97
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
98
+ should.raise(TypeError).
99
+ message.should.equal "expected Hash (got String) for param `y'"
100
+
101
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
102
+ should.raise(TypeError).
103
+ message.should.equal "expected Array (got Hash) for param `x'"
104
+
105
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
106
+ should.raise(TypeError).
107
+ message.should.equal "expected Array (got String) for param `y'"
108
+ end
109
+
110
+ specify "should build query strings correctly" do
111
+ Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
112
+ Rack::Utils.build_query("foo" => ["bar", "quux"]).
113
+ should.equal "foo=bar&foo=quux"
114
+ Rack::Utils.build_query("foo" => "1", "bar" => "2").
115
+ should.equal "foo=1&bar=2"
116
+ Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
117
+ should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
118
+ end
119
+
120
+ specify "should build nested query strings correctly" do
121
+ Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
122
+ Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
123
+ Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
124
+
125
+ Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
126
+ should.equal "foo=1&bar=2"
127
+ Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
128
+ should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
129
+
130
+ Rack::Utils.build_nested_query("foo" => [nil]).
131
+ should.equal "foo[]"
132
+ Rack::Utils.build_nested_query("foo" => [""]).
133
+ should.equal "foo[]="
134
+ Rack::Utils.build_nested_query("foo" => ["bar"]).
135
+ should.equal "foo[]=bar"
136
+
137
+ # The ordering of the output query string is unpredictable with 1.8's
138
+ # unordered hash. Test that build_nested_query performs the inverse
139
+ # function of parse_nested_query.
140
+ [{"foo" => nil, "bar" => ""},
141
+ {"foo" => "bar", "baz" => ""},
142
+ {"foo" => ["1", "2"]},
143
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
144
+ {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
145
+ {"foo" => ["1", "2"]},
146
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
147
+ {"x" => {"y" => {"z" => "1"}}},
148
+ {"x" => {"y" => {"z" => ["1"]}}},
149
+ {"x" => {"y" => {"z" => ["1", "2"]}}},
150
+ {"x" => {"y" => [{"z" => "1"}]}},
151
+ {"x" => {"y" => [{"z" => ["1"]}]}},
152
+ {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
153
+ {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
154
+ {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
155
+ {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
156
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
157
+ ].each { |params|
158
+ qs = Rack::Utils.build_nested_query(params)
159
+ Rack::Utils.parse_nested_query(qs).should.equal params
160
+ }
161
+
162
+ lambda { Rack::Utils.build_nested_query("foo=bar") }.
163
+ should.raise(ArgumentError).
164
+ message.should.equal "value must be a Hash"
165
+ end
166
+
167
+ specify "should figure out which encodings are acceptable" do
168
+ helper = lambda do |a, b|
169
+ request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
170
+ Rack::Utils.select_best_encoding(a, b)
171
+ end
172
+
173
+ helper.call(%w(), [["x", 1]]).should.equal(nil)
174
+ helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
175
+ helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
176
+
177
+ helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
178
+
179
+ helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
180
+ helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
181
+
182
+ helper.call(%w(foo bar identity), []).should.equal("identity")
183
+ helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
184
+ helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
185
+
186
+ helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
187
+ helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
188
+ end
189
+
190
+ specify "should return the bytesize of String" do
191
+ Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
192
+ end
193
+ end
194
+
195
+ context "Rack::Utils::HeaderHash" do
196
+ specify "should retain header case" do
197
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
198
+ h['ETag'] = 'Boo!'
199
+ h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
200
+ end
201
+
202
+ specify "should check existence of keys case insensitively" do
203
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
204
+ h.should.include 'content-md5'
205
+ h.should.not.include 'ETag'
206
+ end
207
+
208
+ specify "should merge case-insensitively" do
209
+ h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
210
+ merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
211
+ merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
212
+ end
213
+
214
+ specify "should overwrite case insensitively and assume the new key's case" do
215
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
216
+ h["foo-bar"] = "bizzle"
217
+ h["FOO-BAR"].should.equal "bizzle"
218
+ h.length.should.equal 1
219
+ h.to_hash.should.equal "foo-bar" => "bizzle"
220
+ end
221
+
222
+ specify "should be converted to real Hash" do
223
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
224
+ h.to_hash.should.be.instance_of Hash
225
+ end
226
+
227
+ specify "should convert Array values to Strings when converting to Hash" do
228
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
229
+ h.to_hash.should.equal({ "foo" => "bar\nbaz" })
230
+ end
231
+
232
+ specify "should replace hashes correctly" do
233
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
234
+ j = {"foo" => "bar"}
235
+ h.replace(j)
236
+ h["foo"].should.equal "bar"
237
+ end
238
+
239
+ specify "should be able to delete the given key case-sensitively" do
240
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
241
+ h.delete("foo")
242
+ h["foo"].should.be.nil
243
+ h["FOO"].should.be.nil
244
+ end
245
+
246
+ specify "should be able to delete the given key case-insensitively" do
247
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
248
+ h.delete("FOO")
249
+ h["foo"].should.be.nil
250
+ h["FOO"].should.be.nil
251
+ end
252
+
253
+ specify "should return the deleted value when #delete is called on an existing key" do
254
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
255
+ h.delete("Foo").should.equal("bar")
256
+ end
257
+
258
+ specify "should return nil when #delete is called on a non-existant key" do
259
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
260
+ h.delete("Hello").should.be.nil
261
+ end
262
+ end
263
+
264
+ context "Rack::Utils::Context" do
265
+ class ContextTest
266
+ attr_reader :app
267
+ def initialize app; @app=app; end
268
+ def call env; context env; end
269
+ def context env, app=@app; app.call(env); end
270
+ end
271
+ test_target1 = proc{|e| e.to_s+' world' }
272
+ test_target2 = proc{|e| e.to_i+2 }
273
+ test_target3 = proc{|e| nil }
274
+ test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
275
+ test_app = ContextTest.new test_target4
276
+
277
+ specify "should set context correctly" do
278
+ test_app.app.should.equal test_target4
279
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
280
+ c1.for.should.equal test_app
281
+ c1.app.should.equal test_target1
282
+ c2 = Rack::Utils::Context.new(test_app, test_target2)
283
+ c2.for.should.equal test_app
284
+ c2.app.should.equal test_target2
285
+ end
286
+
287
+ specify "should alter app on recontexting" do
288
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
289
+ c2 = c1.recontext(test_target2)
290
+ c2.for.should.equal test_app
291
+ c2.app.should.equal test_target2
292
+ c3 = c2.recontext(test_target3)
293
+ c3.for.should.equal test_app
294
+ c3.app.should.equal test_target3
295
+ end
296
+
297
+ specify "should run different apps" do
298
+ c1 = Rack::Utils::Context.new test_app, test_target1
299
+ c2 = c1.recontext test_target2
300
+ c3 = c2.recontext test_target3
301
+ c4 = c3.recontext test_target4
302
+ a4 = Rack::Lint.new c4
303
+ a5 = Rack::Lint.new test_app
304
+ r1 = c1.call('hello')
305
+ r1.should.equal 'hello world'
306
+ r2 = c2.call(2)
307
+ r2.should.equal 4
308
+ r3 = c3.call(:misc_symbol)
309
+ r3.should.be.nil
310
+ r4 = Rack::MockRequest.new(a4).get('/')
311
+ r4.status.should.be 200
312
+ r5 = Rack::MockRequest.new(a5).get('/')
313
+ r5.status.should.be 200
314
+ r4.body.should.equal r5.body
315
+ end
316
+ end
317
+
318
+ context "Rack::Utils::Multipart" do
319
+ specify "should return nil if content type is not multipart" do
320
+ env = Rack::MockRequest.env_for("/",
321
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
322
+ Rack::Utils::Multipart.parse_multipart(env).should.equal nil
323
+ end
324
+
325
+ specify "should parse multipart upload with text file" do
326
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
327
+ params = Rack::Utils::Multipart.parse_multipart(env)
328
+ params["submit-name"].should.equal "Larry"
329
+ params["files"][:type].should.equal "text/plain"
330
+ params["files"][:filename].should.equal "file1.txt"
331
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
332
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
333
+ "Content-Type: text/plain\r\n"
334
+ params["files"][:name].should.equal "files"
335
+ params["files"][:tempfile].read.should.equal "contents"
336
+ end
337
+
338
+ specify "should parse multipart upload with nested parameters" do
339
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
340
+ params = Rack::Utils::Multipart.parse_multipart(env)
341
+ params["foo"]["submit-name"].should.equal "Larry"
342
+ params["foo"]["files"][:type].should.equal "text/plain"
343
+ params["foo"]["files"][:filename].should.equal "file1.txt"
344
+ params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " +
345
+ "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" +
346
+ "Content-Type: text/plain\r\n"
347
+ params["foo"]["files"][:name].should.equal "foo[files]"
348
+ params["foo"]["files"][:tempfile].read.should.equal "contents"
349
+ end
350
+
351
+ specify "should parse multipart upload with binary file" do
352
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
353
+ params = Rack::Utils::Multipart.parse_multipart(env)
354
+ params["submit-name"].should.equal "Larry"
355
+ params["files"][:type].should.equal "image/png"
356
+ params["files"][:filename].should.equal "rack-logo.png"
357
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
358
+ "name=\"files\"; filename=\"rack-logo.png\"\r\n" +
359
+ "Content-Type: image/png\r\n"
360
+ params["files"][:name].should.equal "files"
361
+ params["files"][:tempfile].read.length.should.equal 26473
362
+ end
363
+
364
+ specify "should parse multipart upload with empty file" do
365
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
366
+ params = Rack::Utils::Multipart.parse_multipart(env)
367
+ params["submit-name"].should.equal "Larry"
368
+ params["files"][:type].should.equal "text/plain"
369
+ params["files"][:filename].should.equal "file1.txt"
370
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
371
+ "name=\"files\"; filename=\"file1.txt\"\r\n" +
372
+ "Content-Type: text/plain\r\n"
373
+ params["files"][:name].should.equal "files"
374
+ params["files"][:tempfile].read.should.equal ""
375
+ end
376
+
377
+ specify "should not include file params if no file was selected" do
378
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
379
+ params = Rack::Utils::Multipart.parse_multipart(env)
380
+ params["submit-name"].should.equal "Larry"
381
+ params["files"].should.equal nil
382
+ params.keys.should.not.include "files"
383
+ end
384
+
385
+ specify "should parse IE multipart upload and clean up filename" do
386
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
387
+ params = Rack::Utils::Multipart.parse_multipart(env)
388
+ params["files"][:type].should.equal "text/plain"
389
+ params["files"][:filename].should.equal "file1.txt"
390
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
391
+ "name=\"files\"; " +
392
+ 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' +
393
+ "\r\nContent-Type: text/plain\r\n"
394
+ params["files"][:name].should.equal "files"
395
+ params["files"][:tempfile].read.should.equal "contents"
396
+ end
397
+
398
+ specify "rewinds input after parsing upload" do
399
+ options = multipart_fixture(:text)
400
+ input = options[:input]
401
+ env = Rack::MockRequest.env_for("/", options)
402
+ params = Rack::Utils::Multipart.parse_multipart(env)
403
+ params["submit-name"].should.equal "Larry"
404
+ params["files"][:filename].should.equal "file1.txt"
405
+ input.read.length.should.equal 197
406
+ end
407
+
408
+ specify "builds multipart body" do
409
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
410
+ data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
411
+
412
+ options = {
413
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
414
+ "CONTENT_LENGTH" => data.length.to_s,
415
+ :input => StringIO.new(data)
416
+ }
417
+ env = Rack::MockRequest.env_for("/", options)
418
+ params = Rack::Utils::Multipart.parse_multipart(env)
419
+ params["submit-name"].should.equal "Larry"
420
+ params["files"][:filename].should.equal "file1.txt"
421
+ params["files"][:tempfile].read.should.equal "contents"
422
+ end
423
+
424
+ specify "builds nested multipart body" do
425
+ files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
426
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
427
+
428
+ options = {
429
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
430
+ "CONTENT_LENGTH" => data.length.to_s,
431
+ :input => StringIO.new(data)
432
+ }
433
+ env = Rack::MockRequest.env_for("/", options)
434
+ params = Rack::Utils::Multipart.parse_multipart(env)
435
+ params["people"][0]["submit-name"].should.equal "Larry"
436
+ params["people"][0]["files"][:filename].should.equal "file1.txt"
437
+ params["people"][0]["files"][:tempfile].read.should.equal "contents"
438
+ end
439
+
440
+ specify "should return nil if no UploadedFiles were used" do
441
+ data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
442
+ data.should.equal nil
443
+ end
444
+
445
+ specify "should raise ArgumentError if params is not a Hash" do
446
+ lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }.
447
+ should.raise(ArgumentError).
448
+ message.should.equal "value must be a Hash"
449
+ end
450
+
451
+ private
452
+ def multipart_fixture(name)
453
+ file = multipart_file(name)
454
+ data = File.open(file, 'rb') { |io| io.read }
455
+
456
+ type = "multipart/form-data; boundary=AaB03x"
457
+ length = data.respond_to?(:bytesize) ? data.bytesize : data.size
458
+
459
+ { "CONTENT_TYPE" => type,
460
+ "CONTENT_LENGTH" => length.to_s,
461
+ :input => StringIO.new(data) }
462
+ end
463
+
464
+ def multipart_file(name)
465
+ File.join(File.dirname(__FILE__), "multipart", name.to_s)
466
+ end
467
+ end